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