chiark / gitweb /
Fix the usage stats submit. Some minor cleanup.
[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()))
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                 if len(files) > 0:
270                         profile.setPluginConfig([])
271                         self.updateProfileToControls()
272                 self.scene.loadScene(files)
273
274         def OnModelMRU(self, e):
275                 fileNum = e.GetId() - self.ID_MRU_MODEL1
276                 path = self.modelFileHistory.GetHistoryFile(fileNum)
277                 # Update Model MRU
278                 self.modelFileHistory.AddFileToHistory(path)  # move up the list
279                 self.config.SetPath("/ModelMRU")
280                 self.modelFileHistory.Save(self.config)
281                 self.config.Flush()
282                 # Load Model
283                 profile.putPreference('lastFile', path)
284                 filelist = [ path ]
285                 self.scene.loadScene(filelist)
286
287         def addToModelMRU(self, file):
288                 self.modelFileHistory.AddFileToHistory(file)
289                 self.config.SetPath("/ModelMRU")
290                 self.modelFileHistory.Save(self.config)
291                 self.config.Flush()
292
293         def OnProfileMRU(self, e):
294                 fileNum = e.GetId() - self.ID_MRU_PROFILE1
295                 path = self.profileFileHistory.GetHistoryFile(fileNum)
296                 # Update Profile MRU
297                 self.profileFileHistory.AddFileToHistory(path)  # move up the list
298                 self.config.SetPath("/ProfileMRU")
299                 self.profileFileHistory.Save(self.config)
300                 self.config.Flush()
301                 # Load Profile  
302                 profile.loadProfile(path)
303                 self.updateProfileToControls()
304
305         def addToProfileMRU(self, file):
306                 self.profileFileHistory.AddFileToHistory(file)
307                 self.config.SetPath("/ProfileMRU")
308                 self.profileFileHistory.Save(self.config)
309                 self.config.Flush()                     
310
311         def updateProfileToControls(self):
312                 self.scene.updateProfileToControls()
313                 self.normalSettingsPanel.updateProfileToControls()
314                 self.simpleSettingsPanel.updateProfileToControls()
315
316         def OnLoadProfile(self, e):
317                 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)
318                 dlg.SetWildcard("ini files (*.ini)|*.ini")
319                 if dlg.ShowModal() == wx.ID_OK:
320                         profileFile = dlg.GetPath()
321                         profile.loadProfile(profileFile)
322                         self.updateProfileToControls()
323
324                         # Update the Profile MRU
325                         self.addToProfileMRU(profileFile)
326                 dlg.Destroy()
327
328         def OnLoadProfileFromGcode(self, e):
329                 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)
330                 dlg.SetWildcard("gcode files (*.gcode)|*.gcode;*.g")
331                 if dlg.ShowModal() == wx.ID_OK:
332                         gcodeFile = dlg.GetPath()
333                         f = open(gcodeFile, 'r')
334                         hasProfile = False
335                         for line in f:
336                                 if line.startswith(';CURA_PROFILE_STRING:'):
337                                         profile.loadProfileFromString(line[line.find(':')+1:].strip())
338                                         hasProfile = True
339                         if hasProfile:
340                                 self.updateProfileToControls()
341                         else:
342                                 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)
343                 dlg.Destroy()
344
345         def OnSaveProfile(self, e):
346                 dlg=wx.FileDialog(self, _("Select profile file to save"), os.path.split(profile.getPreference('lastFile'))[0], style=wx.FD_SAVE)
347                 dlg.SetWildcard("ini files (*.ini)|*.ini")
348                 if dlg.ShowModal() == wx.ID_OK:
349                         profileFile = dlg.GetPath()
350                         profile.saveProfile(profileFile)
351                 dlg.Destroy()
352
353         def OnResetProfile(self, e):
354                 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)
355                 result = dlg.ShowModal() == wx.ID_YES
356                 dlg.Destroy()
357                 if result:
358                         profile.resetProfile()
359                         self.updateProfileToControls()
360
361         def OnSimpleSwitch(self, e):
362                 profile.putPreference('startMode', 'Simple')
363                 self.updateSliceMode()
364
365         def OnNormalSwitch(self, e):
366                 profile.putPreference('startMode', 'Normal')
367                 self.updateSliceMode()
368
369         def OnDefaultMarlinFirmware(self, e):
370                 firmwareInstall.InstallFirmware()
371
372         def OnCustomFirmware(self, e):
373                 if profile.getPreference('machine_type').startswith('ultimaker'):
374                         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)
375                 dlg=wx.FileDialog(self, _("Open firmware to upload"), os.path.split(profile.getPreference('lastFile'))[0], style=wx.FD_OPEN|wx.FD_FILE_MUST_EXIST)
376                 dlg.SetWildcard("HEX file (*.hex)|*.hex;*.HEX")
377                 if dlg.ShowModal() == wx.ID_OK:
378                         filename = dlg.GetPath()
379                         if not(os.path.exists(filename)):
380                                 return
381                         #For some reason my Ubuntu 10.10 crashes here.
382                         firmwareInstall.InstallFirmware(filename)
383
384         def OnFirstRunWizard(self, e):
385                 configWizard.configWizard()
386                 self.updateProfileToControls()
387
388         def OnBedLevelWizard(self, e):
389                 configWizard.bedLevelWizard()
390
391         def OnHeadOffsetWizard(self, e):
392                 configWizard.headOffsetWizard()
393
394         def OnExpertOpen(self, e):
395                 ecw = expertConfig.expertConfigWindow(lambda : self.scene.sceneUpdated())
396                 ecw.Centre()
397                 ecw.Show()
398
399         def OnMinecraftImport(self, e):
400                 mi = minecraftImport.minecraftImportWindow(self)
401                 mi.Centre()
402                 mi.Show(True)
403
404         def OnPIDDebugger(self, e):
405                 debugger = pidDebugger.debuggerWindow(self)
406                 debugger.Centre()
407                 debugger.Show(True)
408
409         def OnCheckForUpdate(self, e):
410                 newVersion = version.checkForNewerVersion()
411                 if newVersion is not None:
412                         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:
413                                 webbrowser.open(newVersion)
414                 else:
415                         wx.MessageBox(_("You are running the latest version of Cura!"), _("Awesome!"), wx.ICON_INFORMATION)
416
417         def OnAbout(self, e):
418                 info = wx.AboutDialogInfo()
419                 info.SetName("Cura")
420                 info.SetDescription(_("End solution for Open Source Fused Filament Fabrication 3D printing."))
421                 info.SetWebSite('http://software.ultimaker.com/')
422                 info.SetCopyright(_("Copyright (C) David Braam"))
423                 info.SetLicence("""
424     This program is free software: you can redistribute it and/or modify
425     it under the terms of the GNU Affero General Public License as published by
426     the Free Software Foundation, either version 3 of the License, or
427     (at your option) any later version.
428
429     This program is distributed in the hope that it will be useful,
430     but WITHOUT ANY WARRANTY; without even the implied warranty of
431     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
432     GNU Affero General Public License for more details.
433
434     You should have received a copy of the GNU Affero General Public License
435     along with this program.  If not, see <http://www.gnu.org/licenses/>.
436 """)
437                 wx.AboutBox(info)
438
439         def OnClose(self, e):
440                 profile.saveProfile(profile.getDefaultProfilePath())
441
442                 # Save the window position, size & state from the preferences file
443                 profile.putPreference('window_maximized', self.IsMaximized())
444                 if not self.IsMaximized() and not self.IsIconized():
445                         (posx, posy) = self.GetPosition()
446                         profile.putPreference('window_pos_x', posx)
447                         profile.putPreference('window_pos_y', posy)
448                         (width, height) = self.GetSize()
449                         profile.putPreference('window_width', width)
450                         profile.putPreference('window_height', height)                  
451                         
452                         # Save normal sash position.  If in normal mode (!simple mode), get last position of sash before saving it...
453                         isSimple = profile.getPreference('startMode') == 'Simple'
454                         if not isSimple:
455                                 self.normalSashPos = self.splitter.GetSashPosition()
456                         profile.putPreference('window_normal_sash', self.normalSashPos)
457
458                 #HACK: Set the paint function of the glCanvas to nothing so it won't keep refreshing. Which keeps wxWidgets from quiting.
459                 print "Closing down"
460                 self.scene.OnPaint = lambda e : e
461                 self.scene._slicer.cleanup()
462                 self.Destroy()
463
464         def OnQuit(self, e):
465                 self.Close()
466
467 class normalSettingsPanel(configBase.configPanelBase):
468         "Main user interface window"
469         def __init__(self, parent, callback = None):
470                 super(normalSettingsPanel, self).__init__(parent, callback)
471
472                 #Main tabs
473                 self.nb = wx.Notebook(self)
474                 self.SetSizer(wx.BoxSizer(wx.HORIZONTAL))
475                 self.GetSizer().Add(self.nb, 1, wx.EXPAND)
476
477                 (left, right, self.printPanel) = self.CreateDynamicConfigTab(self.nb, 'Basic')
478                 self._addSettingsToPanels('basic', left, right)
479                 self.SizeLabelWidths(left, right)
480                 
481                 (left, right, self.advancedPanel) = self.CreateDynamicConfigTab(self.nb, 'Advanced')
482                 self._addSettingsToPanels('advanced', left, right)
483                 self.SizeLabelWidths(left, right)
484
485                 #Plugin page
486                 self.pluginPanel = pluginPanel.pluginPanel(self.nb, callback)
487                 if len(self.pluginPanel.pluginList) > 0:
488                         self.nb.AddPage(self.pluginPanel, "Plugins")
489                 else:
490                         self.pluginPanel.Show(False)
491
492                 #Alteration page
493                 if profile.getMachineSetting('gcode_flavor') == 'UltiGCode':
494                         self.alterationPanel = None
495                 else:
496                         self.alterationPanel = alterationPanel.alterationPanel(self.nb, callback)
497                         self.nb.AddPage(self.alterationPanel, "Start/End-GCode")
498
499                 self.Bind(wx.EVT_SIZE, self.OnSize)
500
501                 self.nb.SetSize(self.GetSize())
502                 self.UpdateSize(self.printPanel)
503                 self.UpdateSize(self.advancedPanel)
504
505         def _addSettingsToPanels(self, category, left, right):
506                 count = len(profile.getSubCategoriesFor(category)) + len(profile.getSettingsForCategory(category))
507
508                 p = left
509                 n = 0
510                 for title in profile.getSubCategoriesFor(category):
511                         n += 1 + len(profile.getSettingsForCategory(category, title))
512                         if n > count / 2:
513                                 p = right
514                         configBase.TitleRow(p, title)
515                         for s in profile.getSettingsForCategory(category, title):
516                                 if s.checkConditions():
517                                         configBase.SettingRow(p, s.getName())
518
519         def SizeLabelWidths(self, left, right):
520                 leftWidth = self.getLabelColumnWidth(left)
521                 rightWidth = self.getLabelColumnWidth(right)
522                 maxWidth = max(leftWidth, rightWidth)
523                 self.setLabelColumnWidth(left, maxWidth)
524                 self.setLabelColumnWidth(right, maxWidth)
525
526         def OnSize(self, e):
527                 # Make the size of the Notebook control the same size as this control
528                 self.nb.SetSize(self.GetSize())
529                 
530                 # Propegate the OnSize() event (just in case)
531                 e.Skip()
532                 
533                 # Perform out resize magic
534                 self.UpdateSize(self.printPanel)
535                 self.UpdateSize(self.advancedPanel)
536         
537         def UpdateSize(self, configPanel):
538                 sizer = configPanel.GetSizer()
539                 
540                 # Pseudocde
541                 # if horizontal:
542                 #     if width(col1) < best_width(col1) || width(col2) < best_width(col2):
543                 #         switch to vertical
544                 # else:
545                 #     if width(col1) > (best_width(col1) + best_width(col1)):
546                 #         switch to horizontal
547                 #
548                                 
549                 col1 = configPanel.leftPanel
550                 colSize1 = col1.GetSize()
551                 colBestSize1 = col1.GetBestSize()
552                 col2 = configPanel.rightPanel
553                 colSize2 = col2.GetSize()
554                 colBestSize2 = col2.GetBestSize()
555
556                 orientation = sizer.GetOrientation()
557                 
558                 if orientation == wx.HORIZONTAL:
559                         if (colSize1[0] <= colBestSize1[0]) or (colSize2[0] <= colBestSize2[0]):
560                                 configPanel.Freeze()
561                                 sizer = wx.BoxSizer(wx.VERTICAL)
562                                 sizer.Add(configPanel.leftPanel, flag=wx.EXPAND)
563                                 sizer.Add(configPanel.rightPanel, flag=wx.EXPAND)
564                                 configPanel.SetSizer(sizer)
565                                 #sizer.Layout()
566                                 configPanel.Layout()
567                                 self.Layout()
568                                 configPanel.Thaw()
569                 else:
570                         if max(colSize1[0], colSize2[0]) > (colBestSize1[0] + colBestSize2[0]):
571                                 configPanel.Freeze()
572                                 sizer = wx.BoxSizer(wx.HORIZONTAL)
573                                 sizer.Add(configPanel.leftPanel, proportion=1, border=35, flag=wx.EXPAND)
574                                 sizer.Add(configPanel.rightPanel, proportion=1, flag=wx.EXPAND)
575                                 configPanel.SetSizer(sizer)
576                                 #sizer.Layout()
577                                 configPanel.Layout()
578                                 self.Layout()
579                                 configPanel.Thaw()
580
581         def updateProfileToControls(self):
582                 super(normalSettingsPanel, self).updateProfileToControls()
583                 if self.alterationPanel is not None:
584                         self.alterationPanel.updateProfileToControls()
585                 self.pluginPanel.updateProfileToControls()