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