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