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