1 __copyright__ = "Copyright (C) 2013 David Braam - Released under terms of the AGPLv3 License"
2 from __future__ import absolute_import
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()
394 self.scene.sceneUpdated()
396 def OnMinecraftImport(self, e):
397 mi = minecraftImport.minecraftImportWindow(self)
401 def OnSuperformula(self, e):
402 sf = superformula.superformulaWindow(self)
403 #sf = superformula.superformulaEvolver(self)
407 def OnCheckForUpdate(self, e):
408 newVersion = version.checkForNewerVersion()
409 if newVersion is not None:
410 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:
411 webbrowser.open(newVersion)
413 wx.MessageBox('You are running the latest version of Cura!', 'Awesome!', wx.ICON_INFORMATION)
415 def OnAbout(self, e):
416 info = wx.AboutDialogInfo()
418 info.SetDescription('End solution for Open Source Fused Filament Fabrication 3D printing.')
419 info.SetWebSite('http://software.ultimaker.com/')
420 info.SetCopyright('Copyright (C) David Braam')
422 This program is free software: you can redistribute it and/or modify
423 it under the terms of the GNU Affero General Public License as published by
424 the Free Software Foundation, either version 3 of the License, or
425 (at your option) any later version.
427 This program is distributed in the hope that it will be useful,
428 but WITHOUT ANY WARRANTY; without even the implied warranty of
429 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
430 GNU Affero General Public License for more details.
432 You should have received a copy of the GNU Affero General Public License
433 along with this program. If not, see <http://www.gnu.org/licenses/>.
437 def OnClose(self, e):
438 profile.saveProfile(profile.getDefaultProfilePath())
440 # Save the window position, size & state from the preferences file
441 profile.putPreference('window_maximized', self.IsMaximized())
442 if not self.IsMaximized() and not self.IsIconized():
443 (posx, posy) = self.GetPosition()
444 profile.putPreference('window_pos_x', posx)
445 profile.putPreference('window_pos_y', posy)
446 (width, height) = self.GetSize()
447 profile.putPreference('window_width', width)
448 profile.putPreference('window_height', height)
450 # Save normal sash position. If in normal mode (!simple mode), get last position of sash before saving it...
451 isSimple = profile.getPreference('startMode') == 'Simple'
453 self.normalSashPos = self.splitter.GetSashPosition()
454 profile.putPreference('window_normal_sash', self.normalSashPos)
456 #HACK: Set the paint function of the glCanvas to nothing so it won't keep refreshing. Which keeps wxWidgets from quiting.
458 self.scene.OnPaint = lambda e : e
459 self.scene._slicer.cleanup()
465 class normalSettingsPanel(configBase.configPanelBase):
466 "Main user interface window"
467 def __init__(self, parent, callback = None):
468 super(normalSettingsPanel, self).__init__(parent, callback)
471 self.nb = wx.Notebook(self)
472 self.SetSizer(wx.BoxSizer(wx.HORIZONTAL))
473 self.GetSizer().Add(self.nb, 1, wx.EXPAND)
475 (left, right, self.printPanel) = self.CreateDynamicConfigTab(self.nb, 'Basic')
476 self._addSettingsToPanels('basic', left, right)
477 self.SizeLabelWidths(left, right)
479 (left, right, self.advancedPanel) = self.CreateDynamicConfigTab(self.nb, 'Advanced')
480 self._addSettingsToPanels('advanced', left, right)
481 self.SizeLabelWidths(left, right)
484 self.pluginPanel = pluginPanel.pluginPanel(self.nb, callback)
485 if len(self.pluginPanel.pluginList) > 0:
486 self.nb.AddPage(self.pluginPanel, "Plugins")
488 self.pluginPanel.Show(False)
491 self.alterationPanel = alterationPanel.alterationPanel(self.nb, callback)
492 self.nb.AddPage(self.alterationPanel, "Start/End-GCode")
494 self.Bind(wx.EVT_SIZE, self.OnSize)
496 self.nb.SetSize(self.GetSize())
497 self.UpdateSize(self.printPanel)
498 self.UpdateSize(self.advancedPanel)
500 def _addSettingsToPanels(self, category, left, right):
501 count = len(profile.getSubCategoriesFor(category)) + len(profile.getSettingsForCategory(category))
505 for title in profile.getSubCategoriesFor(category):
506 n += 1 + len(profile.getSettingsForCategory(category, title))
509 configBase.TitleRow(p, title)
510 for s in profile.getSettingsForCategory(category, title):
511 if s.checkConditions():
512 configBase.SettingRow(p, s.getName())
514 def SizeLabelWidths(self, left, right):
515 leftWidth = self.getLabelColumnWidth(left)
516 rightWidth = self.getLabelColumnWidth(right)
517 maxWidth = max(leftWidth, rightWidth)
518 self.setLabelColumnWidth(left, maxWidth)
519 self.setLabelColumnWidth(right, maxWidth)
522 # Make the size of the Notebook control the same size as this control
523 self.nb.SetSize(self.GetSize())
525 # Propegate the OnSize() event (just in case)
528 # Perform out resize magic
529 self.UpdateSize(self.printPanel)
530 self.UpdateSize(self.advancedPanel)
532 def UpdateSize(self, configPanel):
533 sizer = configPanel.GetSizer()
537 # if width(col1) < best_width(col1) || width(col2) < best_width(col2):
540 # if width(col1) > (best_width(col1) + best_width(col1)):
541 # switch to horizontal
544 col1 = configPanel.leftPanel
545 colSize1 = col1.GetSize()
546 colBestSize1 = col1.GetBestSize()
547 col2 = configPanel.rightPanel
548 colSize2 = col2.GetSize()
549 colBestSize2 = col2.GetBestSize()
551 orientation = sizer.GetOrientation()
553 if orientation == wx.HORIZONTAL:
554 if (colSize1[0] <= colBestSize1[0]) or (colSize2[0] <= colBestSize2[0]):
556 sizer = wx.BoxSizer(wx.VERTICAL)
557 sizer.Add(configPanel.leftPanel, flag=wx.EXPAND)
558 sizer.Add(configPanel.rightPanel, flag=wx.EXPAND)
559 configPanel.SetSizer(sizer)
565 if max(colSize1[0], colSize2[0]) > (colBestSize1[0] + colBestSize2[0]):
567 sizer = wx.BoxSizer(wx.HORIZONTAL)
568 sizer.Add(configPanel.leftPanel, proportion=1, border=35, flag=wx.EXPAND)
569 sizer.Add(configPanel.rightPanel, proportion=1, flag=wx.EXPAND)
570 configPanel.SetSizer(sizer)
576 def updateProfileToControls(self):
577 super(normalSettingsPanel, self).updateProfileToControls()
578 self.alterationPanel.updateProfileToControls()
579 self.pluginPanel.updateProfileToControls()