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