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