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