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