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