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