Hugintrunk  0.1
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
EditOutputIniDialog.cpp
Go to the documentation of this file.
1 // -*- c-basic-offset: 4 -*-
2 
11 /* This is free software; you can redistribute it and/or
12  * modify it under the terms of the GNU General Public
13  * License as published by the Free Software Foundation; either
14  * version 2 of the License, or (at your option) any later version.
15  *
16  * This software is distributed in the hope that it will be useful,
17  * but WITHOUT ANY WARRANTY; without even the implied warranty of
18  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
19  * Lesser General Public License for more details.
20  *
21  * You should have received a copy of the GNU General Public
22  * License along with this software. If not, see
23  * <http://www.gnu.org/licenses/>.
24  *
25  */
26 
27 #include "EditOutputIniDialog.h"
28 #include "hugin_config.h"
29 #include "panoinc_WX.h"
30 #include "hugin/huginApp.h"
31 #include <wx/fileconf.h>
32 #include <wx/wfstream.h>
33 #include <wx/sstream.h>
34 #include <wx/propgrid/advprops.h>
35 
36 static wxArrayString BlenderArray;
37 static wxArrayString ProjectionArray;
38 static wxArrayString OutputTypeArray;
39 static wxArrayString LDRFileTypeArray;
40 static wxArrayString HDRFileTypeArray;
41 
42 void InitArrays()
43 {
44  BlenderArray.Add("Enblend");
45  BlenderArray.Add("Internal");
46 
47  int nP = panoProjectionFormatCount();
48  for (int n = 0; n < nP; n++)
49  {
50  pano_projection_features proj;
51  if (panoProjectionFeaturesQuery(n, &proj))
52  {
53  ProjectionArray.Add(wxString(proj.name, wxConvLocal));
54  };
55  };
56 
57  OutputTypeArray.Add("Normal");
58  OutputTypeArray.Add("StacksFusedBlended");
59  OutputTypeArray.Add("ExposureLayersFused");
60  OutputTypeArray.Add("HDR");
61  OutputTypeArray.Add("Remap");
62  OutputTypeArray.Add("RemapOrig");
63  OutputTypeArray.Add("HDRRemap");
64  OutputTypeArray.Add("FusedStacks");
65  OutputTypeArray.Add("HDRStacks");
66  OutputTypeArray.Add("ExposureLayers");
67 
68  LDRFileTypeArray.Add("jpg");
69  LDRFileTypeArray.Add("tif");
70  LDRFileTypeArray.Add("png");
71 
72  HDRFileTypeArray.Add("exr");
73  HDRFileTypeArray.Add("tif");
74 }
75 
77 static const wxString defaultIni
78 {
79  "[Cylindrical]\n"
80  "Canvas=70%\n"
81  "Condition1=ImageCount>1\n"
82  "Condition2=PanoVFOV<100\n"
83  "Condition3=PanoHFOV>=100\n"
84  "Crop=auto\n"
85  "FOV=auto\n"
86  "Projection=Cylindrical\n"
87  "[Equirectangular]\n"
88  "Canvas=70%\n"
89  "Condition1=ImageCount>1\n"
90  "Condition2=PanoVFOV>=100\n"
91  "Crop=auto\n"
92  "FOV=auto\n"
93  "Projection=Equirectangular\n"
94  "[Rectilinear]\n"
95  "Canvas=70%\n"
96  "Condition1=ImageCount>1\n"
97  "Condition2=PanoHFOV<100\n"
98  "Condition3=PanoVFOV<100\n"
99  "Crop=auto\n"
100  "FOV=auto\n"
101  "Projection=Rectilinear"
102 };
103 
105 bool contains(const wxArrayString& stringArray, const wxString& string, bool caseInSensitive = true)
106 {
107  if (caseInSensitive)
108  {
109  for (size_t i = 0; i < stringArray.GetCount(); ++i)
110  {
111  if (stringArray[i].CmpNoCase(string) == 0)
112  {
113  return true;
114  };
115  };
116  }
117  else
118  {
119  for (size_t i = 0; i < stringArray.GetCount(); ++i)
120  {
121  if (stringArray[i].Cmp(string) == 0)
122  {
123  return true;
124  };
125  };
126  };
127  return false;
128 }
129 
130 EditOutputIniDialog::EditOutputIniDialog(wxWindow* parent) :wxDialog(parent, wxID_ANY, _("Edit assistant output settings"), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER)
131 {
132  wxSizer* topSizer = new wxBoxSizer(wxVERTICAL);
133  m_grid = new wxPropertyGridManager(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxPG_AUTO_SORT | wxPG_DESCRIPTION | wxPG_SPLITTER_AUTO_CENTER);
134  // bind context menu only to the grid
135  // when binding to whole control it would also be invoked above the text area below
136  m_grid->GetGrid()->Bind(wxEVT_CONTEXT_MENU, &EditOutputIniDialog::OnContextMenu, this);
137  topSizer->Add(m_grid, wxSizerFlags(1).Expand().Border(wxALL, 10));
138  wxStdDialogButtonSizer* buttonSizer = CreateStdDialogButtonSizer(wxOK | wxCANCEL);
139  buttonSizer->GetAffirmativeButton()->Bind(wxEVT_BUTTON, &EditOutputIniDialog::OnOk, this);
140  topSizer->Add(buttonSizer, wxSizerFlags().Expand().Border(wxALL, 6));
141  SetSizerAndFit(topSizer);
142  InitArrays();
143  ReadIni();
144  RestoreFramePosition(this, "EditOutputIniDialog");
145 }
146 
148 {
149  StoreFramePosition(this, "EditOutputIniDialog");
150 }
151 
152 void EditOutputIniDialog::OnOk(wxCommandEvent& e)
153 {
154  WriteIni();
155  EndModal(wxOK);
156 }
157 
158 void EditOutputIniDialog::AddKey(wxPGProperty* section, const wxString& key, const wxString& value)
159 {
160  if (key.SubString(0, 8).CmpNoCase("Condition") == 0)
161  {
162  wxPGProperty* p = m_grid->AppendIn(section, new wxStringProperty(key, wxPG_LABEL, value));
163  p->SetHelpString(_("Define which condition should be fulfilled to apply this setting.\nThe variables CameraModel, CameraMaker, Lens, HFOV, LensType, Focallength, Focallength35mm, Filename, Path, ImageCount, PanoHFOV and PanoVFOV can be used.\nFor comparison you can use ==, !=, =~ and !~ for strings and ==, !=, <, <=, > and >= for numbers."));
164  return;
165  };
166  if (key.CmpNoCase("Projection") == 0)
167  {
168  wxPGProperty* p = m_grid->AppendIn(section, new wxEnumProperty(key, wxPG_LABEL, ProjectionArray));
169  if (value.IsEmpty())
170  {
171  p->SetValue(ProjectionArray[0]);
172  }
173  else
174  {
175  p->SetValue(value);
176  };
177  p->SetHelpString(_("Select which projection should be selected."));
178  return;
179  };
180  if (key.CmpNoCase("FOV") == 0)
181  {
182  wxPGProperty* p = m_grid->AppendIn(section, new wxStringProperty(key, wxPG_LABEL, value));
183  p->SetHelpString(_("Select which field of view should be set.\nCan be Auto for automatic detection, or a fixed horizontal field of view (e.g. 120) or horizontal x vertical field of view (e.g. 140x70)."));
184  return;
185  };
186  if (key.CmpNoCase("Canvas") == 0)
187  {
188  wxPGProperty* p = m_grid->AppendIn(section, new wxStringProperty(key, wxPG_LABEL, value));
189  p->SetHelpString(_("Select to which size the canvas should be set.\nFor automatic detection use Auto or a scale factor in percent (e.g. 70%).\nOr give a width x height (e.g. 8000x4000)."));
190  return;
191  };
192  if (key.CmpNoCase("Crop") == 0)
193  {
194  wxPGProperty* p = m_grid->AppendIn(section, new wxStringProperty(key, wxPG_LABEL, value));
195  p->SetHelpString(_("Select which crop should be used.\nFor automatic detection use Auto, AutoOutside or AutoHDR.\nOr give 4 numbers as left, right, top and bottom. You can use absolute pixel values (e.g. 100,7900,1000,3900) or relative values by appending a percentage sign (e.g. 10,90,10,90%)."));
196  return;
197  };
198  if (key.CmpNoCase("OutputExposure") == 0)
199  {
200  wxPGProperty* p = m_grid->AppendIn(section, new wxStringProperty(key, wxPG_LABEL, value));
201  p->SetHelpString(_("Select which output exposure value should be used.\nFor automatic detection use Auto or give a fixed exposure value."));
202  return;
203  };
204  if (key.CmpNoCase("OutputType") == 0)
205  {
206  // split string into individual items
207  wxArrayString values = wxStringTokenize(value, ",");
208  for (size_t i = 0; i < values.size(); ++i)
209  {
210  // remove whitespace from begin and end
211  values[i].Trim(true).Trim(false);
212  }
213  wxPGProperty* p = m_grid->AppendIn(section, new wxMultiChoiceProperty(key, wxPG_LABEL, OutputTypeArray, values));
214  p->SetHelpString(_("Select which output types should be activated."));
215  return;
216  };
217  if (key.CmpNoCase("Blender") == 0)
218  {
219  wxPGProperty* p = m_grid->AppendIn(section, new wxEnumProperty(key, wxPG_LABEL, BlenderArray));
220  if (value.IsEmpty())
221  {
222  p->SetValue(BlenderArray[0]);
223  }
224  else
225  {
226  p->SetValue(value);
227  };
228  p->SetHelpString(_("Select which blender should be used (can be enblend or internal)."));
229  return;
230  };
231  if (key.CmpNoCase("BlenderArgs") == 0)
232  {
233  wxPGProperty* p = m_grid->AppendIn(section, new wxStringProperty(key, wxPG_LABEL, value));
234  p->SetHelpString(_("Select which arguments should be passed to the blender."));
235  return;
236  };
237  if (key.CmpNoCase("LDRFileType") == 0)
238  {
239  wxPGProperty* p = m_grid->AppendIn(section, new wxEnumProperty(key, wxPG_LABEL, LDRFileTypeArray));
240  if (value.IsEmpty())
241  {
242  p->SetValue(LDRFileTypeArray[0]);
243  }
244  else
245  {
246  p->SetValue(value);
247  };
248  p->SetHelpString(_("Select fileformat for LDR output should be set."));
249  return;
250  };
251  if (key.CmpNoCase("LDRCompression") == 0)
252  {
253  wxPGProperty* p = m_grid->AppendIn(section, new wxStringProperty(key, wxPG_LABEL, value));
254  p->SetHelpString(_("Select which compressions should be used for LDR images.\nFor TIFF images NONE, PACKBITS, LZW and DEFLATE are supported.\nFor JPEG image give a number as quality (0-100)."));
255  return;
256  };
257  if (key.CmpNoCase("HDRFileType") == 0)
258  {
259  wxPGProperty* p = m_grid->AppendIn(section, new wxEnumProperty(key, wxPG_LABEL, HDRFileTypeArray));
260  if (value.IsEmpty())
261  {
262  p->SetValue(HDRFileTypeArray[0]);
263  }
264  else
265  {
266  p->SetValue(value);
267  };
268  p->SetHelpString(_("Select fileformat for HDR output should be set."));
269  return;
270  };
271  if (key.CmpNoCase("HDRCompression") == 0)
272  {
273  wxPGProperty* p = m_grid->AppendIn(section, new wxStringProperty(key, wxPG_LABEL, value));
274  p->SetHelpString(_("Select which compressions should be used for HDR images.\nFor TIFF images NONE, PACKBITS, LZW and DEFLATE are supported."));
275  return;
276  }
277  // fall-through, should not be called
278  wxPGProperty* newProperty = m_grid->AppendIn(section, new wxStringProperty(key, wxPG_LABEL, value));
279 };
280 
282 {
283  wxFileName iniFilename = GetIniFileName();
284  wxInputStream* iniStream{ nullptr };
285 
286  if (iniFilename.FileExists())
287  {
288  iniStream = new wxFileInputStream(iniFilename.GetFullPath());
289  if (!iniStream->IsOk())
290  {
291  delete iniStream;
292  iniStream = nullptr;
293  }
294  };
295  if (iniStream == nullptr)
296  {
297  iniStream = new wxStringInputStream(defaultIni);
298  };
299  // now read from stream
300  wxFileConfig iniFile(*iniStream);
301  m_grid->Freeze();
302  wxString section;
303  long index;
304  bool hasSections = iniFile.GetFirstGroup(section, index);
305  while (hasSections)
306  {
307  wxPGProperty* pgSection = new wxPropertyCategory(section);
308  m_grid->Append(pgSection);
309  iniFile.SetPath(section);
310  wxString key;
311  long indexKey;
312  bool hasKeys = iniFile.GetFirstEntry(key, indexKey);
313  while (hasKeys)
314  {
315  AddKey(pgSection, key, iniFile.Read(key, wxEmptyString));
316  hasKeys = iniFile.GetNextEntry(key, indexKey);
317  };
318  iniFile.SetPath("/");
319  hasSections = iniFile.GetNextGroup(section, index);
320  };
321  m_grid->Thaw();
322  delete iniStream;
323 };
324 
326 {
327  wxFileConfig iniFile;
328  // iterate all entries
329  for (wxPropertyGridIterator it = m_grid->GetIterator(wxPG_ITERATE_ALL); !it.AtEnd(); it++)
330  {
331  wxPGProperty* p = it.GetProperty();
332  if (!p)
333  {
334  continue;
335  };
336  if (p->IsCategory())
337  {
338  iniFile.SetPath("/" + p->GetLabel());
339  }
340  else
341  {
342  wxVariant value = p->GetValue();
343  if (!value.IsNull())
344  {
345  if (value.GetType().CmpNoCase("string") == 0)
346  {
347  const wxString s(value.GetString());
348  iniFile.Write(p->GetLabel(), s);
349  }
350  else
351  {
352  if (value.GetType().CmpNoCase("long") == 0)
353  {
354  const long i = value.GetLong();
355  wxPGChoices choices = p->GetChoices();
356  iniFile.Write(p->GetLabel(), choices[i].GetText());
357  }
358  else
359  {
360  if (value.GetType().CmpNoCase("arrstring") == 0)
361  {
362  wxArrayString values = value.GetArrayString();
363  wxString s;
364  for (size_t i = 0; i < values.size(); ++i)
365  {
366  s << values[i] << ",";
367  };
368  if (!s.IsEmpty())
369  {
370  // remove the last separator
371  s.RemoveLast();
372  };
373  iniFile.Write(p->GetLabel(), s);
374  };
375  };
376  };
377  };
378  };
379  };
380  // finally write to disc
381  wxFileOutputStream iniStream(GetIniFileName().GetFullPath());
382  bool success = true;
383  if (iniStream.IsOk())
384  {
385  success = iniFile.Save(iniStream);
386  };
387  if (!success)
388  {
389  wxMessageBox(wxString::Format(_("Could not save ini file \"%s\"."), GetIniFileName().GetFullPath()), _("Error"), wxOK | wxICON_ERROR, this);
390  };
391 }
392 
394 {
395  return wxFileName(hugin_utils::GetUserAppDataDir(), "output.ini");
396 }
397 
398 void EditOutputIniDialog::BuildAddContextMenu(wxMenu& menu, wxPGProperty* category, const bool addSeparator)
399 {
400  // first get list of known values
401  wxArrayString knownValues=GetChildren(category);
402  m_currentSection = category;
403  wxMenuItem* item = menu.Append(wxID_ANY, _("Add condition"));
404  Bind(wxEVT_MENU, &EditOutputIniDialog::OnAddCondition, this, item->GetId());
405  wxString val;
406 #define ADDKEYITEM(s) val=s;\
407  if (!contains(knownValues, val))\
408  {\
409  item = menu.Append(wxID_ANY, wxString::Format(_("Add %s"), val.c_str()));\
410  Bind(wxEVT_MENU, [this, category, val](wxCommandEvent&) { AddKey(category, val, wxEmptyString); m_grid->RefreshGrid(); }, item->GetId());\
411  };
412  ADDKEYITEM("Projection")
413  ADDKEYITEM("FOV")
414  ADDKEYITEM("Canvas")
415  ADDKEYITEM("Crop")
416  ADDKEYITEM("OutputExposure")
417  ADDKEYITEM("OutputType")
418  ADDKEYITEM("Blender")
419  ADDKEYITEM("BlenderArgs")
420  ADDKEYITEM("LDRFileType")
421  ADDKEYITEM("LDRCompression")
422  ADDKEYITEM("HDRFileType")
423  ADDKEYITEM("HDRCompression")
424 #undef ADDKEYITEM
425  // and finally a separator
426  if (addSeparator)
427  {
428  menu.AppendSeparator();
429  };
430 }
431 
432 void EditOutputIniDialog::OnContextMenu(wxContextMenuEvent& e)
433 {
434  wxPropertyGridHitTestResult hitTest = m_grid->GetGrid()->HitTest(m_grid->GetGrid()->CalcScrolledPosition(m_grid->ScreenToClient(e.GetPosition())));
435  wxMenu contextMenu;
436  wxMenuItem* menuItem = contextMenu.Append(wxID_ANY, _("Create new section"));
437  Bind(wxEVT_MENU, &EditOutputIniDialog::OnAddSection, this, menuItem->GetId());
438  contextMenu.AppendSeparator();
439  wxPGProperty* prop = hitTest.GetProperty();
440  if (prop)
441  {
442  if (prop->IsCategory())
443  {
444  BuildAddContextMenu(contextMenu, prop, true);
445  // rename section menu item
446  menuItem = contextMenu.Append(wxID_ANY, wxString::Format(_("Rename section %s"), prop->GetLabel().c_str()));
447  Bind(wxEVT_MENU, &EditOutputIniDialog::OnRenameSection, this, menuItem->GetId());
448  }
449  else
450  {
451  BuildAddContextMenu(contextMenu, prop->GetParent(), true);
452  };
453  // remove section/item menu item
454  if (prop->IsCategory())
455  {
456  menuItem = contextMenu.Append(wxID_ANY, wxString::Format(_("Remove section %s"), prop->GetLabel().c_str()));
457  }
458  else
459  {
460  menuItem = contextMenu.Append(wxID_ANY, wxString::Format(_("Remove item %s"), prop->GetLabel().c_str()));
461  };
462  Bind(wxEVT_MENU, [this, prop](wxCommandEvent&) { m_grid->DeleteProperty(prop); m_grid->RefreshGrid(); }, menuItem->GetId());
463  }
464  else
465  {
466  // user clicked below last entry, take last category as target
467  wxPGProperty* lastCategory{ nullptr };
468  for (wxPropertyGridIterator it = m_grid->GetGrid()->GetIterator(wxPG_ITERATE_CATEGORIES); !it.AtEnd(); it++)
469  {
470  lastCategory = it.GetProperty();
471  }
472  if(lastCategory)
473  {
474  BuildAddContextMenu(contextMenu, lastCategory, false);
475  };
476  };
477  // show popup menu
478  PopupMenu(&contextMenu);
479 }
480 
481 wxArrayString EditOutputIniDialog::GetSections() const
482 {
483  wxArrayString sections;
484  for (wxPropertyGridIterator it = m_grid->GetIterator(wxPG_ITERATE_CATEGORIES); !it.AtEnd(); it++)
485  {
486  sections.Add(it.GetProperty()->GetLabel());
487  };
488  return sections;
489 }
490 
491 wxArrayString EditOutputIniDialog::GetChildren(wxPGProperty* prop) const
492 {
493  wxArrayString values;
494  size_t counter = 0;
495  for (wxPropertyGridIterator it = m_grid->GetIterator(wxPG_ITERATE_DEFAULT, prop); !it.AtEnd() && counter < prop->GetChildCount(); it++, ++counter)
496  {
497  values.Add(it.GetProperty()->GetLabel());
498  };
499  return values;
500 }
501 
502 void EditOutputIniDialog::OnAddSection(wxCommandEvent& e)
503 {
504  // get list of known sections
505  const wxArrayString knownSections = GetSections();
506  // ask user for new name
507  wxTextEntryDialog dialog(this, _("Name of new section:"), _("Create new section"));
508  if (dialog.ShowModal() == wxID_OK)
509  {
510  const wxString newSection = dialog.GetValue();
511  // check if new name is unique
512  if (contains(knownSections, newSection))
513  {
514  wxMessageBox(wxString::Format(_("Section \"%s\" is already defined.\nPlease use another name."), newSection.c_str()), _("Duplicate value."), wxOK | wxICON_ERROR);
515  }
516  else
517  {
518  // all ok, finally add new section to grid
519  wxPGProperty* pgSection = new wxPropertyCategory(newSection);
520  m_grid->AppendIn(m_grid->GetPageRoot(0), pgSection);
521  m_grid->RefreshGrid();
522  };
523  };
524 }
525 
527 {
528  wxPGProperty* currentSection = m_grid->GetSelection();
529  if (!currentSection->IsCategory())
530  {
531  return;
532  };
533  // get list of known sections, ignore current selected section
534  wxArrayString knownSections=GetSections();
535  knownSections.Remove(currentSection->GetLabel());
536  // ask user for new name
537  wxTextEntryDialog dialog(this, _("New name of section:"), _("Create new section"), currentSection->GetLabel());
538  if (dialog.ShowModal() == wxID_OK)
539  {
540  const wxString newSection = dialog.GetValue();
541  // check if new name is unique
542  if (contains(knownSections, newSection))
543  {
544  wxMessageBox(wxString::Format(_("Section \"%s\" is already defined.\nPlease use another name."), newSection.c_str()), _("Duplicate value."), wxOK | wxICON_ERROR);
545  }
546  else
547  {
548  // all ok, finally add new section to grid
549  currentSection->SetLabel(newSection);
550  m_grid->RefreshGrid();
551  };
552  };
553 }
554 
555 void EditOutputIniDialog::OnAddCondition(wxCommandEvent& e)
556 {
557  wxArrayString children = GetChildren(m_currentSection);
558  // get currently highest used number on condition
559  size_t index = 0;
560  for (size_t i = 0; i < children.GetCount(); ++i)
561  {
562  if (children[i].SubString(0, 8).CmpNoCase("Condition") == 0)
563  {
564  long x;
565  if (children[i].Mid(9).ToLong(&x))
566  {
567  index = std::max<long>(index, x);
568  };
569  };
570  };
571  ++index;
572  const wxString val = wxString::Format("Condition%d", index);
573  AddKey(m_currentSection, val, wxEmptyString);
574  m_grid->RefreshGrid();
575 }
void ReadIni()
read the ini file and populate the control
void OnAddSection(wxCommandEvent &e)
add a new section
Definition of dialog for editing user-defined output ini file.
wxArrayString GetSections() const
return wxArrayString with all sections
void InitArrays()
wxPGProperty * m_currentSection
EditOutputIniDialog(wxWindow *parent)
Constructor, constructs dialog; restore last uses settings, size and position.
wxPropertyGridManager * m_grid
static wxArrayString OutputTypeArray
static wxArrayString HDRFileTypeArray
static wxArrayString LDRFileTypeArray
void BuildAddContextMenu(wxMenu &menu, wxPGProperty *category, const bool addSeparator)
function to build context menu with all missing entries
wxArrayString GetChildren(wxPGProperty *prop) const
return wxArrayString with all sub-entries of given wxPGProperty
wxFileName GetIniFileName()
return the filename of the default ini file
void StoreFramePosition(wxTopLevelWindow *frame, const wxString &basename)
Store window size and position in configfile/registry.
Definition: LensCalApp.cpp:210
void OnContextMenu(wxContextMenuEvent &e)
right click handler, show popup menu
bool contains(const wxArrayString &stringArray, const wxString &string, bool caseInSensitive=true)
check if given string is in wxArrayString, do comparison case insentive or case sensitive ...
void RestoreFramePosition(wxTopLevelWindow *frame, const wxString &basename)
Restore window size and position from configfile/registry.
Definition: LensCalApp.cpp:156
void OnOk(wxCommandEvent &e)
save ini and close dialog
static wxArrayString ProjectionArray
void OnAddCondition(wxCommandEvent &e)
adds a new condition to list
void AddKey(wxPGProperty *section, const wxString &key, const wxString &value)
add key with value to wxPropertyGrid, generate if necessary all sub properties
include file for the hugin project
void OnRenameSection(wxCommandEvent &e)
renames a new section
~EditOutputIniDialog()
destructor, saves size and position
std::string GetUserAppDataDir()
returns the directory for user specific Hugin settings, e.g.
Definition: utils.cpp:497
static const wxString defaultIni
default ini, if no one exists load this one
void WriteIni()
write the ini to disc
#define ADDKEYITEM(s)
static wxArrayString BlenderArray