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