diff --git a/gui/graphFrame/__init__.py b/gui/graphFrame/__init__.py
new file mode 100644
index 000000000..10c552c17
--- /dev/null
+++ b/gui/graphFrame/__init__.py
@@ -0,0 +1,21 @@
+# =============================================================================
+# 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 .
+# =============================================================================
+
+
+from .frame import GraphFrame
diff --git a/gui/graphFrame/events.py b/gui/graphFrame/events.py
new file mode 100644
index 000000000..2adab83b1
--- /dev/null
+++ b/gui/graphFrame/events.py
@@ -0,0 +1,24 @@
+# =============================================================================
+# 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 .
+# =============================================================================
+
+
+import wx.lib.newevent
+
+
+RefreshGraph, REFRESH_GRAPH = wx.lib.newevent.NewEvent()
diff --git a/gui/graphFrame.py b/gui/graphFrame/frame.py
similarity index 53%
rename from gui/graphFrame.py
rename to gui/graphFrame/frame.py
index 8df2272f8..6849afa90 100644
--- a/gui/graphFrame.py
+++ b/gui/graphFrame/frame.py
@@ -17,9 +17,10 @@
# along with pyfa. If not, see .
# =============================================================================
-import math
+
import os
import traceback
+from itertools import chain
# noinspection PyPackageRequirements
import wx
@@ -31,6 +32,7 @@ import gui.mainFrame
from gui.bitmap_loader import BitmapLoader
from gui.builtinGraphs.base import Graph
from service.fit import Fit
+from .panel import GraphControlPanel
pyfalog = Logger(__name__)
@@ -113,19 +115,9 @@ class GraphFrame(wx.Frame):
self.mainSizer = wx.BoxSizer(wx.VERTICAL)
self.SetSizer(self.mainSizer)
- sFit = Fit.getInstance()
- fit = sFit.getFit(self.mainFrame.getActiveFit())
- self.fits = [fit] if fit is not None else []
- self.fitList = FitList(self)
- self.fitList.SetMinSize((270, -1))
- self.fitList.fitList.update(self.fits)
- self.targets = []
- self.targetList = TargetList(self)
- self.targetList.SetMinSize((270, -1))
- self.targetList.targetList.update(self.targets)
-
self.graphSelection = wx.Choice(self, wx.ID_ANY, style=0)
self.mainSizer.Add(self.graphSelection, 0, wx.EXPAND)
+ self.selectedYRbMap = {}
self.figure = Figure(figsize=(5, 3), tight_layout={'pad': 1.08})
@@ -144,26 +136,9 @@ class GraphFrame(wx.Frame):
self.mainSizer.Add(wx.StaticLine(self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.LI_HORIZONTAL), 0,
wx.EXPAND)
- self.graphCtrlPanel = wx.Panel(self)
- self.mainSizer.Add(self.graphCtrlPanel, 0, wx.EXPAND | wx.ALL, 0)
- self.showY0 = True
- self.selectedY = None
- self.selectedYRbMap = {}
-
- ctrlPanelSizer = wx.BoxSizer(wx.HORIZONTAL)
- viewOptSizer = wx.BoxSizer(wx.VERTICAL)
- self.showY0Cb = wx.CheckBox(self.graphCtrlPanel, wx.ID_ANY, 'Always show Y = 0', wx.DefaultPosition, wx.DefaultSize, 0)
- self.showY0Cb.SetValue(self.showY0)
- self.showY0Cb.Bind(wx.EVT_CHECKBOX, self.OnShowY0Update)
- viewOptSizer.Add(self.showY0Cb, 0, wx.LEFT | wx.TOP | wx.RIGHT | wx.EXPAND, 5)
- self.graphSubselSizer = wx.BoxSizer(wx.VERTICAL)
- viewOptSizer.Add(self.graphSubselSizer, 0, wx.ALL | wx.EXPAND, 5)
- ctrlPanelSizer.Add(viewOptSizer, 0, wx.EXPAND | wx.LEFT | wx.TOP | wx.BOTTOM, 5)
- self.inputsSizer = wx.FlexGridSizer(0, 4, 0, 0)
- self.inputsSizer.AddGrowableCol(1)
- ctrlPanelSizer.Add(self.inputsSizer, 1, wx.EXPAND | wx.RIGHT | wx.TOP | wx.BOTTOM, 5)
- self.graphCtrlPanel.SetSizer(ctrlPanelSizer)
+ self.ctrlPanel = GraphControlPanel(self, self)
+ self.mainSizer.Add(self.ctrlPanel, 0, wx.EXPAND | wx.ALL, 0)
self.drawTimer = wx.Timer(self)
self.Bind(wx.EVT_TIMER, self.draw, self.drawTimer)
@@ -178,14 +153,10 @@ class GraphFrame(wx.Frame):
self.sl1 = wx.StaticLine(self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.LI_HORIZONTAL)
self.mainSizer.Add(self.sl1, 0, wx.EXPAND)
- fitSizer = wx.BoxSizer(wx.HORIZONTAL)
- fitSizer.Add(self.fitList, 1, wx.EXPAND)
- fitSizer.Add(self.targetList, 1, wx.EXPAND)
- self.mainSizer.Add(fitSizer, 0, wx.EXPAND)
-
- self.fitList.fitList.Bind(wx.EVT_LEFT_DCLICK, self.OnLeftDClick)
- self.fitList.fitList.Bind(wx.EVT_CONTEXT_MENU, self.OnContextMenu)
+ self.ctrlPanel.fitList.fitList.Bind(wx.EVT_LEFT_DCLICK, self.OnLeftDClick)
+ self.ctrlPanel.fitList.fitList.Bind(wx.EVT_CONTEXT_MENU, self.OnContextMenu)
+ self.ctrlPanel.showY0Cb.Bind(wx.EVT_CHECKBOX, self.OnNonDestructiveControlsUpdate)
self.mainFrame.Bind(GE.FIT_CHANGED, self.OnFitChanged)
self.mainFrame.Bind(GE.FIT_REMOVED, self.OnFitRemoved)
self.Bind(wx.EVT_CLOSE, self.closeEvent)
@@ -217,7 +188,7 @@ class GraphFrame(wx.Frame):
self.closeWindow()
return
elif keycode == 65 and mstate.GetModifiers() == wx.MOD_CONTROL:
- self.fitList.fitList.selectAll()
+ self.ctrlPanel.fitList.fitList.selectAll()
elif keycode in (wx.WXK_DELETE, wx.WXK_NUMPAD_DELETE) and mstate.GetModifiers() == wx.MOD_NONE:
self.removeFits(self.getSelectedFits())
event.Skip()
@@ -247,18 +218,18 @@ class GraphFrame(wx.Frame):
def OnFitRemoved(self, event):
event.Skip()
- fit = next((f for f in self.fits if f.ID == event.fitID), None)
+ fit = next((f for f in self.ctrlPanel.fits if f.ID == event.fitID), None)
if fit is not None:
self.removeFits([fit])
def graphChanged(self, event):
- self.selectedY = None
+ self.ctrlPanel.selectedY = None
self.updateGraphWidgets()
event.Skip()
def closeWindow(self):
from gui.builtinStatsViews.resistancesViewFull import EFFECTIVE_HP_TOGGLED # Grr gons
- self.fitList.fitList.Unbind(wx.EVT_LEFT_DCLICK, handler=self.OnLeftDClick)
+ self.ctrlPanel.fitList.fitList.Unbind(wx.EVT_LEFT_DCLICK, handler=self.OnLeftDClick)
self.mainFrame.Unbind(GE.FIT_CHANGED, handler=self.OnFitChanged)
self.mainFrame.Unbind(GE.FIT_REMOVED, handler=self.OnFitRemoved)
self.mainFrame.Unbind(EFFECTIVE_HP_TOGGLED, handler=self.OnEhpToggled)
@@ -274,25 +245,24 @@ class GraphFrame(wx.Frame):
return values
- def OnShowY0Update(self, event):
+ def OnNonDestructiveControlsUpdate(self, event):
event.Skip()
- self.showY0 = self.showY0Cb.GetValue()
self.draw()
def OnYTypeUpdate(self, event):
event.Skip()
obj = event.GetEventObject()
formatName = obj.GetLabel()
- self.selectedY = self.selectedYRbMap[formatName]
+ self.ctrlPanel.selectedY = self.selectedYRbMap[formatName]
self.draw()
def updateGraphWidgets(self):
view = self.getView()
view.clearCache()
- self.graphSubselSizer.Clear()
- self.inputsSizer.Clear()
- for child in self.graphCtrlPanel.Children:
- if child is not self.showY0Cb:
+ self.ctrlPanel.graphSubselSizer.Clear()
+ self.ctrlPanel.inputsSizer.Clear()
+ for child in self.ctrlPanel.Children:
+ if child not in (self.ctrlPanel.showY0Cb, self.ctrlPanel.fitList, self.ctrlPanel.targetList):
child.Destroy()
self.fields.clear()
@@ -302,22 +272,22 @@ class GraphFrame(wx.Frame):
i = 0
for yAlias, yDef in view.yDefs.items():
if i == 0:
- rdo = wx.RadioButton(self.graphCtrlPanel, wx.ID_ANY, yDef.switchLabel, style=wx.RB_GROUP)
+ rdo = wx.RadioButton(self.ctrlPanel, wx.ID_ANY, yDef.switchLabel, style=wx.RB_GROUP)
else:
- rdo = wx.RadioButton(self.graphCtrlPanel, wx.ID_ANY, yDef.switchLabel)
+ rdo = wx.RadioButton(self.ctrlPanel, wx.ID_ANY, yDef.switchLabel)
rdo.Bind(wx.EVT_RADIOBUTTON, self.OnYTypeUpdate)
- if i == (self.selectedY or 0):
+ if i == (self.ctrlPanel.selectedY or 0):
rdo.SetValue(True)
- self.graphSubselSizer.Add(rdo, 0, wx.ALL | wx.EXPAND, 0)
+ self.ctrlPanel.graphSubselSizer.Add(rdo, 0, wx.ALL | wx.EXPAND, 0)
self.selectedYRbMap[yDef.switchLabel] = i
i += 1
# Setup inputs
for fieldHandle, fieldDef in (('x', view.xDef), *view.extraInputs.items()):
- textBox = wx.TextCtrl(self.graphCtrlPanel, wx.ID_ANY, style=0)
+ textBox = wx.TextCtrl(self.ctrlPanel, wx.ID_ANY, style=0)
self.fields[fieldHandle] = textBox
textBox.Bind(wx.EVT_TEXT, self.onFieldChanged)
- self.inputsSizer.Add(textBox, 1, wx.EXPAND | wx.ALIGN_CENTER_VERTICAL | wx.ALL, 3)
+ self.ctrlPanel.inputsSizer.Add(textBox, 1, wx.EXPAND | wx.ALIGN_CENTER_VERTICAL | wx.ALL, 3)
if fieldDef.inputDefault is not None:
inputDefault = fieldDef.inputDefault
if not isinstance(inputDefault, str):
@@ -331,13 +301,13 @@ class GraphFrame(wx.Frame):
if fieldDef.inputIconID:
icon = BitmapLoader.getBitmap(fieldDef.inputIconID, 'icons')
if icon is not None:
- static = wx.StaticBitmap(self.graphCtrlPanel)
+ static = wx.StaticBitmap(self.ctrlPanel)
static.SetBitmap(icon)
imgLabelSizer.Add(static, 0, wx.ALL | wx.ALIGN_CENTER_VERTICAL, 1)
- imgLabelSizer.Add(wx.StaticText(self.graphCtrlPanel, wx.ID_ANY, fieldDef.inputLabel), 0,
+ imgLabelSizer.Add(wx.StaticText(self.ctrlPanel, wx.ID_ANY, fieldDef.inputLabel), 0,
wx.LEFT | wx.RIGHT | wx.ALIGN_CENTER_VERTICAL, 3)
- self.inputsSizer.Add(imgLabelSizer, 0, wx.ALIGN_CENTER_VERTICAL)
+ self.ctrlPanel.inputsSizer.Add(imgLabelSizer, 0, wx.ALIGN_CENTER_VERTICAL)
self.Layout()
self.draw()
@@ -365,19 +335,19 @@ class GraphFrame(wx.Frame):
self.subplot.grid(True)
legend = []
- min_y = 0 if self.showY0 else None
- max_y = 0 if self.showY0 else None
+ min_y = 0 if self.ctrlPanel.showY0 else None
+ max_y = 0 if self.ctrlPanel.showY0 else None
xRange = values['x']
extraInputs = {ih: values[ih] for ih in view.extraInputs}
try:
- chosenY = [i for i in view.yDefs.keys()][self.selectedY or 0]
+ chosenY = [i for i in view.yDefs.keys()][self.ctrlPanel.selectedY or 0]
except IndexError:
chosenY = [i for i in view.yDefs.keys()][0]
self.subplot.set(xlabel=view.xDef.axisLabel, ylabel=view.yDefs[chosenY].axisLabel)
- for fit in self.fits:
+ for fit in self.ctrlPanel.fits:
try:
xs, ys = view.getPlotPoints(fit, extraInputs, xRange, 100, chosenY)
@@ -468,29 +438,29 @@ class GraphFrame(wx.Frame):
def AppendFitToList(self, fitID):
sFit = Fit.getInstance()
fit = sFit.getFit(fitID)
- if fit not in self.fits:
- self.fits.append(fit)
+ if fit not in self.ctrlPanel.fits:
+ self.ctrlPanel.fits.append(fit)
- self.fitList.fitList.update(self.fits)
+ self.ctrlPanel.fitList.fitList.update(self.ctrlPanel.fits)
self.draw()
def OnLeftDClick(self, event):
- row, _ = self.fitList.fitList.HitTest(event.Position)
+ row, _ = self.ctrlPanel.fitList.fitList.HitTest(event.Position)
if row != -1:
try:
- fit = self.fits[row]
+ fit = self.ctrlPanel.fits[row]
except IndexError:
pass
else:
self.removeFits([fit])
def removeFits(self, fits):
- toRemove = [f for f in fits if f in self.fits]
+ toRemove = [f for f in fits if f in self.ctrlPanel.fits]
if not toRemove:
return
for fit in toRemove:
- self.fits.remove(fit)
- self.fitList.fitList.update(self.fits)
+ self.ctrlPanel.fits.remove(fit)
+ self.ctrlPanel.fitList.fitList.update(self.ctrlPanel.fits)
view = self.getView()
for fit in fits:
view.clearCache(key=fit.ID)
@@ -498,255 +468,10 @@ class GraphFrame(wx.Frame):
def getSelectedFits(self):
fits = []
- for row in self.fitList.fitList.getSelectedRows():
+ for row in self.ctrlPanel.fitList.fitList.getSelectedRows():
try:
- fit = self.fits[row]
+ fit = self.ctrlPanel.fits[row]
except IndexError:
continue
fits.append(fit)
return fits
-
-
-class FitList(wx.Panel):
-
- def __init__(self, parent):
- wx.Panel.__init__(self, parent)
- self.mainSizer = wx.BoxSizer(wx.VERTICAL)
- self.SetSizer(self.mainSizer)
-
- self.fitList = FitDisplay(self)
- self.mainSizer.Add(self.fitList, 1, wx.EXPAND)
- fitToolTip = wx.ToolTip('Drag a fit into this list to graph it')
- self.fitList.SetToolTip(fitToolTip)
-
-
-class FitDisplay(gui.display.Display):
- DEFAULT_COLS = ['Base Icon',
- 'Base Name']
-
- def __init__(self, parent):
- gui.display.Display.__init__(self, parent)
-
-
-class TargetList(wx.Panel):
-
- def __init__(self, parent):
- wx.Panel.__init__(self, parent)
- self.mainSizer = wx.BoxSizer(wx.VERTICAL)
- self.SetSizer(self.mainSizer)
-
- self.targetList = TargetDisplay(self)
- self.mainSizer.Add(self.targetList, 1, wx.EXPAND)
- fitToolTip = wx.ToolTip('Drag a fit into this list to graph it')
- self.targetList.SetToolTip(fitToolTip)
-
-
-class TargetDisplay(gui.display.Display):
- DEFAULT_COLS = ['Base Icon',
- 'Base Name']
-
- def __init__(self, parent):
- gui.display.Display.__init__(self, parent)
-
-
-# class VectorEvent(wx.PyCommandEvent):
-# def __init__(self, evtType, id):
-# wx.PyCommandEvent.__init__(self, evtType, id)
-# self._angle = 0
-# self._length = 0
-#
-# def GetValue(self):
-# return self._angle, self._length
-#
-# def GetAngle(self):
-# return self._angle
-#
-# def GetLength(self):
-# return self._length
-
-
-class VectorPicker(wx.Control):
-
- myEVT_VECTOR_CHANGED = wx.NewEventType()
- EVT_VECTOR_CHANGED = wx.PyEventBinder(myEVT_VECTOR_CHANGED, 1)
-
- def __init__(self, *args, **kwargs):
- self._label = str(kwargs.pop('label', ''))
- self._labelpos = int(kwargs.pop('labelpos', 0))
- self._offset = float(kwargs.pop('offset', 0))
- self._size = max(0, float(kwargs.pop('size', 50)))
- self._fontsize = max(1, float(kwargs.pop('fontsize', 8)))
- wx.Control.__init__(self, *args, **kwargs)
- self._font = wx.Font(self._fontsize, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False)
- self._angle = 0
- self._length = 1
- self._left = False
- self._right = False
- self._tooltip = 'Click to set angle and velocity, right-click for increments; mouse wheel for velocity only'
- self.SetToolTip(wx.ToolTip(self._tooltip))
- self.Bind(wx.EVT_PAINT, self.OnPaint)
- self.Bind(wx.EVT_ERASE_BACKGROUND, self.OnEraseBackground)
- self.Bind(wx.EVT_LEFT_DOWN, self.OnLeftDown)
- self.Bind(wx.EVT_RIGHT_DOWN, self.OnRightDown)
- self.Bind(wx.EVT_MOUSEWHEEL, self.OnWheel)
-
- def DoGetBestSize(self):
- return wx.Size(self._size, self._size)
-
- def AcceptsFocusFromKeyboard(self):
- return False
-
- def GetValue(self):
- return self._angle, self._length
-
- def GetAngle(self):
- return self._angle
-
- def GetLength(self):
- return self._length
-
- def SetValue(self, angle=None, length=None):
- if angle is not None:
- self._angle = min(max(angle, -180), 180)
- if length is not None:
- self._length = min(max(length, 0), 1)
- self.Refresh()
-
- def SetAngle(self, angle):
- self.SetValue(angle, None)
-
- def SetLength(self, length):
- self.SetValue(None, length)
-
- def OnPaint(self, event):
- dc = wx.BufferedPaintDC(self)
- self.Draw(dc)
-
- def Draw(self, dc):
- width, height = self.GetClientSize()
- if not width or not height:
- return
-
- dc.SetBackground(wx.Brush(self.GetBackgroundColour(), wx.BRUSHSTYLE_SOLID))
- dc.Clear()
- dc.SetTextForeground(wx.Colour(0))
- dc.SetFont(self._font)
-
- radius = min(width, height) / 2 - 2
- dc.SetBrush(wx.WHITE_BRUSH)
- dc.DrawCircle(radius + 2, radius + 2, radius)
- a = math.radians(self._angle + self._offset)
- x = math.sin(a) * radius
- y = math.cos(a) * radius
- dc.DrawLine(radius + 2, radius + 2, radius + 2 + x * self._length, radius + 2 - y * self._length)
- dc.SetBrush(wx.BLACK_BRUSH)
- dc.DrawCircle(radius + 2 + x * self._length, radius + 2 - y * self._length, 2)
-
- if self._label:
- labelText = self._label
- labelTextW, labelTextH = dc.GetTextExtent(labelText)
- labelTextX = (radius * 2 + 4 - labelTextW) if (self._labelpos & 1) else 0
- labelTextY = (radius * 2 + 4 - labelTextH) if (self._labelpos & 2) else 0
- dc.DrawText(labelText, labelTextX, labelTextY)
-
- lengthText = '%d%%' % (100 * self._length,)
- lengthTextW, lengthTextH = dc.GetTextExtent(lengthText)
- lengthTextX = radius + 2 + x / 2 - y / 3 - lengthTextW / 2
- lengthTextY = radius + 2 - y / 2 - x / 3 - lengthTextH / 2
- dc.DrawText(lengthText, lengthTextX, lengthTextY)
-
- angleText = '%d\u00B0' % (self._angle,)
- angleTextW, angleTextH = dc.GetTextExtent(angleText)
- angleTextX = radius + 2 - x / 2 - angleTextW / 2
- angleTextY = radius + 2 + y / 2 - angleTextH / 2
- dc.DrawText(angleText, angleTextX, angleTextY)
-
- def OnEraseBackground(self, event):
- pass
-
- def OnLeftDown(self, event):
- self._left = True
- self.SetToolTip(None)
- self.Bind(wx.EVT_LEFT_UP, self.OnLeftUp)
- self.Bind(wx.EVT_MOUSE_CAPTURE_LOST, self.OnLeftUp)
- if not self._right:
- self.Bind(wx.EVT_MOTION, self.OnMotion)
- if not self.HasCapture():
- self.CaptureMouse()
- self.HandleMouseEvent(event)
-
- def OnRightDown(self, event):
- self._right = True
- self.SetToolTip(None)
- self.Bind(wx.EVT_RIGHT_UP, self.OnRightUp)
- self.Bind(wx.EVT_MOUSE_CAPTURE_LOST, self.OnRightUp)
- if not self._left:
- self.Bind(wx.EVT_MOTION, self.OnMotion)
- if not self.HasCapture():
- self.CaptureMouse()
- self.HandleMouseEvent(event)
-
- def OnLeftUp(self, event):
- self.HandleMouseEvent(event)
- self.Unbind(wx.EVT_LEFT_UP, handler=self.OnLeftUp)
- self.Unbind(wx.EVT_MOUSE_CAPTURE_LOST, handler=self.OnLeftUp)
- self._left = False
- if not self._right:
- self.Unbind(wx.EVT_MOTION, handler=self.OnMotion)
- self.SendChangeEvent()
- self.SetToolTip(wx.ToolTip(self._tooltip))
- if self.HasCapture():
- self.ReleaseMouse()
-
- def OnRightUp(self, event):
- self.HandleMouseEvent(event)
- self.Unbind(wx.EVT_RIGHT_UP, handler=self.OnRightUp)
- self.Unbind(wx.EVT_MOUSE_CAPTURE_LOST, handler=self.OnRightUp)
- self._right = False
- if not self._left:
- self.Unbind(wx.EVT_MOTION, handler=self.OnMotion)
- self.SendChangeEvent()
- self.SetToolTip(wx.ToolTip(self._tooltip))
- if self.HasCapture():
- self.ReleaseMouse()
-
- def OnMotion(self, event):
- self.HandleMouseEvent(event)
- event.Skip()
-
- def OnWheel(self, event):
- amount = 0.1 * event.GetWheelRotation() / event.GetWheelDelta()
- self._length = min(max(self._length + amount, 0.0), 1.0)
- self.Refresh()
- self.SendChangeEvent()
-
- def HandleMouseEvent(self, event):
- width, height = self.GetClientSize()
- if width and height:
- center = min(width, height) / 2
- x, y = event.GetPositionTuple()
- x = x - center
- y = center - y
- angle = self._angle
- length = min((x * x + y * y) ** 0.5 / (center - 2), 1.0)
- if length < 0.01:
- length = 0
- else:
- angle = ((math.degrees(math.atan2(x, y)) - self._offset + 180) % 360) - 180
- if (self._right and not self._left) or event.ShiftDown():
- angle = round(angle / 15.0) * 15.0
- # floor() for length to make it easier to hit 0%, can still hit 100% outside the circle
- length = math.floor(length / 0.05) * 0.05
- if (angle != self._angle) or (length != self._length):
- self._angle = angle
- self._length = length
- self.Refresh()
- if self._right and not self._left:
- self.SendChangeEvent()
-
- def SendChangeEvent(self):
- changeEvent = wx.CommandEvent(self.myEVT_VECTOR_CHANGED, self.GetId())
- changeEvent._object = self
- changeEvent._angle = self._angle
- changeEvent._length = self._length
- self.GetEventHandler().ProcessEvent(changeEvent)
diff --git a/gui/graphFrame/lists.py b/gui/graphFrame/lists.py
new file mode 100644
index 000000000..4a89650ec
--- /dev/null
+++ b/gui/graphFrame/lists.py
@@ -0,0 +1,68 @@
+# =============================================================================
+# 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
+
+import gui.display
+
+
+class FitList(wx.Panel):
+
+ def __init__(self, parent):
+ wx.Panel.__init__(self, parent)
+ self.mainSizer = wx.BoxSizer(wx.VERTICAL)
+ self.SetSizer(self.mainSizer)
+
+ self.fitList = FitDisplay(self)
+ self.mainSizer.Add(self.fitList, 1, wx.EXPAND)
+ fitToolTip = wx.ToolTip('Drag a fit into this list to graph it')
+ self.fitList.SetToolTip(fitToolTip)
+
+
+class FitDisplay(gui.display.Display):
+
+ DEFAULT_COLS = ['Base Icon',
+ 'Base Name']
+
+ def __init__(self, parent):
+ gui.display.Display.__init__(self, parent)
+
+
+class TargetList(wx.Panel):
+
+ def __init__(self, parent):
+ wx.Panel.__init__(self, parent)
+ self.mainSizer = wx.BoxSizer(wx.VERTICAL)
+ self.SetSizer(self.mainSizer)
+
+ self.targetList = TargetDisplay(self)
+ self.mainSizer.Add(self.targetList, 1, wx.EXPAND)
+ fitToolTip = wx.ToolTip('Drag a fit into this list to graph it')
+ self.targetList.SetToolTip(fitToolTip)
+
+
+class TargetDisplay(gui.display.Display):
+
+ DEFAULT_COLS = ['Base Icon',
+ 'Base Name']
+
+ def __init__(self, parent):
+ gui.display.Display.__init__(self, parent)
diff --git a/gui/graphFrame/panel.py b/gui/graphFrame/panel.py
new file mode 100644
index 000000000..e7db62034
--- /dev/null
+++ b/gui/graphFrame/panel.py
@@ -0,0 +1,70 @@
+# =============================================================================
+# 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 service.fit import Fit
+from .lists import FitList, TargetList
+
+
+class GraphControlPanel(wx.Panel):
+
+ def __init__(self, graphFrame, parent):
+ super().__init__(parent)
+ self.graphFrame = graphFrame
+
+ self.selectedY = None
+
+ mainSizer = wx.BoxSizer(wx.VERTICAL)
+
+ paramSizer = wx.BoxSizer(wx.HORIZONTAL)
+ viewOptSizer = wx.BoxSizer(wx.VERTICAL)
+ self.showY0Cb = wx.CheckBox(self, wx.ID_ANY, 'Always show Y = 0', wx.DefaultPosition, wx.DefaultSize, 0)
+ self.showY0Cb.SetValue(True)
+ viewOptSizer.Add(self.showY0Cb, 0, wx.LEFT | wx.TOP | wx.RIGHT | wx.EXPAND, 5)
+ self.graphSubselSizer = wx.BoxSizer(wx.VERTICAL)
+ viewOptSizer.Add(self.graphSubselSizer, 0, wx.ALL | wx.EXPAND, 5)
+ paramSizer.Add(viewOptSizer, 0, wx.EXPAND | wx.LEFT | wx.TOP | wx.BOTTOM, 5)
+ self.inputsSizer = wx.FlexGridSizer(0, 4, 0, 0)
+ self.inputsSizer.AddGrowableCol(1)
+ paramSizer.Add(self.inputsSizer, 1, wx.EXPAND | wx.RIGHT | wx.TOP | wx.BOTTOM, 5)
+ mainSizer.Add(paramSizer, 0, wx.EXPAND | wx.ALL, 0)
+
+ srcTgtSizer = wx.BoxSizer(wx.HORIZONTAL)
+ fit = Fit.getInstance().getFit(self.graphFrame.mainFrame.getActiveFit())
+ self.fits = [fit] if fit is not None else []
+ self.fitList = FitList(self)
+ self.fitList.SetMinSize((270, -1))
+ self.fitList.fitList.update(self.fits)
+ srcTgtSizer.Add(self.fitList, 1, wx.EXPAND)
+ self.targets = []
+ self.targetList = TargetList(self)
+ self.targetList.SetMinSize((270, -1))
+ self.targetList.targetList.update(self.targets)
+ srcTgtSizer.Add(self.targetList, 1, wx.EXPAND)
+ mainSizer.Add(srcTgtSizer, 1, wx.EXPAND | wx.ALL, 0)
+
+ self.SetSizer(mainSizer)
+
+ @property
+ def showY0(self):
+ return self.showY0Cb.GetValue()
+
diff --git a/gui/graphFrame/vector.py b/gui/graphFrame/vector.py
new file mode 100644
index 000000000..ba99a3cda
--- /dev/null
+++ b/gui/graphFrame/vector.py
@@ -0,0 +1,211 @@
+# =============================================================================
+# 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 .
+# =============================================================================
+
+
+import math
+
+# noinspection PyPackageRequirements
+import wx
+
+
+class VectorPicker(wx.Control):
+
+ myEVT_VECTOR_CHANGED = wx.NewEventType()
+ EVT_VECTOR_CHANGED = wx.PyEventBinder(myEVT_VECTOR_CHANGED, 1)
+
+ def __init__(self, *args, **kwargs):
+ self._label = str(kwargs.pop('label', ''))
+ self._labelpos = int(kwargs.pop('labelpos', 0))
+ self._offset = float(kwargs.pop('offset', 0))
+ self._size = max(0, float(kwargs.pop('size', 50)))
+ self._fontsize = max(1, float(kwargs.pop('fontsize', 8)))
+ wx.Control.__init__(self, *args, **kwargs)
+ self._font = wx.Font(self._fontsize, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False)
+ self._angle = 0
+ self._length = 1
+ self._left = False
+ self._right = False
+ self._tooltip = 'Click to set angle and velocity, right-click for increments; mouse wheel for velocity only'
+ self.SetToolTip(wx.ToolTip(self._tooltip))
+ self.Bind(wx.EVT_PAINT, self.OnPaint)
+ self.Bind(wx.EVT_ERASE_BACKGROUND, self.OnEraseBackground)
+ self.Bind(wx.EVT_LEFT_DOWN, self.OnLeftDown)
+ self.Bind(wx.EVT_RIGHT_DOWN, self.OnRightDown)
+ self.Bind(wx.EVT_MOUSEWHEEL, self.OnWheel)
+
+ def DoGetBestSize(self):
+ return wx.Size(self._size, self._size)
+
+ def AcceptsFocusFromKeyboard(self):
+ return False
+
+ def GetValue(self):
+ return self._angle, self._length
+
+ def GetAngle(self):
+ return self._angle
+
+ def GetLength(self):
+ return self._length
+
+ def SetValue(self, angle=None, length=None):
+ if angle is not None:
+ self._angle = min(max(angle, -180), 180)
+ if length is not None:
+ self._length = min(max(length, 0), 1)
+ self.Refresh()
+
+ def SetAngle(self, angle):
+ self.SetValue(angle, None)
+
+ def SetLength(self, length):
+ self.SetValue(None, length)
+
+ def OnPaint(self, event):
+ dc = wx.BufferedPaintDC(self)
+ self.Draw(dc)
+
+ def Draw(self, dc):
+ width, height = self.GetClientSize()
+ if not width or not height:
+ return
+
+ dc.SetBackground(wx.Brush(self.GetBackgroundColour(), wx.BRUSHSTYLE_SOLID))
+ dc.Clear()
+ dc.SetTextForeground(wx.Colour(0))
+ dc.SetFont(self._font)
+
+ radius = min(width, height) / 2 - 2
+ dc.SetBrush(wx.WHITE_BRUSH)
+ dc.DrawCircle(radius + 2, radius + 2, radius)
+ a = math.radians(self._angle + self._offset)
+ x = math.sin(a) * radius
+ y = math.cos(a) * radius
+ dc.DrawLine(radius + 2, radius + 2, radius + 2 + x * self._length, radius + 2 - y * self._length)
+ dc.SetBrush(wx.BLACK_BRUSH)
+ dc.DrawCircle(radius + 2 + x * self._length, radius + 2 - y * self._length, 2)
+
+ if self._label:
+ labelText = self._label
+ labelTextW, labelTextH = dc.GetTextExtent(labelText)
+ labelTextX = (radius * 2 + 4 - labelTextW) if (self._labelpos & 1) else 0
+ labelTextY = (radius * 2 + 4 - labelTextH) if (self._labelpos & 2) else 0
+ dc.DrawText(labelText, labelTextX, labelTextY)
+
+ lengthText = '%d%%' % (100 * self._length,)
+ lengthTextW, lengthTextH = dc.GetTextExtent(lengthText)
+ lengthTextX = radius + 2 + x / 2 - y / 3 - lengthTextW / 2
+ lengthTextY = radius + 2 - y / 2 - x / 3 - lengthTextH / 2
+ dc.DrawText(lengthText, lengthTextX, lengthTextY)
+
+ angleText = '%d\u00B0' % (self._angle,)
+ angleTextW, angleTextH = dc.GetTextExtent(angleText)
+ angleTextX = radius + 2 - x / 2 - angleTextW / 2
+ angleTextY = radius + 2 + y / 2 - angleTextH / 2
+ dc.DrawText(angleText, angleTextX, angleTextY)
+
+ def OnEraseBackground(self, event):
+ pass
+
+ def OnLeftDown(self, event):
+ self._left = True
+ self.SetToolTip(None)
+ self.Bind(wx.EVT_LEFT_UP, self.OnLeftUp)
+ self.Bind(wx.EVT_MOUSE_CAPTURE_LOST, self.OnLeftUp)
+ if not self._right:
+ self.Bind(wx.EVT_MOTION, self.OnMotion)
+ if not self.HasCapture():
+ self.CaptureMouse()
+ self.HandleMouseEvent(event)
+
+ def OnRightDown(self, event):
+ self._right = True
+ self.SetToolTip(None)
+ self.Bind(wx.EVT_RIGHT_UP, self.OnRightUp)
+ self.Bind(wx.EVT_MOUSE_CAPTURE_LOST, self.OnRightUp)
+ if not self._left:
+ self.Bind(wx.EVT_MOTION, self.OnMotion)
+ if not self.HasCapture():
+ self.CaptureMouse()
+ self.HandleMouseEvent(event)
+
+ def OnLeftUp(self, event):
+ self.HandleMouseEvent(event)
+ self.Unbind(wx.EVT_LEFT_UP, handler=self.OnLeftUp)
+ self.Unbind(wx.EVT_MOUSE_CAPTURE_LOST, handler=self.OnLeftUp)
+ self._left = False
+ if not self._right:
+ self.Unbind(wx.EVT_MOTION, handler=self.OnMotion)
+ self.SendChangeEvent()
+ self.SetToolTip(wx.ToolTip(self._tooltip))
+ if self.HasCapture():
+ self.ReleaseMouse()
+
+ def OnRightUp(self, event):
+ self.HandleMouseEvent(event)
+ self.Unbind(wx.EVT_RIGHT_UP, handler=self.OnRightUp)
+ self.Unbind(wx.EVT_MOUSE_CAPTURE_LOST, handler=self.OnRightUp)
+ self._right = False
+ if not self._left:
+ self.Unbind(wx.EVT_MOTION, handler=self.OnMotion)
+ self.SendChangeEvent()
+ self.SetToolTip(wx.ToolTip(self._tooltip))
+ if self.HasCapture():
+ self.ReleaseMouse()
+
+ def OnMotion(self, event):
+ self.HandleMouseEvent(event)
+ event.Skip()
+
+ def OnWheel(self, event):
+ amount = 0.1 * event.GetWheelRotation() / event.GetWheelDelta()
+ self._length = min(max(self._length + amount, 0.0), 1.0)
+ self.Refresh()
+ self.SendChangeEvent()
+
+ def HandleMouseEvent(self, event):
+ width, height = self.GetClientSize()
+ if width and height:
+ center = min(width, height) / 2
+ x, y = event.GetPositionTuple()
+ x = x - center
+ y = center - y
+ angle = self._angle
+ length = min((x * x + y * y) ** 0.5 / (center - 2), 1.0)
+ if length < 0.01:
+ length = 0
+ else:
+ angle = ((math.degrees(math.atan2(x, y)) - self._offset + 180) % 360) - 180
+ if (self._right and not self._left) or event.ShiftDown():
+ angle = round(angle / 15.0) * 15.0
+ # floor() for length to make it easier to hit 0%, can still hit 100% outside the circle
+ length = math.floor(length / 0.05) * 0.05
+ if (angle != self._angle) or (length != self._length):
+ self._angle = angle
+ self._length = length
+ self.Refresh()
+ if self._right and not self._left:
+ self.SendChangeEvent()
+
+ def SendChangeEvent(self):
+ changeEvent = wx.CommandEvent(self.myEVT_VECTOR_CHANGED, self.GetId())
+ changeEvent._object = self
+ changeEvent._angle = self._angle
+ changeEvent._length = self._length
+ self.GetEventHandler().ProcessEvent(changeEvent)
diff --git a/gui/mainFrame.py b/gui/mainFrame.py
index 6ca90d031..35f8e7a82 100644
--- a/gui/mainFrame.py
+++ b/gui/mainFrame.py
@@ -962,9 +962,9 @@ class MainFrame(wx.Frame):
if not self.graphFrame:
self.graphFrame = GraphFrame(self)
- if graphFrame.graphFrame_enabled:
+ if graphFrame.frame.graphFrame_enabled:
self.graphFrame.Show()
- elif graphFrame.graphFrame_enabled:
+ elif graphFrame.frame.graphFrame_enabled:
self.graphFrame.SetFocus()
def openWXInspectTool(self, event):
diff --git a/gui/mainMenuBar.py b/gui/mainMenuBar.py
index 0d8193c01..2da10be26 100644
--- a/gui/mainMenuBar.py
+++ b/gui/mainMenuBar.py
@@ -102,7 +102,7 @@ class MainMenuBar(wx.MenuBar):
graphFrameItem = wx.MenuItem(fitMenu, self.graphFrameId, "&Graphs\tCTRL+G")
graphFrameItem.SetBitmap(BitmapLoader.getBitmap("graphs_small", "gui"))
fitMenu.Append(graphFrameItem)
- if not gui.graphFrame.graphFrame_enabled:
+ if not gui.graphFrame.frame.graphFrame_enabled:
self.Enable(self.graphFrameId, False)
self.ignoreRestrictionItem = fitMenu.Append(self.toggleIgnoreRestrictionID, "Disable Fitting Re&strictions")