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