# ============================================================================= # 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 # noinspection PyPackageRequirements import wx.lib.newevent from logbook import Logger import gui.builtinViews.emptyView import gui.display as d import gui.fitCommands as cmd import gui.globalEvents as GE import gui.mainFrame import gui.multiSwitch from eos.saveddata.mode import Mode from eos.saveddata.module import Module, Rack from eos.const import FittingSlot from gui.bitmap_loader import BitmapLoader from gui.builtinMarketBrowser.events import ITEM_SELECTED from gui.builtinShipBrowser.events import EVT_FIT_SELECTED, FitSelected from gui.builtinViewColumns.state import State from gui.chrome_tabs import EVT_NOTEBOOK_PAGE_CHANGED from gui.contextMenu import ContextMenu from gui.utils.staticHelpers import DragDropHelper from gui.utils.dark import isDark from service.fit import Fit from service.market import Market from config import slotColourMap, slotColourMapDark, errColor, errColorDark from gui.fitCommands.helpers import getSimilarModPositions pyfalog = Logger(__name__) _t = wx.GetTranslation # Tab spawning handler class FitSpawner(gui.multiSwitch.TabSpawner): def __init__(self, multiSwitch): self.multiSwitch = multiSwitch self.mainFrame = mainFrame = gui.mainFrame.MainFrame.getInstance() mainFrame.Bind(EVT_FIT_SELECTED, self.fitSelected) self.multiSwitch.tabs_container.handleDrag = self.handleDrag def fitSelected(self, event): count = -1 # @todo pheonix: _pages is supposed to be private? for index, page in enumerate(self.multiSwitch._pages): if not isinstance(page, gui.builtinViews.emptyView.BlankPage): # Don't try and process it if it's a blank page. try: if page.activeFitID == event.fitID: count += 1 self.multiSwitch.SetSelection(index) wx.PostEvent(self.mainFrame, GE.FitChanged(fitIDs=(event.fitID,))) break except (KeyboardInterrupt, SystemExit): raise except Exception as e: pyfalog.critical("Caught exception in fitSelected") pyfalog.critical(e) if count < 0: startup = getattr(event, "startup", False) # see OpenFitsThread in gui.mainFrame from_import = getattr(event, "from_import", False) # always open imported into a new tab sFit = Fit.getInstance() openFitInNew = sFit.serviceFittingOptions["openFitInNew"] mstate = wx.GetMouseState() modifierKey = mstate.GetModifiers() == wx.MOD_CONTROL if from_import or (not openFitInNew and modifierKey) or startup or (openFitInNew and not modifierKey): self.multiSwitch.AddPage() view = self.multiSwitch.GetSelectedPage() if not isinstance(view, FittingView): view = FittingView(self.multiSwitch) pyfalog.debug("###################### Created new view:" + repr(view)) self.multiSwitch.ReplaceActivePage(view) view.fitSelected(event) def handleDrag(self, type, fitID): if type == "fit": for page in self.multiSwitch._pages: if isinstance(page, FittingView) and page.activeFitID == fitID: index = self.multiSwitch.GetPageIndex(page) self.multiSwitch.SetSelection(index) wx.PostEvent(self.mainFrame, GE.FitChanged(fitIDs=(fitID,))) return elif isinstance(page, gui.builtinViews.emptyView.BlankPage): view = FittingView(self.multiSwitch) self.multiSwitch.ReplaceActivePage(view) view.handleDrag(type, fitID) return view = FittingView(self.multiSwitch) self.multiSwitch.AddPage(view) view.handleDrag(type, fitID) FitSpawner.register() # Drag'n'drop handler class FittingViewDrop(wx.DropTarget): def __init__(self, dropFn, *args, **kwargs): super(FittingViewDrop, self).__init__(*args, **kwargs) self.dropFn = dropFn # this is really transferring an EVE itemID self.dropData = wx.TextDataObject() self.SetDataObject(self.dropData) def OnData(self, x, y, t): if self.GetData(): dragged_data = DragDropHelper.data # pyfalog.debug("fittingView: recieved drag: " + self.dropData.GetText()) if dragged_data is None: return t data = dragged_data.split(':') self.dropFn(x, y, data) return t class FittingView(d.Display): DEFAULT_COLS = ["State", "Ammo Icon", "Base Icon", "Base Name", "attr:power", "attr:cpu", "Capacitor Usage", "Max Range", "Miscellanea", "Price", "Ammo", ] def __init__(self, parent): d.Display.__init__(self, parent, size=(0, 0), style=wx.BORDER_NONE) self.Show(False) self.parent = parent self.mainFrame.Bind(GE.FIT_CHANGED, self.fitChanged) self.mainFrame.Bind(GE.FIT_RENAMED, self.fitRenamed) self.mainFrame.Bind(GE.FIT_REMOVED, self.fitRemoved) self.mainFrame.Bind(ITEM_SELECTED, self.appendItem) self.font = wx.SystemSettings.GetFont(wx.SYS_DEFAULT_GUI_FONT) self.Bind(wx.EVT_LEFT_DCLICK, self.removeItem) self.Bind(wx.EVT_LIST_BEGIN_DRAG, self.startDrag) self.Bind(wx.EVT_CONTEXT_MENU, self.spawnMenu) self.SetDropTarget(FittingViewDrop(self.handleListDrag)) self.activeFitID = None self.FVsnapshot = None self.itemCount = 0 self.hoveredRow = None self.hoveredColumn = None self.Bind(wx.EVT_KEY_UP, self.kbEvent) self.Bind(wx.EVT_LEFT_DOWN, self.click) self.Bind(wx.EVT_RIGHT_DOWN, self.click) self.Bind(wx.EVT_MIDDLE_DOWN, self.click) self.Bind(wx.EVT_SHOW, self.OnShow) self.Bind(wx.EVT_MOTION, self.OnMouseMove) self.Bind(wx.EVT_LEAVE_WINDOW, self.OnLeaveWindow) self.parent.Bind(EVT_NOTEBOOK_PAGE_CHANGED, self.pageChanged) pyfalog.debug("------------------ new fitting view -------------------") pyfalog.debug(self) def OnLeaveWindow(self, event): self.SetToolTip(None) self.hoveredRow = None self.hoveredColumn = None event.Skip() 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 row not in self.blanks and col != -1 and col < len(self.DEFAULT_COLS): mod = self.mods[row] tooltip = self.activeColumns[col].getToolTip(mod) if tooltip is not None: self.SetToolTip(tooltip) else: self.SetToolTip(None) else: self.SetToolTip(None) event.Skip() def handleListDrag(self, x, y, data): """ Handles dragging of items from various pyfa displays which support it data is list with two items: data[0] is hard-coded str of originating source data[1] is typeID or index of data we want to manipulate """ if data[0] == "fitting": self.swapItems(x, y, int(data[1])) elif data[0] == "cargo": self.swapCargo(x, y, int(data[1])) elif data[0] == "market": self.addModule(x, y, int(data[1])) def handleDrag(self, type, fitID): # Those are drags coming from pyfa sources, NOT builtin wx drags if type == "fit": wx.PostEvent(self.mainFrame, FitSelected(fitID=fitID)) def Destroy(self): pyfalog.debug("+++++ Destroy " + repr(self)) d.Display.Destroy(self) def pageChanged(self, event): if self.parent.IsActive(self): fitID = self.getActiveFit() sFit = Fit.getInstance() sFit.switchFit(fitID) wx.PostEvent(self.mainFrame, GE.FitChanged(fitIDs=(fitID,))) event.Skip() def getActiveFit(self): return self.activeFitID def startDrag(self, event): srcRow = event.GetIndex() if srcRow == -1: return if srcRow in self.blanks: return try: mod = self.mods[srcRow] except IndexError: return if not isinstance(self.mods[srcRow], Module): return if mod.isEmpty: return fit = Fit.getInstance().getFit(self.activeFitID) if mod not in fit.modules: return self.unselectAll() self.Select(srcRow, True) data = wx.TextDataObject() dataStr = "fitting:" + str(fit.modules.index(mod)) data.SetText(dataStr) dropSource = wx.DropSource(self) dropSource.SetData(data) DragDropHelper.data = dataStr dropSource.DoDragDrop() def getSelectedMods(self): mods = [] for row in self.getSelectedRows(): try: mod = self.mods[row] except IndexError: continue if mod and not isinstance(mod, Rack): mods.append(mod) return mods def kbEvent(self, event): keycode = event.GetKeyCode() modifiers = event.GetModifiers() if keycode == wx.WXK_ESCAPE and modifiers == wx.MOD_NONE: self.unselectAll() elif keycode == 65 and modifiers == wx.MOD_CONTROL: self.selectAll() elif keycode in (wx.WXK_DELETE, wx.WXK_NUMPAD_DELETE) and modifiers == wx.MOD_NONE: modules = [m for m in self.getSelectedMods() if not m.isEmpty] self.removeModule(modules) event.Skip() def fitRemoved(self, event): """ If fit is removed and active, the page is deleted. We also refresh the fit of the new current page in case delete fit caused change in stats (projected) todo: move this to the notebook, not the page. We don't want the page being responsible for deleting itself """ pyfalog.debug("FittingView::fitRemoved") if not self: event.Skip() return if event.fitID == self.getActiveFit(): pyfalog.debug(" Deleted fit is currently active") self.parent.DeletePage(self.parent.GetPageIndex(self)) try: # Sometimes there is no active page after deletion, hence the try block sFit = Fit.getInstance() # stopgap for #1384 fit = sFit.getFit(self.getActiveFit()) if fit: sFit.refreshFit(self.getActiveFit()) wx.PostEvent(self.mainFrame, GE.FitChanged(fitIDs=(self.activeFitID,))) except RuntimeError: pyfalog.warning("Caught dead object") pass event.Skip() def fitRenamed(self, event): if not self: event.Skip() return fitID = event.fitID if fitID == self.getActiveFit(): self.updateTab() event.Skip() def fitSelected(self, event): pyfalog.debug('====== Fit Selected: ' + repr(self) + str(bool(self))) if self.parent.IsActive(self): fitID = event.fitID startup = getattr(event, "startup", False) self.activeFitID = fitID sFit = Fit.getInstance() self.updateTab() if not startup or startup == 2: # see OpenFitsThread in gui.mainFrame self.Show(fitID is not None) self.slotsChanged() sFit.switchFit(fitID) # @todo pheonix: had to disable this as it was causing a crash at the wxWidgets level. Dunno why, investigate wx.PostEvent(self.mainFrame, GE.FitChanged(fitIDs=(fitID,))) event.Skip() def updateTab(self): sFit = Fit.getInstance() fit = sFit.getFit(self.getActiveFit(), basic=True) bitmap = BitmapLoader.getImage("race_%s_small" % fit.ship.item.race, "gui") text = "%s: %s" % (fit.ship.item.name, fit.name) pageIndex = self.parent.GetPageIndex(self) if pageIndex is not None: self.parent.SetPageTextIcon(pageIndex, text, bitmap) def appendItem(self, event): """ Adds items that are double clicks from the market browser. We handle both modules and ammo """ if not self: event.Skip() return if self.parent.IsActive(self): itemID = event.itemID fitID = self.activeFitID if fitID is not None: item = Market.getInstance().getItem(itemID, eager='group.category') if item is None: event.Skip() return batchOp = wx.GetMouseState().GetModifiers() == wx.MOD_ALT and getattr(event, 'allowBatch', None) is not False if (item.isModule and not batchOp) or item.isSubsystem: self.mainFrame.command.Submit(cmd.GuiAddLocalModuleCommand(fitID=fitID, itemID=itemID)) elif item.isModule and batchOp: self.mainFrame.command.Submit(cmd.GuiFillWithNewLocalModulesCommand(fitID=fitID, itemID=itemID)) event.Skip() def removeItem(self, event): """Double Left Click - remove module""" if event.GetModifiers() == wx.MOD_CONTROL: return row, _ = self.HitTest(event.Position) if row != -1 and row not in self.blanks and isinstance(self.mods[row], Module): col = self.getColumn(event.Position) if col != self.getColIndex(State): try: mod = self.mods[row] except IndexError: return if not isinstance(mod, Module) or mod.isEmpty: return if event.GetModifiers() == wx.MOD_ALT: fit = Fit.getInstance().getFit(self.activeFitID) positions = getSimilarModPositions(fit.modules, mod) self.mainFrame.command.Submit(cmd.GuiRemoveLocalModuleCommand( fitID=self.activeFitID, positions=positions)) else: self.removeModule(mod) else: if "wxMSW" in wx.PlatformInfo: self.click(event) def removeModule(self, modules): """Removes a list of modules from the fit""" if not isinstance(modules, list): modules = [modules] fit = Fit.getInstance().getFit(self.activeFitID) positions = [] for position, mod in enumerate(fit.modules): if mod in modules: positions.append(position) self.mainFrame.command.Submit(cmd.GuiRemoveLocalModuleCommand( fitID=self.activeFitID, positions=positions)) def addModule(self, x, y, itemID): """Add a module from the market browser (from dragging it)""" fitID = self.mainFrame.getActiveFit() item = Market.getInstance().getItem(itemID) fit = Fit.getInstance().getFit(fitID) dstRow, _ = self.HitTest((x, y)) if dstRow == -1 or dstRow in self.blanks: dstMod = None else: try: dstMod = self.mods[dstRow] except IndexError: dstMod = None if not isinstance(dstMod, Module): dstMod = None if dstMod not in fit.modules: dstMod = None dstPos = fit.modules.index(dstMod) if dstMod is not None else None mstate = wx.GetMouseState() # If we dropping on a module, try to replace, or add if replacement fails if item.isModule and dstMod is not None and not dstMod.isEmpty: positions = getSimilarModPositions(fit.modules, dstMod) if mstate.GetModifiers() == wx.MOD_ALT else [dstPos] command = cmd.GuiReplaceLocalModuleCommand(fitID=fitID, itemID=itemID, positions=positions) if not self.mainFrame.command.Submit(command): if mstate.GetModifiers() == wx.MOD_ALT: self.mainFrame.command.Submit(cmd.GuiFillWithNewLocalModulesCommand(fitID=fitID, itemID=itemID)) else: self.mainFrame.command.Submit(cmd.GuiAddLocalModuleCommand(fitID=fitID, itemID=itemID)) elif item.isModule: if mstate.GetModifiers() == wx.MOD_ALT: self.mainFrame.command.Submit(cmd.GuiFillWithNewLocalModulesCommand(fitID=fitID, itemID=itemID)) elif dstPos is not None: self.mainFrame.command.Submit(cmd.GuiReplaceLocalModuleCommand(fitID=fitID, itemID=itemID, positions=[dstPos])) else: self.mainFrame.command.Submit(cmd.GuiAddLocalModuleCommand(fitID=fitID, itemID=itemID)) elif item.isSubsystem: self.mainFrame.command.Submit(cmd.GuiAddLocalModuleCommand(fitID=fitID, itemID=itemID)) elif item.isCharge: failoverToAll = False positionsAll = list(range(len(fit.modules))) if dstMod is None or dstMod.isEmpty: positions = positionsAll elif mstate.GetModifiers() == wx.MOD_ALT: positions = getSimilarModPositions(fit.modules, dstMod) failoverToAll = True else: positions = [fit.modules.index(dstMod)] if len(positions) > 0: command = cmd.GuiChangeLocalModuleChargesCommand(fitID=fitID, positions=positions, chargeItemID=itemID) if not self.mainFrame.command.Submit(command) and failoverToAll: self.mainFrame.command.Submit(cmd.GuiChangeLocalModuleChargesCommand( fitID=fitID, positions=positionsAll, chargeItemID=itemID)) def swapCargo(self, x, y, cargoItemID): """Swap a module from cargo to fitting window""" dstRow, _ = self.HitTest((x, y)) if dstRow != -1 and dstRow not in self.blanks: mod = self.mods[dstRow] if not isinstance(mod, Module): return fitID = self.mainFrame.getActiveFit() fit = Fit.getInstance().getFit(fitID) if mod in fit.modules: position = fit.modules.index(mod) self.mainFrame.command.Submit(cmd.GuiCargoToLocalModuleCommand( fitID=fitID, cargoItemID=cargoItemID, modPosition=position, copy=wx.GetMouseState().GetModifiers() == wx.MOD_CONTROL)) def swapItems(self, x, y, srcIdx): """Swap two modules in fitting window""" sFit = Fit.getInstance() fit = sFit.getFit(self.activeFitID) dstRow, _ = self.HitTest((x, y)) if dstRow != -1 and dstRow not in self.blanks: try: mod1 = fit.modules[srcIdx] mod2 = self.mods[dstRow] except IndexError: return if not isinstance(mod2, Module): return # can't swap modules to different racks if mod1.slot != mod2.slot: return if mod2 not in fit.modules: pyfalog.error("Missing module position for: {0}", str(getattr(mod2, "ID", "Unknown"))) return mod2Position = fit.modules.index(mod2) mstate = wx.GetMouseState() if mstate.GetModifiers() == wx.MOD_CONTROL | wx.MOD_ALT: self.mainFrame.command.Submit(cmd.GuiFillWithClonedLocalModulesCommand( fitID=self.activeFitID, position=srcIdx)) elif mstate.GetModifiers() == wx.MOD_CONTROL and mod2.isEmpty: self.mainFrame.command.Submit(cmd.GuiCloneLocalModuleCommand( fitID=self.activeFitID, srcPosition=srcIdx, dstPosition=mod2Position)) elif mstate.GetModifiers() == wx.MOD_NONE: self.mainFrame.command.Submit(cmd.GuiSwapLocalModulesCommand( fitID=self.activeFitID, position1=srcIdx, position2=mod2Position)) def generateMods(self): """ Generate module list. This also injects dummy modules to visually separate racks. These modules are only known to the display, and not the backend, so it's safe. """ sFit = Fit.getInstance() fit = sFit.getFit(self.activeFitID) slotOrder = [ FittingSlot.SUBSYSTEM, FittingSlot.HIGH, FittingSlot.MED, FittingSlot.LOW, FittingSlot.RIG, FittingSlot.SERVICE ] if fit is not None: self.mods = fit.modules[:] self.mods.sort(key=lambda _mod: (slotOrder.index(_mod.slot), _mod.position)) # Blanks is a list of indexes that mark non-module positions (such # as Racks and tactical Modes. This allows us to skip over common # module operations such as swapping, removing, copying, etc. that # would otherwise cause complications self.blanks = [] # preliminary markers where blanks will be inserted if sFit.serviceFittingOptions["rackSlots"]: # flag to know when to add blanks, based on previous slot if sFit.serviceFittingOptions["rackLabels"] or len(self.mods) == 0: slotDivider = None else: slotDivider = self.mods[0].slot # first loop finds where slot dividers must go before modifying self.mods for i, mod in enumerate(self.mods): if mod.slot != slotDivider: slotDivider = mod.slot self.blanks.append((i, slotDivider)) # where and what # second loop modifies self.mods, rewrites self.blanks to represent actual index of blanks for i, (x, slot) in enumerate(self.blanks): self.blanks[i] = x + i # modify blanks with actual index self.mods.insert(x + i, Rack.buildRack(slot, sum(m.slot == slot for m in self.mods))) if fit.mode: # Modes are special snowflakes and need a little manual loving # We basically append the Mode rack and Mode to the modules # while also marking the mode header position in the Blanks list if sFit.serviceFittingOptions["rackSlots"]: self.blanks.append(len(self.mods)) self.mods.append(Rack.buildRack(FittingSlot.MODE, None)) self.mods.append(fit.mode) else: self.mods = None def slotsChanged(self): self.generateMods() self.populate(self.mods) def fitChanged(self, event): event.Skip() if not self: return activeFitID = self.mainFrame.getActiveFit() if activeFitID is not None and activeFitID not in event.fitIDs: return try: if self.activeFitID is not None and self.activeFitID == activeFitID: self.generateMods() if self.GetItemCount() != len(self.mods): # This only happens when turning on/off slot divisions self.populate(self.mods) self.refresh(self.mods) self.Refresh() self.Show(self.activeFitID is not None and self.activeFitID == activeFitID) except RuntimeError: pyfalog.error("Caught dead object") def spawnMenu(self, event): clickedPos = self.getRowByAbs(event.Position) self.ensureSelection(clickedPos) if self.activeFitID is None or self.getColumn(self.screenToClientFixed(event.Position)) == self.getColIndex(State): return selection = [] for mod in self.getSelectedMods(): # Test if this is a mode, which is a special snowflake of a Module if isinstance(mod, Mode): selection.append(mod) elif not mod.isEmpty: selection.append(mod) fit = Fit.getInstance().getFit(self.activeFitID) mainMod = None if clickedPos != -1: try: mod = self.mods[clickedPos] except IndexError: pass else: if mod is not None and (mod in fit.modules or mod is fit.mode): mainMod = mod sMkt = Market.getInstance() contexts = [] if isinstance(mainMod, Module) and not mainMod.isEmpty: srcContext = "fittingModule" itemContext = sMkt.getCategoryByItem(mainMod.item).displayName fullContext = (srcContext, itemContext) if srcContext not in tuple(fCtx[0] for fCtx in contexts): contexts.append(fullContext) if mainMod.charge is not None: srcContext = "fittingCharge" itemContext = sMkt.getCategoryByItem(mainMod.charge).displayName fullContext = (srcContext, itemContext) if srcContext not in tuple(fCtxt[0] for fCtxt in contexts): contexts.append(fullContext) elif isinstance(mainMod, Mode): srcContext = "fittingMode" itemContext = _t("Tactical Mode") fullContext = (srcContext, itemContext) if srcContext not in tuple(fCtx[0] for fCtx in contexts): contexts.append(fullContext) contexts.append(("fittingShip", _t("Ship") if not fit.isStructure else _t("Citadel"))) menu = ContextMenu.getMenu(self, mainMod, selection, *contexts) self.PopupMenu(menu) def click(self, event): """ Handle click event on modules. This is only useful for the State column. If multiple items are selected, and we have clicked the State column, iterate through the selections and change State """ clickedRow, _, col = self.HitTestSubItem(event.Position) # only do State column and ignore invalid rows if clickedRow != -1 and clickedRow not in self.blanks and col == self.getColIndex(State): selectedRows = [] currentRow = self.GetFirstSelected() while currentRow != -1 and clickedRow not in self.blanks: selectedRows.append(currentRow) currentRow = self.GetNextSelected(currentRow) if clickedRow not in selectedRows: try: selectedMods = [self.mods[clickedRow]] except IndexError: return else: selectedMods = self.getSelectedMods() click = "ctrl" if event.GetModifiers() == wx.MOD_CONTROL or event.middleIsDown else "right" if event.GetButton() == 3 else "left" try: mainMod = self.mods[clickedRow] except IndexError: return if mainMod.isEmpty: return fitID = self.mainFrame.getActiveFit() fit = Fit.getInstance().getFit(fitID) if mainMod not in fit.modules: return mainPosition = fit.modules.index(mainMod) if event.GetModifiers() == wx.MOD_ALT: positions = getSimilarModPositions(fit.modules, mainMod) else: positions = [] for position, mod in enumerate(fit.modules): if mod in selectedMods: positions.append(position) self.mainFrame.command.Submit(cmd.GuiChangeLocalModuleStatesCommand( fitID=fitID, mainPosition=mainPosition, positions=positions, click=click)) # update state tooltip tooltip = self.activeColumns[col].getToolTip(self.mods[clickedRow]) if tooltip: self.SetToolTip(tooltip) else: event.Skip() def slotColour(self, slot): if isDark(): return slotColourMapDark.get(slot) or self.GetBackgroundColour() else: return slotColourMap.get(slot) or self.GetBackgroundColour() def refresh(self, stuff): """ Displays fitting Sends data to d.Display.refresh where the rows and columns are set up, then does a bit of post-processing (colors) """ self.Freeze() d.Display.refresh(self, stuff) sFit = Fit.getInstance() fit = sFit.getFit(self.activeFitID) slotMap = {} # test for too many modules (happens with t3s / CCP change in slot layout) for slot in [e.value for e in FittingSlot]: slotMap[slot] = fit.getSlotsFree(slot) < 0 for i, mod in enumerate(self.mods): self.SetItemBackgroundColour(i, self.GetBackgroundColour()) # only consider changing color if we're dealing with a Module if isinstance(mod, Module): hasRestrictionOverriden = False if not mod.isEmpty: fits = mod.fits(fit, False) hasRestrictionOverriden = getattr(mod, 'restrictionOverridden', None) # If module had broken fitting restrictions but now doesn't, # ensure it is now valid, and remove restrictionOverridden # variable. More in #1519 if not fit.ignoreRestrictions and hasRestrictionOverriden: clean = False if fits: if not mod.hardpoint: clean = True elif fit.getHardpointsFree(mod.hardpoint) >= 0: clean = True if clean: del mod.restrictionOverridden hasRestrictionOverriden = not hasRestrictionOverriden if slotMap[mod.slot] or hasRestrictionOverriden: # Color too many modules as red self.SetItemBackgroundColour(i, errColorDark if isDark() else errColor) elif sFit.serviceFittingOptions["colorFitBySlot"]: # Color by slot it enabled self.SetItemBackgroundColour(i, self.slotColour(mod.slot)) # Set rack face to bold if isinstance(mod, Rack) and \ sFit.serviceFittingOptions["rackSlots"] and \ sFit.serviceFittingOptions["rackLabels"]: self.font.SetWeight(wx.FONTWEIGHT_BOLD) self.SetItemFont(i, self.font) else: self.font.SetWeight(wx.FONTWEIGHT_NORMAL) self.SetItemFont(i, self.font) self.Thaw() self.itemCount = self.GetItemCount() # if 'wxMac' in wx.PlatformInfo: # try: # self.MakeSnapshot() # except Exception as e: # pyfalog.critical("Failed to make snapshot") # pyfalog.critical(e) def OnShow(self, event): if self and not self.IsShown(): try: self.MakeSnapshot() except (KeyboardInterrupt, SystemExit): raise except Exception as e: pyfalog.critical("Failed to make snapshot") pyfalog.critical(e) event.Skip() def Snapshot(self): return self.FVsnapshot # noinspection PyPropertyAccess def MakeSnapshot(self, maxColumns=1337): if self.FVsnapshot: self.FVsnapshot = None tbmp = wx.Bitmap(16, 16) tdc = wx.MemoryDC() tdc.SelectObject(tbmp) tdc.SetFont(self.font) columnsWidths = [] for i in range(len(self.DEFAULT_COLS)): columnsWidths.append(0) sFit = Fit.getInstance() try: fit = sFit.getFit(self.activeFitID) except (KeyboardInterrupt, SystemExit): raise except Exception as e: pyfalog.critical("Failed to get fit") pyfalog.critical(e) return if fit is None: return slotMap = {} for slot in [e.value for e in FittingSlot]: slotMap[slot] = fit.getSlotsFree(slot) < 0 padding = 2 isize = 16 headerSize = max(isize, tdc.GetTextExtent("W")[0]) + padding * 2 maxRowHeight = isize rows = 0 for st in self.mods: for i, col in enumerate(self.activeColumns): if i > maxColumns: break name = col.getText(st) if not isinstance(name, str): name = "" nx, ny = tdc.GetTextExtent(name) imgId = col.getImageId(st) cw = 0 if imgId != -1: cw += isize + padding if name != "": cw += nx + 4 * padding if imgId == -1 and name == "": cw += isize + padding maxRowHeight = max(ny, maxRowHeight) columnsWidths[i] = max(columnsWidths[i], cw) rows += 1 render = wx.RendererNative.Get() # Fix column widths (use biggest between header or items) for i, col in enumerate(self.activeColumns): if i > maxColumns: break name = col.columnText imgId = col.imageId if not isinstance(name, str): name = "" opts = wx.HeaderButtonParams() if name != "": opts.m_labelText = name if imgId != -1: opts.m_labelBitmap = wx.Bitmap(round(isize), round(isize)) width = render.DrawHeaderButton(self, tdc, (0, 0, 16, 16), sortArrow=wx.HDR_SORT_ICON_NONE, params=opts) columnsWidths[i] = max(columnsWidths[i], width) tdc.SelectObject(wx.NullBitmap) maxWidth = padding * 2 for i in range(len(self.DEFAULT_COLS)): if i > maxColumns: break maxWidth += columnsWidths[i] mdc = wx.MemoryDC() mbmp = wx.Bitmap(round(maxWidth), round(maxRowHeight * rows + padding * 4 + headerSize)) mdc.SelectObject(mbmp) mdc.SetBackground(wx.Brush(wx.SystemSettings.GetColour(wx.SYS_COLOUR_WINDOW))) mdc.Clear() mdc.SetFont(self.font) mdc.SetTextForeground(wx.SystemSettings.GetColour(wx.SYS_COLOUR_WINDOWTEXT)) cx = padding for i, col in enumerate(self.activeColumns): if i > maxColumns: break name = col.columnText imgId = col.imageId if not isinstance(name, str): name = "" opts = wx.HeaderButtonParams() opts.m_labelAlignment = wx.ALIGN_LEFT if name != "": opts.m_labelText = name if imgId != -1: bmp = col.bitmap opts.m_labelBitmap = bmp render.DrawHeaderButton(self, mdc, (cx, padding, columnsWidths[i], headerSize), wx.CONTROL_CURRENT, sortArrow=wx.HDR_SORT_ICON_NONE, params=opts) cx += columnsWidths[i] brush = wx.Brush(wx.Colour(224, 51, 51)) pen = wx.Pen(wx.Colour(224, 51, 51)) mdc.SetPen(pen) mdc.SetBrush(brush) cy = padding * 2 + headerSize for st in self.mods: cx = padding if slotMap[st.slot]: mdc.DrawRectangle(round(cx), round(cy), round(maxWidth - cx), round(maxRowHeight)) for i, col in enumerate(self.activeColumns): if i > maxColumns: break name = col.getText(st) if not isinstance(name, str): name = "" imgId = col.getImageId(st) tcx = cx if imgId != -1: self.imageList.Draw(imgId, mdc, cx, cy, wx.IMAGELIST_DRAW_TRANSPARENT, False) tcx += isize + padding if name != "": nx, ny = mdc.GetTextExtent(name) rect = wx.Rect() rect.top = cy rect.left = cx + 2 * padding rect.width = nx rect.height = maxRowHeight + padding mdc.DrawLabel(name, rect, wx.ALIGN_CENTER_VERTICAL) tcx += nx + padding cx += columnsWidths[i] cy += maxRowHeight mdc.SelectObject(wx.NullBitmap) self.FVsnapshot = mbmp