chiark / gitweb /
Update UM2 firmware to 13.10-3 to fix the SD-Card read error and the 280C bug.
[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 + 0x1000, 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() - 0x1000), 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
400                 self.defaultFirmwareInstallMenuItem = self.machineMenu.Append(-1, _("Install default firmware..."))
401                 self.Bind(wx.EVT_MENU, self.OnDefaultMarlinFirmware, self.defaultFirmwareInstallMenuItem)
402
403                 i = self.machineMenu.Append(-1, _("Install custom firmware..."))
404                 self.Bind(wx.EVT_MENU, self.OnCustomFirmware, i)
405
406         def OnLoadProfile(self, e):
407                 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)
408                 dlg.SetWildcard("ini files (*.ini)|*.ini")
409                 if dlg.ShowModal() == wx.ID_OK:
410                         profileFile = dlg.GetPath()
411                         profile.loadProfile(profileFile)
412                         self.updateProfileToAllControls()
413
414                         # Update the Profile MRU
415                         self.addToProfileMRU(profileFile)
416                 dlg.Destroy()
417
418         def OnLoadProfileFromGcode(self, e):
419                 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)
420                 dlg.SetWildcard("gcode files (*.gcode)|*.gcode;*.g")
421                 if dlg.ShowModal() == wx.ID_OK:
422                         gcodeFile = dlg.GetPath()
423                         f = open(gcodeFile, 'r')
424                         hasProfile = False
425                         for line in f:
426                                 if line.startswith(';CURA_PROFILE_STRING:'):
427                                         profile.setProfileFromString(line[line.find(':')+1:].strip())
428                                         hasProfile = True
429                         if hasProfile:
430                                 self.updateProfileToAllControls()
431                         else:
432                                 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)
433                 dlg.Destroy()
434
435         def OnSaveProfile(self, e):
436                 dlg=wx.FileDialog(self, _("Select profile file to save"), os.path.split(profile.getPreference('lastFile'))[0], style=wx.FD_SAVE)
437                 dlg.SetWildcard("ini files (*.ini)|*.ini")
438                 if dlg.ShowModal() == wx.ID_OK:
439                         profileFile = dlg.GetPath()
440                         profile.saveProfile(profileFile)
441                 dlg.Destroy()
442
443         def OnResetProfile(self, e):
444                 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)
445                 result = dlg.ShowModal() == wx.ID_YES
446                 dlg.Destroy()
447                 if result:
448                         profile.resetProfile()
449                         self.updateProfileToAllControls()
450
451         def OnSimpleSwitch(self, e):
452                 profile.putPreference('startMode', 'Simple')
453                 self.updateSliceMode()
454
455         def OnNormalSwitch(self, e):
456                 profile.putPreference('startMode', 'Normal')
457                 self.updateSliceMode()
458
459         def OnDefaultMarlinFirmware(self, e):
460                 firmwareInstall.InstallFirmware()
461
462         def OnCustomFirmware(self, e):
463                 if profile.getMachineSetting('machine_type').startswith('ultimaker'):
464                         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)
465                 dlg=wx.FileDialog(self, _("Open firmware to upload"), os.path.split(profile.getPreference('lastFile'))[0], style=wx.FD_OPEN|wx.FD_FILE_MUST_EXIST)
466                 dlg.SetWildcard("HEX file (*.hex)|*.hex;*.HEX")
467                 if dlg.ShowModal() == wx.ID_OK:
468                         filename = dlg.GetPath()
469                         if not(os.path.exists(filename)):
470                                 return
471                         #For some reason my Ubuntu 10.10 crashes here.
472                         firmwareInstall.InstallFirmware(filename)
473
474         def OnFirstRunWizard(self, e):
475                 self.Hide()
476                 configWizard.configWizard()
477                 self.Show()
478                 self.reloadSettingPanels()
479
480         def OnAddNewMachine(self, e):
481                 self.Hide()
482                 profile.setActiveMachine(profile.getMachineCount())
483                 configWizard.configWizard(True)
484                 self.Show()
485                 self.reloadSettingPanels()
486                 self.updateMachineMenu()
487
488         def OnSelectMachine(self, index):
489                 profile.setActiveMachine(index)
490                 self.reloadSettingPanels()
491
492         def OnBedLevelWizard(self, e):
493                 configWizard.bedLevelWizard()
494
495         def OnHeadOffsetWizard(self, e):
496                 configWizard.headOffsetWizard()
497
498         def OnExpertOpen(self, e):
499                 ecw = expertConfig.expertConfigWindow(lambda : self.scene.sceneUpdated())
500                 ecw.Centre()
501                 ecw.Show()
502
503         def OnMinecraftImport(self, e):
504                 mi = minecraftImport.minecraftImportWindow(self)
505                 mi.Centre()
506                 mi.Show(True)
507
508         def OnPIDDebugger(self, e):
509                 debugger = pidDebugger.debuggerWindow(self)
510                 debugger.Centre()
511                 debugger.Show(True)
512
513         def onCopyProfileClipboard(self, e):
514                 try:
515                         if not wx.TheClipboard.IsOpened():
516                                 wx.TheClipboard.Open()
517                                 clipData = wx.TextDataObject()
518                                 self.lastTriedClipboard = profile.getProfileString()
519                                 profileString = profile.insertNewlines("CURA_PROFILE_STRING:" + self.lastTriedClipboard)
520                                 clipData.SetText(profileString)
521                                 wx.TheClipboard.SetData(clipData)
522                                 wx.TheClipboard.Close()
523                 except:
524                         print "Could not write to clipboard, unable to get ownership. Another program is using the clipboard."
525
526         def OnCheckForUpdate(self, e):
527                 newVersion = version.checkForNewerVersion()
528                 if newVersion is not None:
529                         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:
530                                 webbrowser.open(newVersion)
531                 else:
532                         wx.MessageBox(_("You are running the latest version of Cura!"), _("Awesome!"), wx.ICON_INFORMATION)
533
534         def OnAbout(self, e):
535                 aboutBox = aboutWindow.aboutWindow()
536                 aboutBox.Centre()
537                 aboutBox.Show()
538
539         def OnClose(self, e):
540                 profile.saveProfile(profile.getDefaultProfilePath())
541
542                 # Save the window position, size & state from the preferences file
543                 profile.putPreference('window_maximized', self.IsMaximized())
544                 if not self.IsMaximized() and not self.IsIconized():
545                         (posx, posy) = self.GetPosition()
546                         profile.putPreference('window_pos_x', posx)
547                         profile.putPreference('window_pos_y', posy)
548                         (width, height) = self.GetSize()
549                         profile.putPreference('window_width', width)
550                         profile.putPreference('window_height', height)
551
552                         # Save normal sash position.  If in normal mode (!simple mode), get last position of sash before saving it...
553                         isSimple = profile.getPreference('startMode') == 'Simple'
554                         if not isSimple:
555                                 self.normalSashPos = self.splitter.GetSashPosition()
556                         profile.putPreference('window_normal_sash', self.normalSashPos)
557
558                 #HACK: Set the paint function of the glCanvas to nothing so it won't keep refreshing. Which can keep wxWidgets from quiting.
559                 print "Closing down"
560                 self.scene.OnPaint = lambda e : e
561                 self.scene._slicer.cleanup()
562                 self.Destroy()
563
564         def OnQuit(self, e):
565                 self.Close()
566
567 class normalSettingsPanel(configBase.configPanelBase):
568         "Main user interface window"
569         def __init__(self, parent, callback = None):
570                 super(normalSettingsPanel, self).__init__(parent, callback)
571
572                 #Main tabs
573                 self.nb = wx.Notebook(self)
574                 self.SetSizer(wx.BoxSizer(wx.HORIZONTAL))
575                 self.GetSizer().Add(self.nb, 1, wx.EXPAND)
576
577                 (left, right, self.printPanel) = self.CreateDynamicConfigTab(self.nb, 'Basic')
578                 self._addSettingsToPanels('basic', left, right)
579                 self.SizeLabelWidths(left, right)
580
581                 (left, right, self.advancedPanel) = self.CreateDynamicConfigTab(self.nb, 'Advanced')
582                 self._addSettingsToPanels('advanced', left, right)
583                 self.SizeLabelWidths(left, right)
584
585                 #Plugin page
586                 self.pluginPanel = pluginPanel.pluginPanel(self.nb, callback)
587                 self.nb.AddPage(self.pluginPanel, _("Plugins"))
588
589                 #Alteration page
590                 if profile.getMachineSetting('gcode_flavor') == 'UltiGCode':
591                         self.alterationPanel = None
592                 else:
593                         self.alterationPanel = alterationPanel.alterationPanel(self.nb, callback)
594                         self.nb.AddPage(self.alterationPanel, "Start/End-GCode")
595
596                 self.Bind(wx.EVT_SIZE, self.OnSize)
597
598                 self.nb.SetSize(self.GetSize())
599                 self.UpdateSize(self.printPanel)
600                 self.UpdateSize(self.advancedPanel)
601
602         def _addSettingsToPanels(self, category, left, right):
603                 count = len(profile.getSubCategoriesFor(category)) + len(profile.getSettingsForCategory(category))
604
605                 p = left
606                 n = 0
607                 for title in profile.getSubCategoriesFor(category):
608                         n += 1 + len(profile.getSettingsForCategory(category, title))
609                         if n > count / 2:
610                                 p = right
611                         configBase.TitleRow(p, _(title))
612                         for s in profile.getSettingsForCategory(category, title):
613                                 configBase.SettingRow(p, s.getName())
614
615         def SizeLabelWidths(self, left, right):
616                 leftWidth = self.getLabelColumnWidth(left)
617                 rightWidth = self.getLabelColumnWidth(right)
618                 maxWidth = max(leftWidth, rightWidth)
619                 self.setLabelColumnWidth(left, maxWidth)
620                 self.setLabelColumnWidth(right, maxWidth)
621
622         def OnSize(self, e):
623                 # Make the size of the Notebook control the same size as this control
624                 self.nb.SetSize(self.GetSize())
625
626                 # Propegate the OnSize() event (just in case)
627                 e.Skip()
628
629                 # Perform out resize magic
630                 self.UpdateSize(self.printPanel)
631                 self.UpdateSize(self.advancedPanel)
632
633         def UpdateSize(self, configPanel):
634                 sizer = configPanel.GetSizer()
635
636                 # Pseudocde
637                 # if horizontal:
638                 #     if width(col1) < best_width(col1) || width(col2) < best_width(col2):
639                 #         switch to vertical
640                 # else:
641                 #     if width(col1) > (best_width(col1) + best_width(col1)):
642                 #         switch to horizontal
643                 #
644
645                 col1 = configPanel.leftPanel
646                 colSize1 = col1.GetSize()
647                 colBestSize1 = col1.GetBestSize()
648                 col2 = configPanel.rightPanel
649                 colSize2 = col2.GetSize()
650                 colBestSize2 = col2.GetBestSize()
651
652                 orientation = sizer.GetOrientation()
653
654                 if orientation == wx.HORIZONTAL:
655                         if (colSize1[0] <= colBestSize1[0]) or (colSize2[0] <= colBestSize2[0]):
656                                 configPanel.Freeze()
657                                 sizer = wx.BoxSizer(wx.VERTICAL)
658                                 sizer.Add(configPanel.leftPanel, flag=wx.EXPAND)
659                                 sizer.Add(configPanel.rightPanel, flag=wx.EXPAND)
660                                 configPanel.SetSizer(sizer)
661                                 #sizer.Layout()
662                                 configPanel.Layout()
663                                 self.Layout()
664                                 configPanel.Thaw()
665                 else:
666                         if max(colSize1[0], colSize2[0]) > (colBestSize1[0] + colBestSize2[0]):
667                                 configPanel.Freeze()
668                                 sizer = wx.BoxSizer(wx.HORIZONTAL)
669                                 sizer.Add(configPanel.leftPanel, proportion=1, border=35, flag=wx.EXPAND)
670                                 sizer.Add(configPanel.rightPanel, proportion=1, flag=wx.EXPAND)
671                                 configPanel.SetSizer(sizer)
672                                 #sizer.Layout()
673                                 configPanel.Layout()
674                                 self.Layout()
675                                 configPanel.Thaw()
676
677         def updateProfileToControls(self):
678                 super(normalSettingsPanel, self).updateProfileToControls()
679                 if self.alterationPanel is not None:
680                         self.alterationPanel.updateProfileToControls()
681                 self.pluginPanel.updateProfileToControls()