chiark / gitweb /
Update the GUI when changing machine settings.
[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 reloadSettingPanels(self):
359                 self.leftSizer.Detach(self.simpleSettingsPanel)
360                 self.leftSizer.Detach(self.normalSettingsPanel)
361                 self.simpleSettingsPanel.Destroy()
362                 self.normalSettingsPanel.Destroy()
363                 self.simpleSettingsPanel = simpleMode.simpleModePanel(self.leftPane, lambda : self.scene.sceneUpdated())
364                 self.normalSettingsPanel = normalSettingsPanel(self.leftPane, lambda : self.scene.sceneUpdated())
365                 self.leftSizer.Add(self.simpleSettingsPanel, 1)
366                 self.leftSizer.Add(self.normalSettingsPanel, 1, wx.EXPAND)
367                 self.updateSliceMode()
368                 self.updateProfileToAllControls()
369
370         def OnLoadProfile(self, e):
371                 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)
372                 dlg.SetWildcard("ini files (*.ini)|*.ini")
373                 if dlg.ShowModal() == wx.ID_OK:
374                         profileFile = dlg.GetPath()
375                         profile.loadProfile(profileFile)
376                         self.updateProfileToAllControls()
377
378                         # Update the Profile MRU
379                         self.addToProfileMRU(profileFile)
380                 dlg.Destroy()
381
382         def OnLoadProfileFromGcode(self, e):
383                 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)
384                 dlg.SetWildcard("gcode files (*.gcode)|*.gcode;*.g")
385                 if dlg.ShowModal() == wx.ID_OK:
386                         gcodeFile = dlg.GetPath()
387                         f = open(gcodeFile, 'r')
388                         hasProfile = False
389                         for line in f:
390                                 if line.startswith(';CURA_PROFILE_STRING:'):
391                                         profile.setProfileFromString(line[line.find(':')+1:].strip())
392                                         hasProfile = True
393                         if hasProfile:
394                                 self.updateProfileToAllControls()
395                         else:
396                                 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)
397                 dlg.Destroy()
398
399         def OnSaveProfile(self, e):
400                 dlg=wx.FileDialog(self, _("Select profile file to save"), os.path.split(profile.getPreference('lastFile'))[0], style=wx.FD_SAVE)
401                 dlg.SetWildcard("ini files (*.ini)|*.ini")
402                 if dlg.ShowModal() == wx.ID_OK:
403                         profileFile = dlg.GetPath()
404                         profile.saveProfile(profileFile)
405                 dlg.Destroy()
406
407         def OnResetProfile(self, e):
408                 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)
409                 result = dlg.ShowModal() == wx.ID_YES
410                 dlg.Destroy()
411                 if result:
412                         profile.resetProfile()
413                         self.updateProfileToAllControls()
414
415         def OnSimpleSwitch(self, e):
416                 profile.putPreference('startMode', 'Simple')
417                 self.updateSliceMode()
418
419         def OnNormalSwitch(self, e):
420                 profile.putPreference('startMode', 'Normal')
421                 self.updateSliceMode()
422
423         def OnDefaultMarlinFirmware(self, e):
424                 firmwareInstall.InstallFirmware()
425
426         def OnCustomFirmware(self, e):
427                 if profile.getMachineSetting('machine_type').startswith('ultimaker'):
428                         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)
429                 dlg=wx.FileDialog(self, _("Open firmware to upload"), os.path.split(profile.getPreference('lastFile'))[0], style=wx.FD_OPEN|wx.FD_FILE_MUST_EXIST)
430                 dlg.SetWildcard("HEX file (*.hex)|*.hex;*.HEX")
431                 if dlg.ShowModal() == wx.ID_OK:
432                         filename = dlg.GetPath()
433                         if not(os.path.exists(filename)):
434                                 return
435                         #For some reason my Ubuntu 10.10 crashes here.
436                         firmwareInstall.InstallFirmware(filename)
437
438         def OnFirstRunWizard(self, e):
439                 self.Hide()
440                 configWizard.configWizard()
441                 self.Show()
442                 self.reloadSettingPanels()
443
444         def OnAddNewMachine(self, e):
445                 self.Hide()
446                 n = 0
447                 while profile.getMachineSetting('machine_name', n) != '':
448                         n += 1
449                 profile.setActiveMachine(n)
450                 configWizard.configWizard(True)
451                 self.Show()
452                 self.reloadSettingPanels()
453
454         def OnBedLevelWizard(self, e):
455                 configWizard.bedLevelWizard()
456
457         def OnHeadOffsetWizard(self, e):
458                 configWizard.headOffsetWizard()
459
460         def OnExpertOpen(self, e):
461                 ecw = expertConfig.expertConfigWindow(lambda : self.scene.sceneUpdated())
462                 ecw.Centre()
463                 ecw.Show()
464
465         def OnMinecraftImport(self, e):
466                 mi = minecraftImport.minecraftImportWindow(self)
467                 mi.Centre()
468                 mi.Show(True)
469
470         def OnPIDDebugger(self, e):
471                 debugger = pidDebugger.debuggerWindow(self)
472                 debugger.Centre()
473                 debugger.Show(True)
474
475         def onCopyProfileClipboard(self, e):
476                 try:
477                         if not wx.TheClipboard.IsOpened():
478                                 wx.TheClipboard.Open()
479                                 clipData = wx.TextDataObject()
480                                 self.lastTriedClipboard = profile.getProfileString()
481                                 profileString = profile.insertNewlines("CURA_PROFILE_STRING:" + self.lastTriedClipboard)
482                                 clipData.SetText(profileString)
483                                 wx.TheClipboard.SetData(clipData)
484                                 wx.TheClipboard.Close()
485                 except:
486                         print "Could not write to clipboard, unable to get ownership. Another program is using the clipboard."
487
488         def OnCheckForUpdate(self, e):
489                 newVersion = version.checkForNewerVersion()
490                 if newVersion is not None:
491                         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:
492                                 webbrowser.open(newVersion)
493                 else:
494                         wx.MessageBox(_("You are running the latest version of Cura!"), _("Awesome!"), wx.ICON_INFORMATION)
495
496         def OnAbout(self, e):
497                 info = wx.AboutDialogInfo()
498                 info.SetName("Cura")
499                 info.SetDescription(_("End solution for Open Source Fused Filament Fabrication 3D printing."))
500                 info.SetWebSite('http://software.ultimaker.com/')
501                 info.SetCopyright(_("Copyright (C) David Braam"))
502                 info.SetLicence("""
503     This program is free software: you can redistribute it and/or modify
504     it under the terms of the GNU Affero General Public License as published by
505     the Free Software Foundation, either version 3 of the License, or
506     (at your option) any later version.
507
508     This program is distributed in the hope that it will be useful,
509     but WITHOUT ANY WARRANTY; without even the implied warranty of
510     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
511     GNU Affero General Public License for more details.
512
513     You should have received a copy of the GNU Affero General Public License
514     along with this program.  If not, see <http://www.gnu.org/licenses/>.
515 """)
516                 wx.AboutBox(info)
517
518         def OnClose(self, e):
519                 profile.saveProfile(profile.getDefaultProfilePath())
520
521                 # Save the window position, size & state from the preferences file
522                 profile.putPreference('window_maximized', self.IsMaximized())
523                 if not self.IsMaximized() and not self.IsIconized():
524                         (posx, posy) = self.GetPosition()
525                         profile.putPreference('window_pos_x', posx)
526                         profile.putPreference('window_pos_y', posy)
527                         (width, height) = self.GetSize()
528                         profile.putPreference('window_width', width)
529                         profile.putPreference('window_height', height)
530
531                         # Save normal sash position.  If in normal mode (!simple mode), get last position of sash before saving it...
532                         isSimple = profile.getPreference('startMode') == 'Simple'
533                         if not isSimple:
534                                 self.normalSashPos = self.splitter.GetSashPosition()
535                         profile.putPreference('window_normal_sash', self.normalSashPos)
536
537                 #HACK: Set the paint function of the glCanvas to nothing so it won't keep refreshing. Which keeps wxWidgets from quiting.
538                 print "Closing down"
539                 self.scene.OnPaint = lambda e : e
540                 self.scene._slicer.cleanup()
541                 self.Destroy()
542
543         def OnQuit(self, e):
544                 self.Close()
545
546 class normalSettingsPanel(configBase.configPanelBase):
547         "Main user interface window"
548         def __init__(self, parent, callback = None):
549                 super(normalSettingsPanel, self).__init__(parent, callback)
550
551                 #Main tabs
552                 self.nb = wx.Notebook(self)
553                 self.SetSizer(wx.BoxSizer(wx.HORIZONTAL))
554                 self.GetSizer().Add(self.nb, 1, wx.EXPAND)
555
556                 (left, right, self.printPanel) = self.CreateDynamicConfigTab(self.nb, 'Basic')
557                 self._addSettingsToPanels('basic', left, right)
558                 self.SizeLabelWidths(left, right)
559
560                 (left, right, self.advancedPanel) = self.CreateDynamicConfigTab(self.nb, 'Advanced')
561                 self._addSettingsToPanels('advanced', left, right)
562                 self.SizeLabelWidths(left, right)
563
564                 #Plugin page
565                 self.pluginPanel = pluginPanel.pluginPanel(self.nb, callback)
566                 if len(self.pluginPanel.pluginList) > 0:
567                         self.nb.AddPage(self.pluginPanel, "Plugins")
568                 else:
569                         self.pluginPanel.Show(False)
570
571                 #Alteration page
572                 if profile.getMachineSetting('gcode_flavor') == 'UltiGCode':
573                         self.alterationPanel = None
574                 else:
575                         self.alterationPanel = alterationPanel.alterationPanel(self.nb, callback)
576                         self.nb.AddPage(self.alterationPanel, "Start/End-GCode")
577
578                 self.Bind(wx.EVT_SIZE, self.OnSize)
579
580                 self.nb.SetSize(self.GetSize())
581                 self.UpdateSize(self.printPanel)
582                 self.UpdateSize(self.advancedPanel)
583
584         def _addSettingsToPanels(self, category, left, right):
585                 count = len(profile.getSubCategoriesFor(category)) + len(profile.getSettingsForCategory(category))
586
587                 p = left
588                 n = 0
589                 for title in profile.getSubCategoriesFor(category):
590                         n += 1 + len(profile.getSettingsForCategory(category, title))
591                         if n > count / 2:
592                                 p = right
593                         configBase.TitleRow(p, title)
594                         for s in profile.getSettingsForCategory(category, title):
595                                 configBase.SettingRow(p, s.getName())
596
597         def SizeLabelWidths(self, left, right):
598                 leftWidth = self.getLabelColumnWidth(left)
599                 rightWidth = self.getLabelColumnWidth(right)
600                 maxWidth = max(leftWidth, rightWidth)
601                 self.setLabelColumnWidth(left, maxWidth)
602                 self.setLabelColumnWidth(right, maxWidth)
603
604         def OnSize(self, e):
605                 # Make the size of the Notebook control the same size as this control
606                 self.nb.SetSize(self.GetSize())
607                 
608                 # Propegate the OnSize() event (just in case)
609                 e.Skip()
610                 
611                 # Perform out resize magic
612                 self.UpdateSize(self.printPanel)
613                 self.UpdateSize(self.advancedPanel)
614         
615         def UpdateSize(self, configPanel):
616                 sizer = configPanel.GetSizer()
617                 
618                 # Pseudocde
619                 # if horizontal:
620                 #     if width(col1) < best_width(col1) || width(col2) < best_width(col2):
621                 #         switch to vertical
622                 # else:
623                 #     if width(col1) > (best_width(col1) + best_width(col1)):
624                 #         switch to horizontal
625                 #
626                                 
627                 col1 = configPanel.leftPanel
628                 colSize1 = col1.GetSize()
629                 colBestSize1 = col1.GetBestSize()
630                 col2 = configPanel.rightPanel
631                 colSize2 = col2.GetSize()
632                 colBestSize2 = col2.GetBestSize()
633
634                 orientation = sizer.GetOrientation()
635                 
636                 if orientation == wx.HORIZONTAL:
637                         if (colSize1[0] <= colBestSize1[0]) or (colSize2[0] <= colBestSize2[0]):
638                                 configPanel.Freeze()
639                                 sizer = wx.BoxSizer(wx.VERTICAL)
640                                 sizer.Add(configPanel.leftPanel, flag=wx.EXPAND)
641                                 sizer.Add(configPanel.rightPanel, flag=wx.EXPAND)
642                                 configPanel.SetSizer(sizer)
643                                 #sizer.Layout()
644                                 configPanel.Layout()
645                                 self.Layout()
646                                 configPanel.Thaw()
647                 else:
648                         if max(colSize1[0], colSize2[0]) > (colBestSize1[0] + colBestSize2[0]):
649                                 configPanel.Freeze()
650                                 sizer = wx.BoxSizer(wx.HORIZONTAL)
651                                 sizer.Add(configPanel.leftPanel, proportion=1, border=35, flag=wx.EXPAND)
652                                 sizer.Add(configPanel.rightPanel, proportion=1, flag=wx.EXPAND)
653                                 configPanel.SetSizer(sizer)
654                                 #sizer.Layout()
655                                 configPanel.Layout()
656                                 self.Layout()
657                                 configPanel.Thaw()
658
659         def updateProfileToControls(self):
660                 super(normalSettingsPanel, self).updateProfileToControls()
661                 if self.alterationPanel is not None:
662                         self.alterationPanel.updateProfileToControls()
663                 self.pluginPanel.updateProfileToControls()