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