1 from __future__ import absolute_import
2 __copyright__ = "Copyright (C) 2013 David Braam - Released under terms of the AGPLv3 License"
8 from Cura.gui import configBase
9 from Cura.gui import expertConfig
10 from Cura.gui import alterationPanel
11 from Cura.gui import pluginPanel
12 from Cura.gui import preferencesDialog
13 from Cura.gui import configWizard
14 from Cura.gui import firmwareInstall
15 from Cura.gui import simpleMode
16 from Cura.gui import sceneView
17 #from Cura.gui.tools import batchRun
18 from Cura.gui.util import dropTarget
19 from Cura.gui.tools import minecraftImport
20 from Cura.gui.tools import superformula
21 from Cura.util import profile
22 from Cura.util import version
23 from Cura.util import meshLoader
25 class mainWindow(wx.Frame):
27 super(mainWindow, self).__init__(None, title='Cura - ' + version.getVersion())
29 self.extruderCount = int(profile.getPreference('extruder_amount'))
31 wx.EVT_CLOSE(self, self.OnClose)
33 self.SetDropTarget(dropTarget.FileDropTarget(self.OnDropFiles, meshLoader.loadSupportedExtensions()))
35 self.normalModeOnlyItems = []
37 mruFile = os.path.join(profile.getBasePath(), 'mru_filelist.ini')
38 self.config = wx.FileConfig(appName="Cura",
39 localFilename=mruFile,
40 style=wx.CONFIG_USE_LOCAL_FILE)
42 self.ID_MRU_MODEL1, self.ID_MRU_MODEL2, self.ID_MRU_MODEL3, self.ID_MRU_MODEL4, self.ID_MRU_MODEL5, self.ID_MRU_MODEL6, self.ID_MRU_MODEL7, self.ID_MRU_MODEL8, self.ID_MRU_MODEL9, self.ID_MRU_MODEL10 = [wx.NewId() for line in xrange(10)]
43 self.modelFileHistory = wx.FileHistory(10, self.ID_MRU_MODEL1)
44 self.config.SetPath("/ModelMRU")
45 self.modelFileHistory.Load(self.config)
47 self.ID_MRU_PROFILE1, self.ID_MRU_PROFILE2, self.ID_MRU_PROFILE3, self.ID_MRU_PROFILE4, self.ID_MRU_PROFILE5, self.ID_MRU_PROFILE6, self.ID_MRU_PROFILE7, self.ID_MRU_PROFILE8, self.ID_MRU_PROFILE9, self.ID_MRU_PROFILE10 = [wx.NewId() for line in xrange(10)]
48 self.profileFileHistory = wx.FileHistory(10, self.ID_MRU_PROFILE1)
49 self.config.SetPath("/ProfileMRU")
50 self.profileFileHistory.Load(self.config)
52 self.menubar = wx.MenuBar()
53 self.fileMenu = wx.Menu()
54 i = self.fileMenu.Append(-1, 'Load model file...\tCTRL+L')
55 self.Bind(wx.EVT_MENU, lambda e: self.scene.showLoadModel(), i)
56 i = self.fileMenu.Append(-1, 'Save model...\tCTRL+S')
57 self.Bind(wx.EVT_MENU, lambda e: self.scene.showSaveModel(), i)
59 self.fileMenu.AppendSeparator()
60 i = self.fileMenu.Append(-1, 'Print...\tCTRL+P')
61 self.Bind(wx.EVT_MENU, lambda e: self.scene.showPrintWindow(), i)
62 i = self.fileMenu.Append(-1, 'Save GCode...')
63 self.Bind(wx.EVT_MENU, lambda e: self.scene.showSaveGCode(), i)
65 self.fileMenu.AppendSeparator()
66 i = self.fileMenu.Append(-1, 'Open Profile...')
67 self.normalModeOnlyItems.append(i)
68 self.Bind(wx.EVT_MENU, self.OnLoadProfile, i)
69 i = self.fileMenu.Append(-1, 'Save Profile...')
70 self.normalModeOnlyItems.append(i)
71 self.Bind(wx.EVT_MENU, self.OnSaveProfile, i)
72 i = self.fileMenu.Append(-1, 'Load Profile from GCode...')
73 self.normalModeOnlyItems.append(i)
74 self.Bind(wx.EVT_MENU, self.OnLoadProfileFromGcode, i)
75 self.fileMenu.AppendSeparator()
76 i = self.fileMenu.Append(-1, 'Reset Profile to default')
77 self.normalModeOnlyItems.append(i)
78 self.Bind(wx.EVT_MENU, self.OnResetProfile, i)
80 self.fileMenu.AppendSeparator()
81 i = self.fileMenu.Append(-1, 'Preferences...\tCTRL+,')
82 self.Bind(wx.EVT_MENU, self.OnPreferences, i)
83 self.fileMenu.AppendSeparator()
86 modelHistoryMenu = wx.Menu()
87 self.fileMenu.AppendMenu(wx.NewId(), "&Recent Model Files", modelHistoryMenu)
88 self.modelFileHistory.UseMenu(modelHistoryMenu)
89 self.modelFileHistory.AddFilesToMenu()
90 self.Bind(wx.EVT_MENU_RANGE, self.OnModelMRU, id=self.ID_MRU_MODEL1, id2=self.ID_MRU_MODEL10)
93 profileHistoryMenu = wx.Menu()
94 self.fileMenu.AppendMenu(wx.NewId(), "&Recent Profile Files", profileHistoryMenu)
95 self.profileFileHistory.UseMenu(profileHistoryMenu)
96 self.profileFileHistory.AddFilesToMenu()
97 self.Bind(wx.EVT_MENU_RANGE, self.OnProfileMRU, id=self.ID_MRU_PROFILE1, id2=self.ID_MRU_PROFILE10)
99 self.fileMenu.AppendSeparator()
100 i = self.fileMenu.Append(wx.ID_EXIT, 'Quit')
101 self.Bind(wx.EVT_MENU, self.OnQuit, i)
102 self.menubar.Append(self.fileMenu, '&File')
104 toolsMenu = wx.Menu()
105 i = toolsMenu.Append(-1, 'Switch to quickprint...')
106 self.switchToQuickprintMenuItem = i
107 self.Bind(wx.EVT_MENU, self.OnSimpleSwitch, i)
108 i = toolsMenu.Append(-1, 'Switch to full settings...')
109 self.switchToNormalMenuItem = i
110 self.Bind(wx.EVT_MENU, self.OnNormalSwitch, i)
111 toolsMenu.AppendSeparator()
112 #i = toolsMenu.Append(-1, 'Batch run...')
113 #self.Bind(wx.EVT_MENU, self.OnBatchRun, i)
114 #self.normalModeOnlyItems.append(i)
115 if minecraftImport.hasMinecraft():
116 i = toolsMenu.Append(-1, 'Minecraft import...')
117 self.Bind(wx.EVT_MENU, self.OnMinecraftImport, i)
118 i = toolsMenu.Append(-1, 'Super-shaper...')
119 self.Bind(wx.EVT_MENU, self.OnSuperformula, i)
120 self.menubar.Append(toolsMenu, 'Tools')
122 expertMenu = wx.Menu()
123 i = expertMenu.Append(-1, 'Open expert settings...')
124 self.normalModeOnlyItems.append(i)
125 self.Bind(wx.EVT_MENU, self.OnExpertOpen, i)
126 expertMenu.AppendSeparator()
127 if firmwareInstall.getDefaultFirmware() is not None:
128 i = expertMenu.Append(-1, 'Install default Marlin firmware')
129 self.Bind(wx.EVT_MENU, self.OnDefaultMarlinFirmware, i)
130 i = expertMenu.Append(-1, 'Install custom firmware')
131 self.Bind(wx.EVT_MENU, self.OnCustomFirmware, i)
132 expertMenu.AppendSeparator()
133 i = expertMenu.Append(-1, 'Run first run wizard...')
134 self.Bind(wx.EVT_MENU, self.OnFirstRunWizard, i)
135 i = expertMenu.Append(-1, 'Run bed leveling wizard...')
136 self.Bind(wx.EVT_MENU, self.OnBedLevelWizard, i)
137 if self.extruderCount > 1:
138 i = expertMenu.Append(-1, 'Run head offset wizard...')
139 self.Bind(wx.EVT_MENU, self.OnHeadOffsetWizard, i)
140 self.menubar.Append(expertMenu, 'Expert')
143 i = helpMenu.Append(-1, 'Online documentation...')
144 self.Bind(wx.EVT_MENU, lambda e: webbrowser.open('http://daid.github.com/Cura'), i)
145 i = helpMenu.Append(-1, 'Report a problem...')
146 self.Bind(wx.EVT_MENU, lambda e: webbrowser.open('https://github.com/daid/Cura/issues'), i)
147 i = helpMenu.Append(-1, 'Check for update...')
148 self.Bind(wx.EVT_MENU, self.OnCheckForUpdate, i)
149 i = helpMenu.Append(-1, 'About Cura...')
150 self.Bind(wx.EVT_MENU, self.OnAbout, i)
151 self.menubar.Append(helpMenu, 'Help')
152 self.SetMenuBar(self.menubar)
154 self.splitter = wx.SplitterWindow(self, style = wx.SP_3D | wx.SP_LIVE_UPDATE)
155 self.leftPane = wx.Panel(self.splitter, style=wx.BORDER_NONE)
156 self.rightPane = wx.Panel(self.splitter, style=wx.BORDER_NONE)
157 self.splitter.Bind(wx.EVT_SPLITTER_DCLICK, lambda evt: evt.Veto())
160 self.simpleSettingsPanel = simpleMode.simpleModePanel(self.leftPane, lambda : self.scene.sceneUpdated())
161 self.normalSettingsPanel = normalSettingsPanel(self.leftPane, lambda : self.scene.sceneUpdated())
163 self.leftSizer = wx.BoxSizer(wx.VERTICAL)
164 self.leftSizer.Add(self.simpleSettingsPanel)
165 self.leftSizer.Add(self.normalSettingsPanel, 1, wx.EXPAND)
166 self.leftPane.SetSizer(self.leftSizer)
169 self.scene = sceneView.SceneView(self.rightPane)
171 #Main sizer, to position the preview window, buttons and tab control
172 sizer = wx.BoxSizer()
173 self.rightPane.SetSizer(sizer)
174 sizer.Add(self.scene, 1, flag=wx.EXPAND)
177 sizer = wx.BoxSizer(wx.VERTICAL)
179 sizer.Add(self.splitter, 1, wx.EXPAND)
183 self.updateProfileToControls()
185 self.SetBackgroundColour(self.normalSettingsPanel.GetBackgroundColour())
187 self.simpleSettingsPanel.Show(False)
188 self.normalSettingsPanel.Show(False)
190 # Set default window size & position
191 self.SetSize((wx.Display().GetClientArea().GetWidth()/2,wx.Display().GetClientArea().GetHeight()/2))
194 # Restore the window position, size & state from the preferences file
196 if profile.getPreference('window_maximized') == 'True':
199 posx = int(profile.getPreference('window_pos_x'))
200 posy = int(profile.getPreference('window_pos_y'))
201 width = int(profile.getPreference('window_width'))
202 height = int(profile.getPreference('window_height'))
203 if posx > 0 or posy > 0:
204 self.SetPosition((posx,posy))
205 if width > 0 and height > 0:
206 self.SetSize((width,height))
208 self.normalSashPos = int(profile.getPreference('window_normal_sash'))
210 self.normalSashPos = 0
212 if self.normalSashPos < self.normalSettingsPanel.printPanel.GetBestSize()[0] + 5:
213 self.normalSashPos = self.normalSettingsPanel.printPanel.GetBestSize()[0] + 5
215 self.splitter.SplitVertically(self.leftPane, self.rightPane, self.normalSashPos)
217 if wx.Display.GetFromPoint(self.GetPosition()) < 0:
219 if wx.Display.GetFromPoint((self.GetPositionTuple()[0] + self.GetSizeTuple()[1], self.GetPositionTuple()[1] + self.GetSizeTuple()[1])) < 0:
221 if wx.Display.GetFromPoint(self.GetPosition()) < 0:
222 self.SetSize((800,600))
225 self.updateSliceMode()
227 def updateSliceMode(self):
228 isSimple = profile.getPreference('startMode') == 'Simple'
230 self.normalSettingsPanel.Show(not isSimple)
231 self.simpleSettingsPanel.Show(isSimple)
232 self.leftPane.Layout()
234 for i in self.normalModeOnlyItems:
235 i.Enable(not isSimple)
236 self.switchToQuickprintMenuItem.Enable(not isSimple)
237 self.switchToNormalMenuItem.Enable(isSimple)
239 # Set splitter sash position & size
241 # Save normal mode sash
242 self.normalSashPos = self.splitter.GetSashPosition()
244 # Change location of sash to width of quick mode pane
245 (width, height) = self.simpleSettingsPanel.GetSizer().GetSize()
246 self.splitter.SetSashPosition(width, True)
249 self.splitter.SetSashSize(0)
251 self.splitter.SetSashPosition(self.normalSashPos, True)
253 self.splitter.SetSashSize(4)
254 self.scene.updateProfileToControls()
256 def OnPreferences(self, e):
257 prefDialog = preferencesDialog.preferencesDialog(self)
261 def OnDropFiles(self, files):
262 profile.setPluginConfig([])
263 self.updateProfileToControls()
264 self.scene.loadScene(files)
266 def OnModelMRU(self, e):
267 fileNum = e.GetId() - self.ID_MRU_MODEL1
268 path = self.modelFileHistory.GetHistoryFile(fileNum)
270 self.modelFileHistory.AddFileToHistory(path) # move up the list
271 self.config.SetPath("/ModelMRU")
272 self.modelFileHistory.Save(self.config)
276 self.scene.loadScene(filelist)
278 def addToModelMRU(self, file):
279 self.modelFileHistory.AddFileToHistory(file)
280 self.config.SetPath("/ModelMRU")
281 self.modelFileHistory.Save(self.config)
284 def OnProfileMRU(self, e):
285 fileNum = e.GetId() - self.ID_MRU_PROFILE1
286 path = self.profileFileHistory.GetHistoryFile(fileNum)
288 self.profileFileHistory.AddFileToHistory(path) # move up the list
289 self.config.SetPath("/ProfileMRU")
290 self.profileFileHistory.Save(self.config)
293 profile.loadProfile(path)
294 self.updateProfileToControls()
296 def addToProfileMRU(self, file):
297 self.profileFileHistory.AddFileToHistory(file)
298 self.config.SetPath("/ProfileMRU")
299 self.profileFileHistory.Save(self.config)
302 def updateProfileToControls(self):
303 self.scene.updateProfileToControls()
304 self.normalSettingsPanel.updateProfileToControls()
305 self.simpleSettingsPanel.updateProfileToControls()
307 def OnLoadProfile(self, e):
308 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)
309 dlg.SetWildcard("ini files (*.ini)|*.ini")
310 if dlg.ShowModal() == wx.ID_OK:
311 profileFile = dlg.GetPath()
312 profile.loadProfile(profileFile)
313 self.updateProfileToControls()
315 # Update the Profile MRU
316 self.addToProfileMRU(profileFile)
319 def OnLoadProfileFromGcode(self, e):
320 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)
321 dlg.SetWildcard("gcode files (*.gcode)|*.gcode;*.g")
322 if dlg.ShowModal() == wx.ID_OK:
323 gcodeFile = dlg.GetPath()
324 f = open(gcodeFile, 'r')
327 if line.startswith(';CURA_PROFILE_STRING:'):
328 profile.loadProfileFromString(line[line.find(':')+1:].strip())
331 self.updateProfileToControls()
333 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)
336 def OnSaveProfile(self, e):
337 dlg=wx.FileDialog(self, "Select profile file to save", os.path.split(profile.getPreference('lastFile'))[0], style=wx.FD_SAVE)
338 dlg.SetWildcard("ini files (*.ini)|*.ini")
339 if dlg.ShowModal() == wx.ID_OK:
340 profileFile = dlg.GetPath()
341 profile.saveProfile(profileFile)
344 def OnResetProfile(self, e):
345 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)
346 result = dlg.ShowModal() == wx.ID_YES
349 profile.resetProfile()
350 self.updateProfileToControls()
352 def OnBatchRun(self, e):
353 br = batchRun.batchRunWindow(self)
357 def OnSimpleSwitch(self, e):
358 profile.putPreference('startMode', 'Simple')
359 self.updateSliceMode()
361 def OnNormalSwitch(self, e):
362 profile.putPreference('startMode', 'Normal')
363 self.updateSliceMode()
365 def OnDefaultMarlinFirmware(self, e):
366 firmwareInstall.InstallFirmware()
368 def OnCustomFirmware(self, e):
369 if profile.getPreference('machine_type') == 'ultimaker':
370 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)
371 dlg=wx.FileDialog(self, "Open firmware to upload", os.path.split(profile.getPreference('lastFile'))[0], style=wx.FD_OPEN|wx.FD_FILE_MUST_EXIST)
372 dlg.SetWildcard("HEX file (*.hex)|*.hex;*.HEX")
373 if dlg.ShowModal() == wx.ID_OK:
374 filename = dlg.GetPath()
375 if not(os.path.exists(filename)):
377 #For some reason my Ubuntu 10.10 crashes here.
378 firmwareInstall.InstallFirmware(filename)
380 def OnFirstRunWizard(self, e):
381 configWizard.configWizard()
382 self.updateProfileToControls()
384 def OnBedLevelWizard(self, e):
385 configWizard.bedLevelWizard()
387 def OnHeadOffsetWizard(self, e):
388 configWizard.headOffsetWizard()
390 def OnExpertOpen(self, e):
391 ecw = expertConfig.expertConfigWindow(lambda : self.scene.sceneUpdated())
395 def OnMinecraftImport(self, e):
396 mi = minecraftImport.minecraftImportWindow(self)
400 def OnSuperformula(self, e):
401 sf = superformula.superformulaWindow(self)
402 #sf = superformula.superformulaEvolver(self)
406 def OnCheckForUpdate(self, e):
407 newVersion = version.checkForNewerVersion()
408 if newVersion is not None:
409 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:
410 webbrowser.open(newVersion)
412 wx.MessageBox('You are running the latest version of Cura!', 'Awesome!', wx.ICON_INFORMATION)
414 def OnAbout(self, e):
415 info = wx.AboutDialogInfo()
417 info.SetDescription('End solution for Open Source Fused Filament Fabrication 3D printing.')
418 info.SetWebSite('http://software.ultimaker.com/')
419 info.SetCopyright('Copyright (C) David Braam')
421 This program is free software: you can redistribute it and/or modify
422 it under the terms of the GNU Affero General Public License as published by
423 the Free Software Foundation, either version 3 of the License, or
424 (at your option) any later version.
426 This program is distributed in the hope that it will be useful,
427 but WITHOUT ANY WARRANTY; without even the implied warranty of
428 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
429 GNU Affero General Public License for more details.
431 You should have received a copy of the GNU Affero General Public License
432 along with this program. If not, see <http://www.gnu.org/licenses/>.
436 def OnClose(self, e):
437 profile.saveProfile(profile.getDefaultProfilePath())
439 # Save the window position, size & state from the preferences file
440 profile.putPreference('window_maximized', self.IsMaximized())
441 if not self.IsMaximized() and not self.IsIconized():
442 (posx, posy) = self.GetPosition()
443 profile.putPreference('window_pos_x', posx)
444 profile.putPreference('window_pos_y', posy)
445 (width, height) = self.GetSize()
446 profile.putPreference('window_width', width)
447 profile.putPreference('window_height', height)
449 # Save normal sash position. If in normal mode (!simple mode), get last position of sash before saving it...
450 isSimple = profile.getPreference('startMode') == 'Simple'
452 self.normalSashPos = self.splitter.GetSashPosition()
453 profile.putPreference('window_normal_sash', self.normalSashPos)
455 #HACK: Set the paint function of the glCanvas to nothing so it won't keep refreshing. Which keeps wxWidgets from quiting.
457 self.scene.OnPaint = lambda e : e
458 self.scene._slicer.cleanup()
464 class normalSettingsPanel(configBase.configPanelBase):
465 "Main user interface window"
466 def __init__(self, parent, callback = None):
467 super(normalSettingsPanel, self).__init__(parent, callback)
470 self.nb = wx.Notebook(self)
471 self.SetSizer(wx.BoxSizer(wx.HORIZONTAL))
472 self.GetSizer().Add(self.nb, 1, wx.EXPAND)
474 (left, right, self.printPanel) = self.CreateDynamicConfigTab(self.nb, 'Basic')
475 self._addSettingsToPanels('basic', left, right)
476 self.SizeLabelWidths(left, right)
478 (left, right, self.advancedPanel) = self.CreateDynamicConfigTab(self.nb, 'Advanced')
479 self._addSettingsToPanels('advanced', left, right)
480 self.SizeLabelWidths(left, right)
483 self.pluginPanel = pluginPanel.pluginPanel(self.nb, callback)
484 if len(self.pluginPanel.pluginList) > 0:
485 self.nb.AddPage(self.pluginPanel, "Plugins")
487 self.pluginPanel.Show(False)
490 self.alterationPanel = alterationPanel.alterationPanel(self.nb, callback)
491 self.nb.AddPage(self.alterationPanel, "Start/End-GCode")
493 self.Bind(wx.EVT_SIZE, self.OnSize)
495 self.nb.SetSize(self.GetSize())
496 self.UpdateSize(self.printPanel)
497 self.UpdateSize(self.advancedPanel)
499 def _addSettingsToPanels(self, category, left, right):
500 count = len(profile.getSubCategoriesFor(category)) + len(profile.getSettingsForCategory(category))
504 for title in profile.getSubCategoriesFor(category):
505 n += 1 + len(profile.getSettingsForCategory(category, title))
508 configBase.TitleRow(p, title)
509 for s in profile.getSettingsForCategory(category, title):
510 if s.checkConditions():
511 configBase.SettingRow(p, s.getName())
513 def SizeLabelWidths(self, left, right):
514 leftWidth = self.getLabelColumnWidth(left)
515 rightWidth = self.getLabelColumnWidth(right)
516 maxWidth = max(leftWidth, rightWidth)
517 self.setLabelColumnWidth(left, maxWidth)
518 self.setLabelColumnWidth(right, maxWidth)
521 # Make the size of the Notebook control the same size as this control
522 self.nb.SetSize(self.GetSize())
524 # Propegate the OnSize() event (just in case)
527 # Perform out resize magic
528 self.UpdateSize(self.printPanel)
529 self.UpdateSize(self.advancedPanel)
531 def UpdateSize(self, configPanel):
532 sizer = configPanel.GetSizer()
536 # if width(col1) < best_width(col1) || width(col2) < best_width(col2):
539 # if width(col1) > (best_width(col1) + best_width(col1)):
540 # switch to horizontal
543 col1 = configPanel.leftPanel
544 colSize1 = col1.GetSize()
545 colBestSize1 = col1.GetBestSize()
546 col2 = configPanel.rightPanel
547 colSize2 = col2.GetSize()
548 colBestSize2 = col2.GetBestSize()
550 orientation = sizer.GetOrientation()
552 if orientation == wx.HORIZONTAL:
553 if (colSize1[0] <= colBestSize1[0]) or (colSize2[0] <= colBestSize2[0]):
555 sizer = wx.BoxSizer(wx.VERTICAL)
556 sizer.Add(configPanel.leftPanel, flag=wx.EXPAND)
557 sizer.Add(configPanel.rightPanel, flag=wx.EXPAND)
558 configPanel.SetSizer(sizer)
564 if max(colSize1[0], colSize2[0]) > (colBestSize1[0] + colBestSize2[0]):
566 sizer = wx.BoxSizer(wx.HORIZONTAL)
567 sizer.Add(configPanel.leftPanel, proportion=1, border=35, flag=wx.EXPAND)
568 sizer.Add(configPanel.rightPanel, proportion=1, flag=wx.EXPAND)
569 configPanel.SetSizer(sizer)
575 def updateProfileToControls(self):
576 super(normalSettingsPanel, self).updateProfileToControls()
577 self.alterationPanel.updateProfileToControls()
578 self.pluginPanel.updateProfileToControls()