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