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