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