From 4c1c15e69e58762e51fd1d96059fa83fab39cfe2 Mon Sep 17 00:00:00 2001 From: DarkPhoenix Date: Tue, 30 Jul 2019 17:11:53 +0300 Subject: [PATCH] Rework target profile editor input boxes for better editing experience --- gui/graphFrame/panel.py | 6 +- gui/mainFrame.py | 4 +- gui/targetProfileEditor.py | 124 +++++++++---------- gui/{graphFrame/input.py => utils/inputs.py} | 53 ++++++-- 4 files changed, 107 insertions(+), 80 deletions(-) rename gui/{graphFrame/input.py => utils/inputs.py} (62%) diff --git a/gui/graphFrame/panel.py b/gui/graphFrame/panel.py index 70ebc95c2..03283117f 100644 --- a/gui/graphFrame/panel.py +++ b/gui/graphFrame/panel.py @@ -27,7 +27,7 @@ from gui.bitmap_loader import BitmapLoader from gui.contextMenu import ContextMenu from service.const import GraphCacheCleanupReason from service.fit import Fit -from .input import ConstantBox, RangeBox +from gui.utils.inputs import FloatBox, FloatRangeBox from .lists import FitList, TargetList from .vector import VectorPicker @@ -213,9 +213,9 @@ class GraphControlPanel(wx.Panel): handledHandles.add(inputDef.handle) fieldSizer = wx.BoxSizer(wx.HORIZONTAL) if mainInput: - fieldTextBox = RangeBox(self, self._storedRanges.get((inputDef.handle, inputDef.unit), inputDef.defaultRange)) + fieldTextBox = FloatRangeBox(self, self._storedRanges.get((inputDef.handle, inputDef.unit), inputDef.defaultRange)) else: - fieldTextBox = ConstantBox(self, self._storedConsts.get((inputDef.handle, inputDef.unit), inputDef.defaultValue)) + fieldTextBox = FloatBox(self, self._storedConsts.get((inputDef.handle, inputDef.unit), inputDef.defaultValue)) fieldTextBox.Bind(wx.EVT_TEXT, self.OnFieldChanged) fieldSizer.Add(fieldTextBox, 0, wx.EXPAND | wx.ALIGN_CENTER_VERTICAL | wx.RIGHT, 5) fieldIcon = None diff --git a/gui/mainFrame.py b/gui/mainFrame.py index dc81bdbfc..007df2ff7 100644 --- a/gui/mainFrame.py +++ b/gui/mainFrame.py @@ -57,7 +57,7 @@ from gui.marketBrowser import MarketBrowser from gui.multiSwitch import MultiSwitch from gui.patternEditor import DmgPatternEditorDlg from gui.preferenceDialog import PreferenceDialog -from gui.targetProfileEditor import ResistsEditorDlg +from gui.targetProfileEditor import TargetProfileEditorDlg from gui.setEditor import ImplantSetEditorDlg from gui.shipBrowser import ShipBrowser from gui.statsPane import StatsPane @@ -398,7 +398,7 @@ class MainFrame(wx.Frame): dlg.Show() def showTargetProfileEditor(self, event): - ResistsEditorDlg(self) + TargetProfileEditorDlg(self) def showDamagePatternEditor(self, event): dlg = DmgPatternEditorDlg(self) diff --git a/gui/targetProfileEditor.py b/gui/targetProfileEditor.py index c06dbdb55..d684fd2de 100644 --- a/gui/targetProfileEditor.py +++ b/gui/targetProfileEditor.py @@ -30,6 +30,7 @@ import gui.globalEvents as GE from gui.bitmap_loader import BitmapLoader from gui.builtinViews.entityEditor import EntityEditor, BaseValidator from gui.utils.clipboard import toClipboard, fromClipboard +from gui.utils.inputs import FloatBox, InputValidator, strToFloat from service.fit import Fit from service.targetProfile import TargetProfile @@ -37,13 +38,26 @@ from service.targetProfile import TargetProfile pyfalog = Logger(__name__) -class TargetProfileTextValidator(BaseValidator): +class ResistValidator(InputValidator): + + def _validateWithReason(self, value): + if not value: + return True, '' + value = strToFloat(value) + if value is None: + return False, 'Incorrect formatting (decimals only)' + if value < 0 or value > 100: + return False, 'Incorrect range (must be 0-100)' + return True, '' + + +class TargetProfileNameValidator(BaseValidator): def __init__(self): BaseValidator.__init__(self) def Clone(self): - return TargetProfileTextValidator() + return TargetProfileNameValidator() def Validate(self, win): entityEditor = win.parent @@ -68,7 +82,7 @@ class TargetProfileEntityEditor(EntityEditor): def __init__(self, parent): EntityEditor.__init__(self, parent, "Target Profile") - self.SetEditorValidator(TargetProfileTextValidator) + self.SetEditorValidator(TargetProfileNameValidator) def getEntitiesFromContext(self): sTR = TargetProfile.getInstance() @@ -94,7 +108,7 @@ class TargetProfileEntityEditor(EntityEditor): sTR.deletePattern(entity) -class ResistsEditorDlg(wx.Dialog): +class TargetProfileEditorDlg(wx.Dialog): DAMAGE_TYPES = OrderedDict([ ("em", "EM resistance"), ("thermal", "Thermal resistance"), @@ -146,11 +160,11 @@ class ResistsEditorDlg(wx.Dialog): bmp.SetToolTip(wx.ToolTip(ttText)) resistEditSizer.Add(bmp, 0, style, border) # set text edit - setattr(self, "%sEdit" % type_, wx.TextCtrl(self, wx.ID_ANY, "", wx.DefaultPosition, defSize)) - editObj = getattr(self, "%sEdit" % type_) - editObj.SetToolTip(wx.ToolTip(ttText)) - editObj.Bind(wx.EVT_TEXT, self.ValuesUpdated) - resistEditSizer.Add(editObj, 0, wx.BOTTOM | wx.TOP | wx.ALIGN_CENTER_VERTICAL, 5) + setattr(self, "%sEdit" % type_, FloatBox(parent=self, id=wx.ID_ANY, value=None, pos=wx.DefaultPosition, size=defSize, validator=ResistValidator())) + editBox = getattr(self, "%sEdit" % type_) + editBox.SetToolTip(wx.ToolTip(ttText)) + self.Bind(event=wx.EVT_TEXT, handler=self.OnFieldChanged, source=editBox) + resistEditSizer.Add(editBox, 0, wx.BOTTOM | wx.TOP | wx.ALIGN_CENTER_VERTICAL, 5) unit = wx.StaticText(self, wx.ID_ANY, "%", wx.DefaultPosition, wx.DefaultSize, 0) unit.SetToolTip(wx.ToolTip(ttText)) resistEditSizer.Add(unit, 0, wx.BOTTOM | wx.TOP | wx.ALIGN_CENTER_VERTICAL, 5) @@ -171,20 +185,17 @@ class ResistsEditorDlg(wx.Dialog): bmp.SetToolTip(wx.ToolTip(ttText)) miscAttrSizer.Add(bmp, 0, wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_RIGHT, 5) # set text edit - setattr(self, "%sEdit" % attr, wx.TextCtrl(self, wx.ID_ANY, "", wx.DefaultPosition, defSize)) - editObj = getattr(self, "%sEdit" % attr) - editObj.SetToolTip(wx.ToolTip(ttText)) - editObj.Bind(wx.EVT_TEXT, self.ValuesUpdated) - miscAttrSizer.Add(editObj, 0, wx.BOTTOM | wx.TOP | wx.ALIGN_CENTER_VERTICAL, 5) + setattr(self, "%sEdit" % attr, FloatBox(parent=self, id=wx.ID_ANY, value=None, pos=wx.DefaultPosition, size=defSize)) + editBox = getattr(self, "%sEdit" % attr) + editBox.SetToolTip(wx.ToolTip(ttText)) + self.Bind(event=wx.EVT_TEXT, handler=self.OnFieldChanged, source=editBox) + miscAttrSizer.Add(editBox, 0, wx.BOTTOM | wx.TOP | wx.ALIGN_CENTER_VERTICAL, 5) unit = wx.StaticText(self, wx.ID_ANY, unitText, wx.DefaultPosition, wx.DefaultSize, 0) unit.SetToolTip(wx.ToolTip(ttText)) miscAttrSizer.Add(unit, 0, wx.BOTTOM | wx.TOP | wx.ALIGN_CENTER_VERTICAL, 5) contentSizer.Add(miscAttrSizer, 1, wx.EXPAND | wx.ALL, 5) - # Color we use to reset invalid value color - self.colorReset = wx.SystemSettings.GetColour(wx.SYS_COLOUR_WINDOWTEXT) - self.slfooter = wx.StaticLine(self) contentSizer.Add(self.slfooter, 0, wx.EXPAND | wx.TOP, 5) @@ -235,58 +246,47 @@ class ResistsEditorDlg(wx.Dialog): self.Bind(wx.EVT_CLOSE, self.onClose) self.Bind(wx.EVT_CHAR_HOOK, self.kbEvent) + self.inputTimer = wx.Timer(self) + self.Bind(wx.EVT_TIMER, self.OnInputTimer, self.inputTimer) + self.patternChanged() self.ShowModal() - def ValuesUpdated(self, event=None): - """ - Event that is fired when resists values change. Iterates through all - resist edit fields. If blank, sets it to 0.0. If it is not a proper - decimal value, sets text color to red and refuses to save changes until - issue is resolved - """ + def OnFieldChanged(self, event=None): + if event is not None: + event.Skip() + self.inputTimer.Stop() + self.inputTimer.Start(Fit.getInstance().serviceFittingOptions['marketSearchDelay'], True) + + def OnInputTimer(self, event): + event.Skip() + if self.block: return - editObj = None - try: p = self.entityEditor.getActiveEntity() for type_ in self.DAMAGE_TYPES: - editObj = getattr(self, "%sEdit" % type_) + editBox = getattr(self, "%sEdit" % type_) + # Raise exception if value is not valid + if not editBox.isValid(): + reason = editBox.getInvalidationReason() + raise ValueError(reason) - if editObj.GetValue() == "": - # if we are blank, overwrite with 0 - editObj.ChangeValue("0.0") - editObj.SetInsertionPointEnd() - - value = float(editObj.GetValue()) - - # assertion, because they're easy - assert 0 <= value <= 100 - - # if everything checks out, set resist attribute + value = editBox.GetValueFloat() or 0 setattr(p, "%sAmount" % type_, value / 100) - editObj.SetForegroundColour(self.colorReset) for attr in self.ATTRIBUTES: - editObj = getattr(self, "%sEdit" % attr) + editBox = getattr(self, "%sEdit" % attr) + # Raise exception if value is not valid + if not editBox.isValid(): + reason = editBox.getInvalidationReason() + raise ValueError(reason) - if editObj.GetValue() == "" and attr != "signatureRadius": - # if we are blank, overwrite with 0 except for signatureRadius - editObj.ChangeValue("0.0") - editObj.SetInsertionPointEnd() - - # if everything checks out, set attribute - value = editObj.GetValue() - if value == '': - value = None - else: - value = float(value) + value = editBox.GetValueFloat() setattr(p, attr, value) - editObj.SetForegroundColour(self.colorReset) self.stNotice.SetLabel("") self.totSizer.Layout() @@ -297,16 +297,8 @@ class ResistsEditorDlg(wx.Dialog): TargetProfile.getInstance().saveChanges(p) wx.PostEvent(self.mainFrame, GE.TargetProfileChanged(profileID=p.ID)) - except ValueError: - editObj.SetForegroundColour(wx.RED) - msg = "Incorrect Formatting (decimals only)" - pyfalog.warning(msg) - self.stNotice.SetLabel(msg) - except AssertionError: - editObj.SetForegroundColour(wx.RED) - msg = "Incorrect Range (must be 0-100)" - pyfalog.warning(msg) - self.stNotice.SetLabel(msg) + except ValueError as e: + self.stNotice.SetLabel(e.args[0]) finally: # Refresh for color changes to take effect immediately self.Refresh() @@ -326,18 +318,18 @@ class ResistsEditorDlg(wx.Dialog): for field in self.DAMAGE_TYPES: edit = getattr(self, "%sEdit" % field) amount = getattr(p, "%sAmount" % field) * 100 - edit.ChangeValue(str(amount)) + edit.ChangeValueFloat(amount) for attr in self.ATTRIBUTES: edit = getattr(self, "%sEdit" % attr) amount = getattr(p, attr) if amount == math.inf: - edit.ChangeValue('') + edit.ChangeValueFloat(None) else: - edit.ChangeValue(str(amount)) + edit.ChangeValueFloat(amount) self.block = False - self.ValuesUpdated() + self.OnFieldChanged() def __del__(self): pass diff --git a/gui/graphFrame/input.py b/gui/utils/inputs.py similarity index 62% rename from gui/graphFrame/input.py rename to gui/utils/inputs.py index 5ddccb72c..dd643efed 100644 --- a/gui/graphFrame/input.py +++ b/gui/utils/inputs.py @@ -19,6 +19,7 @@ import re +from abc import ABCMeta, abstractmethod import wx @@ -38,18 +39,51 @@ def strToFloat(val): return None -class ConstantBox(wx.TextCtrl): +class InputValidator(metaclass=ABCMeta): - def __init__(self, parent, initial): - super().__init__(parent, wx.ID_ANY, style=0) + def validate(self, value): + return self._validateWithReason(value)[0] + + def getReason(self, value): + return self._validateWithReason(value)[1] + + @abstractmethod + def _validateWithReason(self, value): + raise NotImplementedError + + +class FloatBox(wx.TextCtrl): + + def __init__(self, parent, value, id=wx.ID_ANY, style=0, validator=None, **kwargs): + super().__init__(parent=parent, id=id, style=style, **kwargs) self.Bind(wx.EVT_TEXT, self.OnText) self._storedValue = '' - self.ChangeValue(valToStr(initial)) - + self._validator = validator + self.ChangeValue(valToStr(value)) def ChangeValue(self, value): self._storedValue = value super().ChangeValue(value) + self.updateColor() + + def ChangeValueFloat(self, value): + self.ChangeValue(valToStr(value)) + + def updateColor(self): + if self.isValid(): + self.SetForegroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_WINDOWTEXT)) + else: + self.SetForegroundColour(wx.RED) + + def isValid(self): + if self._validator is None: + return True + return self._validator.validate(self.GetValue()) + + def getInvalidationReason(self): + if self._validator is None: + return None + return self._validator.getReason(self.GetValue()) def OnText(self, event): currentValue = self.GetValue() @@ -58,6 +92,7 @@ class ConstantBox(wx.TextCtrl): return if currentValue == '' or re.match('^\d*\.?\d*$', currentValue): self._storedValue = currentValue + self.updateColor() event.Skip() else: self.ChangeValue(self._storedValue) @@ -66,13 +101,13 @@ class ConstantBox(wx.TextCtrl): return strToFloat(self.GetValue()) -class RangeBox(wx.TextCtrl): +class FloatRangeBox(wx.TextCtrl): - def __init__(self, parent, initRange): - super().__init__(parent, wx.ID_ANY, style=0) + def __init__(self, parent, value, id=wx.ID_ANY, style=0, **kwargs): + super().__init__(parent=parent, id=id, style=style, **kwargs) self.Bind(wx.EVT_TEXT, self.OnText) self._storedValue = '' - self.ChangeValue('{}-{}'.format(valToStr(min(initRange)), valToStr(max(initRange)))) + self.ChangeValue('{}-{}'.format(valToStr(min(value)), valToStr(max(value)))) def ChangeValue(self, value): self._storedValue = value