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