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