Files
pyfa/graphs/gui/lists.py
2019-10-09 23:09:09 +03:00

380 lines
13 KiB
Python

# =============================================================================
# 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 <http://www.gnu.org/licenses/>.
# =============================================================================
# noinspection PyPackageRequirements
import wx
import gui.display
from eos.saveddata.targetProfile import TargetProfile
from graphs.style import BASE_COLORS, LIGHTNESSES, STYLES
from graphs.wrapper import SourceWrapper, TargetWrapper
from gui.builtinViewColumns.graphColor import GraphColor
from gui.builtinViewColumns.graphLightness import GraphLightness
from gui.builtinViewColumns.graphLineStyle import GraphLineStyle
from gui.contextMenu import ContextMenu
from service.const import GraphCacheCleanupReason
from service.fit import Fit
from .stylePickers import ColorPickerPopup, LightnessPickerPopup, LineStylePickerPopup
class BaseWrapperList(gui.display.Display):
def __init__(self, graphFrame, parent):
super().__init__(parent)
self.graphFrame = graphFrame
self._wrappers = []
self.hoveredRow = None
self.hoveredColumn = None
self.Bind(wx.EVT_CHAR_HOOK, self.kbEvent)
self.Bind(wx.EVT_LEFT_DOWN, self.OnLeftDown)
self.Bind(wx.EVT_LEFT_DCLICK, self.OnLeftDClick)
self.Bind(wx.EVT_MOTION, self.OnMouseMove)
self.Bind(wx.EVT_LEAVE_WINDOW, self.OnLeaveWindow)
@property
def wrappers(self):
# Sort fits first, then target profiles
return sorted(self._wrappers, key=lambda w: not w.isFit)
# UI-related stuff
@property
def defaultTTText(self):
raise NotImplementedError
def refreshExtraColumns(self, extraColSpecs):
baseColNames = set()
for baseColName in self.DEFAULT_COLS:
if ":" in baseColName:
baseColName = baseColName.split(":", 1)[0]
baseColNames.add(baseColName)
columnsToRemove = set()
for col in self.activeColumns:
if col.name not in baseColNames:
columnsToRemove.add(col)
for col in columnsToRemove:
self.removeColumn(col)
for colSpec in extraColSpecs:
self.appendColumnBySpec(colSpec)
self.refreshView()
def refreshView(self):
self.refresh(self.wrappers)
def updateView(self):
self.update(self.wrappers)
# UI event handling
def OnMouseMove(self, event):
row, _, col = self.HitTestSubItem(event.Position)
if row != self.hoveredRow or col != self.hoveredColumn:
if self.ToolTip is not None:
self.SetToolTip(None)
else:
self.hoveredRow = row
self.hoveredColumn = col
if row != -1 and col != -1 and col < self.ColumnCount:
item = self.getWrapper(row)
if item is None:
return
tooltip = self.activeColumns[col].getToolTip(item)
if tooltip:
self.SetToolTip(tooltip)
else:
self.SetToolTip(None)
else:
self.SetToolTip(self.defaultTTText)
event.Skip()
def OnLeaveWindow(self, event):
self.SetToolTip(None)
self.hoveredRow = None
self.hoveredColumn = None
event.Skip()
def handleDrag(self, type, fitID):
if type == 'fit' and not self.containsFitID(fitID):
sFit = Fit.getInstance()
fit = sFit.getFit(fitID)
self.appendItem(fit)
self.updateView()
self.graphFrame.draw()
def OnLeftDown(self, event):
row, _ = self.HitTest(event.Position)
if row != -1:
pickers = {
self.getColIndex(GraphColor): ColorPickerPopup,
self.getColIndex(GraphLightness): LightnessPickerPopup,
self.getColIndex(GraphLineStyle): LineStylePickerPopup}
# In case we had no index for some column, remove None
pickers.pop(None, None)
col = self.getColumn(event.Position)
if col in pickers:
picker = pickers[col]
wrapper = self.getWrapper(row)
if wrapper is not None:
win = picker(parent=self, wrapper=wrapper)
pos = wx.GetMousePosition()
win.Position(pos, (0, 0))
win.Popup()
return
event.Skip()
def OnLineStyleChange(self):
self.updateView()
self.graphFrame.draw()
def OnLeftDClick(self, event):
row, _ = self.HitTest(event.Position)
wrapper = self.getWrapper(row)
if wrapper is None:
return
self.removeWrappers([wrapper])
def kbEvent(self, event):
keycode = event.GetKeyCode()
modifiers = event.GetModifiers()
if keycode == 65 and modifiers == wx.MOD_CONTROL:
self.selectAll()
elif keycode in (wx.WXK_DELETE, wx.WXK_NUMPAD_DELETE) and modifiers == wx.MOD_NONE:
self.removeWrappers(self.getSelectedWrappers())
event.Skip()
# Wrapper-related methods
def getWrapper(self, row):
if row == -1:
return None
try:
return self.wrappers[row]
except IndexError:
return None
def removeWrappers(self, wrappers):
wrappers = set(wrappers).intersection(self._wrappers)
if not wrappers:
return
for wrapper in wrappers:
self._wrappers.remove(wrapper)
self.updateView()
for wrapper in wrappers:
if wrapper.isFit:
self.graphFrame.clearCache(reason=GraphCacheCleanupReason.fitRemoved, extraData=wrapper.item.ID)
elif wrapper.isProfile:
self.graphFrame.clearCache(reason=GraphCacheCleanupReason.profileRemoved, extraData=wrapper.item.ID)
self.graphFrame.draw()
def getSelectedWrappers(self):
wrappers = []
for row in self.getSelectedRows():
wrapper = self.getWrapper(row)
if wrapper is None:
continue
wrappers.append(wrapper)
return wrappers
def appendItem(self, item):
raise NotImplemented
def containsFitID(self, fitID):
for wrapper in self._wrappers:
if wrapper.isFit and wrapper.item.ID == fitID:
return True
return False
def containsProfileID(self, profileID):
for wrapper in self._wrappers:
if wrapper.isProfile and wrapper.item.ID == profileID:
return True
return False
# Wrapper-related events
def OnFitRenamed(self, event):
if self.containsFitID(event.fitID):
self.updateView()
def OnFitChanged(self, event):
if set(event.fitIDs).intersection(w.item.ID for w in self._wrappers if w.isFit):
self.updateView()
def OnFitRemoved(self, event):
wrapper = next((w for w in self._wrappers if w.isFit and w.item.ID == event.fitID), None)
if wrapper is not None:
self._wrappers.remove(wrapper)
self.updateView()
def OnProfileRenamed(self, event):
if self.containsProfileID(event.profileID):
self.updateView()
def OnProfileChanged(self, event):
if self.containsProfileID(event.profileID):
self.updateView()
def OnProfileRemoved(self, event):
wrapper = next((w for w in self._wrappers if w.isProfile and w.item.ID == event.profileID), None)
if wrapper is not None:
self._wrappers.remove(wrapper)
self.updateView()
# Context menu handlers
def addFit(self, fit):
if fit is None:
return
if self.containsFitID(fit.ID):
return
self.appendItem(fit)
self.updateView()
self.graphFrame.draw()
def getExistingFitIDs(self):
return [w.item.ID for w in self._wrappers if w.isFit]
def addFitsByIDs(self, fitIDs):
sFit = Fit.getInstance()
for fitID in fitIDs:
if self.containsFitID(fitID):
continue
fit = sFit.getFit(fitID)
if fit is not None:
self.appendItem(fit)
self.updateView()
self.graphFrame.draw()
class SourceWrapperList(BaseWrapperList):
DEFAULT_COLS = (
'Graph Color',
'Base Icon',
'Base Name')
def __init__(self, graphFrame, parent):
super().__init__(graphFrame, parent)
self.Bind(wx.EVT_CONTEXT_MENU, self.spawnMenu)
fit = Fit.getInstance().getFit(self.graphFrame.mainFrame.getActiveFit())
if fit is not None:
self.appendItem(fit)
self.updateView()
def appendItem(self, item):
# Find out least used color
colorUseMap = {c: 0 for c in BASE_COLORS}
for wrapper in self._wrappers:
if wrapper.colorID not in colorUseMap:
continue
colorUseMap[wrapper.colorID] += 1
def getDefaultParams():
leastUses = min(colorUseMap.values(), default=0)
for colorID in BASE_COLORS:
if leastUses == colorUseMap.get(colorID, 0):
return colorID
return None
colorID = getDefaultParams()
self._wrappers.append(SourceWrapper(item=item, colorID=colorID))
def spawnMenu(self, event):
clickedPos = self.getRowByAbs(event.Position)
self.ensureSelection(clickedPos)
selection = self.getSelectedWrappers()
mainItem = self.getWrapper(clickedPos)
itemContext = None if mainItem is None else 'Fit'
menu = ContextMenu.getMenu(self, mainItem, selection, ('graphFitList', itemContext), ('graphFitListMisc', itemContext))
if menu:
self.PopupMenu(menu)
@property
def defaultTTText(self):
return 'Drag a fit into this list to graph it'
class TargetWrapperList(BaseWrapperList):
DEFAULT_COLS = (
'Graph Lightness',
'Graph Line Style',
'Base Icon',
'Base Name')
def __init__(self, graphFrame, parent):
super().__init__(graphFrame, parent)
self.Bind(wx.EVT_CONTEXT_MENU, self.spawnMenu)
self.appendItem(TargetProfile.getIdeal())
self.updateView()
def appendItem(self, item):
# Find out least used lightness
lightnessUseMap = {(l, s): 0 for l in LIGHTNESSES for s in STYLES}
for wrapper in self._wrappers:
key = (wrapper.lightnessID, wrapper.lineStyleID)
if key not in lightnessUseMap:
continue
lightnessUseMap[key] += 1
def getDefaultParams():
leastUses = min(lightnessUseMap.values(), default=0)
for lineStyleID in STYLES:
for lightnessID in LIGHTNESSES:
if leastUses == lightnessUseMap.get((lightnessID, lineStyleID), 0):
return lightnessID, lineStyleID
return None, None
lightnessID, lineStyleID = getDefaultParams()
self._wrappers.append(TargetWrapper(item=item, lightnessID=lightnessID, lineStyleID=lineStyleID))
def spawnMenu(self, event):
clickedPos = self.getRowByAbs(event.Position)
self.ensureSelection(clickedPos)
selection = self.getSelectedWrappers()
mainItem = self.getWrapper(clickedPos)
itemContext = None if mainItem is None else 'Target'
menu = ContextMenu.getMenu(self, mainItem, selection, ('graphTgtList', itemContext), ('graphTgtListMisc', itemContext))
if menu:
self.PopupMenu(menu)
def OnResistModeChanged(self, event):
if set(event.fitIDs).intersection(w.item.ID for w in self._wrappers if w.isFit):
self.updateView()
@property
def defaultTTText(self):
return 'Drag a fit into this list to have your fits graphed against it'
# Context menu handlers
def addProfile(self, profile):
if profile is None:
return
if self.containsProfileID(profile.ID):
return
self.appendItem(profile)
self.updateView()
self.graphFrame.draw()