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