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