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