# ============================================================================= # Copyright (C) 2010 Diego Duclos # # This file is part of pyfa. # # pyfa is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # pyfa is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with pyfa. If not, see . # ============================================================================= # noinspection PyPackageRequirements import wx from gui.bitmap_loader import BitmapLoader from service.fit import Fit from .input import ConstantBox, RangeBox from .lists import FitList, TargetList from .vector import VectorPicker def range2Const(defConst, defRange, limits, oldRange): if oldRange[0] is None or oldRange[1] is None: return defConst if oldRange[0] == oldRange[1]: return oldRange[0] if defConst is None: defConst = 0 defPos = (defConst - min(defRange)) / (max(defRange) - min(defRange)) newConst = min(oldRange) + (max(oldRange) - min(oldRange)) * defPos newConst = max(newConst, min(limits)) newConst = min(newConst, max(limits)) newConst = round(newConst, 3) return newConst def const2Range(defConst, defRange, limits, oldConst): if oldConst is None: return defRange if defConst is None: defConst = 0 newMin = oldConst - defConst + min(defRange) newMax = oldConst - defConst + max(defRange) # Fits into limits if newMin >= min(limits) and newMax <= max(limits): pass # Both do not fit elif newMin < min(limits) and newMax > max(limits): newMin = min(limits) newMax = max(limits) # Min doesn't fit elif newMin < min(limits): shift = min(limits) - newMin newMin += shift newMax += shift # Max doesn't fit elif newMax > max(limits): shift = newMax - max(limits) newMin -= shift newMax -= shift return round(newMin, 3), round(newMax, 3) class GraphControlPanel(wx.Panel): def __init__(self, graphFrame, parent): super().__init__(parent) self.graphFrame = graphFrame self.inputs = {} self.storedValues = {} mainSizer = wx.BoxSizer(wx.VERTICAL) optsSizer = wx.BoxSizer(wx.HORIZONTAL) commonOptsSizer = wx.BoxSizer(wx.VERTICAL) ySubSelectionSizer = wx.BoxSizer(wx.HORIZONTAL) yText = wx.StaticText(self, wx.ID_ANY, 'Axis Y:') ySubSelectionSizer.Add(yText, 0, wx.ALIGN_CENTER_VERTICAL | wx.RIGHT, 5) self.ySubSelection = wx.Choice(self, wx.ID_ANY) self.ySubSelection.Bind(wx.EVT_CHOICE, self.OnYTypeUpdate) ySubSelectionSizer.Add(self.ySubSelection, 1, wx.EXPAND | wx.ALL, 0) commonOptsSizer.Add(ySubSelectionSizer, 0, wx.EXPAND | wx.ALL, 0) xSubSelectionSizer = wx.BoxSizer(wx.HORIZONTAL) xText = wx.StaticText(self, wx.ID_ANY, 'Axis X:') xSubSelectionSizer.Add(xText, 0, wx.ALIGN_CENTER_VERTICAL | wx.RIGHT, 5) self.xSubSelection = wx.Choice(self, wx.ID_ANY) self.xSubSelection.Bind(wx.EVT_CHOICE, self.OnXTypeUpdate) xSubSelectionSizer.Add(self.xSubSelection, 1, wx.EXPAND | wx.ALL, 0) commonOptsSizer.Add(xSubSelectionSizer, 0, wx.EXPAND | wx.TOP, 5) self.showY0Cb = wx.CheckBox(self, wx.ID_ANY, 'Always show Y = 0', wx.DefaultPosition, wx.DefaultSize, 0) self.showY0Cb.SetValue(True) self.showY0Cb.Bind(wx.EVT_CHECKBOX, self.OnShowY0Change) commonOptsSizer.Add(self.showY0Cb, 0, wx.EXPAND | wx.TOP, 5) optsSizer.Add(commonOptsSizer, 0, wx.EXPAND | wx.RIGHT, 10) graphOptsSizer = wx.BoxSizer(wx.HORIZONTAL) self.inputsSizer = wx.BoxSizer(wx.VERTICAL) graphOptsSizer.Add(self.inputsSizer, 1, wx.EXPAND | wx.ALL, 0) self.srcVectorSizer = wx.BoxSizer(wx.VERTICAL) self.srcVectorLabel = wx.StaticText(self, wx.ID_ANY, '') self.srcVectorSizer.Add(self.srcVectorLabel, 0, wx.ALIGN_CENTER_HORIZONTAL| wx.BOTTOM, 5) self.srcVector = VectorPicker(self, style=wx.NO_BORDER, size=75, offset=90) self.srcVector.Bind(VectorPicker.EVT_VECTOR_CHANGED, self.OnFieldChanged) self.srcVectorSizer.Add(self.srcVector, 0, wx.SHAPED | wx.ALIGN_CENTER_HORIZONTAL | wx.ALIGN_CENTER_VERTICAL | wx.ALL, 0) graphOptsSizer.Add(self.srcVectorSizer, 0, wx.EXPAND | wx.LEFT, 15) self.tgtVectorSizer = wx.BoxSizer(wx.VERTICAL) self.tgtVectorLabel = wx.StaticText(self, wx.ID_ANY, '') self.tgtVectorSizer.Add(self.tgtVectorLabel, 0, wx.ALIGN_CENTER_HORIZONTAL | wx.BOTTOM, 5) self.tgtVector = VectorPicker(self, style=wx.NO_BORDER, size=75, offset=-90) self.tgtVector.Bind(VectorPicker.EVT_VECTOR_CHANGED, self.OnFieldChanged) self.tgtVectorSizer.Add(self.tgtVector, 0, wx.SHAPED | wx.ALIGN_CENTER_HORIZONTAL | wx.ALIGN_CENTER_VERTICAL | wx.ALL, 0) graphOptsSizer.Add(self.tgtVectorSizer, 0, wx.EXPAND | wx.LEFT, 10) optsSizer.Add(graphOptsSizer, 1, wx.EXPAND | wx.ALL, 0) mainSizer.Add(optsSizer, 0, wx.EXPAND | wx.ALL, 10) srcTgtSizer = wx.BoxSizer(wx.HORIZONTAL) self.fitList = FitList(graphFrame, self) self.fitList.SetMinSize((270, -1)) srcTgtSizer.Add(self.fitList, 1, wx.EXPAND | wx.ALL, 0) self.targets = [] self.targetList = TargetList(graphFrame, self) self.targetList.SetMinSize((270, -1)) self.targetList.update(self.targets) srcTgtSizer.Add(self.targetList, 1, wx.EXPAND | wx.LEFT, 10) mainSizer.Add(srcTgtSizer, 1, wx.EXPAND | wx.LEFT | wx.BOTTOM | wx.RIGHT, 10) self.SetSizer(mainSizer) self.drawTimer = wx.Timer(self) self.Bind(wx.EVT_TIMER, self.OnDrawTimer, self.drawTimer) self.setVectorDefaults() def storeCurrentValues(self): for k, v in self.getValues().items(): handle = k value, unit = v self.storedValues[(handle, unit)] = value def setVectorDefaults(self): self.srcVector.SetValue(length=0, angle=0) self.tgtVector.SetValue(length=1, angle=90) def updateControls(self, layout=True): view = self.graphFrame.getView() self.ySubSelection.Clear() self.xSubSelection.Clear() for yDef in view.yDefs: self.ySubSelection.Append(self._formatLabel(yDef), (yDef.handle, yDef.unit)) self.ySubSelection.SetSelection(0) for xDef in view.xDefs: self.xSubSelection.Append(self._formatLabel(xDef), (xDef.handle, xDef.unit)) self.xSubSelection.SetSelection(0) # Vectors self.setVectorDefaults() if view.srcVectorDef is not None: self.srcVectorLabel.SetLabel(view.srcVectorDef.label) self.srcVector.Show(True) self.srcVectorLabel.Show(True) else: self.srcVector.Show(False) self.srcVectorLabel.Show(False) if view.tgtVectorDef is not None: self.tgtVectorLabel.SetLabel(view.tgtVectorDef.label) self.tgtVector.Show(True) self.tgtVectorLabel.Show(True) else: self.tgtVector.Show(False) self.tgtVectorLabel.Show(False) # Target list self.targetList.Show(view.hasTargets) # Inputs self.updateInputs() if layout: self.graphFrame.Layout() self.graphFrame.UpdateWindowSize() def updateInputs(self): self.storeCurrentValues() # Clean up old inputs for children in self.inputs.values(): for child in children: if child is not None: child.Destroy() self.inputsSizer.Clear() self.inputs.clear() def addInputField(inputDef, handledHandles, mainInput=False): handledHandles.add(inputDef.handle) fieldSizer = wx.BoxSizer(wx.HORIZONTAL) if mainInput: initRange = self.storedValues.get((inputDef.handle, inputDef.unit), inputDef.defaultRange) if not isinstance(initRange, (tuple, list)): initRange = const2Range( defConst=inputDef.defaultValue, defRange=inputDef.defaultRange, limits=inputDef.limits, oldConst=initRange) fieldTextBox = RangeBox(self, initRange) else: initConst = self.storedValues.get((inputDef.handle, inputDef.unit), inputDef.defaultValue) if isinstance(initConst, (tuple, list)): initConst = range2Const( defConst=inputDef.defaultValue, defRange=inputDef.defaultRange, limits=inputDef.limits, oldRange=initConst) fieldTextBox = ConstantBox(self, initConst) fieldTextBox.Bind(wx.EVT_TEXT, self.OnFieldChanged) fieldSizer.Add(fieldTextBox, 0, wx.EXPAND | wx.ALIGN_CENTER_VERTICAL | wx.RIGHT, 5) fieldIcon = None if inputDef.iconID is not None: icon = BitmapLoader.getBitmap(inputDef.iconID, 'icons') if icon is not None: fieldIcon = wx.StaticBitmap(self) fieldIcon.SetBitmap(icon) fieldSizer.Add(fieldIcon, 0, wx.ALIGN_CENTER_VERTICAL | wx.RIGHT, 3) fieldLabel = wx.StaticText(self, wx.ID_ANY, self._formatLabel(inputDef)) fieldSizer.Add(fieldLabel, 0, wx.ALIGN_CENTER_VERTICAL | wx.ALL, 0) self.inputs[(inputDef.handle, inputDef.unit)] = (fieldTextBox, fieldIcon, fieldLabel) self.inputsSizer.Add(fieldSizer, 0, wx.EXPAND | wx.BOTTOM, 5) view = self.graphFrame.getView() handledHandles = set() srcVectorDef = view.srcVectorDef if srcVectorDef is not None: handledHandles.add(srcVectorDef.lengthHandle) handledHandles.add(srcVectorDef.angleHandle) tgtVectorDef = view.tgtVectorDef if tgtVectorDef is not None: handledHandles.add(tgtVectorDef.lengthHandle) handledHandles.add(tgtVectorDef.angleHandle) selectedX = view.xDefMap[self.xType] # Always add main input addInputField(view.inputMap[selectedX.mainInput], handledHandles, mainInput=True) for inputDef in view.inputs: if inputDef.mainOnly: continue if inputDef.handle in handledHandles: continue addInputField(inputDef, handledHandles) # Modify vectors mainInputHandle = selectedX.mainInput[0] self.srcVector.SetDirectionOnly(view.srcVectorDef.lengthHandle == mainInputHandle) self.tgtVector.SetDirectionOnly(view.tgtVectorDef.lengthHandle == mainInputHandle) def OnShowY0Change(self, event): event.Skip() self.graphFrame.draw() def OnYTypeUpdate(self, event): event.Skip() self.graphFrame.draw() def OnXTypeUpdate(self, event): event.Skip() self.updateInputs() self.graphFrame.Layout() self.graphFrame.UpdateWindowSize() self.graphFrame.draw() def OnFieldChanged(self, event): event.Skip() self.drawTimer.Stop() self.drawTimer.Start(Fit.getInstance().serviceFittingOptions['marketSearchDelay'], True) def OnDrawTimer(self, event): event.Skip() self.graphFrame.clearCache() self.graphFrame.draw() def getValues(self): view = self.graphFrame.getView() values = {} # Vectors srcVectorDef = view.srcVectorDef if srcVectorDef is not None: if not self.srcVector.IsDirectionOnly: values[srcVectorDef.lengthHandle] = (self.srcVector.GetLength() * 100, srcVectorDef.lengthUnit) values[srcVectorDef.angleHandle] = (self.srcVector.GetAngle(), srcVectorDef.angleUnit) tgtVectorDef = view.tgtVectorDef if tgtVectorDef is not None: if not self.tgtVector.IsDirectionOnly: values[tgtVectorDef.lengthHandle] = (self.tgtVector.GetLength() * 100, tgtVectorDef.lengthUnit) values[tgtVectorDef.angleHandle] = (self.tgtVector.GetAngle(), srcVectorDef.angleUnit) # Input boxes for k, v in self.inputs.items(): inputHandle, inputUnit = k inputBox = v[0] if isinstance(inputBox, RangeBox): values[inputHandle] = (inputBox.GetValueRange(), inputUnit) elif isinstance(inputBox, ConstantBox): values[inputHandle] = (inputBox.GetValueFloat(), inputUnit) return values @property def showY0(self): return self.showY0Cb.GetValue() @property def yType(self): return self.ySubSelection.GetClientData(self.ySubSelection.GetSelection()) @property def xType(self): return self.xSubSelection.GetClientData(self.xSubSelection.GetSelection()) def unbindExternalEvents(self): self.fitList.unbindExternalEvents() def _formatLabel(self, axisDef): if axisDef.unit is None: return axisDef.label return '{}, {}'.format(axisDef.label, axisDef.unit)