chiark / gitweb /
Only enable the default firmware menu item when we have a default firmware for the...
[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                                 wx.TheClipboard.Open()
258                                 do = wx.TextDataObject()
259                                 if wx.TheClipboard.GetData(do):
260                                         profileString = do.GetText()
261                                 wx.TheClipboard.Close()
262
263                                 startTag = "CURA_PROFILE_STRING:"
264                                 if startTag in profileString:
265                                         #print "Found correct syntax on clipboard"
266                                         profileString = profileString.replace("\n","").strip()
267                                         profileString = profileString[profileString.find(startTag)+len(startTag):]
268                                         if profileString != self.lastTriedClipboard:
269                                                 print profileString
270                                                 self.lastTriedClipboard = profileString
271                                                 profile.setProfileFromString(profileString)
272                                                 self.scene.notification.message("Loaded new profile from clipboard.")
273                                                 self.updateProfileToAllControls()
274                 except:
275                         print "Unable to read from clipboard"
276
277
278         def updateSliceMode(self):
279                 isSimple = profile.getPreference('startMode') == 'Simple'
280
281                 self.normalSettingsPanel.Show(not isSimple)
282                 self.simpleSettingsPanel.Show(isSimple)
283                 self.leftPane.Layout()
284
285                 for i in self.normalModeOnlyItems:
286                         i.Enable(not isSimple)
287                 if isSimple:
288                         self.switchToQuickprintMenuItem.Check()
289                 else:
290                         self.switchToNormalMenuItem.Check()
291
292                 # Set splitter sash position & size
293                 if isSimple:
294                         # Save normal mode sash
295                         self.normalSashPos = self.splitter.GetSashPosition()
296
297                         # Change location of sash to width of quick mode pane
298                         (width, height) = self.simpleSettingsPanel.GetSizer().GetSize()
299                         self.splitter.SetSashPosition(width, True)
300
301                         # Disable sash
302                         self.splitter.SetSashSize(0)
303                 else:
304                         self.splitter.SetSashPosition(self.normalSashPos, True)
305                         # Enabled sash
306                         self.splitter.SetSashSize(4)
307                 self.defaultFirmwareInstallMenuItem.Enable(firmwareInstall.getDefaultFirmware() is not None)
308                 self.scene.updateProfileToControls()
309
310         def OnPreferences(self, e):
311                 prefDialog = preferencesDialog.preferencesDialog(self)
312                 prefDialog.Centre()
313                 prefDialog.Show()
314                 wx.CallAfter(prefDialog.Show)
315
316         def OnMachineSettings(self, e):
317                 prefDialog = preferencesDialog.machineSettingsDialog(self)
318                 prefDialog.Centre()
319                 prefDialog.Show()
320
321         def OnDropFiles(self, files):
322                 if len(files) > 0:
323                         profile.setPluginConfig([])
324                         self.updateProfileToAllControls()
325                 self.scene.loadFiles(files)
326
327         def OnModelMRU(self, e):
328                 fileNum = e.GetId() - self.ID_MRU_MODEL1
329                 path = self.modelFileHistory.GetHistoryFile(fileNum)
330                 # Update Model MRU
331                 self.modelFileHistory.AddFileToHistory(path)  # move up the list
332                 self.config.SetPath("/ModelMRU")
333                 self.modelFileHistory.Save(self.config)
334                 self.config.Flush()
335                 # Load Model
336                 profile.putPreference('lastFile', path)
337                 filelist = [ path ]
338                 self.scene.loadFiles(filelist)
339
340         def addToModelMRU(self, file):
341                 self.modelFileHistory.AddFileToHistory(file)
342                 self.config.SetPath("/ModelMRU")
343                 self.modelFileHistory.Save(self.config)
344                 self.config.Flush()
345
346         def OnProfileMRU(self, e):
347                 fileNum = e.GetId() - self.ID_MRU_PROFILE1
348                 path = self.profileFileHistory.GetHistoryFile(fileNum)
349                 # Update Profile MRU
350                 self.profileFileHistory.AddFileToHistory(path)  # move up the list
351                 self.config.SetPath("/ProfileMRU")
352                 self.profileFileHistory.Save(self.config)
353                 self.config.Flush()
354                 # Load Profile
355                 profile.loadProfile(path)
356                 self.updateProfileToAllControls()
357
358         def addToProfileMRU(self, file):
359                 self.profileFileHistory.AddFileToHistory(file)
360                 self.config.SetPath("/ProfileMRU")
361                 self.profileFileHistory.Save(self.config)
362                 self.config.Flush()
363
364         def updateProfileToAllControls(self):
365                 self.scene.updateProfileToControls()
366                 self.normalSettingsPanel.updateProfileToControls()
367                 self.simpleSettingsPanel.updateProfileToControls()
368
369         def reloadSettingPanels(self):
370                 self.leftSizer.Detach(self.simpleSettingsPanel)
371                 self.leftSizer.Detach(self.normalSettingsPanel)
372                 self.simpleSettingsPanel.Destroy()
373                 self.normalSettingsPanel.Destroy()
374                 self.simpleSettingsPanel = simpleMode.simpleModePanel(self.leftPane, lambda : self.scene.sceneUpdated())
375                 self.normalSettingsPanel = normalSettingsPanel(self.leftPane, lambda : self.scene.sceneUpdated())
376                 self.leftSizer.Add(self.simpleSettingsPanel, 1)
377                 self.leftSizer.Add(self.normalSettingsPanel, 1, wx.EXPAND)
378                 self.updateSliceMode()
379                 self.updateProfileToAllControls()
380
381         def updateMachineMenu(self):
382                 #Remove all items so we can rebuild the menu. Inserting items seems to cause crashes, so this is the safest way.
383                 for item in self.machineMenu.GetMenuItems():
384                         self.machineMenu.RemoveItem(item)
385
386                 #Add a menu item for each machine configuration.
387                 for n in xrange(0, profile.getMachineCount()):
388                         i = self.machineMenu.Append(n, profile.getMachineSetting('machine_name', n).title(), kind=wx.ITEM_RADIO)
389                         if n == int(profile.getPreferenceFloat('active_machine')):
390                                 i.Check(True)
391                         self.Bind(wx.EVT_MENU, lambda e: self.OnSelectMachine(e.GetId()), i)
392
393                 self.machineMenu.AppendSeparator()
394                 i = self.machineMenu.Append(-1, _("Add new machine..."))
395                 self.Bind(wx.EVT_MENU, self.OnAddNewMachine, i)
396
397                 #Add tools for machines.
398                 self.machineMenu.AppendSeparator()
399                 i = self.machineMenu.Append(-1, _("Install custom firmware..."))
400                 self.Bind(wx.EVT_MENU, self.OnCustomFirmware, i)
401
402                 self.defaultFirmwareInstallMenuItem = self.machineMenu.Append(-1, _("Install default Marlin firmware..."))
403                 self.Bind(wx.EVT_MENU, self.OnDefaultMarlinFirmware, i)
404
405         def OnLoadProfile(self, e):
406                 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)
407                 dlg.SetWildcard("ini files (*.ini)|*.ini")
408                 if dlg.ShowModal() == wx.ID_OK:
409                         profileFile = dlg.GetPath()
410                         profile.loadProfile(profileFile)
411                         self.updateProfileToAllControls()
412
413                         # Update the Profile MRU
414                         self.addToProfileMRU(profileFile)
415                 dlg.Destroy()
416
417         def OnLoadProfileFromGcode(self, e):
418                 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)
419                 dlg.SetWildcard("gcode files (*.gcode)|*.gcode;*.g")
420                 if dlg.ShowModal() == wx.ID_OK:
421                         gcodeFile = dlg.GetPath()
422                         f = open(gcodeFile, 'r')
423                         hasProfile = False
424                         for line in f:
425                                 if line.startswith(';CURA_PROFILE_STRING:'):
426                                         profile.setProfileFromString(line[line.find(':')+1:].strip())
427                                         hasProfile = True
428                         if hasProfile:
429                                 self.updateProfileToAllControls()
430                         else:
431                                 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)
432                 dlg.Destroy()
433
434         def OnSaveProfile(self, e):
435                 dlg=wx.FileDialog(self, _("Select profile file to save"), os.path.split(profile.getPreference('lastFile'))[0], style=wx.FD_SAVE)
436                 dlg.SetWildcard("ini files (*.ini)|*.ini")
437                 if dlg.ShowModal() == wx.ID_OK:
438                         profileFile = dlg.GetPath()
439                         profile.saveProfile(profileFile)
440                 dlg.Destroy()
441
442         def OnResetProfile(self, e):
443                 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)
444                 result = dlg.ShowModal() == wx.ID_YES
445                 dlg.Destroy()
446                 if result:
447                         profile.resetProfile()
448                         self.updateProfileToAllControls()
449
450         def OnSimpleSwitch(self, e):
451                 profile.putPreference('startMode', 'Simple')
452                 self.updateSliceMode()
453
454         def OnNormalSwitch(self, e):
455                 profile.putPreference('startMode', 'Normal')
456                 self.updateSliceMode()
457
458         def OnDefaultMarlinFirmware(self, e):
459                 firmwareInstall.InstallFirmware()
460
461         def OnCustomFirmware(self, e):
462                 if profile.getMachineSetting('machine_type').startswith('ultimaker'):
463                         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)
464                 dlg=wx.FileDialog(self, _("Open firmware to upload"), os.path.split(profile.getPreference('lastFile'))[0], style=wx.FD_OPEN|wx.FD_FILE_MUST_EXIST)
465                 dlg.SetWildcard("HEX file (*.hex)|*.hex;*.HEX")
466                 if dlg.ShowModal() == wx.ID_OK:
467                         filename = dlg.GetPath()
468                         if not(os.path.exists(filename)):
469                                 return
470                         #For some reason my Ubuntu 10.10 crashes here.
471                         firmwareInstall.InstallFirmware(filename)
472
473         def OnFirstRunWizard(self, e):
474                 self.Hide()
475                 configWizard.configWizard()
476                 self.Show()
477                 self.reloadSettingPanels()
478
479         def OnAddNewMachine(self, e):
480                 self.Hide()
481                 profile.setActiveMachine(profile.getMachineCount())
482                 configWizard.configWizard(True)
483                 self.Show()
484                 self.reloadSettingPanels()
485                 self.updateMachineMenu()
486
487         def OnSelectMachine(self, index):
488                 profile.setActiveMachine(index)
489                 self.reloadSettingPanels()
490
491         def OnBedLevelWizard(self, e):
492                 configWizard.bedLevelWizard()
493
494         def OnHeadOffsetWizard(self, e):
495                 configWizard.headOffsetWizard()
496
497         def OnExpertOpen(self, e):
498                 ecw = expertConfig.expertConfigWindow(lambda : self.scene.sceneUpdated())
499                 ecw.Centre()
500                 ecw.Show()
501
502         def OnMinecraftImport(self, e):
503                 mi = minecraftImport.minecraftImportWindow(self)
504                 mi.Centre()
505                 mi.Show(True)
506
507         def OnPIDDebugger(self, e):
508                 debugger = pidDebugger.debuggerWindow(self)
509                 debugger.Centre()
510                 debugger.Show(True)
511
512         def onCopyProfileClipboard(self, e):
513                 try:
514                         if not wx.TheClipboard.IsOpened():
515                                 wx.TheClipboard.Open()
516                                 clipData = wx.TextDataObject()
517                                 self.lastTriedClipboard = profile.getProfileString()
518                                 profileString = profile.insertNewlines("CURA_PROFILE_STRING:" + self.lastTriedClipboard)
519                                 clipData.SetText(profileString)
520                                 wx.TheClipboard.SetData(clipData)
521                                 wx.TheClipboard.Close()
522                 except:
523                         print "Could not write to clipboard, unable to get ownership. Another program is using the clipboard."
524
525         def OnCheckForUpdate(self, e):
526                 newVersion = version.checkForNewerVersion()
527                 if newVersion is not None:
528                         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:
529                                 webbrowser.open(newVersion)
530                 else:
531                         wx.MessageBox(_("You are running the latest version of Cura!"), _("Awesome!"), wx.ICON_INFORMATION)
532
533         def OnAbout(self, e):
534                 aboutBox = aboutWindow.aboutWindow()
535                 aboutBox.Centre()
536                 aboutBox.Show()
537
538         def OnClose(self, e):
539                 profile.saveProfile(profile.getDefaultProfilePath())
540
541                 # Save the window position, size & state from the preferences file
542                 profile.putPreference('window_maximized', self.IsMaximized())
543                 if not self.IsMaximized() and not self.IsIconized():
544                         (posx, posy) = self.GetPosition()
545                         profile.putPreference('window_pos_x', posx)
546                         profile.putPreference('window_pos_y', posy)
547                         (width, height) = self.GetSize()
548                         profile.putPreference('window_width', width)
549                         profile.putPreference('window_height', height)
550
551                         # Save normal sash position.  If in normal mode (!simple mode), get last position of sash before saving it...
552                         isSimple = profile.getPreference('startMode') == 'Simple'
553                         if not isSimple:
554                                 self.normalSashPos = self.splitter.GetSashPosition()
555                         profile.putPreference('window_normal_sash', self.normalSashPos)
556
557                 #HACK: Set the paint function of the glCanvas to nothing so it won't keep refreshing. Which can keep wxWidgets from quiting.
558                 print "Closing down"
559                 self.scene.OnPaint = lambda e : e
560                 self.scene._slicer.cleanup()
561                 self.Destroy()
562
563         def OnQuit(self, e):
564                 self.Close()
565
566 class normalSettingsPanel(configBase.configPanelBase):
567         "Main user interface window"
568         def __init__(self, parent, callback = None):
569                 super(normalSettingsPanel, self).__init__(parent, callback)
570
571                 #Main tabs
572                 self.nb = wx.Notebook(self)
573                 self.SetSizer(wx.BoxSizer(wx.HORIZONTAL))
574                 self.GetSizer().Add(self.nb, 1, wx.EXPAND)
575
576                 (left, right, self.printPanel) = self.CreateDynamicConfigTab(self.nb, 'Basic')
577                 self._addSettingsToPanels('basic', left, right)
578                 self.SizeLabelWidths(left, right)
579
580                 (left, right, self.advancedPanel) = self.CreateDynamicConfigTab(self.nb, 'Advanced')
581                 self._addSettingsToPanels('advanced', left, right)
582                 self.SizeLabelWidths(left, right)
583
584                 #Plugin page
585                 self.pluginPanel = pluginPanel.pluginPanel(self.nb, callback)
586                 self.nb.AddPage(self.pluginPanel, _("Plugins"))
587
588                 #Alteration page
589                 if profile.getMachineSetting('gcode_flavor') == 'UltiGCode':
590                         self.alterationPanel = None
591                 else:
592                         self.alterationPanel = alterationPanel.alterationPanel(self.nb, callback)
593                         self.nb.AddPage(self.alterationPanel, "Start/End-GCode")
594
595                 self.Bind(wx.EVT_SIZE, self.OnSize)
596
597                 self.nb.SetSize(self.GetSize())
598                 self.UpdateSize(self.printPanel)
599                 self.UpdateSize(self.advancedPanel)
600
601         def _addSettingsToPanels(self, category, left, right):
602                 count = len(profile.getSubCategoriesFor(category)) + len(profile.getSettingsForCategory(category))
603
604                 p = left
605                 n = 0
606                 for title in profile.getSubCategoriesFor(category):
607                         n += 1 + len(profile.getSettingsForCategory(category, title))
608                         if n > count / 2:
609                                 p = right
610                         configBase.TitleRow(p, _(title))
611                         for s in profile.getSettingsForCategory(category, title):
612                                 configBase.SettingRow(p, s.getName())
613
614         def SizeLabelWidths(self, left, right):
615                 leftWidth = self.getLabelColumnWidth(left)
616                 rightWidth = self.getLabelColumnWidth(right)
617                 maxWidth = max(leftWidth, rightWidth)
618                 self.setLabelColumnWidth(left, maxWidth)
619                 self.setLabelColumnWidth(right, maxWidth)
620
621         def OnSize(self, e):
622                 # Make the size of the Notebook control the same size as this control
623                 self.nb.SetSize(self.GetSize())
624
625                 # Propegate the OnSize() event (just in case)
626                 e.Skip()
627
628                 # Perform out resize magic
629                 self.UpdateSize(self.printPanel)
630                 self.UpdateSize(self.advancedPanel)
631
632         def UpdateSize(self, configPanel):
633                 sizer = configPanel.GetSizer()
634
635                 # Pseudocde
636                 # if horizontal:
637                 #     if width(col1) < best_width(col1) || width(col2) < best_width(col2):
638                 #         switch to vertical
639                 # else:
640                 #     if width(col1) > (best_width(col1) + best_width(col1)):
641                 #         switch to horizontal
642                 #
643
644                 col1 = configPanel.leftPanel
645                 colSize1 = col1.GetSize()
646                 colBestSize1 = col1.GetBestSize()
647                 col2 = configPanel.rightPanel
648                 colSize2 = col2.GetSize()
649                 colBestSize2 = col2.GetBestSize()
650
651                 orientation = sizer.GetOrientation()
652
653                 if orientation == wx.HORIZONTAL:
654                         if (colSize1[0] <= colBestSize1[0]) or (colSize2[0] <= colBestSize2[0]):
655                                 configPanel.Freeze()
656                                 sizer = wx.BoxSizer(wx.VERTICAL)
657                                 sizer.Add(configPanel.leftPanel, flag=wx.EXPAND)
658                                 sizer.Add(configPanel.rightPanel, flag=wx.EXPAND)
659                                 configPanel.SetSizer(sizer)
660                                 #sizer.Layout()
661                                 configPanel.Layout()
662                                 self.Layout()
663                                 configPanel.Thaw()
664                 else:
665                         if max(colSize1[0], colSize2[0]) > (colBestSize1[0] + colBestSize2[0]):
666                                 configPanel.Freeze()
667                                 sizer = wx.BoxSizer(wx.HORIZONTAL)
668                                 sizer.Add(configPanel.leftPanel, proportion=1, border=35, flag=wx.EXPAND)
669                                 sizer.Add(configPanel.rightPanel, proportion=1, flag=wx.EXPAND)
670                                 configPanel.SetSizer(sizer)
671                                 #sizer.Layout()
672                                 configPanel.Layout()
673                                 self.Layout()
674                                 configPanel.Thaw()
675
676         def updateProfileToControls(self):
677                 super(normalSettingsPanel, self).updateProfileToControls()
678                 if self.alterationPanel is not None:
679                         self.alterationPanel.updateProfileToControls()
680                 self.pluginPanel.updateProfileToControls()