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