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