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