chiark / gitweb /
Fix the expert window on MacOS. Remove the exception if the GCode loader is interrupt...
[cura.git] / Cura / gui / mainWindow.py
1 from __future__ import absolute_import
2 __copyright__ = "Copyright (C) 2013 David Braam - Released under terms of the AGPLv3 License"
3
4 import wx
5 import os
6 import webbrowser
7
8 from Cura.gui import configBase
9 from Cura.gui import expertConfig
10 from Cura.gui import alterationPanel
11 from Cura.gui import pluginPanel
12 from Cura.gui import preferencesDialog
13 from Cura.gui import configWizard
14 from Cura.gui import firmwareInstall
15 from Cura.gui import simpleMode
16 from Cura.gui import sceneView
17 #from Cura.gui.tools import batchRun
18 from Cura.gui.util import dropTarget
19 from Cura.gui.tools import minecraftImport
20 from Cura.gui.tools import superformula
21 from Cura.util import profile
22 from Cura.util import version
23 from Cura.util import meshLoader
24
25 class mainWindow(wx.Frame):
26         def __init__(self):
27                 super(mainWindow, self).__init__(None, title='Cura - ' + version.getVersion())
28
29                 self.extruderCount = int(profile.getPreference('extruder_amount'))
30
31                 wx.EVT_CLOSE(self, self.OnClose)
32
33                 self.SetDropTarget(dropTarget.FileDropTarget(self.OnDropFiles, meshLoader.loadSupportedExtensions()))
34
35                 self.normalModeOnlyItems = []
36
37                 mruFile = os.path.join(profile.getBasePath(), 'mru_filelist.ini')
38                 self.config = wx.FileConfig(appName="Cura", 
39                                                 localFilename=mruFile,
40                                                 style=wx.CONFIG_USE_LOCAL_FILE)
41                                                 
42                 self.ID_MRU_MODEL1, self.ID_MRU_MODEL2, self.ID_MRU_MODEL3, self.ID_MRU_MODEL4, self.ID_MRU_MODEL5, self.ID_MRU_MODEL6, self.ID_MRU_MODEL7, self.ID_MRU_MODEL8, self.ID_MRU_MODEL9, self.ID_MRU_MODEL10 = [wx.NewId() for line in xrange(10)]
43                 self.modelFileHistory = wx.FileHistory(10, self.ID_MRU_MODEL1)
44                 self.config.SetPath("/ModelMRU")
45                 self.modelFileHistory.Load(self.config)
46
47                 self.ID_MRU_PROFILE1, self.ID_MRU_PROFILE2, self.ID_MRU_PROFILE3, self.ID_MRU_PROFILE4, self.ID_MRU_PROFILE5, self.ID_MRU_PROFILE6, self.ID_MRU_PROFILE7, self.ID_MRU_PROFILE8, self.ID_MRU_PROFILE9, self.ID_MRU_PROFILE10 = [wx.NewId() for line in xrange(10)]
48                 self.profileFileHistory = wx.FileHistory(10, self.ID_MRU_PROFILE1)
49                 self.config.SetPath("/ProfileMRU")
50                 self.profileFileHistory.Load(self.config)
51
52                 self.menubar = wx.MenuBar()
53                 self.fileMenu = wx.Menu()
54                 i = self.fileMenu.Append(-1, 'Load model file...\tCTRL+L')
55                 self.Bind(wx.EVT_MENU, lambda e: self.scene.showLoadModel(), i)
56                 i = self.fileMenu.Append(-1, 'Save model...\tCTRL+S')
57                 self.Bind(wx.EVT_MENU, lambda e: self.scene.showSaveModel(), i)
58
59                 self.fileMenu.AppendSeparator()
60                 i = self.fileMenu.Append(-1, 'Print...\tCTRL+P')
61                 self.Bind(wx.EVT_MENU, lambda e: self.scene.showPrintWindow(), i)
62                 i = self.fileMenu.Append(-1, 'Save GCode...')
63                 self.Bind(wx.EVT_MENU, lambda e: self.scene.showSaveGCode(), i)
64
65                 self.fileMenu.AppendSeparator()
66                 i = self.fileMenu.Append(-1, 'Open Profile...')
67                 self.normalModeOnlyItems.append(i)
68                 self.Bind(wx.EVT_MENU, self.OnLoadProfile, i)
69                 i = self.fileMenu.Append(-1, 'Save Profile...')
70                 self.normalModeOnlyItems.append(i)
71                 self.Bind(wx.EVT_MENU, self.OnSaveProfile, i)
72                 i = self.fileMenu.Append(-1, 'Load Profile from GCode...')
73                 self.normalModeOnlyItems.append(i)
74                 self.Bind(wx.EVT_MENU, self.OnLoadProfileFromGcode, i)
75                 self.fileMenu.AppendSeparator()
76                 i = self.fileMenu.Append(-1, 'Reset Profile to default')
77                 self.normalModeOnlyItems.append(i)
78                 self.Bind(wx.EVT_MENU, self.OnResetProfile, i)
79
80                 self.fileMenu.AppendSeparator()
81                 i = self.fileMenu.Append(-1, 'Preferences...\tCTRL+,')
82                 self.Bind(wx.EVT_MENU, self.OnPreferences, i)
83                 self.fileMenu.AppendSeparator()
84
85                 # Model MRU list
86                 modelHistoryMenu = wx.Menu()
87                 self.fileMenu.AppendMenu(wx.NewId(), "&Recent Model Files", modelHistoryMenu)
88                 self.modelFileHistory.UseMenu(modelHistoryMenu)
89                 self.modelFileHistory.AddFilesToMenu()
90                 self.Bind(wx.EVT_MENU_RANGE, self.OnModelMRU, id=self.ID_MRU_MODEL1, id2=self.ID_MRU_MODEL10)
91
92                 # Profle MRU list
93                 profileHistoryMenu = wx.Menu()
94                 self.fileMenu.AppendMenu(wx.NewId(), "&Recent Profile Files", profileHistoryMenu)
95                 self.profileFileHistory.UseMenu(profileHistoryMenu)
96                 self.profileFileHistory.AddFilesToMenu()
97                 self.Bind(wx.EVT_MENU_RANGE, self.OnProfileMRU, id=self.ID_MRU_PROFILE1, id2=self.ID_MRU_PROFILE10)
98                 
99                 self.fileMenu.AppendSeparator()
100                 i = self.fileMenu.Append(wx.ID_EXIT, 'Quit')
101                 self.Bind(wx.EVT_MENU, self.OnQuit, i)
102                 self.menubar.Append(self.fileMenu, '&File')
103
104                 toolsMenu = wx.Menu()
105                 i = toolsMenu.Append(-1, 'Switch to quickprint...')
106                 self.switchToQuickprintMenuItem = i
107                 self.Bind(wx.EVT_MENU, self.OnSimpleSwitch, i)
108                 i = toolsMenu.Append(-1, 'Switch to full settings...')
109                 self.switchToNormalMenuItem = i
110                 self.Bind(wx.EVT_MENU, self.OnNormalSwitch, i)
111                 toolsMenu.AppendSeparator()
112                 #i = toolsMenu.Append(-1, 'Batch run...')
113                 #self.Bind(wx.EVT_MENU, self.OnBatchRun, i)
114                 #self.normalModeOnlyItems.append(i)
115                 if minecraftImport.hasMinecraft():
116                         i = toolsMenu.Append(-1, 'Minecraft import...')
117                         self.Bind(wx.EVT_MENU, self.OnMinecraftImport, i)
118                 i = toolsMenu.Append(-1, 'Super-shaper...')
119                 self.Bind(wx.EVT_MENU, self.OnSuperformula, i)
120                 self.menubar.Append(toolsMenu, 'Tools')
121
122                 expertMenu = wx.Menu()
123                 i = expertMenu.Append(-1, 'Open expert settings...')
124                 self.normalModeOnlyItems.append(i)
125                 self.Bind(wx.EVT_MENU, self.OnExpertOpen, i)
126                 expertMenu.AppendSeparator()
127                 if firmwareInstall.getDefaultFirmware() is not None:
128                         i = expertMenu.Append(-1, 'Install default Marlin firmware')
129                         self.Bind(wx.EVT_MENU, self.OnDefaultMarlinFirmware, i)
130                 i = expertMenu.Append(-1, 'Install custom firmware')
131                 self.Bind(wx.EVT_MENU, self.OnCustomFirmware, i)
132                 expertMenu.AppendSeparator()
133                 i = expertMenu.Append(-1, 'Run first run wizard...')
134                 self.Bind(wx.EVT_MENU, self.OnFirstRunWizard, i)
135                 i = expertMenu.Append(-1, 'Run bed leveling wizard...')
136                 self.Bind(wx.EVT_MENU, self.OnBedLevelWizard, i)
137                 if self.extruderCount > 1:
138                         i = expertMenu.Append(-1, 'Run head offset wizard...')
139                         self.Bind(wx.EVT_MENU, self.OnHeadOffsetWizard, i)
140                 self.menubar.Append(expertMenu, 'Expert')
141
142                 helpMenu = wx.Menu()
143                 i = helpMenu.Append(-1, 'Online documentation...')
144                 self.Bind(wx.EVT_MENU, lambda e: webbrowser.open('http://daid.github.com/Cura'), i)
145                 i = helpMenu.Append(-1, 'Report a problem...')
146                 self.Bind(wx.EVT_MENU, lambda e: webbrowser.open('https://github.com/daid/Cura/issues'), i)
147                 i = helpMenu.Append(-1, 'Check for update...')
148                 self.Bind(wx.EVT_MENU, self.OnCheckForUpdate, i)
149                 i = helpMenu.Append(-1, 'About Cura...')
150                 self.Bind(wx.EVT_MENU, self.OnAbout, i)
151                 self.menubar.Append(helpMenu, 'Help')
152                 self.SetMenuBar(self.menubar)
153
154                 self.splitter = wx.SplitterWindow(self, style = wx.SP_3D | wx.SP_LIVE_UPDATE)
155                 self.leftPane = wx.Panel(self.splitter, style=wx.BORDER_NONE)
156                 self.rightPane = wx.Panel(self.splitter, style=wx.BORDER_NONE)
157                 self.splitter.Bind(wx.EVT_SPLITTER_DCLICK, lambda evt: evt.Veto())
158
159                 ##Gui components##
160                 self.simpleSettingsPanel = simpleMode.simpleModePanel(self.leftPane, lambda : self.scene.sceneUpdated())
161                 self.normalSettingsPanel = normalSettingsPanel(self.leftPane, lambda : self.scene.sceneUpdated())
162
163                 self.leftSizer = wx.BoxSizer(wx.VERTICAL)
164                 self.leftSizer.Add(self.simpleSettingsPanel)
165                 self.leftSizer.Add(self.normalSettingsPanel, 1, wx.EXPAND)
166                 self.leftPane.SetSizer(self.leftSizer)
167                 
168                 #Preview window
169                 self.scene = sceneView.SceneView(self.rightPane)
170
171                 #Main sizer, to position the preview window, buttons and tab control
172                 sizer = wx.BoxSizer()
173                 self.rightPane.SetSizer(sizer)
174                 sizer.Add(self.scene, 1, flag=wx.EXPAND)
175
176                 # Main window sizer
177                 sizer = wx.BoxSizer(wx.VERTICAL)
178                 self.SetSizer(sizer)
179                 sizer.Add(self.splitter, 1, wx.EXPAND)
180                 sizer.Layout()
181                 self.sizer = sizer
182
183                 self.updateProfileToControls()
184
185                 self.SetBackgroundColour(self.normalSettingsPanel.GetBackgroundColour())
186
187                 self.simpleSettingsPanel.Show(False)
188                 self.normalSettingsPanel.Show(False)
189
190                 # Set default window size & position
191                 self.SetSize((wx.Display().GetClientArea().GetWidth()/2,wx.Display().GetClientArea().GetHeight()/2))
192                 self.Centre()
193
194                 # Restore the window position, size & state from the preferences file
195                 try:
196                         if profile.getPreference('window_maximized') == 'True':
197                                 self.Maximize(True)
198                         else:
199                                 posx = int(profile.getPreference('window_pos_x'))
200                                 posy = int(profile.getPreference('window_pos_y'))
201                                 width = int(profile.getPreference('window_width'))
202                                 height = int(profile.getPreference('window_height'))
203                                 if posx > 0 or posy > 0:
204                                         self.SetPosition((posx,posy))
205                                 if width > 0 and height > 0:
206                                         self.SetSize((width,height))
207                                 
208                         self.normalSashPos = int(profile.getPreference('window_normal_sash'))
209                 except:
210                         self.normalSashPos = 0
211                         self.Maximize(True)
212                 if self.normalSashPos < self.normalSettingsPanel.printPanel.GetBestSize()[0] + 5:
213                         self.normalSashPos = self.normalSettingsPanel.printPanel.GetBestSize()[0] + 5
214
215                 self.splitter.SplitVertically(self.leftPane, self.rightPane, self.normalSashPos)
216
217                 if wx.Display.GetFromPoint(self.GetPosition()) < 0:
218                         self.Centre()
219                 if wx.Display.GetFromPoint((self.GetPositionTuple()[0] + self.GetSizeTuple()[1], self.GetPositionTuple()[1] + self.GetSizeTuple()[1])) < 0:
220                         self.Centre()
221                 if wx.Display.GetFromPoint(self.GetPosition()) < 0:
222                         self.SetSize((800,600))
223                         self.Centre()
224
225                 self.updateSliceMode()
226
227         def updateSliceMode(self):
228                 isSimple = profile.getPreference('startMode') == 'Simple'
229
230                 self.normalSettingsPanel.Show(not isSimple)
231                 self.simpleSettingsPanel.Show(isSimple)
232                 self.leftPane.Layout()
233
234                 for i in self.normalModeOnlyItems:
235                         i.Enable(not isSimple)
236                 self.switchToQuickprintMenuItem.Enable(not isSimple)
237                 self.switchToNormalMenuItem.Enable(isSimple)
238
239                 # Set splitter sash position & size
240                 if isSimple:
241                         # Save normal mode sash
242                         self.normalSashPos = self.splitter.GetSashPosition()
243                         
244                         # Change location of sash to width of quick mode pane 
245                         (width, height) = self.simpleSettingsPanel.GetSizer().GetSize() 
246                         self.splitter.SetSashPosition(width, True)
247                         
248                         # Disable sash
249                         self.splitter.SetSashSize(0)
250                 else:
251                         self.splitter.SetSashPosition(self.normalSashPos, True)
252                         # Enabled sash
253                         self.splitter.SetSashSize(4)
254                 self.scene.updateProfileToControls()
255                                                                 
256         def OnPreferences(self, e):
257                 prefDialog = preferencesDialog.preferencesDialog(self)
258                 prefDialog.Centre()
259                 prefDialog.Show()
260
261         def OnDropFiles(self, files):
262                 profile.setPluginConfig([])
263                 self.updateProfileToControls()
264                 self.scene.loadScene(files)
265
266         def OnModelMRU(self, e):
267                 fileNum = e.GetId() - self.ID_MRU_MODEL1
268                 path = self.modelFileHistory.GetHistoryFile(fileNum)
269                 # Update Model MRU
270                 self.modelFileHistory.AddFileToHistory(path)  # move up the list
271                 self.config.SetPath("/ModelMRU")
272                 self.modelFileHistory.Save(self.config)
273                 self.config.Flush()
274                 # Load Model
275                 filelist = [ path ]
276                 self.scene.loadScene(filelist)
277
278         def addToModelMRU(self, file):
279                 self.modelFileHistory.AddFileToHistory(file)
280                 self.config.SetPath("/ModelMRU")
281                 self.modelFileHistory.Save(self.config)
282                 self.config.Flush()
283         
284         def OnProfileMRU(self, e):
285                 fileNum = e.GetId() - self.ID_MRU_PROFILE1
286                 path = self.profileFileHistory.GetHistoryFile(fileNum)
287                 # Update Profile MRU
288                 self.profileFileHistory.AddFileToHistory(path)  # move up the list
289                 self.config.SetPath("/ProfileMRU")
290                 self.profileFileHistory.Save(self.config)
291                 self.config.Flush()
292                 # Load Profile  
293                 profile.loadProfile(path)
294                 self.updateProfileToControls()
295
296         def addToProfileMRU(self, file):
297                 self.profileFileHistory.AddFileToHistory(file)
298                 self.config.SetPath("/ProfileMRU")
299                 self.profileFileHistory.Save(self.config)
300                 self.config.Flush()                     
301
302         def updateProfileToControls(self):
303                 self.scene.updateProfileToControls()
304                 self.normalSettingsPanel.updateProfileToControls()
305                 self.simpleSettingsPanel.updateProfileToControls()
306
307         def OnLoadProfile(self, e):
308                 dlg=wx.FileDialog(self, "Select profile file to load", os.path.split(profile.getPreference('lastFile'))[0], style=wx.FD_OPEN|wx.FD_FILE_MUST_EXIST)
309                 dlg.SetWildcard("ini files (*.ini)|*.ini")
310                 if dlg.ShowModal() == wx.ID_OK:
311                         profileFile = dlg.GetPath()
312                         profile.loadProfile(profileFile)
313                         self.updateProfileToControls()
314
315                         # Update the Profile MRU
316                         self.addToProfileMRU(profileFile)
317                 dlg.Destroy()
318
319         def OnLoadProfileFromGcode(self, e):
320                 dlg=wx.FileDialog(self, "Select gcode file to load profile from", os.path.split(profile.getPreference('lastFile'))[0], style=wx.FD_OPEN|wx.FD_FILE_MUST_EXIST)
321                 dlg.SetWildcard("gcode files (*.gcode)|*.gcode;*.g")
322                 if dlg.ShowModal() == wx.ID_OK:
323                         gcodeFile = dlg.GetPath()
324                         f = open(gcodeFile, 'r')
325                         hasProfile = False
326                         for line in f:
327                                 if line.startswith(';CURA_PROFILE_STRING:'):
328                                         profile.loadProfileFromString(line[line.find(':')+1:].strip())
329                                         hasProfile = True
330                         if hasProfile:
331                                 self.updateProfileToControls()
332                         else:
333                                 wx.MessageBox('No profile found in GCode file.\nThis feature only works with GCode files made by Cura 12.07 or newer.', 'Profile load error', wx.OK | wx.ICON_INFORMATION)
334                 dlg.Destroy()
335
336         def OnSaveProfile(self, e):
337                 dlg=wx.FileDialog(self, "Select profile file to save", os.path.split(profile.getPreference('lastFile'))[0], style=wx.FD_SAVE)
338                 dlg.SetWildcard("ini files (*.ini)|*.ini")
339                 if dlg.ShowModal() == wx.ID_OK:
340                         profileFile = dlg.GetPath()
341                         profile.saveProfile(profileFile)
342                 dlg.Destroy()
343
344         def OnResetProfile(self, e):
345                 dlg = wx.MessageDialog(self, 'This will reset all profile settings to defaults.\nUnless you have saved your current profile, all settings will be lost!\nDo you really want to reset?', 'Profile reset', wx.YES_NO | wx.ICON_QUESTION)
346                 result = dlg.ShowModal() == wx.ID_YES
347                 dlg.Destroy()
348                 if result:
349                         profile.resetProfile()
350                         self.updateProfileToControls()
351
352         def OnBatchRun(self, e):
353                 br = batchRun.batchRunWindow(self)
354                 br.Centre()
355                 br.Show(True)
356
357         def OnSimpleSwitch(self, e):
358                 profile.putPreference('startMode', 'Simple')
359                 self.updateSliceMode()
360
361         def OnNormalSwitch(self, e):
362                 profile.putPreference('startMode', 'Normal')
363                 self.updateSliceMode()
364
365         def OnDefaultMarlinFirmware(self, e):
366                 firmwareInstall.InstallFirmware()
367
368         def OnCustomFirmware(self, e):
369                 if profile.getPreference('machine_type') == 'ultimaker':
370                         wx.MessageBox('Warning: Installing a custom firmware does not guarantee that you machine will function correctly, and could damage your machine.', 'Firmware update', wx.OK | wx.ICON_EXCLAMATION)
371                 dlg=wx.FileDialog(self, "Open firmware to upload", os.path.split(profile.getPreference('lastFile'))[0], style=wx.FD_OPEN|wx.FD_FILE_MUST_EXIST)
372                 dlg.SetWildcard("HEX file (*.hex)|*.hex;*.HEX")
373                 if dlg.ShowModal() == wx.ID_OK:
374                         filename = dlg.GetPath()
375                         if not(os.path.exists(filename)):
376                                 return
377                         #For some reason my Ubuntu 10.10 crashes here.
378                         firmwareInstall.InstallFirmware(filename)
379
380         def OnFirstRunWizard(self, e):
381                 configWizard.configWizard()
382                 self.updateProfileToControls()
383
384         def OnBedLevelWizard(self, e):
385                 configWizard.bedLevelWizard()
386
387         def OnHeadOffsetWizard(self, e):
388                 configWizard.headOffsetWizard()
389
390         def OnExpertOpen(self, e):
391                 ecw = expertConfig.expertConfigWindow(lambda : self.scene.sceneUpdated())
392                 ecw.Centre()
393                 ecw.Show()
394
395         def OnMinecraftImport(self, e):
396                 mi = minecraftImport.minecraftImportWindow(self)
397                 mi.Centre()
398                 mi.Show(True)
399
400         def OnSuperformula(self, e):
401                 sf = superformula.superformulaWindow(self)
402                 #sf = superformula.superformulaEvolver(self)
403                 sf.Centre()
404                 sf.Show(True)
405
406         def OnCheckForUpdate(self, e):
407                 newVersion = version.checkForNewerVersion()
408                 if newVersion is not None:
409                         if wx.MessageBox('A new version of Cura is available, would you like to download?', 'New version available', wx.YES_NO | wx.ICON_INFORMATION) == wx.YES:
410                                 webbrowser.open(newVersion)
411                 else:
412                         wx.MessageBox('You are running the latest version of Cura!', 'Awesome!', wx.ICON_INFORMATION)
413
414         def OnAbout(self, e):
415                 info = wx.AboutDialogInfo()
416                 info.SetName('Cura')
417                 info.SetDescription('End solution for Open Source Fused Filament Fabrication 3D printing.')
418                 info.SetWebSite('http://software.ultimaker.com/')
419                 info.SetCopyright('Copyright (C) David Braam')
420                 info.SetLicence("""
421     This program is free software: you can redistribute it and/or modify
422     it under the terms of the GNU Affero General Public License as published by
423     the Free Software Foundation, either version 3 of the License, or
424     (at your option) any later version.
425
426     This program is distributed in the hope that it will be useful,
427     but WITHOUT ANY WARRANTY; without even the implied warranty of
428     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
429     GNU Affero General Public License for more details.
430
431     You should have received a copy of the GNU Affero General Public License
432     along with this program.  If not, see <http://www.gnu.org/licenses/>.
433 """)
434                 wx.AboutBox(info)
435
436         def OnClose(self, e):
437                 profile.saveProfile(profile.getDefaultProfilePath())
438
439                 # Save the window position, size & state from the preferences file
440                 profile.putPreference('window_maximized', self.IsMaximized())
441                 if not self.IsMaximized() and not self.IsIconized():
442                         (posx, posy) = self.GetPosition()
443                         profile.putPreference('window_pos_x', posx)
444                         profile.putPreference('window_pos_y', posy)
445                         (width, height) = self.GetSize()
446                         profile.putPreference('window_width', width)
447                         profile.putPreference('window_height', height)                  
448                         
449                         # Save normal sash position.  If in normal mode (!simple mode), get last position of sash before saving it...
450                         isSimple = profile.getPreference('startMode') == 'Simple'
451                         if not isSimple:
452                                 self.normalSashPos = self.splitter.GetSashPosition()
453                         profile.putPreference('window_normal_sash', self.normalSashPos)
454
455                 #HACK: Set the paint function of the glCanvas to nothing so it won't keep refreshing. Which keeps wxWidgets from quiting.
456                 print "Closing down"
457                 self.scene.OnPaint = lambda e : e
458                 self.scene._slicer.cleanup()
459                 self.Destroy()
460
461         def OnQuit(self, e):
462                 self.Close()
463
464 class normalSettingsPanel(configBase.configPanelBase):
465         "Main user interface window"
466         def __init__(self, parent, callback = None):
467                 super(normalSettingsPanel, self).__init__(parent, callback)
468
469                 #Main tabs
470                 self.nb = wx.Notebook(self)
471                 self.SetSizer(wx.BoxSizer(wx.HORIZONTAL))
472                 self.GetSizer().Add(self.nb, 1, wx.EXPAND)
473
474                 (left, right, self.printPanel) = self.CreateDynamicConfigTab(self.nb, 'Basic')
475                 self._addSettingsToPanels('basic', left, right)
476                 self.SizeLabelWidths(left, right)
477                 
478                 (left, right, self.advancedPanel) = self.CreateDynamicConfigTab(self.nb, 'Advanced')
479                 self._addSettingsToPanels('advanced', left, right)
480                 self.SizeLabelWidths(left, right)
481
482                 #Plugin page
483                 self.pluginPanel = pluginPanel.pluginPanel(self.nb, callback)
484                 if len(self.pluginPanel.pluginList) > 0:
485                         self.nb.AddPage(self.pluginPanel, "Plugins")
486                 else:
487                         self.pluginPanel.Show(False)
488
489                 #Alteration page
490                 self.alterationPanel = alterationPanel.alterationPanel(self.nb, callback)
491                 self.nb.AddPage(self.alterationPanel, "Start/End-GCode")
492
493                 self.Bind(wx.EVT_SIZE, self.OnSize)
494
495                 self.nb.SetSize(self.GetSize())
496                 self.UpdateSize(self.printPanel)
497                 self.UpdateSize(self.advancedPanel)
498
499         def _addSettingsToPanels(self, category, left, right):
500                 count = len(profile.getSubCategoriesFor(category)) + len(profile.getSettingsForCategory(category))
501
502                 p = left
503                 n = 0
504                 for title in profile.getSubCategoriesFor(category):
505                         n += 1 + len(profile.getSettingsForCategory(category, title))
506                         if n > count / 2:
507                                 p = right
508                         configBase.TitleRow(p, title)
509                         for s in profile.getSettingsForCategory(category, title):
510                                 if s.checkConditions():
511                                         configBase.SettingRow(p, s.getName())
512
513         def SizeLabelWidths(self, left, right):
514                 leftWidth = self.getLabelColumnWidth(left)
515                 rightWidth = self.getLabelColumnWidth(right)
516                 maxWidth = max(leftWidth, rightWidth)
517                 self.setLabelColumnWidth(left, maxWidth)
518                 self.setLabelColumnWidth(right, maxWidth)
519
520         def OnSize(self, e):
521                 # Make the size of the Notebook control the same size as this control
522                 self.nb.SetSize(self.GetSize())
523                 
524                 # Propegate the OnSize() event (just in case)
525                 e.Skip()
526                 
527                 # Perform out resize magic
528                 self.UpdateSize(self.printPanel)
529                 self.UpdateSize(self.advancedPanel)
530         
531         def UpdateSize(self, configPanel):
532                 sizer = configPanel.GetSizer()
533                 
534                 # Pseudocde
535                 # if horizontal:
536                 #     if width(col1) < best_width(col1) || width(col2) < best_width(col2):
537                 #         switch to vertical
538                 # else:
539                 #     if width(col1) > (best_width(col1) + best_width(col1)):
540                 #         switch to horizontal
541                 #
542                                 
543                 col1 = configPanel.leftPanel
544                 colSize1 = col1.GetSize()
545                 colBestSize1 = col1.GetBestSize()
546                 col2 = configPanel.rightPanel
547                 colSize2 = col2.GetSize()
548                 colBestSize2 = col2.GetBestSize()
549
550                 orientation = sizer.GetOrientation()
551                 
552                 if orientation == wx.HORIZONTAL:
553                         if (colSize1[0] <= colBestSize1[0]) or (colSize2[0] <= colBestSize2[0]):
554                                 configPanel.Freeze()
555                                 sizer = wx.BoxSizer(wx.VERTICAL)
556                                 sizer.Add(configPanel.leftPanel, flag=wx.EXPAND)
557                                 sizer.Add(configPanel.rightPanel, flag=wx.EXPAND)
558                                 configPanel.SetSizer(sizer)
559                                 #sizer.Layout()
560                                 configPanel.Layout()
561                                 self.Layout()
562                                 configPanel.Thaw()
563                 else:
564                         if max(colSize1[0], colSize2[0]) > (colBestSize1[0] + colBestSize2[0]):
565                                 configPanel.Freeze()
566                                 sizer = wx.BoxSizer(wx.HORIZONTAL)
567                                 sizer.Add(configPanel.leftPanel, proportion=1, border=35, flag=wx.EXPAND)
568                                 sizer.Add(configPanel.rightPanel, proportion=1, flag=wx.EXPAND)
569                                 configPanel.SetSizer(sizer)
570                                 #sizer.Layout()
571                                 configPanel.Layout()
572                                 self.Layout()
573                                 configPanel.Thaw()
574
575         def updateProfileToControls(self):
576                 super(normalSettingsPanel, self).updateProfileToControls()
577                 self.alterationPanel.updateProfileToControls()
578                 self.pluginPanel.updateProfileToControls()