chiark / gitweb /
Hopefully suppress the OpenClipboard failed message.
[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 import aboutWindow
18 from Cura.gui.util import dropTarget
19 #from Cura.gui.tools import batchRun
20 from Cura.gui.tools import pidDebugger
21 from Cura.gui.tools import minecraftImport
22 from Cura.util import profile
23 from Cura.util import version
24 from Cura.util import meshLoader
25
26 class mainWindow(wx.Frame):
27         def __init__(self):
28                 super(mainWindow, self).__init__(None, title='Cura - ' + version.getVersion())
29
30                 self.extruderCount = int(profile.getMachineSetting('extruder_amount'))
31
32                 wx.EVT_CLOSE(self, self.OnClose)
33
34                 # allow dropping any file, restrict later
35                 self.SetDropTarget(dropTarget.FileDropTarget(self.OnDropFiles))
36
37                 self.normalModeOnlyItems = []
38
39                 mruFile = os.path.join(profile.getBasePath(), 'mru_filelist.ini')
40                 self.config = wx.FileConfig(appName="Cura",
41                                                 localFilename=mruFile,
42                                                 style=wx.CONFIG_USE_LOCAL_FILE)
43
44                 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)]
45                 self.modelFileHistory = wx.FileHistory(10, self.ID_MRU_MODEL1)
46                 self.config.SetPath("/ModelMRU")
47                 self.modelFileHistory.Load(self.config)
48
49                 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)]
50                 self.profileFileHistory = wx.FileHistory(10, self.ID_MRU_PROFILE1)
51                 self.config.SetPath("/ProfileMRU")
52                 self.profileFileHistory.Load(self.config)
53
54                 self.menubar = wx.MenuBar()
55                 self.fileMenu = wx.Menu()
56                 i = self.fileMenu.Append(-1, _("Load model file...\tCTRL+L"))
57                 self.Bind(wx.EVT_MENU, lambda e: self.scene.showLoadModel(), i)
58                 i = self.fileMenu.Append(-1, _("Save model...\tCTRL+S"))
59                 self.Bind(wx.EVT_MENU, lambda e: self.scene.showSaveModel(), i)
60                 i = self.fileMenu.Append(-1, _("Clear platform"))
61                 self.Bind(wx.EVT_MENU, lambda e: self.scene.OnDeleteAll(e), i)
62
63                 self.fileMenu.AppendSeparator()
64                 i = self.fileMenu.Append(-1, _("Print...\tCTRL+P"))
65                 self.Bind(wx.EVT_MENU, lambda e: self.scene.showPrintWindow(), i)
66                 i = self.fileMenu.Append(-1, _("Save GCode..."))
67                 self.Bind(wx.EVT_MENU, lambda e: self.scene.showSaveGCode(), i)
68                 i = self.fileMenu.Append(-1, _("Show slice engine log..."))
69                 self.Bind(wx.EVT_MENU, lambda e: self.scene._showSliceLog(), i)
70
71                 self.fileMenu.AppendSeparator()
72                 i = self.fileMenu.Append(-1, _("Open Profile..."))
73                 self.normalModeOnlyItems.append(i)
74                 self.Bind(wx.EVT_MENU, self.OnLoadProfile, i)
75                 i = self.fileMenu.Append(-1, _("Save Profile..."))
76                 self.normalModeOnlyItems.append(i)
77                 self.Bind(wx.EVT_MENU, self.OnSaveProfile, i)
78                 i = self.fileMenu.Append(-1, _("Load Profile from GCode..."))
79                 self.normalModeOnlyItems.append(i)
80                 self.Bind(wx.EVT_MENU, self.OnLoadProfileFromGcode, i)
81                 self.fileMenu.AppendSeparator()
82                 i = self.fileMenu.Append(-1, _("Reset Profile to default"))
83                 self.normalModeOnlyItems.append(i)
84                 self.Bind(wx.EVT_MENU, self.OnResetProfile, i)
85
86                 self.fileMenu.AppendSeparator()
87                 i = self.fileMenu.Append(-1, _("Preferences...\tCTRL+,"))
88                 self.Bind(wx.EVT_MENU, self.OnPreferences, i)
89                 i = self.fileMenu.Append(-1, _("Machine settings..."))
90                 self.Bind(wx.EVT_MENU, self.OnMachineSettings, i)
91                 self.fileMenu.AppendSeparator()
92
93                 # Model MRU list
94                 modelHistoryMenu = wx.Menu()
95                 self.fileMenu.AppendMenu(wx.NewId(), '&' + _("Recent Model Files"), modelHistoryMenu)
96                 self.modelFileHistory.UseMenu(modelHistoryMenu)
97                 self.modelFileHistory.AddFilesToMenu()
98                 self.Bind(wx.EVT_MENU_RANGE, self.OnModelMRU, id=self.ID_MRU_MODEL1, id2=self.ID_MRU_MODEL10)
99
100                 # Profle MRU list
101                 profileHistoryMenu = wx.Menu()
102                 self.fileMenu.AppendMenu(wx.NewId(), _("Recent Profile Files"), profileHistoryMenu)
103                 self.profileFileHistory.UseMenu(profileHistoryMenu)
104                 self.profileFileHistory.AddFilesToMenu()
105                 self.Bind(wx.EVT_MENU_RANGE, self.OnProfileMRU, id=self.ID_MRU_PROFILE1, id2=self.ID_MRU_PROFILE10)
106
107                 self.fileMenu.AppendSeparator()
108                 i = self.fileMenu.Append(wx.ID_EXIT, _("Quit"))
109                 self.Bind(wx.EVT_MENU, self.OnQuit, i)
110                 self.menubar.Append(self.fileMenu, '&' + _("File"))
111
112                 toolsMenu = wx.Menu()
113                 #i = toolsMenu.Append(-1, 'Batch run...')
114                 #self.Bind(wx.EVT_MENU, self.OnBatchRun, i)
115                 #self.normalModeOnlyItems.append(i)
116
117                 if minecraftImport.hasMinecraft():
118                         i = toolsMenu.Append(-1, _("Minecraft map import..."))
119                         self.Bind(wx.EVT_MENU, self.OnMinecraftImport, i)
120
121                 if version.isDevVersion():
122                         i = toolsMenu.Append(-1, _("PID Debugger..."))
123                         self.Bind(wx.EVT_MENU, self.OnPIDDebugger, i)
124
125                 i = toolsMenu.Append(-1, _("Copy profile to clipboard"))
126                 self.Bind(wx.EVT_MENU, self.onCopyProfileClipboard,i)
127                 self.menubar.Append(toolsMenu, _("Tools"))
128
129                 #Machine menu for machine configuration/tooling
130                 self.machineMenu = wx.Menu()
131                 self.updateMachineMenu()
132
133                 self.menubar.Append(self.machineMenu, _("Machine"))
134
135                 expertMenu = wx.Menu()
136                 i = expertMenu.Append(-1, _("Switch to quickprint..."), kind=wx.ITEM_RADIO)
137                 self.switchToQuickprintMenuItem = i
138                 self.Bind(wx.EVT_MENU, self.OnSimpleSwitch, i)
139
140                 i = expertMenu.Append(-1, _("Switch to full settings..."), kind=wx.ITEM_RADIO)
141                 self.switchToNormalMenuItem = i
142                 self.Bind(wx.EVT_MENU, self.OnNormalSwitch, i)
143                 expertMenu.AppendSeparator()
144
145                 i = expertMenu.Append(-1, _("Open expert settings..."))
146                 self.normalModeOnlyItems.append(i)
147                 self.Bind(wx.EVT_MENU, self.OnExpertOpen, i)
148                 expertMenu.AppendSeparator()
149                 i = expertMenu.Append(-1, _("Run first run wizard..."))
150                 self.Bind(wx.EVT_MENU, self.OnFirstRunWizard, i)
151                 i = expertMenu.Append(-1, _("Run bed leveling wizard..."))
152                 self.Bind(wx.EVT_MENU, self.OnBedLevelWizard, i)
153                 if self.extruderCount > 1:
154                         i = expertMenu.Append(-1, _("Run head offset wizard..."))
155                         self.Bind(wx.EVT_MENU, self.OnHeadOffsetWizard, 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                                 if not wx.TheClipboard.Open():
258                                         return
259                                 do = wx.TextDataObject()
260                                 if wx.TheClipboard.GetData(do):
261                                         profileString = do.GetText()
262                                 wx.TheClipboard.Close()
263
264                                 startTag = "CURA_PROFILE_STRING:"
265                                 if startTag in profileString:
266                                         #print "Found correct syntax on clipboard"
267                                         profileString = profileString.replace("\n","").strip()
268                                         profileString = profileString[profileString.find(startTag)+len(startTag):]
269                                         if profileString != self.lastTriedClipboard:
270                                                 print profileString
271                                                 self.lastTriedClipboard = profileString
272                                                 profile.setProfileFromString(profileString)
273                                                 self.scene.notification.message("Loaded new profile from clipboard.")
274                                                 self.updateProfileToAllControls()
275                 except:
276                         print "Unable to read from clipboard"
277
278
279         def updateSliceMode(self):
280                 isSimple = profile.getPreference('startMode') == 'Simple'
281
282                 self.normalSettingsPanel.Show(not isSimple)
283                 self.simpleSettingsPanel.Show(isSimple)
284                 self.leftPane.Layout()
285
286                 for i in self.normalModeOnlyItems:
287                         i.Enable(not isSimple)
288                 if isSimple:
289                         self.switchToQuickprintMenuItem.Check()
290                 else:
291                         self.switchToNormalMenuItem.Check()
292
293                 # Set splitter sash position & size
294                 if isSimple:
295                         # Save normal mode sash
296                         self.normalSashPos = self.splitter.GetSashPosition()
297
298                         # Change location of sash to width of quick mode pane
299                         (width, height) = self.simpleSettingsPanel.GetSizer().GetSize()
300                         self.splitter.SetSashPosition(width, True)
301
302                         # Disable sash
303                         self.splitter.SetSashSize(0)
304                 else:
305                         self.splitter.SetSashPosition(self.normalSashPos, True)
306                         # Enabled sash
307                         self.splitter.SetSashSize(4)
308                 self.defaultFirmwareInstallMenuItem.Enable(firmwareInstall.getDefaultFirmware() is not None)
309                 self.scene.updateProfileToControls()
310
311         def OnPreferences(self, e):
312                 prefDialog = preferencesDialog.preferencesDialog(self)
313                 prefDialog.Centre()
314                 prefDialog.Show()
315                 wx.CallAfter(prefDialog.Show)
316
317         def OnMachineSettings(self, e):
318                 prefDialog = preferencesDialog.machineSettingsDialog(self)
319                 prefDialog.Centre()
320                 prefDialog.Show()
321
322         def OnDropFiles(self, files):
323                 if len(files) > 0:
324                         profile.setPluginConfig([])
325                         self.updateProfileToAllControls()
326                 self.scene.loadFiles(files)
327
328         def OnModelMRU(self, e):
329                 fileNum = e.GetId() - self.ID_MRU_MODEL1
330                 path = self.modelFileHistory.GetHistoryFile(fileNum)
331                 # Update Model MRU
332                 self.modelFileHistory.AddFileToHistory(path)  # move up the list
333                 self.config.SetPath("/ModelMRU")
334                 self.modelFileHistory.Save(self.config)
335                 self.config.Flush()
336                 # Load Model
337                 profile.putPreference('lastFile', path)
338                 filelist = [ path ]
339                 self.scene.loadFiles(filelist)
340
341         def addToModelMRU(self, file):
342                 self.modelFileHistory.AddFileToHistory(file)
343                 self.config.SetPath("/ModelMRU")
344                 self.modelFileHistory.Save(self.config)
345                 self.config.Flush()
346
347         def OnProfileMRU(self, e):
348                 fileNum = e.GetId() - self.ID_MRU_PROFILE1
349                 path = self.profileFileHistory.GetHistoryFile(fileNum)
350                 # Update Profile MRU
351                 self.profileFileHistory.AddFileToHistory(path)  # move up the list
352                 self.config.SetPath("/ProfileMRU")
353                 self.profileFileHistory.Save(self.config)
354                 self.config.Flush()
355                 # Load Profile
356                 profile.loadProfile(path)
357                 self.updateProfileToAllControls()
358
359         def addToProfileMRU(self, file):
360                 self.profileFileHistory.AddFileToHistory(file)
361                 self.config.SetPath("/ProfileMRU")
362                 self.profileFileHistory.Save(self.config)
363                 self.config.Flush()
364
365         def updateProfileToAllControls(self):
366                 self.scene.updateProfileToControls()
367                 self.normalSettingsPanel.updateProfileToControls()
368                 self.simpleSettingsPanel.updateProfileToControls()
369
370         def reloadSettingPanels(self):
371                 self.leftSizer.Detach(self.simpleSettingsPanel)
372                 self.leftSizer.Detach(self.normalSettingsPanel)
373                 self.simpleSettingsPanel.Destroy()
374                 self.normalSettingsPanel.Destroy()
375                 self.simpleSettingsPanel = simpleMode.simpleModePanel(self.leftPane, lambda : self.scene.sceneUpdated())
376                 self.normalSettingsPanel = normalSettingsPanel(self.leftPane, lambda : self.scene.sceneUpdated())
377                 self.leftSizer.Add(self.simpleSettingsPanel, 1)
378                 self.leftSizer.Add(self.normalSettingsPanel, 1, wx.EXPAND)
379                 self.updateSliceMode()
380                 self.updateProfileToAllControls()
381
382         def updateMachineMenu(self):
383                 #Remove all items so we can rebuild the menu. Inserting items seems to cause crashes, so this is the safest way.
384                 for item in self.machineMenu.GetMenuItems():
385                         self.machineMenu.RemoveItem(item)
386
387                 #Add a menu item for each machine configuration.
388                 for n in xrange(0, profile.getMachineCount()):
389                         i = self.machineMenu.Append(n + 0x1000, profile.getMachineSetting('machine_name', n).title(), kind=wx.ITEM_RADIO)
390                         if n == int(profile.getPreferenceFloat('active_machine')):
391                                 i.Check(True)
392                         self.Bind(wx.EVT_MENU, lambda e: self.OnSelectMachine(e.GetId() - 0x1000), i)
393
394                 self.machineMenu.AppendSeparator()
395                 i = self.machineMenu.Append(-1, _("Add new machine..."))
396                 self.Bind(wx.EVT_MENU, self.OnAddNewMachine, i)
397
398                 #Add tools for machines.
399                 self.machineMenu.AppendSeparator()
400
401                 self.defaultFirmwareInstallMenuItem = self.machineMenu.Append(-1, _("Install default firmware..."))
402                 self.Bind(wx.EVT_MENU, self.OnDefaultMarlinFirmware, self.defaultFirmwareInstallMenuItem)
403
404                 i = self.machineMenu.Append(-1, _("Install custom firmware..."))
405                 self.Bind(wx.EVT_MENU, self.OnCustomFirmware, i)
406
407         def OnLoadProfile(self, e):
408                 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)
409                 dlg.SetWildcard("ini files (*.ini)|*.ini")
410                 if dlg.ShowModal() == wx.ID_OK:
411                         profileFile = dlg.GetPath()
412                         profile.loadProfile(profileFile)
413                         self.updateProfileToAllControls()
414
415                         # Update the Profile MRU
416                         self.addToProfileMRU(profileFile)
417                 dlg.Destroy()
418
419         def OnLoadProfileFromGcode(self, e):
420                 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)
421                 dlg.SetWildcard("gcode files (*.gcode)|*.gcode;*.g")
422                 if dlg.ShowModal() == wx.ID_OK:
423                         gcodeFile = dlg.GetPath()
424                         f = open(gcodeFile, 'r')
425                         hasProfile = False
426                         for line in f:
427                                 if line.startswith(';CURA_PROFILE_STRING:'):
428                                         profile.setProfileFromString(line[line.find(':')+1:].strip())
429                                         hasProfile = True
430                         if hasProfile:
431                                 self.updateProfileToAllControls()
432                         else:
433                                 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)
434                 dlg.Destroy()
435
436         def OnSaveProfile(self, e):
437                 dlg=wx.FileDialog(self, _("Select profile file to save"), os.path.split(profile.getPreference('lastFile'))[0], style=wx.FD_SAVE)
438                 dlg.SetWildcard("ini files (*.ini)|*.ini")
439                 if dlg.ShowModal() == wx.ID_OK:
440                         profileFile = dlg.GetPath()
441                         profile.saveProfile(profileFile)
442                 dlg.Destroy()
443
444         def OnResetProfile(self, e):
445                 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)
446                 result = dlg.ShowModal() == wx.ID_YES
447                 dlg.Destroy()
448                 if result:
449                         profile.resetProfile()
450                         self.updateProfileToAllControls()
451
452         def OnSimpleSwitch(self, e):
453                 profile.putPreference('startMode', 'Simple')
454                 self.updateSliceMode()
455
456         def OnNormalSwitch(self, e):
457                 profile.putPreference('startMode', 'Normal')
458                 self.updateSliceMode()
459
460         def OnDefaultMarlinFirmware(self, e):
461                 firmwareInstall.InstallFirmware()
462
463         def OnCustomFirmware(self, e):
464                 if profile.getMachineSetting('machine_type').startswith('ultimaker'):
465                         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)
466                 dlg=wx.FileDialog(self, _("Open firmware to upload"), os.path.split(profile.getPreference('lastFile'))[0], style=wx.FD_OPEN|wx.FD_FILE_MUST_EXIST)
467                 dlg.SetWildcard("HEX file (*.hex)|*.hex;*.HEX")
468                 if dlg.ShowModal() == wx.ID_OK:
469                         filename = dlg.GetPath()
470                         if not(os.path.exists(filename)):
471                                 return
472                         #For some reason my Ubuntu 10.10 crashes here.
473                         firmwareInstall.InstallFirmware(filename)
474
475         def OnFirstRunWizard(self, e):
476                 self.Hide()
477                 configWizard.configWizard()
478                 self.Show()
479                 self.reloadSettingPanels()
480
481         def OnAddNewMachine(self, e):
482                 self.Hide()
483                 profile.setActiveMachine(profile.getMachineCount())
484                 configWizard.configWizard(True)
485                 self.Show()
486                 self.reloadSettingPanels()
487                 self.updateMachineMenu()
488
489         def OnSelectMachine(self, index):
490                 profile.setActiveMachine(index)
491                 self.reloadSettingPanels()
492
493         def OnBedLevelWizard(self, e):
494                 configWizard.bedLevelWizard()
495
496         def OnHeadOffsetWizard(self, e):
497                 configWizard.headOffsetWizard()
498
499         def OnExpertOpen(self, e):
500                 ecw = expertConfig.expertConfigWindow(lambda : self.scene.sceneUpdated())
501                 ecw.Centre()
502                 ecw.Show()
503
504         def OnMinecraftImport(self, e):
505                 mi = minecraftImport.minecraftImportWindow(self)
506                 mi.Centre()
507                 mi.Show(True)
508
509         def OnPIDDebugger(self, e):
510                 debugger = pidDebugger.debuggerWindow(self)
511                 debugger.Centre()
512                 debugger.Show(True)
513
514         def onCopyProfileClipboard(self, e):
515                 try:
516                         if not wx.TheClipboard.IsOpened():
517                                 wx.TheClipboard.Open()
518                                 clipData = wx.TextDataObject()
519                                 self.lastTriedClipboard = profile.getProfileString()
520                                 profileString = profile.insertNewlines("CURA_PROFILE_STRING:" + self.lastTriedClipboard)
521                                 clipData.SetText(profileString)
522                                 wx.TheClipboard.SetData(clipData)
523                                 wx.TheClipboard.Close()
524                 except:
525                         print "Could not write to clipboard, unable to get ownership. Another program is using the clipboard."
526
527         def OnCheckForUpdate(self, e):
528                 newVersion = version.checkForNewerVersion()
529                 if newVersion is not None:
530                         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:
531                                 webbrowser.open(newVersion)
532                 else:
533                         wx.MessageBox(_("You are running the latest version of Cura!"), _("Awesome!"), wx.ICON_INFORMATION)
534
535         def OnAbout(self, e):
536                 aboutBox = aboutWindow.aboutWindow()
537                 aboutBox.Centre()
538                 aboutBox.Show()
539
540         def OnClose(self, e):
541                 profile.saveProfile(profile.getDefaultProfilePath())
542
543                 # Save the window position, size & state from the preferences file
544                 profile.putPreference('window_maximized', self.IsMaximized())
545                 if not self.IsMaximized() and not self.IsIconized():
546                         (posx, posy) = self.GetPosition()
547                         profile.putPreference('window_pos_x', posx)
548                         profile.putPreference('window_pos_y', posy)
549                         (width, height) = self.GetSize()
550                         profile.putPreference('window_width', width)
551                         profile.putPreference('window_height', height)
552
553                         # Save normal sash position.  If in normal mode (!simple mode), get last position of sash before saving it...
554                         isSimple = profile.getPreference('startMode') == 'Simple'
555                         if not isSimple:
556                                 self.normalSashPos = self.splitter.GetSashPosition()
557                         profile.putPreference('window_normal_sash', self.normalSashPos)
558
559                 #HACK: Set the paint function of the glCanvas to nothing so it won't keep refreshing. Which can keep wxWidgets from quiting.
560                 print "Closing down"
561                 self.scene.OnPaint = lambda e : e
562                 self.scene._slicer.cleanup()
563                 self.Destroy()
564
565         def OnQuit(self, e):
566                 self.Close()
567
568 class normalSettingsPanel(configBase.configPanelBase):
569         "Main user interface window"
570         def __init__(self, parent, callback = None):
571                 super(normalSettingsPanel, self).__init__(parent, callback)
572
573                 #Main tabs
574                 self.nb = wx.Notebook(self)
575                 self.SetSizer(wx.BoxSizer(wx.HORIZONTAL))
576                 self.GetSizer().Add(self.nb, 1, wx.EXPAND)
577
578                 (left, right, self.printPanel) = self.CreateDynamicConfigTab(self.nb, 'Basic')
579                 self._addSettingsToPanels('basic', left, right)
580                 self.SizeLabelWidths(left, right)
581
582                 (left, right, self.advancedPanel) = self.CreateDynamicConfigTab(self.nb, 'Advanced')
583                 self._addSettingsToPanels('advanced', left, right)
584                 self.SizeLabelWidths(left, right)
585
586                 #Plugin page
587                 self.pluginPanel = pluginPanel.pluginPanel(self.nb, callback)
588                 self.nb.AddPage(self.pluginPanel, _("Plugins"))
589
590                 #Alteration page
591                 if profile.getMachineSetting('gcode_flavor') == 'UltiGCode':
592                         self.alterationPanel = None
593                 else:
594                         self.alterationPanel = alterationPanel.alterationPanel(self.nb, callback)
595                         self.nb.AddPage(self.alterationPanel, "Start/End-GCode")
596
597                 self.Bind(wx.EVT_SIZE, self.OnSize)
598
599                 self.nb.SetSize(self.GetSize())
600                 self.UpdateSize(self.printPanel)
601                 self.UpdateSize(self.advancedPanel)
602
603         def _addSettingsToPanels(self, category, left, right):
604                 count = len(profile.getSubCategoriesFor(category)) + len(profile.getSettingsForCategory(category))
605
606                 p = left
607                 n = 0
608                 for title in profile.getSubCategoriesFor(category):
609                         n += 1 + len(profile.getSettingsForCategory(category, title))
610                         if n > count / 2:
611                                 p = right
612                         configBase.TitleRow(p, _(title))
613                         for s in profile.getSettingsForCategory(category, title):
614                                 configBase.SettingRow(p, s.getName())
615
616         def SizeLabelWidths(self, left, right):
617                 leftWidth = self.getLabelColumnWidth(left)
618                 rightWidth = self.getLabelColumnWidth(right)
619                 maxWidth = max(leftWidth, rightWidth)
620                 self.setLabelColumnWidth(left, maxWidth)
621                 self.setLabelColumnWidth(right, maxWidth)
622
623         def OnSize(self, e):
624                 # Make the size of the Notebook control the same size as this control
625                 self.nb.SetSize(self.GetSize())
626
627                 # Propegate the OnSize() event (just in case)
628                 e.Skip()
629
630                 # Perform out resize magic
631                 self.UpdateSize(self.printPanel)
632                 self.UpdateSize(self.advancedPanel)
633
634         def UpdateSize(self, configPanel):
635                 sizer = configPanel.GetSizer()
636
637                 # Pseudocde
638                 # if horizontal:
639                 #     if width(col1) < best_width(col1) || width(col2) < best_width(col2):
640                 #         switch to vertical
641                 # else:
642                 #     if width(col1) > (best_width(col1) + best_width(col1)):
643                 #         switch to horizontal
644                 #
645
646                 col1 = configPanel.leftPanel
647                 colSize1 = col1.GetSize()
648                 colBestSize1 = col1.GetBestSize()
649                 col2 = configPanel.rightPanel
650                 colSize2 = col2.GetSize()
651                 colBestSize2 = col2.GetBestSize()
652
653                 orientation = sizer.GetOrientation()
654
655                 if orientation == wx.HORIZONTAL:
656                         if (colSize1[0] <= colBestSize1[0]) or (colSize2[0] <= colBestSize2[0]):
657                                 configPanel.Freeze()
658                                 sizer = wx.BoxSizer(wx.VERTICAL)
659                                 sizer.Add(configPanel.leftPanel, flag=wx.EXPAND)
660                                 sizer.Add(configPanel.rightPanel, flag=wx.EXPAND)
661                                 configPanel.SetSizer(sizer)
662                                 #sizer.Layout()
663                                 configPanel.Layout()
664                                 self.Layout()
665                                 configPanel.Thaw()
666                 else:
667                         if max(colSize1[0], colSize2[0]) > (colBestSize1[0] + colBestSize2[0]):
668                                 configPanel.Freeze()
669                                 sizer = wx.BoxSizer(wx.HORIZONTAL)
670                                 sizer.Add(configPanel.leftPanel, proportion=1, border=35, flag=wx.EXPAND)
671                                 sizer.Add(configPanel.rightPanel, proportion=1, flag=wx.EXPAND)
672                                 configPanel.SetSizer(sizer)
673                                 #sizer.Layout()
674                                 configPanel.Layout()
675                                 self.Layout()
676                                 configPanel.Thaw()
677
678         def updateProfileToControls(self):
679                 super(normalSettingsPanel, self).updateProfileToControls()
680                 if self.alterationPanel is not None:
681                         self.alterationPanel.updateProfileToControls()
682                 self.pluginPanel.updateProfileToControls()