chiark / gitweb /
Make the profile per machine. this code is becoming a mess...
[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                         self.updateProfileToAllControls()
347                 self.scene.loadFiles(files)
348
349         def OnModelMRU(self, e):
350                 fileNum = e.GetId() - self.ID_MRU_MODEL1
351                 path = self.modelFileHistory.GetHistoryFile(fileNum)
352                 # Update Model MRU
353                 self.modelFileHistory.AddFileToHistory(path)  # move up the list
354                 self.config.SetPath("/ModelMRU")
355                 self.modelFileHistory.Save(self.config)
356                 self.config.Flush()
357                 # Load Model
358                 profile.putPreference('lastFile', path)
359                 filelist = [ path ]
360                 self.scene.loadFiles(filelist)
361
362         def addToModelMRU(self, file):
363                 self.modelFileHistory.AddFileToHistory(file)
364                 self.config.SetPath("/ModelMRU")
365                 self.modelFileHistory.Save(self.config)
366                 self.config.Flush()
367
368         def OnProfileMRU(self, e):
369                 fileNum = e.GetId() - self.ID_MRU_PROFILE1
370                 path = self.profileFileHistory.GetHistoryFile(fileNum)
371                 # Update Profile MRU
372                 self.profileFileHistory.AddFileToHistory(path)  # move up the list
373                 self.config.SetPath("/ProfileMRU")
374                 self.profileFileHistory.Save(self.config)
375                 self.config.Flush()
376                 # Load Profile
377                 profile.loadProfile(path)
378                 self.updateProfileToAllControls()
379
380         def addToProfileMRU(self, file):
381                 self.profileFileHistory.AddFileToHistory(file)
382                 self.config.SetPath("/ProfileMRU")
383                 self.profileFileHistory.Save(self.config)
384                 self.config.Flush()
385
386         def updateProfileToAllControls(self):
387                 self.scene.updateProfileToControls()
388                 self.normalSettingsPanel.updateProfileToControls()
389                 self.simpleSettingsPanel.updateProfileToControls()
390
391         def reloadSettingPanels(self):
392                 self.leftSizer.Detach(self.simpleSettingsPanel)
393                 self.leftSizer.Detach(self.normalSettingsPanel)
394                 self.simpleSettingsPanel.Destroy()
395                 self.normalSettingsPanel.Destroy()
396                 self.simpleSettingsPanel = simpleMode.simpleModePanel(self.leftPane, lambda : self.scene.sceneUpdated())
397                 self.normalSettingsPanel = normalSettingsPanel(self.leftPane, lambda : self.scene.sceneUpdated())
398                 self.leftSizer.Add(self.simpleSettingsPanel, 1)
399                 self.leftSizer.Add(self.normalSettingsPanel, 1, wx.EXPAND)
400                 self.updateSliceMode()
401                 self.updateProfileToAllControls()
402
403         def updateMachineMenu(self):
404                 #Remove all items so we can rebuild the menu. Inserting items seems to cause crashes, so this is the safest way.
405                 for item in self.machineMenu.GetMenuItems():
406                         self.machineMenu.RemoveItem(item)
407
408                 #Add a menu item for each machine configuration.
409                 for n in xrange(0, profile.getMachineCount()):
410                         i = self.machineMenu.Append(n + 0x1000, profile.getMachineSetting('machine_name', n).title(), kind=wx.ITEM_RADIO)
411                         if n == int(profile.getPreferenceFloat('active_machine')):
412                                 i.Check(True)
413                         self.Bind(wx.EVT_MENU, lambda e: self.OnSelectMachine(e.GetId() - 0x1000), i)
414
415                 self.machineMenu.AppendSeparator()
416
417                 i = self.machineMenu.Append(-1, _("Machine settings..."))
418                 self.Bind(wx.EVT_MENU, self.OnMachineSettings, i)
419
420                 #Add tools for machines.
421                 self.machineMenu.AppendSeparator()
422
423                 self.defaultFirmwareInstallMenuItem = self.machineMenu.Append(-1, _("Install default firmware..."))
424                 self.Bind(wx.EVT_MENU, self.OnDefaultMarlinFirmware, self.defaultFirmwareInstallMenuItem)
425
426                 i = self.machineMenu.Append(-1, _("Install custom firmware..."))
427                 self.Bind(wx.EVT_MENU, self.OnCustomFirmware, i)
428
429         def OnLoadProfile(self, e):
430                 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)
431                 dlg.SetWildcard("ini files (*.ini)|*.ini")
432                 if dlg.ShowModal() == wx.ID_OK:
433                         profileFile = dlg.GetPath()
434                         profile.loadProfile(profileFile)
435                         self.updateProfileToAllControls()
436
437                         # Update the Profile MRU
438                         self.addToProfileMRU(profileFile)
439                 dlg.Destroy()
440
441         def OnLoadProfileFromGcode(self, e):
442                 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)
443                 dlg.SetWildcard("gcode files (*.gcode)|*.gcode;*.g")
444                 if dlg.ShowModal() == wx.ID_OK:
445                         gcodeFile = dlg.GetPath()
446                         f = open(gcodeFile, 'r')
447                         hasProfile = False
448                         for line in f:
449                                 if line.startswith(';CURA_PROFILE_STRING:'):
450                                         profile.setProfileFromString(line[line.find(':')+1:].strip())
451                                         hasProfile = True
452                         if hasProfile:
453                                 self.updateProfileToAllControls()
454                         else:
455                                 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)
456                 dlg.Destroy()
457
458         def OnSaveProfile(self, e):
459                 dlg=wx.FileDialog(self, _("Select profile file to save"), os.path.split(profile.getPreference('lastFile'))[0], style=wx.FD_SAVE)
460                 dlg.SetWildcard("ini files (*.ini)|*.ini")
461                 if dlg.ShowModal() == wx.ID_OK:
462                         profileFile = dlg.GetPath()
463                         profile.saveProfile(profileFile)
464                 dlg.Destroy()
465
466         def OnResetProfile(self, e):
467                 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)
468                 result = dlg.ShowModal() == wx.ID_YES
469                 dlg.Destroy()
470                 if result:
471                         profile.resetProfile()
472                         self.updateProfileToAllControls()
473
474         def OnSimpleSwitch(self, e):
475                 profile.putPreference('startMode', 'Simple')
476                 self.updateSliceMode()
477
478         def OnNormalSwitch(self, e):
479                 profile.putPreference('startMode', 'Normal')
480                 self.updateSliceMode()
481
482         def OnDefaultMarlinFirmware(self, e):
483                 firmwareInstall.InstallFirmware()
484
485         def OnCustomFirmware(self, e):
486                 if profile.getMachineSetting('machine_type').startswith('ultimaker'):
487                         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)
488                 dlg=wx.FileDialog(self, _("Open firmware to upload"), os.path.split(profile.getPreference('lastFile'))[0], style=wx.FD_OPEN|wx.FD_FILE_MUST_EXIST)
489                 dlg.SetWildcard("HEX file (*.hex)|*.hex;*.HEX")
490                 if dlg.ShowModal() == wx.ID_OK:
491                         filename = dlg.GetPath()
492                         if not(os.path.exists(filename)):
493                                 return
494                         #For some reason my Ubuntu 10.10 crashes here.
495                         firmwareInstall.InstallFirmware(filename)
496
497         def OnFirstRunWizard(self, e):
498                 self.Hide()
499                 configWizard.configWizard()
500                 self.Show()
501                 self.reloadSettingPanels()
502
503         def OnSelectMachine(self, index):
504                 profile.setActiveMachine(index)
505                 self.reloadSettingPanels()
506
507         def OnBedLevelWizard(self, e):
508                 configWizard.bedLevelWizard()
509
510         def OnHeadOffsetWizard(self, e):
511                 configWizard.headOffsetWizard()
512
513         def OnExpertOpen(self, e):
514                 ecw = expertConfig.expertConfigWindow(lambda : self.scene.sceneUpdated())
515                 ecw.Centre()
516                 ecw.Show()
517
518         def OnMinecraftImport(self, e):
519                 mi = minecraftImport.minecraftImportWindow(self)
520                 mi.Centre()
521                 mi.Show(True)
522
523         def OnPIDDebugger(self, e):
524                 debugger = pidDebugger.debuggerWindow(self)
525                 debugger.Centre()
526                 debugger.Show(True)
527
528         def onCopyProfileClipboard(self, e):
529                 try:
530                         if not wx.TheClipboard.IsOpened():
531                                 wx.TheClipboard.Open()
532                                 clipData = wx.TextDataObject()
533                                 self.lastTriedClipboard = profile.getProfileString()
534                                 profileString = profile.insertNewlines("CURA_PROFILE_STRING:" + self.lastTriedClipboard)
535                                 clipData.SetText(profileString)
536                                 wx.TheClipboard.SetData(clipData)
537                                 wx.TheClipboard.Close()
538                 except:
539                         print "Could not write to clipboard, unable to get ownership. Another program is using the clipboard."
540
541         def OnCheckForUpdate(self, e):
542                 newVersion = version.checkForNewerVersion()
543                 if newVersion is not None:
544                         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:
545                                 webbrowser.open(newVersion)
546                 else:
547                         wx.MessageBox(_("You are running the latest version of Cura!"), _("Awesome!"), wx.ICON_INFORMATION)
548
549         def OnAbout(self, e):
550                 aboutBox = aboutWindow.aboutWindow()
551                 aboutBox.Centre()
552                 aboutBox.Show()
553
554         def OnClose(self, e):
555                 profile.saveProfile(profile.getDefaultProfilePath(), True)
556
557                 # Save the window position, size & state from the preferences file
558                 profile.putPreference('window_maximized', self.IsMaximized())
559                 if not self.IsMaximized() and not self.IsIconized():
560                         (posx, posy) = self.GetPosition()
561                         profile.putPreference('window_pos_x', posx)
562                         profile.putPreference('window_pos_y', posy)
563                         (width, height) = self.GetSize()
564                         profile.putPreference('window_width', width)
565                         profile.putPreference('window_height', height)
566
567                         # Save normal sash position.  If in normal mode (!simple mode), get last position of sash before saving it...
568                         isSimple = profile.getPreference('startMode') == 'Simple'
569                         if not isSimple:
570                                 self.normalSashPos = self.splitter.GetSashPosition()
571                         profile.putPreference('window_normal_sash', self.normalSashPos)
572
573                 #HACK: Set the paint function of the glCanvas to nothing so it won't keep refreshing. Which can keep wxWidgets from quiting.
574                 print "Closing down"
575                 self.scene.OnPaint = lambda e : e
576                 self.scene._engine.cleanup()
577                 self.Destroy()
578
579         def OnQuit(self, e):
580                 self.Close()
581
582 class normalSettingsPanel(configBase.configPanelBase):
583         "Main user interface window"
584         def __init__(self, parent, callback = None):
585                 super(normalSettingsPanel, self).__init__(parent, callback)
586
587                 #Main tabs
588                 self.nb = wx.Notebook(self)
589                 self.SetSizer(wx.BoxSizer(wx.HORIZONTAL))
590                 self.GetSizer().Add(self.nb, 1, wx.EXPAND)
591
592                 (left, right, self.printPanel) = self.CreateDynamicConfigTab(self.nb, 'Basic')
593                 self._addSettingsToPanels('basic', left, right)
594                 self.SizeLabelWidths(left, right)
595
596                 (left, right, self.advancedPanel) = self.CreateDynamicConfigTab(self.nb, 'Advanced')
597                 self._addSettingsToPanels('advanced', left, right)
598                 self.SizeLabelWidths(left, right)
599
600                 #Plugin page
601                 self.pluginPanel = pluginPanel.pluginPanel(self.nb, callback)
602                 self.nb.AddPage(self.pluginPanel, _("Plugins"))
603
604                 #Alteration page
605                 if profile.getMachineSetting('gcode_flavor') == 'UltiGCode':
606                         self.alterationPanel = None
607                 else:
608                         self.alterationPanel = alterationPanel.alterationPanel(self.nb, callback)
609                         self.nb.AddPage(self.alterationPanel, "Start/End-GCode")
610
611                 self.Bind(wx.EVT_SIZE, self.OnSize)
612
613                 self.nb.SetSize(self.GetSize())
614                 self.UpdateSize(self.printPanel)
615                 self.UpdateSize(self.advancedPanel)
616
617         def _addSettingsToPanels(self, category, left, right):
618                 count = len(profile.getSubCategoriesFor(category)) + len(profile.getSettingsForCategory(category))
619
620                 p = left
621                 n = 0
622                 for title in profile.getSubCategoriesFor(category):
623                         n += 1 + len(profile.getSettingsForCategory(category, title))
624                         if n > count / 2:
625                                 p = right
626                         configBase.TitleRow(p, _(title))
627                         for s in profile.getSettingsForCategory(category, title):
628                                 configBase.SettingRow(p, s.getName())
629
630         def SizeLabelWidths(self, left, right):
631                 leftWidth = self.getLabelColumnWidth(left)
632                 rightWidth = self.getLabelColumnWidth(right)
633                 maxWidth = max(leftWidth, rightWidth)
634                 self.setLabelColumnWidth(left, maxWidth)
635                 self.setLabelColumnWidth(right, maxWidth)
636
637         def OnSize(self, e):
638                 # Make the size of the Notebook control the same size as this control
639                 self.nb.SetSize(self.GetSize())
640
641                 # Propegate the OnSize() event (just in case)
642                 e.Skip()
643
644                 # Perform out resize magic
645                 self.UpdateSize(self.printPanel)
646                 self.UpdateSize(self.advancedPanel)
647
648         def UpdateSize(self, configPanel):
649                 sizer = configPanel.GetSizer()
650
651                 # Pseudocde
652                 # if horizontal:
653                 #     if width(col1) < best_width(col1) || width(col2) < best_width(col2):
654                 #         switch to vertical
655                 # else:
656                 #     if width(col1) > (best_width(col1) + best_width(col1)):
657                 #         switch to horizontal
658                 #
659
660                 col1 = configPanel.leftPanel
661                 colSize1 = col1.GetSize()
662                 colBestSize1 = col1.GetBestSize()
663                 col2 = configPanel.rightPanel
664                 colSize2 = col2.GetSize()
665                 colBestSize2 = col2.GetBestSize()
666
667                 orientation = sizer.GetOrientation()
668
669                 if orientation == wx.HORIZONTAL:
670                         if (colSize1[0] <= colBestSize1[0]) or (colSize2[0] <= colBestSize2[0]):
671                                 configPanel.Freeze()
672                                 sizer = wx.BoxSizer(wx.VERTICAL)
673                                 sizer.Add(configPanel.leftPanel, flag=wx.EXPAND)
674                                 sizer.Add(configPanel.rightPanel, flag=wx.EXPAND)
675                                 configPanel.SetSizer(sizer)
676                                 #sizer.Layout()
677                                 configPanel.Layout()
678                                 self.Layout()
679                                 configPanel.Thaw()
680                 else:
681                         if max(colSize1[0], colSize2[0]) > (colBestSize1[0] + colBestSize2[0]):
682                                 configPanel.Freeze()
683                                 sizer = wx.BoxSizer(wx.HORIZONTAL)
684                                 sizer.Add(configPanel.leftPanel, proportion=1, border=35, flag=wx.EXPAND)
685                                 sizer.Add(configPanel.rightPanel, proportion=1, flag=wx.EXPAND)
686                                 configPanel.SetSizer(sizer)
687                                 #sizer.Layout()
688                                 configPanel.Layout()
689                                 self.Layout()
690                                 configPanel.Thaw()
691
692         def updateProfileToControls(self):
693                 super(normalSettingsPanel, self).updateProfileToControls()
694                 if self.alterationPanel is not None:
695                         self.alterationPanel.updateProfileToControls()
696                 self.pluginPanel.updateProfileToControls()