chiark / gitweb /
Add multiple tabs for multiple machine configurations in the machine-settings dialog.
[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                 expertMenu = wx.Menu()
138                 i = expertMenu.Append(-1, _("Open expert settings..."))
139                 self.normalModeOnlyItems.append(i)
140                 self.Bind(wx.EVT_MENU, self.OnExpertOpen, i)
141                 expertMenu.AppendSeparator()
142                 if firmwareInstall.getDefaultFirmware() is not None:
143                         i = expertMenu.Append(-1, _("Install default Marlin firmware"))
144                         self.Bind(wx.EVT_MENU, self.OnDefaultMarlinFirmware, i)
145                 i = expertMenu.Append(-1, _("Install custom firmware"))
146                 self.Bind(wx.EVT_MENU, self.OnCustomFirmware, 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                 i = expertMenu.Append(-1, _("Add new machine..."))
157                 self.Bind(wx.EVT_MENU, self.OnAddNewMachine, i)
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                                 wx.TheClipboard.Open()
260                                 do = wx.TextDataObject()
261                                 if wx.TheClipboard.GetData(do):
262                                         profileString = do.GetText()
263                                 wx.TheClipboard.Close()
264
265                                 if "CURA_PROFILE_STRING:" in profileString:
266                                         #print "Found correct syntax on clipboard"
267                                         profileString = profileString.replace("\n","")
268                                         profileString = profileString.replace("CURA_PROFILE_STRING:", "")
269                                         if profileString != self.lastTriedClipboard:
270                                                 self.lastTriedClipboard = profileString
271                                                 profile.setProfileFromString(profileString)
272                                                 print "changed profile"
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 OnLoadProfile(self, e):
378                 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)
379                 dlg.SetWildcard("ini files (*.ini)|*.ini")
380                 if dlg.ShowModal() == wx.ID_OK:
381                         profileFile = dlg.GetPath()
382                         profile.loadProfile(profileFile)
383                         self.updateProfileToAllControls()
384
385                         # Update the Profile MRU
386                         self.addToProfileMRU(profileFile)
387                 dlg.Destroy()
388
389         def OnLoadProfileFromGcode(self, e):
390                 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)
391                 dlg.SetWildcard("gcode files (*.gcode)|*.gcode;*.g")
392                 if dlg.ShowModal() == wx.ID_OK:
393                         gcodeFile = dlg.GetPath()
394                         f = open(gcodeFile, 'r')
395                         hasProfile = False
396                         for line in f:
397                                 if line.startswith(';CURA_PROFILE_STRING:'):
398                                         profile.setProfileFromString(line[line.find(':')+1:].strip())
399                                         hasProfile = True
400                         if hasProfile:
401                                 self.updateProfileToAllControls()
402                         else:
403                                 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)
404                 dlg.Destroy()
405
406         def OnSaveProfile(self, e):
407                 dlg=wx.FileDialog(self, _("Select profile file to save"), os.path.split(profile.getPreference('lastFile'))[0], style=wx.FD_SAVE)
408                 dlg.SetWildcard("ini files (*.ini)|*.ini")
409                 if dlg.ShowModal() == wx.ID_OK:
410                         profileFile = dlg.GetPath()
411                         profile.saveProfile(profileFile)
412                 dlg.Destroy()
413
414         def OnResetProfile(self, e):
415                 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)
416                 result = dlg.ShowModal() == wx.ID_YES
417                 dlg.Destroy()
418                 if result:
419                         profile.resetProfile()
420                         self.updateProfileToAllControls()
421
422         def OnSimpleSwitch(self, e):
423                 profile.putPreference('startMode', 'Simple')
424                 self.updateSliceMode()
425
426         def OnNormalSwitch(self, e):
427                 profile.putPreference('startMode', 'Normal')
428                 self.updateSliceMode()
429
430         def OnDefaultMarlinFirmware(self, e):
431                 firmwareInstall.InstallFirmware()
432
433         def OnCustomFirmware(self, e):
434                 if profile.getMachineSetting('machine_type').startswith('ultimaker'):
435                         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)
436                 dlg=wx.FileDialog(self, _("Open firmware to upload"), os.path.split(profile.getPreference('lastFile'))[0], style=wx.FD_OPEN|wx.FD_FILE_MUST_EXIST)
437                 dlg.SetWildcard("HEX file (*.hex)|*.hex;*.HEX")
438                 if dlg.ShowModal() == wx.ID_OK:
439                         filename = dlg.GetPath()
440                         if not(os.path.exists(filename)):
441                                 return
442                         #For some reason my Ubuntu 10.10 crashes here.
443                         firmwareInstall.InstallFirmware(filename)
444
445         def OnFirstRunWizard(self, e):
446                 self.Hide()
447                 configWizard.configWizard()
448                 self.Show()
449                 self.reloadSettingPanels()
450
451         def OnAddNewMachine(self, e):
452                 self.Hide()
453                 profile.setActiveMachine(profile.getMachineCount())
454                 configWizard.configWizard(True)
455                 self.Show()
456                 self.reloadSettingPanels()
457
458         def OnBedLevelWizard(self, e):
459                 configWizard.bedLevelWizard()
460
461         def OnHeadOffsetWizard(self, e):
462                 configWizard.headOffsetWizard()
463
464         def OnExpertOpen(self, e):
465                 ecw = expertConfig.expertConfigWindow(lambda : self.scene.sceneUpdated())
466                 ecw.Centre()
467                 ecw.Show()
468
469         def OnMinecraftImport(self, e):
470                 mi = minecraftImport.minecraftImportWindow(self)
471                 mi.Centre()
472                 mi.Show(True)
473
474         def OnPIDDebugger(self, e):
475                 debugger = pidDebugger.debuggerWindow(self)
476                 debugger.Centre()
477                 debugger.Show(True)
478
479         def onCopyProfileClipboard(self, e):
480                 try:
481                         if not wx.TheClipboard.IsOpened():
482                                 wx.TheClipboard.Open()
483                                 clipData = wx.TextDataObject()
484                                 self.lastTriedClipboard = profile.getProfileString()
485                                 profileString = profile.insertNewlines("CURA_PROFILE_STRING:" + self.lastTriedClipboard)
486                                 clipData.SetText(profileString)
487                                 wx.TheClipboard.SetData(clipData)
488                                 wx.TheClipboard.Close()
489                 except:
490                         print "Could not write to clipboard, unable to get ownership. Another program is using the clipboard."
491
492         def OnCheckForUpdate(self, e):
493                 newVersion = version.checkForNewerVersion()
494                 if newVersion is not None:
495                         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:
496                                 webbrowser.open(newVersion)
497                 else:
498                         wx.MessageBox(_("You are running the latest version of Cura!"), _("Awesome!"), wx.ICON_INFORMATION)
499
500         def OnAbout(self, e):
501                 info = wx.AboutDialogInfo()
502                 info.SetName("Cura")
503                 info.SetDescription(_("End solution for Open Source Fused Filament Fabrication 3D printing."))
504                 info.SetWebSite('http://software.ultimaker.com/')
505                 info.SetCopyright(_("Copyright (C) David Braam"))
506                 info.SetLicence("""
507     This program is free software: you can redistribute it and/or modify
508     it under the terms of the GNU Affero General Public License as published by
509     the Free Software Foundation, either version 3 of the License, or
510     (at your option) any later version.
511
512     This program is distributed in the hope that it will be useful,
513     but WITHOUT ANY WARRANTY; without even the implied warranty of
514     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
515     GNU Affero General Public License for more details.
516
517     You should have received a copy of the GNU Affero General Public License
518     along with this program.  If not, see <http://www.gnu.org/licenses/>.
519 """)
520                 wx.AboutBox(info)
521
522         def OnClose(self, e):
523                 profile.saveProfile(profile.getDefaultProfilePath())
524
525                 # Save the window position, size & state from the preferences file
526                 profile.putPreference('window_maximized', self.IsMaximized())
527                 if not self.IsMaximized() and not self.IsIconized():
528                         (posx, posy) = self.GetPosition()
529                         profile.putPreference('window_pos_x', posx)
530                         profile.putPreference('window_pos_y', posy)
531                         (width, height) = self.GetSize()
532                         profile.putPreference('window_width', width)
533                         profile.putPreference('window_height', height)
534
535                         # Save normal sash position.  If in normal mode (!simple mode), get last position of sash before saving it...
536                         isSimple = profile.getPreference('startMode') == 'Simple'
537                         if not isSimple:
538                                 self.normalSashPos = self.splitter.GetSashPosition()
539                         profile.putPreference('window_normal_sash', self.normalSashPos)
540
541                 #HACK: Set the paint function of the glCanvas to nothing so it won't keep refreshing. Which keeps wxWidgets from quiting.
542                 print "Closing down"
543                 self.scene.OnPaint = lambda e : e
544                 self.scene._slicer.cleanup()
545                 self.Destroy()
546
547         def OnQuit(self, e):
548                 self.Close()
549
550 class normalSettingsPanel(configBase.configPanelBase):
551         "Main user interface window"
552         def __init__(self, parent, callback = None):
553                 super(normalSettingsPanel, self).__init__(parent, callback)
554
555                 #Main tabs
556                 self.nb = wx.Notebook(self)
557                 self.SetSizer(wx.BoxSizer(wx.HORIZONTAL))
558                 self.GetSizer().Add(self.nb, 1, wx.EXPAND)
559
560                 (left, right, self.printPanel) = self.CreateDynamicConfigTab(self.nb, 'Basic')
561                 self._addSettingsToPanels('basic', left, right)
562                 self.SizeLabelWidths(left, right)
563
564                 (left, right, self.advancedPanel) = self.CreateDynamicConfigTab(self.nb, 'Advanced')
565                 self._addSettingsToPanels('advanced', left, right)
566                 self.SizeLabelWidths(left, right)
567
568                 #Plugin page
569                 self.pluginPanel = pluginPanel.pluginPanel(self.nb, callback)
570                 if len(self.pluginPanel.pluginList) > 0:
571                         self.nb.AddPage(self.pluginPanel, _("Plugins"))
572                 else:
573                         self.pluginPanel.Show(False)
574
575                 #Alteration page
576                 if profile.getMachineSetting('gcode_flavor') == 'UltiGCode':
577                         self.alterationPanel = None
578                 else:
579                         self.alterationPanel = alterationPanel.alterationPanel(self.nb, callback)
580                         self.nb.AddPage(self.alterationPanel, "Start/End-GCode")
581
582                 self.Bind(wx.EVT_SIZE, self.OnSize)
583
584                 self.nb.SetSize(self.GetSize())
585                 self.UpdateSize(self.printPanel)
586                 self.UpdateSize(self.advancedPanel)
587
588         def _addSettingsToPanels(self, category, left, right):
589                 count = len(profile.getSubCategoriesFor(category)) + len(profile.getSettingsForCategory(category))
590
591                 p = left
592                 n = 0
593                 for title in profile.getSubCategoriesFor(category):
594                         n += 1 + len(profile.getSettingsForCategory(category, title))
595                         if n > count / 2:
596                                 p = right
597                         configBase.TitleRow(p, title)
598                         for s in profile.getSettingsForCategory(category, title):
599                                 configBase.SettingRow(p, s.getName())
600
601         def SizeLabelWidths(self, left, right):
602                 leftWidth = self.getLabelColumnWidth(left)
603                 rightWidth = self.getLabelColumnWidth(right)
604                 maxWidth = max(leftWidth, rightWidth)
605                 self.setLabelColumnWidth(left, maxWidth)
606                 self.setLabelColumnWidth(right, maxWidth)
607
608         def OnSize(self, e):
609                 # Make the size of the Notebook control the same size as this control
610                 self.nb.SetSize(self.GetSize())
611
612                 # Propegate the OnSize() event (just in case)
613                 e.Skip()
614
615                 # Perform out resize magic
616                 self.UpdateSize(self.printPanel)
617                 self.UpdateSize(self.advancedPanel)
618
619         def UpdateSize(self, configPanel):
620                 sizer = configPanel.GetSizer()
621
622                 # Pseudocde
623                 # if horizontal:
624                 #     if width(col1) < best_width(col1) || width(col2) < best_width(col2):
625                 #         switch to vertical
626                 # else:
627                 #     if width(col1) > (best_width(col1) + best_width(col1)):
628                 #         switch to horizontal
629                 #
630
631                 col1 = configPanel.leftPanel
632                 colSize1 = col1.GetSize()
633                 colBestSize1 = col1.GetBestSize()
634                 col2 = configPanel.rightPanel
635                 colSize2 = col2.GetSize()
636                 colBestSize2 = col2.GetBestSize()
637
638                 orientation = sizer.GetOrientation()
639
640                 if orientation == wx.HORIZONTAL:
641                         if (colSize1[0] <= colBestSize1[0]) or (colSize2[0] <= colBestSize2[0]):
642                                 configPanel.Freeze()
643                                 sizer = wx.BoxSizer(wx.VERTICAL)
644                                 sizer.Add(configPanel.leftPanel, flag=wx.EXPAND)
645                                 sizer.Add(configPanel.rightPanel, flag=wx.EXPAND)
646                                 configPanel.SetSizer(sizer)
647                                 #sizer.Layout()
648                                 configPanel.Layout()
649                                 self.Layout()
650                                 configPanel.Thaw()
651                 else:
652                         if max(colSize1[0], colSize2[0]) > (colBestSize1[0] + colBestSize2[0]):
653                                 configPanel.Freeze()
654                                 sizer = wx.BoxSizer(wx.HORIZONTAL)
655                                 sizer.Add(configPanel.leftPanel, proportion=1, border=35, flag=wx.EXPAND)
656                                 sizer.Add(configPanel.rightPanel, proportion=1, flag=wx.EXPAND)
657                                 configPanel.SetSizer(sizer)
658                                 #sizer.Layout()
659                                 configPanel.Layout()
660                                 self.Layout()
661                                 configPanel.Thaw()
662
663         def updateProfileToControls(self):
664                 super(normalSettingsPanel, self).updateProfileToControls()
665                 if self.alterationPanel is not None:
666                         self.alterationPanel.updateProfileToControls()
667                 self.pluginPanel.updateProfileToControls()