chiark / gitweb /
Push objects free if the head sizes are adjusted.
[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                 self.scene._scene.pushFree()
334
335         def onOneAtATimeSwitch(self, e):
336                 profile.putPreference('oneAtATime', self.oneAtATime.IsChecked())
337                 if self.oneAtATime.IsChecked() and profile.getMachineSettingFloat('extruder_head_size_height') < 1:
338                         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)
339                 self.scene.updateProfileToControls()
340                 self.scene._scene.pushFree()
341                 self.scene.sceneUpdated()
342
343         def OnPreferences(self, e):
344                 prefDialog = preferencesDialog.preferencesDialog(self)
345                 prefDialog.Centre()
346                 prefDialog.Show()
347                 prefDialog.Raise()
348                 wx.CallAfter(prefDialog.Show)
349
350         def OnMachineSettings(self, e):
351                 prefDialog = preferencesDialog.machineSettingsDialog(self)
352                 prefDialog.Centre()
353                 prefDialog.Show()
354                 prefDialog.Raise()
355
356         def OnDropFiles(self, files):
357                 if len(files) > 0:
358                         self.updateProfileToAllControls()
359                 self.scene.loadFiles(files)
360
361         def OnModelMRU(self, e):
362                 fileNum = e.GetId() - self.ID_MRU_MODEL1
363                 path = self.modelFileHistory.GetHistoryFile(fileNum)
364                 # Update Model MRU
365                 self.modelFileHistory.AddFileToHistory(path)  # move up the list
366                 self.config.SetPath("/ModelMRU")
367                 self.modelFileHistory.Save(self.config)
368                 self.config.Flush()
369                 # Load Model
370                 profile.putPreference('lastFile', path)
371                 filelist = [ path ]
372                 self.scene.loadFiles(filelist)
373
374         def addToModelMRU(self, file):
375                 self.modelFileHistory.AddFileToHistory(file)
376                 self.config.SetPath("/ModelMRU")
377                 self.modelFileHistory.Save(self.config)
378                 self.config.Flush()
379
380         def OnProfileMRU(self, e):
381                 fileNum = e.GetId() - self.ID_MRU_PROFILE1
382                 path = self.profileFileHistory.GetHistoryFile(fileNum)
383                 # Update Profile MRU
384                 self.profileFileHistory.AddFileToHistory(path)  # move up the list
385                 self.config.SetPath("/ProfileMRU")
386                 self.profileFileHistory.Save(self.config)
387                 self.config.Flush()
388                 # Load Profile
389                 profile.loadProfile(path)
390                 self.updateProfileToAllControls()
391
392         def addToProfileMRU(self, file):
393                 self.profileFileHistory.AddFileToHistory(file)
394                 self.config.SetPath("/ProfileMRU")
395                 self.profileFileHistory.Save(self.config)
396                 self.config.Flush()
397
398         def updateProfileToAllControls(self):
399                 self.scene.updateProfileToControls()
400                 self.normalSettingsPanel.updateProfileToControls()
401                 self.simpleSettingsPanel.updateProfileToControls()
402
403         def reloadSettingPanels(self):
404                 self.leftSizer.Detach(self.simpleSettingsPanel)
405                 self.leftSizer.Detach(self.normalSettingsPanel)
406                 self.simpleSettingsPanel.Destroy()
407                 self.normalSettingsPanel.Destroy()
408                 self.simpleSettingsPanel = simpleMode.simpleModePanel(self.leftPane, lambda : self.scene.sceneUpdated())
409                 self.normalSettingsPanel = normalSettingsPanel(self.leftPane, lambda : self.scene.sceneUpdated())
410                 self.leftSizer.Add(self.simpleSettingsPanel, 1)
411                 self.leftSizer.Add(self.normalSettingsPanel, 1, wx.EXPAND)
412                 self.updateSliceMode()
413                 self.updateProfileToAllControls()
414
415         def updateMachineMenu(self):
416                 #Remove all items so we can rebuild the menu. Inserting items seems to cause crashes, so this is the safest way.
417                 for item in self.machineMenu.GetMenuItems():
418                         self.machineMenu.RemoveItem(item)
419
420                 #Add a menu item for each machine configuration.
421                 for n in xrange(0, profile.getMachineCount()):
422                         i = self.machineMenu.Append(n + 0x1000, profile.getMachineSetting('machine_name', n).title(), kind=wx.ITEM_RADIO)
423                         if n == int(profile.getPreferenceFloat('active_machine')):
424                                 i.Check(True)
425                         self.Bind(wx.EVT_MENU, lambda e: self.OnSelectMachine(e.GetId() - 0x1000), i)
426
427                 self.machineMenu.AppendSeparator()
428
429                 i = self.machineMenu.Append(-1, _("Machine settings..."))
430                 self.Bind(wx.EVT_MENU, self.OnMachineSettings, i)
431
432                 #Add tools for machines.
433                 self.machineMenu.AppendSeparator()
434
435                 self.defaultFirmwareInstallMenuItem = self.machineMenu.Append(-1, _("Install default firmware..."))
436                 self.Bind(wx.EVT_MENU, self.OnDefaultMarlinFirmware, self.defaultFirmwareInstallMenuItem)
437
438                 i = self.machineMenu.Append(-1, _("Install custom firmware..."))
439                 self.Bind(wx.EVT_MENU, self.OnCustomFirmware, i)
440
441         def OnLoadProfile(self, e):
442                 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)
443                 dlg.SetWildcard("ini files (*.ini)|*.ini")
444                 if dlg.ShowModal() == wx.ID_OK:
445                         profileFile = dlg.GetPath()
446                         profile.loadProfile(profileFile)
447                         self.updateProfileToAllControls()
448
449                         # Update the Profile MRU
450                         self.addToProfileMRU(profileFile)
451                 dlg.Destroy()
452
453         def OnLoadProfileFromGcode(self, e):
454                 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)
455                 dlg.SetWildcard("gcode files (*.gcode)|*.gcode;*.g")
456                 if dlg.ShowModal() == wx.ID_OK:
457                         gcodeFile = dlg.GetPath()
458                         f = open(gcodeFile, 'r')
459                         hasProfile = False
460                         for line in f:
461                                 if line.startswith(';CURA_PROFILE_STRING:'):
462                                         profile.setProfileFromString(line[line.find(':')+1:].strip())
463                                         hasProfile = True
464                         if hasProfile:
465                                 self.updateProfileToAllControls()
466                         else:
467                                 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)
468                 dlg.Destroy()
469
470         def OnSaveProfile(self, e):
471                 dlg=wx.FileDialog(self, _("Select profile file to save"), os.path.split(profile.getPreference('lastFile'))[0], style=wx.FD_SAVE)
472                 dlg.SetWildcard("ini files (*.ini)|*.ini")
473                 if dlg.ShowModal() == wx.ID_OK:
474                         profileFile = dlg.GetPath()
475                         profile.saveProfile(profileFile)
476                 dlg.Destroy()
477
478         def OnResetProfile(self, e):
479                 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)
480                 result = dlg.ShowModal() == wx.ID_YES
481                 dlg.Destroy()
482                 if result:
483                         profile.resetProfile()
484                         self.updateProfileToAllControls()
485
486         def OnSimpleSwitch(self, e):
487                 profile.putPreference('startMode', 'Simple')
488                 self.updateSliceMode()
489
490         def OnNormalSwitch(self, e):
491                 profile.putPreference('startMode', 'Normal')
492                 self.updateSliceMode()
493
494         def OnDefaultMarlinFirmware(self, e):
495                 firmwareInstall.InstallFirmware()
496
497         def OnCustomFirmware(self, e):
498                 if profile.getMachineSetting('machine_type').startswith('ultimaker'):
499                         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)
500                 dlg=wx.FileDialog(self, _("Open firmware to upload"), os.path.split(profile.getPreference('lastFile'))[0], style=wx.FD_OPEN|wx.FD_FILE_MUST_EXIST)
501                 dlg.SetWildcard("HEX file (*.hex)|*.hex;*.HEX")
502                 if dlg.ShowModal() == wx.ID_OK:
503                         filename = dlg.GetPath()
504                         if not(os.path.exists(filename)):
505                                 return
506                         #For some reason my Ubuntu 10.10 crashes here.
507                         firmwareInstall.InstallFirmware(filename)
508
509         def OnFirstRunWizard(self, e):
510                 self.Hide()
511                 configWizard.configWizard()
512                 self.Show()
513                 self.reloadSettingPanels()
514
515         def OnSelectMachine(self, index):
516                 profile.setActiveMachine(index)
517                 self.reloadSettingPanels()
518
519         def OnBedLevelWizard(self, e):
520                 configWizard.bedLevelWizard()
521
522         def OnHeadOffsetWizard(self, e):
523                 configWizard.headOffsetWizard()
524
525         def OnExpertOpen(self, e):
526                 ecw = expertConfig.expertConfigWindow(lambda : self.scene.sceneUpdated())
527                 ecw.Centre()
528                 ecw.Show()
529
530         def OnMinecraftImport(self, e):
531                 mi = minecraftImport.minecraftImportWindow(self)
532                 mi.Centre()
533                 mi.Show(True)
534
535         def OnPIDDebugger(self, e):
536                 debugger = pidDebugger.debuggerWindow(self)
537                 debugger.Centre()
538                 debugger.Show(True)
539
540         def onCopyProfileClipboard(self, e):
541                 try:
542                         if not wx.TheClipboard.IsOpened():
543                                 wx.TheClipboard.Open()
544                                 clipData = wx.TextDataObject()
545                                 self.lastTriedClipboard = profile.getProfileString()
546                                 profileString = profile.insertNewlines("CURA_PROFILE_STRING:" + self.lastTriedClipboard)
547                                 clipData.SetText(profileString)
548                                 wx.TheClipboard.SetData(clipData)
549                                 wx.TheClipboard.Close()
550                 except:
551                         print "Could not write to clipboard, unable to get ownership. Another program is using the clipboard."
552
553         def OnCheckForUpdate(self, e):
554                 newVersion = version.checkForNewerVersion()
555                 if newVersion is not None:
556                         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:
557                                 webbrowser.open(newVersion)
558                 else:
559                         wx.MessageBox(_("You are running the latest version of Cura!"), _("Awesome!"), wx.ICON_INFORMATION)
560
561         def OnAbout(self, e):
562                 aboutBox = aboutWindow.aboutWindow()
563                 aboutBox.Centre()
564                 aboutBox.Show()
565
566         def OnClose(self, e):
567                 profile.saveProfile(profile.getDefaultProfilePath(), True)
568
569                 # Save the window position, size & state from the preferences file
570                 profile.putPreference('window_maximized', self.IsMaximized())
571                 if not self.IsMaximized() and not self.IsIconized():
572                         (posx, posy) = self.GetPosition()
573                         profile.putPreference('window_pos_x', posx)
574                         profile.putPreference('window_pos_y', posy)
575                         (width, height) = self.GetSize()
576                         profile.putPreference('window_width', width)
577                         profile.putPreference('window_height', height)
578
579                         # Save normal sash position.  If in normal mode (!simple mode), get last position of sash before saving it...
580                         isSimple = profile.getPreference('startMode') == 'Simple'
581                         if not isSimple:
582                                 self.normalSashPos = self.splitter.GetSashPosition()
583                         profile.putPreference('window_normal_sash', self.normalSashPos)
584
585                 #HACK: Set the paint function of the glCanvas to nothing so it won't keep refreshing. Which can keep wxWidgets from quiting.
586                 print "Closing down"
587                 self.scene.OnPaint = lambda e : e
588                 self.scene._engine.cleanup()
589                 self.Destroy()
590
591         def OnQuit(self, e):
592                 self.Close()
593
594 class normalSettingsPanel(configBase.configPanelBase):
595         "Main user interface window"
596         def __init__(self, parent, callback = None):
597                 super(normalSettingsPanel, self).__init__(parent, callback)
598
599                 #Main tabs
600                 self.nb = wx.Notebook(self)
601                 self.SetSizer(wx.BoxSizer(wx.HORIZONTAL))
602                 self.GetSizer().Add(self.nb, 1, wx.EXPAND)
603
604                 (left, right, self.printPanel) = self.CreateDynamicConfigTab(self.nb, 'Basic')
605                 self._addSettingsToPanels('basic', left, right)
606                 self.SizeLabelWidths(left, right)
607
608                 (left, right, self.advancedPanel) = self.CreateDynamicConfigTab(self.nb, 'Advanced')
609                 self._addSettingsToPanels('advanced', left, right)
610                 self.SizeLabelWidths(left, right)
611
612                 #Plugin page
613                 self.pluginPanel = pluginPanel.pluginPanel(self.nb, callback)
614                 self.nb.AddPage(self.pluginPanel, _("Plugins"))
615
616                 #Alteration page
617                 if profile.getMachineSetting('gcode_flavor') == 'UltiGCode':
618                         self.alterationPanel = None
619                 else:
620                         self.alterationPanel = alterationPanel.alterationPanel(self.nb, callback)
621                         self.nb.AddPage(self.alterationPanel, "Start/End-GCode")
622
623                 self.Bind(wx.EVT_SIZE, self.OnSize)
624
625                 self.nb.SetSize(self.GetSize())
626                 self.UpdateSize(self.printPanel)
627                 self.UpdateSize(self.advancedPanel)
628
629         def _addSettingsToPanels(self, category, left, right):
630                 count = len(profile.getSubCategoriesFor(category)) + len(profile.getSettingsForCategory(category))
631
632                 p = left
633                 n = 0
634                 for title in profile.getSubCategoriesFor(category):
635                         n += 1 + len(profile.getSettingsForCategory(category, title))
636                         if n > count / 2:
637                                 p = right
638                         configBase.TitleRow(p, _(title))
639                         for s in profile.getSettingsForCategory(category, title):
640                                 configBase.SettingRow(p, s.getName())
641
642         def SizeLabelWidths(self, left, right):
643                 leftWidth = self.getLabelColumnWidth(left)
644                 rightWidth = self.getLabelColumnWidth(right)
645                 maxWidth = max(leftWidth, rightWidth)
646                 self.setLabelColumnWidth(left, maxWidth)
647                 self.setLabelColumnWidth(right, maxWidth)
648
649         def OnSize(self, e):
650                 # Make the size of the Notebook control the same size as this control
651                 self.nb.SetSize(self.GetSize())
652
653                 # Propegate the OnSize() event (just in case)
654                 e.Skip()
655
656                 # Perform out resize magic
657                 self.UpdateSize(self.printPanel)
658                 self.UpdateSize(self.advancedPanel)
659
660         def UpdateSize(self, configPanel):
661                 sizer = configPanel.GetSizer()
662
663                 # Pseudocde
664                 # if horizontal:
665                 #     if width(col1) < best_width(col1) || width(col2) < best_width(col2):
666                 #         switch to vertical
667                 # else:
668                 #     if width(col1) > (best_width(col1) + best_width(col1)):
669                 #         switch to horizontal
670                 #
671
672                 col1 = configPanel.leftPanel
673                 colSize1 = col1.GetSize()
674                 colBestSize1 = col1.GetBestSize()
675                 col2 = configPanel.rightPanel
676                 colSize2 = col2.GetSize()
677                 colBestSize2 = col2.GetBestSize()
678
679                 orientation = sizer.GetOrientation()
680
681                 if orientation == wx.HORIZONTAL:
682                         if (colSize1[0] <= colBestSize1[0]) or (colSize2[0] <= colBestSize2[0]):
683                                 configPanel.Freeze()
684                                 sizer = wx.BoxSizer(wx.VERTICAL)
685                                 sizer.Add(configPanel.leftPanel, flag=wx.EXPAND)
686                                 sizer.Add(configPanel.rightPanel, flag=wx.EXPAND)
687                                 configPanel.SetSizer(sizer)
688                                 #sizer.Layout()
689                                 configPanel.Layout()
690                                 self.Layout()
691                                 configPanel.Thaw()
692                 else:
693                         if max(colSize1[0], colSize2[0]) > (colBestSize1[0] + colBestSize2[0]):
694                                 configPanel.Freeze()
695                                 sizer = wx.BoxSizer(wx.HORIZONTAL)
696                                 sizer.Add(configPanel.leftPanel, proportion=1, border=35, flag=wx.EXPAND)
697                                 sizer.Add(configPanel.rightPanel, proportion=1, flag=wx.EXPAND)
698                                 configPanel.SetSizer(sizer)
699                                 #sizer.Layout()
700                                 configPanel.Layout()
701                                 self.Layout()
702                                 configPanel.Thaw()
703
704         def updateProfileToControls(self):
705                 super(normalSettingsPanel, self).updateProfileToControls()
706                 if self.alterationPanel is not None:
707                         self.alterationPanel.updateProfileToControls()
708                 self.pluginPanel.updateProfileToControls()