Created
February 9, 2019 07:46
-
-
Save larry801/6389ade9955cb264ccceaafaa688d80e to your computer and use it in GitHub Desktop.
A plugin sub system for NVDA external services like translation and so on.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# coding=utf-8 | |
# Copyright (C) 2019 Larry Wang <larry.wang.801@gmail.com> | |
# This file is covered by the GNU General Public License. | |
import pkgutil | |
import baseObject | |
from gui.settingsDialogs import SettingsPanel, SettingsDialog | |
from gui import guiHelper | |
import gui | |
import wx | |
from synthDriverHandler import StringParameterInfo | |
from wx.lib.expando import ExpandoTextCtrl | |
import winUser | |
import config | |
import addonHandler | |
import inspect | |
from logHandler import log | |
addonHandler.initTranslation() | |
_ = lambda x: x | |
# noinspection PyBroadException | |
class AbstractEngineHandler(object): | |
""" | |
Handlers for OCR / Translation / Weather Data Provider Engines | |
Following properties must be specified before use. | |
engineAddonName | |
enginePackageName | |
enginePackage | |
configSectionName | |
engineClass | |
engineClassName | |
You should also provide defaultEnginePriorityList if you need fallback behaviour like SynthEngine in NVDA. | |
""" | |
engine_list = None | |
engineAddonName = None | |
enginePackageName = None | |
enginePackage = None | |
configSectionName = None | |
engineClass = None | |
engineClassName = None | |
currentEngine = None | |
engine_class_list = None | |
defaultEnginePriorityList = ['empty'] | |
@classmethod | |
def init_config(cls): | |
try: | |
conf = config.conf[cls.configSectionName] | |
except KeyError: | |
config.conf[cls.configSectionName] = {} | |
config.conf[cls.configSectionName]["engine"] = cls.defaultEnginePriorityList[0] | |
@classmethod | |
def get_engine_list(cls): | |
cls.init_config() | |
if cls.engine_list: | |
return cls.engine_list | |
else: | |
cls.engine_list = [] | |
cls.engine_class_list = [] | |
# The engine that should be placed at the end of the list. | |
lastEngine = None | |
for loader, name, isPkg in pkgutil.iter_modules(cls.enginePackage.__path__): | |
if name.startswith('_'): | |
continue | |
try: | |
engine = cls.get_engine(name) | |
except: | |
log.error("Error while importing %s" % name, exc_info=True) | |
continue | |
try: | |
if engine.check(): | |
if engine.name == "empty": | |
lastEngine = (engine.name, engine.description) | |
else: | |
cls.engine_list.append((engine.name, engine.description)) | |
cls.engine_class_list.append(engine) | |
else: | |
log.debugWarning("Engine '%s' doesn't pass the check, excluding from list" % name) | |
except: | |
log.error("", exc_info=True) | |
cls.engine_list.sort(key=lambda s: s[1].lower()) | |
if lastEngine: | |
cls.engine_list.append(lastEngine) | |
return cls.engine_list | |
@classmethod | |
def set_current_engine(cls, name, isFallback=False): | |
if name == 'auto': | |
name = cls.defaultEnginePriorityList[0] | |
if cls.currentEngine: | |
cls.currentEngine.cancel() | |
cls.currentEngine.terminate() | |
prevEngineName = cls.currentEngine.name | |
cls.currentEngine = None | |
else: | |
prevEngineName = None | |
try: | |
new_engine = cls.get_engine_instance(name) | |
cls.currentEngine = new_engine | |
if not isFallback: | |
config.conf[cls.configSectionName]["engine"] = name | |
log.info("Loaded engine %s" % name) | |
return True | |
except: | |
log.error("setSynth", exc_info=True) | |
if prevEngineName: | |
# There was a previous engine, so switch back to that one. | |
cls.set_current_engine(prevEngineName, isFallback=True) | |
else: | |
# There was no previous engine, so fallback to the next available default synthesizer that has not been tried yet. | |
try: | |
nextIndex = cls.defaultEnginePriorityList.index(name) + 1 | |
except ValueError: | |
nextIndex = 0 | |
if nextIndex < len(cls.defaultEnginePriorityList): | |
newName = cls.defaultEnginePriorityList[nextIndex] | |
cls.set_current_engine(newName, isFallback=True) | |
return False | |
@classmethod | |
def get_engine_instance(cls, name): | |
new_engine = cls.get_engine(str(name))() | |
if config.conf[cls.configSectionName].isSet(name): | |
new_engine.loadSettings() | |
else: | |
config.conf[cls.configSectionName][name] = {} | |
new_engine.saveSettings() | |
return new_engine | |
@classmethod | |
def get_engine(cls, name): | |
engine_module = cls.import_class(cls.enginePackageName, name) | |
for items in dir(engine_module): | |
obj = getattr(engine_module, items) | |
if inspect.isclass(obj) and issubclass(obj, cls.engineClass): | |
return obj | |
@classmethod | |
def get_current_engine(cls): | |
return cls.currentEngine | |
@classmethod | |
def import_class(cls, module_name, class_name): | |
imported_module = __import__(module_name, globals(), locals(), [class_name]) | |
return getattr(imported_module, class_name) | |
def handle_post_config_profile_switch(self): | |
pass | |
class EngineSetting(object): | |
""" | |
Represents an engine setting such as voice or variant. | |
""" | |
configSpec = "string(default=None)" | |
def __init__(self, name, displayNameWithAccelerator, availableInEngineSettingsRing=True, displayName=None): | |
self.name = name | |
self.displayNameWithAccelerator = displayNameWithAccelerator | |
if not displayName: | |
# Strip accelerator from displayNameWithAccelerator. | |
displayName = displayNameWithAccelerator.replace("&", "") | |
self.displayName = displayName | |
self.availableInEngineSettingsRing = availableInEngineSettingsRing | |
class TextInputEngineSetting(EngineSetting): | |
""" | |
Represents an engine setting such as API_KEY or API_SECRET. | |
""" | |
pass | |
class NumericEngineSetting(EngineSetting): | |
"""Represents a numeric synthesizer setting such as rate, volume or pitch.""" | |
configSpec = "integer(default=50,min=0,max=100)" | |
def __init__(self, name, displayNameWithAccelerator, availableInEngineSettingsRing=True, minStep=1, normalStep=5, | |
largeStep=10, displayName=None): | |
""" | |
@param minStep: Specifies the minimum step between valid values for each numeric setting. For example, if L{minStep} is set to 10, setting values can only be multiples of 10; 10, 20, 30, etc. | |
@type minStep: int | |
@param normalStep: Specifies the step between values that a user will normally prefer. This is used in the settings ring. | |
@type normalStep: int | |
@param largeStep: Specifies the step between values if a large adjustment is desired. This is used for pageUp/pageDown on sliders in the Voice Settings dialog. | |
@type largeStep: int | |
@note: If necessary, the step values will be normalised so that L{minStep} <= L{normalStep} <= L{largeStep}. | |
""" | |
super(NumericEngineSetting, self).__init__(name, displayNameWithAccelerator, | |
availableInEngineSettingsRing=availableInEngineSettingsRing, | |
displayName=displayName) | |
self.minStep = minStep | |
self.normalStep = max(normalStep, minStep) | |
self.largeStep = max(largeStep, self.normalStep) | |
class BooleanEngineSetting(EngineSetting): | |
"""Represents a boolean synthesiser setting such as rate boost. | |
""" | |
configSpec = "boolean(default=False)" | |
def __init__(self, name, displayNameWithAccelerator, availableInEngineSettingsRing=False, displayName=None): | |
super(BooleanEngineSetting, self).__init__(name, displayNameWithAccelerator, | |
availableInEngineSettingsRing=availableInEngineSettingsRing, | |
displayName=displayName) | |
class AbstractEngine(baseObject.AutoPropertyObject): | |
"""Abstract base engine for external service like OCR Translation and so on. | |
Each engine should be a separate Python module in the enginePackage of handler containing a | |
class which inherits from this base class. | |
At a minimum, engines must set L{name} and L{description} and override the L{check} method. | |
Other methods should be overridden as appropriate. | |
L{supportedSettings} should be set as appropriate for the settings supported by the engine. | |
There are factory functions to create L{EngineSetting} instances for common settings; e.g. L{APIKeySetting} . | |
Each setting is retrieved and set using attributes named after the setting; | |
e.g. the L{apiKey} attribute is used for the L{apiKey} setting. | |
These will usually be properties. | |
@var supportedSettings: The settings supported by the engine. | |
@type supportedSettings: list or tuple of L{EngineSetting} | |
""" | |
#: The name of the engine; must be the original module file name. | |
#: @type: str | |
name = "empty" | |
#: A description of the engine. | |
#: @type: str | |
description = "" | |
@classmethod | |
def APIKeySetting(cls): | |
"""Factory function for creating a language setting.""" | |
# Translators: Label for a setting in voice settings dialog. | |
return TextInputEngineSetting("apiKey", _("API &Key")) | |
@classmethod | |
def APISecretSetting(cls): | |
"""Factory function for creating a language setting.""" | |
# Translators: Label for a setting in voice settings dialog. | |
return TextInputEngineSetting("apiSecret", _("API &Secret Key")) | |
@classmethod | |
def LanguageSetting(cls): | |
"""Factory function for creating a language setting.""" | |
# Translators: Label for a setting in voice settings dialog. | |
return EngineSetting("language", _("Recognition Language")) | |
def _get_supportedSettings(self): | |
raise NotImplementedError | |
@staticmethod | |
def generate_string_settings(settings_dict): | |
""" | |
Generate StringSettings from dict | |
:type settings_dict: dict | |
:param settings_dict: | |
:return: | |
""" | |
return {x: StringParameterInfo(x, settings_dict[x]) for x in settings_dict.keys()} | |
@classmethod | |
def check(cls): | |
return False | |
def cancel(self): | |
""" | |
Cancel current operation. | |
:return: | |
""" | |
pass | |
def terminate(self): | |
""" | |
Clean up resources before exit. | |
:return: | |
""" | |
pass | |
def saveSettings(self): | |
conf = config.conf[self.config_key][self.name] | |
for setting in self.supportedSettings: | |
conf[setting.name] = getattr(self, setting.name) | |
def loadSettings(self, onlyChanged=False): | |
c = config.conf[self.config_key][self.name] | |
for s in self.supportedSettings: | |
try: | |
val = c[s.name] | |
except KeyError: | |
continue | |
if val is None: | |
continue | |
if onlyChanged and getattr(self, s.name) == val: | |
continue | |
setattr(self, s.name, val) | |
class AbstractEngineSettingsPanel(SettingsPanel): | |
""" | |
Settings panel of external services. | |
handler must be specified before use. | |
""" | |
name = _("Engine") | |
engineNameCtrl = None # type: ExpandoTextCtrl | |
engineSettingPanel = None # type: SpecificEnginePanel | |
handler = AbstractEngineHandler # type: AbstractEngineHandler | |
def makeSettings(self, settingsSizer): | |
settingsSizerHelper = guiHelper.BoxSizerHelper(self, sizer=settingsSizer) | |
# Translators: A label for the engines on the engine panel. | |
engineLabel = _("&Engines") | |
engineBox = wx.StaticBox(self, label=engineLabel) | |
engineGroup = guiHelper.BoxSizerHelper(self, sizer=wx.StaticBoxSizer(engineBox, wx.HORIZONTAL)) | |
settingsSizerHelper.addItem(engineGroup) | |
# Use a ExpandoTextCtrl because even when readonly it accepts focus from keyboard, which | |
# standard readonly TextCtrl does not. ExpandoTextCtrl is a TE_MULTILINE control, however | |
# by default it renders as a single line. Standard TextCtrl with TE_MULTILINE has two lines, | |
# and a vertical scroll bar. This is not necessary for the single line of text we wish to | |
# display here. | |
engineDesc = self.handler.get_current_engine().description | |
self.engineNameCtrl = ExpandoTextCtrl(self, size=(self.scaleSize(250), -1), value=engineDesc, | |
style=wx.TE_READONLY) | |
self.engineNameCtrl.Bind(wx.EVT_CHAR_HOOK, self._enterTriggersOnChangeEngine) | |
# Translators: This is the label for the button used to change engines, | |
# it appears in the context of a engine group on the speech settings panel. | |
changeEngineBtn = wx.Button(self, label=_("C&hange...")) | |
engineGroup.addItem( | |
guiHelper.associateElements( | |
self.engineNameCtrl, | |
changeEngineBtn | |
) | |
) | |
changeEngineBtn.Bind(wx.EVT_BUTTON, self.onChangeEngine) | |
self.engineSettingPanel = SpecificEnginePanel(self, self.handler) | |
settingsSizerHelper.addItem(self.engineSettingPanel) | |
def _enterTriggersOnChangeEngine(self, evt): | |
if evt.KeyCode == wx.WXK_RETURN: | |
self.onChangeEngine(evt) | |
else: | |
evt.Skip() | |
def onChangeEngine(self, evt): | |
change_engine = EnginesSelectionDialog(self, self.handler, multiInstanceAllowed=True) | |
ret = change_engine.ShowModal() | |
if ret == wx.ID_OK: | |
self.Freeze() | |
# trigger a refresh of the settings | |
self.onPanelActivated() | |
self._sendLayoutUpdatedEvent() | |
self.Thaw() | |
def updateCurrentEngine(self): | |
engine_description = self.handler.get_current_engine().description | |
self.engineNameCtrl.SetValue(engine_description) | |
def onPanelActivated(self): | |
# call super after all panel updates have been completed, we do not want the panel to show until this is complete. | |
self.engineSettingPanel.onPanelActivated() | |
super(AbstractEngineSettingsPanel, self).onPanelActivated() | |
def onPanelDeactivated(self): | |
self.engineSettingPanel.onPanelDeactivated() | |
super(AbstractEngineSettingsPanel, self).onPanelDeactivated() | |
def onDiscard(self): | |
self.engineSettingPanel.onDiscard() | |
def onSave(self): | |
self.engineSettingPanel.onSave() | |
class EnginesSelectionDialog(SettingsDialog): | |
""" | |
""" | |
engineNames = [] # type: list | |
engineList = None # type: wx.Choice | |
handler = None | |
# Translators: This is the label for the synthesizer selection dialog | |
title = _("Select Engines") | |
def __init__(self, parent, handler, multiInstanceAllowed=True): | |
self.handler = handler | |
super(EnginesSelectionDialog, self).__init__(parent, multiInstanceAllowed=multiInstanceAllowed) | |
def makeSettings(self, settingsSizer): | |
settingsSizerHelper = guiHelper.BoxSizerHelper(self, sizer=settingsSizer) | |
# Translators: This is a label for the select | |
# synthesizer combobox in the synthesizer dialog. | |
engineListLabelText = _("&Engines:") | |
self.engineList = settingsSizerHelper.addLabeledControl(engineListLabelText, wx.Choice, choices=[]) | |
self.updateEngineList() | |
def postInit(self): | |
# Finally, ensure that focus is on the engine list | |
self.engineList.SetFocus() | |
def updateEngineList(self): | |
handler = self.handler # type: AbstractEngineHandler | |
driverList = handler.engine_list | |
self.engineNames = [x[0] for x in driverList] | |
options = [x[1] for x in driverList] | |
self.engineList.Clear() | |
self.engineList.AppendItems(options) | |
try: | |
index = self.engineNames.index(handler.get_current_engine().name) | |
self.engineList.SetSelection(index) | |
except: | |
pass | |
def onOk(self, evt): | |
handler = self.handler # type: AbstractEngineHandler | |
if not self.engineNames: | |
# The list of engines has not been populated yet, so we didn't change anything in this panel | |
return | |
newEngineName = self.engineNames[self.engineList.GetSelection()] | |
if not handler.get_engine(newEngineName): | |
# Translators: This message is presented when | |
# NVDA is unable to load the selected engine. | |
gui.messageBox(_("Could not load the %s engine.") % newEngineName, _("Engine Error"), | |
wx.OK | wx.ICON_WARNING, self) | |
return | |
handler.set_current_engine(newEngineName) | |
if self.IsModal(): | |
# Hack: we need to update the engine in our parent window before closing. | |
# Otherwise, NVDA will report the old engine even though the new engine is reflected visually. | |
self.Parent.updateCurrentEngine() | |
super(EnginesSelectionDialog, self).onOk(evt) | |
class EngineSettingChanger(object): | |
"""Functor which acts as callback for GUI events.""" | |
def __init__(self, setting, engine): | |
self.engine = engine | |
self.setting = setting | |
def __call__(self, evt): | |
val = evt.GetSelection() | |
setattr(self.engine, self.setting.name, val) | |
class StringEngineSettingChanger(EngineSettingChanger): | |
"""Same as L{EngineSettingChanger} but handles combobox events.""" | |
def __init__(self, setting, engine, panel): | |
self.panel = panel | |
super(StringEngineSettingChanger, self).__init__(setting, engine) | |
def __call__(self, evt): | |
setattr(self.engine, self.setting.name, | |
getattr(self.panel, "_%ss" % self.setting.name)[evt.GetSelection()].ID) | |
class VoiceSettingsSlider(wx.Slider): | |
def __init__(self, *args, **kwargs): | |
super(VoiceSettingsSlider, self).__init__(*args, **kwargs) | |
self.Bind(wx.EVT_CHAR, self.onSliderChar) | |
def SetValue(self, i): | |
super(VoiceSettingsSlider, self).SetValue(i) | |
evt = wx.CommandEvent(wx.wxEVT_COMMAND_SLIDER_UPDATED, self.GetId()) | |
evt.SetInt(i) | |
self.ProcessEvent(evt) | |
# HACK: Win events don't seem to be sent for certain explicitly set values, | |
# so send our own win event. | |
# This will cause duplicates in some cases, but NVDA will filter them out. | |
winUser.user32.NotifyWinEvent(winUser.EVENT_OBJECT_VALUECHANGE, self.Handle, winUser.OBJID_CLIENT, | |
winUser.CHILDID_SELF) | |
def onSliderChar(self, evt): | |
key = evt.KeyCode | |
if key == wx.WXK_UP: | |
newValue = min(self.Value + self.LineSize, self.Max) | |
elif key == wx.WXK_DOWN: | |
newValue = max(self.Value - self.LineSize, self.Min) | |
elif key == wx.WXK_PAGEUP: | |
newValue = min(self.Value + self.PageSize, self.Max) | |
elif key == wx.WXK_PAGEDOWN: | |
newValue = max(self.Value - self.PageSize, self.Min) | |
elif key == wx.WXK_HOME: | |
newValue = self.Max | |
elif key == wx.WXK_END: | |
newValue = self.Min | |
else: | |
evt.Skip() | |
return | |
self.SetValue(newValue) | |
class SpecificEnginePanel(SettingsPanel): | |
sizerDict = None # type: dict | |
lastControl = None # type: wx.Window | |
# Translators: This is the label for the voice settings panel. | |
title = _("Voice") | |
handler = None # type: AbstractEngineHandler | |
def __init__(self, parent, handler, ): | |
self.handler = handler | |
super(SpecificEnginePanel, self).__init__(parent) | |
@classmethod | |
def _setSliderStepSizes(cls, slider, setting): | |
slider.SetLineSize(setting.minStep) | |
slider.SetPageSize(setting.largeStep) | |
def makeSettingControl(self, setting): | |
"""Constructs appropriate GUI controls for given L{EngineSetting} such as label and slider. | |
@param setting: Setting to construct controls for | |
@type setting: L{NumericEngineSetting} | |
@returns: WXSizer containing newly created controls. | |
@rtype: L{wx.BoxSizer} | |
""" | |
engine = self.handler.get_current_engine() | |
sizer = wx.BoxSizer(wx.HORIZONTAL) | |
label = wx.StaticText(self, wx.ID_ANY, label="%s:" % setting.displayNameWithAccelerator) | |
slider = VoiceSettingsSlider(self, wx.ID_ANY, minValue=0, maxValue=100) | |
setattr(self, "%sSlider" % setting.name, slider) | |
slider.Bind(wx.EVT_SLIDER, EngineSettingChanger(setting, engine)) | |
self._setSliderStepSizes(slider, setting) | |
slider.SetValue(getattr(engine, setting.name)) | |
sizer.Add(label) | |
sizer.Add(slider) | |
if self.lastControl: | |
slider.MoveAfterInTabOrder(self.lastControl) | |
self.lastControl = slider | |
return sizer | |
def makeStringSettingControl(self, setting): | |
"""Same as L{makeSettingControl} but for string settings. Returns sizer with label and combobox.""" | |
labelText = "%s:" % setting.displayNameWithAccelerator | |
engine = self.handler.get_current_engine() | |
setattr(self, "_%ss" % setting.name, getattr(engine, "available%ss" % setting.name.capitalize()).values()) | |
l = getattr(self, "_%ss" % setting.name) | |
labeledControl = guiHelper.LabeledControlHelper(self, labelText, wx.Choice, choices=[x.name for x in l]) | |
lCombo = labeledControl.control | |
setattr(self, "%sList" % setting.name, lCombo) | |
try: | |
cur = getattr(engine, setting.name) | |
i = [x.ID for x in l].index(cur) | |
lCombo.SetSelection(i) | |
except ValueError: | |
pass | |
lCombo.Bind(wx.EVT_CHOICE, StringEngineSettingChanger(setting, engine, self)) | |
if self.lastControl: | |
lCombo.MoveAfterInTabOrder(self.lastControl) | |
self.lastControl = lCombo | |
return labeledControl.sizer | |
def makeBooleanSettingControl(self, setting): | |
"""Same as L{makeSettingControl} but for boolean settings. Returns checkbox.""" | |
engine = self.handler.get_current_engine() | |
checkbox = wx.CheckBox(self, wx.ID_ANY, label=setting.displayNameWithAccelerator) | |
setattr(self, "%sCheckbox" % setting.name, checkbox) | |
value = getattr(engine, setting.name) | |
if value == u"False": | |
value = False | |
else: | |
value = True | |
checkbox.SetValue(value) | |
if self.lastControl: | |
checkbox.MoveAfterInTabOrder(self.lastControl) | |
self.lastControl = checkbox | |
return checkbox | |
def makeTextInputSettingControl(self, setting): | |
""" | |
:param setting: | |
:type setting: TextInputEngineSetting | |
:return: | |
""" | |
labelText = "%s:" % setting.displayNameWithAccelerator | |
engine = self.handler.get_current_engine() | |
# textCtrl = wx.TextCtrl(self, wx.ID_ANY) | |
# textLabel = wx.StaticText(self, wx.ID_ANY, label=setting.displayNameWithAccelerator) | |
# setattr(self, "%sLabel" % setting.name, textLabel) | |
labeledControl = guiHelper.LabeledControlHelper(self, labelText, wx.TextCtrl) | |
# textCtrl.Bind(wx.EVT_CHECKBOX, | |
# lambda evt: setattr(engine, setting.name, evt.IsChecked())) | |
textCtrl = labeledControl.control | |
setattr(self, "%sTextCtrl" % setting.name, textCtrl) | |
textCtrl.SetValue(getattr(engine, setting.name)) | |
if self.lastControl: | |
textCtrl.MoveAfterInTabOrder(self.lastControl) | |
self.lastControl = textCtrl | |
return textCtrl | |
def onPanelActivated(self): | |
engine = self.handler.get_current_engine() | |
if engine.name is not self._synth.name: | |
if gui._isDebug(): | |
log.debug("refreshing voice panel") | |
self.sizerDict.clear() | |
self.settingsSizer.Clear(delete_windows=True) | |
self.makeSettings(self.settingsSizer) | |
super(SpecificEnginePanel, self).onPanelActivated() | |
def makeSettings(self, settingsSizer): | |
self.sizerDict = {} | |
self.lastControl = None | |
# Create controls for engine Settings | |
self.updateVoiceSettings() | |
def updateVoiceSettings(self, changedSetting=None): | |
"""Creates, hides or updates existing GUI controls for all of supported settings.""" | |
engine = self._synth = self.handler.get_current_engine() | |
log.info(engine) | |
# firstly check already created options | |
from six import iteritems | |
for name, sizer in iteritems(self.sizerDict): | |
if name == changedSetting: | |
# Changing a setting shouldn't cause that setting itself to disappear. | |
continue | |
if not engine.isSupported(name): | |
self.settingsSizer.Hide(sizer) | |
# Create new controls, update already existing | |
settings = engine.supportedSettings | |
for setting in settings: | |
if setting.name == changedSetting: | |
# Changing a setting shouldn't cause that setting's own values to change. | |
continue | |
if setting.name in self.sizerDict: # update a value | |
self.settingsSizer.Show(self.sizerDict[setting.name]) | |
if isinstance(setting, NumericEngineSetting): | |
getattr(self, "%sSlider" % setting.name).SetValue(getattr(engine, setting.name)) | |
elif isinstance(setting, BooleanEngineSetting): | |
getattr(self, "%sCheckbox" % setting.name).SetValue(getattr(engine, setting.name)) | |
elif isinstance(setting, TextInputEngineSetting): | |
getattr(self, "%sTextCtrl" % setting.name).SetValue(getattr(engine, setting.name)) | |
else: | |
l = getattr(self, "_%ss" % setting.name) | |
lCombo = getattr(self, "%sList" % setting.name) | |
try: | |
cur = getattr(engine, setting.name) | |
i = [x.ID for x in l].index(cur) | |
lCombo.SetSelection(i) | |
except ValueError: | |
pass | |
else: # create a new control | |
if isinstance(setting, NumericEngineSetting): | |
settingMaker = self.makeSettingControl | |
elif isinstance(setting, BooleanEngineSetting): | |
settingMaker = self.makeBooleanSettingControl | |
elif isinstance(setting, TextInputEngineSetting): | |
settingMaker = self.makeTextInputSettingControl | |
else: | |
settingMaker = self.makeStringSettingControl | |
s = settingMaker(setting) | |
if isinstance(s, tuple): | |
self.sizerDict[setting.name] = s[0] | |
self.sizerDict[setting.name + "Label"] = s[1] | |
self.settingsSizer.Insert(len(self.sizerDict) - 2, s[0], border=10, flag=wx.BOTTOM) | |
self.settingsSizer.Insert(len(self.sizerDict) - 1, s[1], border=10, flag=wx.BOTTOM) | |
else: | |
self.sizerDict[setting.name] = s | |
self.settingsSizer.Insert(len(self.sizerDict) - 1, s, border=10, flag=wx.BOTTOM) | |
# Update graphical layout of the dialog | |
self.settingsSizer.Layout() | |
def onDiscard(self): | |
engine = self.handler.get_current_engine() | |
# unbind change events for string settings as wx closes combo boxes on cancel | |
for setting in engine.supportedSettings: | |
if isinstance(setting, (NumericEngineSetting, BooleanEngineSetting, TextInputEngineSetting)): | |
continue | |
getattr(self, "%sList" % setting.name).Unbind(wx.EVT_CHOICE) | |
# restore settings | |
engine.loadSettings() | |
super(SpecificEnginePanel, self).onDiscard() | |
def onSave(self): | |
engine = self.handler.get_current_engine() | |
engine.saveSettings() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment