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