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