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