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