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