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