import wx from logbook import Logger import gui.builtinMarketBrowser.pfSearchBox as SBox import gui.globalEvents as GE from config import get_slotColourMap, get_slotColourMapDark from eos.saveddata.module import Module from eos.const import FittingSlot from gui.builtinMarketBrowser.events import ItemSelected, RECENTLY_USED_MODULES, CHARGES_FOR_FIT from gui.contextMenu import ContextMenu from gui.display import Display from gui.utils.staticHelpers import DragDropHelper from gui.utils.dark import isDark from service.fit import Fit from service.market import Market from service.ammo import Ammo pyfalog = Logger(__name__) class ItemView(Display): DEFAULT_COLS = ["Base Icon", "Base Name", "Price", "attr:power,,,True", "attr:cpu,,,True"] def __init__(self, parent, marketBrowser): Display.__init__(self, parent, style=wx.LC_SINGLE_SEL) pyfalog.debug("Initialize ItemView") marketBrowser.Bind(wx.EVT_TREE_SEL_CHANGED, self.treeSelectionChanged) self.unfilteredStore = set() self.filteredStore = set() self.sMkt = marketBrowser.sMkt self.sFit = Fit.getInstance() self.sAmmo = Ammo.getInstance() self.marketBrowser = marketBrowser self.marketView = marketBrowser.marketView # Set up timer for delaying search on every EVT_TEXT self.searchTimer = wx.Timer(self) self.Bind(wx.EVT_TIMER, self.scheduleSearch, self.searchTimer) # Make sure our search actually does interesting stuff self.marketBrowser.search.Bind(SBox.EVT_TEXT_ENTER, self.scheduleSearch) self.marketBrowser.search.Bind(SBox.EVT_SEARCH_BTN, self.scheduleSearch) self.marketBrowser.search.Bind(SBox.EVT_CANCEL_BTN, self.clearSearch) self.marketBrowser.search.Bind(SBox.EVT_TEXT, self.delaySearch) # Make sure WE do interesting stuff too self.Bind(wx.EVT_CONTEXT_MENU, self.contextMenu) self.Bind(wx.EVT_LIST_ITEM_ACTIVATED, self.itemActivated) self.Bind(wx.EVT_LIST_BEGIN_DRAG, self.startDrag) # the "charges for active fitting" needs to listen to fitting changes self.mainFrame.Bind(GE.FIT_CHANGED, self.fitChanged) self.active = [] def delaySearch(self, evt): sFit = Fit.getInstance() self.searchTimer.Stop() self.searchTimer.Start(sFit.serviceFittingOptions["marketSearchDelay"], True) def startDrag(self, event): row = self.GetFirstSelected() if row != -1: data = wx.TextDataObject() dataStr = "market:" + str(self.active[row].ID) pyfalog.debug("Dragging from market: " + dataStr) data.SetText(dataStr) dropSource = wx.DropSource(self) dropSource.SetData(data) DragDropHelper.data = dataStr dropSource.DoDragDrop() def itemActivated(self, event=None): # Check if something is selected, if so, spawn the menu for it sel = self.GetFirstSelected() if sel == -1: return if self.mainFrame.getActiveFit(): wx.PostEvent(self.mainFrame, ItemSelected(itemID=self.active[sel].ID)) def treeSelectionChanged(self, event=None): self.selectionMade('tree') def selectionMade(self, context): self.marketBrowser.mode = 'normal' # Grab the threeview selection and check if it's fine sel = self.marketView.GetSelection() if sel.IsOk(): # Get data field of the selected item (which is a marketGroup ID if anything was selected) seldata = self.marketView.GetItemData(sel) if seldata == RECENTLY_USED_MODULES: items = self.sMkt.getRecentlyUsed() elif seldata == CHARGES_FOR_FIT: items = self.getChargesForActiveFit() elif seldata is not None: # If market group treeview item doesn't have children (other market groups or dummies), # then it should have items in it and we want to request them if self.marketView.ItemHasChildren(sel) is False: sMkt = self.sMkt # Get current market group mg = sMkt.getMarketGroup(seldata, eager=("items", "items.metaGroup")) # Get all its items items = sMkt.getItemsByMarketGroup(mg) else: items = set() else: items = set() # Fill store self.updateItemStore(items) # Set toggle buttons / use search mode flag if recently used modules category is selected (in order to have all modules listed and not filtered) if seldata == RECENTLY_USED_MODULES: self.marketBrowser.mode = 'recent' if seldata == CHARGES_FOR_FIT: self.marketBrowser.mode = 'charges' self.setToggles() if context == 'tree' and self.marketBrowser.settings.get('marketMGMarketSelectMode') == 1: for btn in self.marketBrowser.metaButtons: if not btn.GetValue(): btn.setUserSelection(True) self.filterItemStore() def getChargesForActiveFit(self): fitId = self.mainFrame.getActiveFit() # no active fit => no charges if fitId is None: return set() fit = self.sFit.getFit(fitId) # use a set so we only add one entry for each charge items = set() for mod in fit.modules: charges = self.sAmmo.getModuleFlatAmmo(mod) for charge in charges: items.add(charge) return items def fitChanged(self, event): # skip the event so the other handlers also get called event.Skip() activeFitID = self.mainFrame.getActiveFit() # if it was not the active fitting that was changed, do not do anything if activeFitID is not None and activeFitID not in event.fitIDs: return # Handle charges mode if self.marketBrowser.mode == 'charges': items = self.getChargesForActiveFit() # update the UI self.updateItemStore(items) self.filterItemStore() return # If "Fits" filter is active, re-filter the current view if self.marketBrowser.getFitsFilter(): self.filterItemStore() def updateItemStore(self, items): self.unfilteredStore = items def filterItemStore(self): filteredItems = self.filterItems() if len(filteredItems) == 0 and len(self.unfilteredStore) > 0: setting = self.marketBrowser.settings.get('marketMGEmptyMode') # Enable leftmost available if setting == 1: for btn in self.marketBrowser.metaButtons: if btn.IsEnabled() and not btn.userSelected: btn.setUserSelection(True) break filteredItems = self.filterItems() # Enable all elif setting == 2: for btn in self.marketBrowser.metaButtons: if btn.IsEnabled() and not btn.userSelected: btn.setUserSelection(True) filteredItems = self.filterItems() self.filteredStore = filteredItems self.update(self.filteredStore) def filterItems(self): sMkt = self.sMkt selectedMetas = set() for btn in self.marketBrowser.metaButtons: if btn.userSelected: selectedMetas.update(sMkt.META_MAP[btn.metaName]) filteredItems = sMkt.filterItemsByMeta(self.unfilteredStore, selectedMetas) # Apply slot/fits filters - works IDENTICALLY to meta buttons (filters CURRENT VIEW only) activeSlotFilters = [] fitsFilterActive = False for btn in self.marketBrowser.slotButtons: if btn.userSelected and btn.IsEnabled(): if btn.filterType == "fits": fitsFilterActive = True elif btn.filterType == "slot": activeSlotFilters.append(btn.slotType) # Apply fits filter if fitsFilterActive: filteredItems = self._filterByFits(filteredItems) # Apply slot filters if activeSlotFilters: filteredItems = [item for item in filteredItems if Module.calculateSlot(item) in activeSlotFilters] return filteredItems def _filterByFits(self, items): """Filter items by remaining CPU/PG - filters CURRENT VIEW only""" fitId = self.mainFrame.getActiveFit() if fitId is None: return [] fit = self.sFit.getFit(fitId) # Get remaining CPU and power grid cpuOutput = fit.ship.getModifiedItemAttr("cpuOutput") powerOutput = fit.ship.getModifiedItemAttr("powerOutput") cpuUsed = fit.cpuUsed pgUsed = fit.pgUsed cpuRemaining = cpuOutput - cpuUsed pgRemaining = powerOutput - pgUsed # Get remaining calibration (for rigs) calibrationCapacity = fit.ship.getModifiedItemAttr("upgradeCapacity") calibrationUsed = fit.calibrationUsed calibrationRemaining = None if calibrationCapacity is not None and calibrationCapacity > 0: calibrationRemaining = calibrationCapacity - calibrationUsed fittingItems = [] for item in items: # Check if item is a module (has a slot) slot = Module.calculateSlot(item) if slot is None: continue # Rigs don't use CPU/power, they use calibration - check rig size and calibration if slot == FittingSlot.RIG: # Check if item can fit on the ship if not fit.canFit(item): continue # Check rig size compatibility with ship shipRigSize = fit.ship.getModifiedItemAttr("rigSize") itemRigSize = item.attributes.get("rigSize") if shipRigSize is not None and itemRigSize is not None: if shipRigSize != itemRigSize.value: continue # Check calibration requirement if calibrationRemaining is not None and calibrationRemaining > 0: itemCalibration = item.attributes.get("upgradeCost") if itemCalibration is not None: itemCalibrationValue = itemCalibration.value if itemCalibrationValue > calibrationRemaining: continue fittingItems.append(item) continue # For non-rigs, check CPU and power requirements itemCpu = item.attributes.get("cpu") itemPower = item.attributes.get("power") # Skip items without CPU or power (not modules) if itemCpu is None and itemPower is None: continue # Check CPU requirement if itemCpu is not None: itemCpuValue = itemCpu.value if itemCpuValue > cpuRemaining: continue # Check power requirement if itemPower is not None: itemPowerValue = itemPower.value if itemPowerValue > pgRemaining: continue # Check if item can fit on the ship (most expensive check, do last) if not fit.canFit(item): continue fittingItems.append(item) return fittingItems def setToggles(self): metaIDs = set() slotIDs = set() sMkt = self.sMkt for item in self.unfilteredStore: metaIDs.add(sMkt.getMetaGroupIdByItem(item)) slot = Module.calculateSlot(item) if slot is not None: slotIDs.add(slot) for btn in self.marketBrowser.metaButtons: btn.reset() btnMetas = sMkt.META_MAP[btn.metaName] if len(metaIDs.intersection(btnMetas)) > 0: btn.setMetaAvailable(True) else: btn.setMetaAvailable(False) # Set toggles for slot/fits buttons for btn in self.marketBrowser.slotButtons: btn.reset() if btn.filterType == "fits": # Fits button is available if there's an active fit fitId = self.mainFrame.getActiveFit() isAvailable = fitId is not None btn.setMetaAvailable(isAvailable) if not isAvailable: btn.setUserSelection(False) elif btn.filterType == "slot": # Slot button is available if items with that slot exist in current view isAvailable = btn.slotType in slotIDs btn.setMetaAvailable(isAvailable) if not isAvailable: btn.setUserSelection(False) def scheduleSearch(self, event=None): self.searchTimer.Stop() # Cancel any pending timers search = self.marketBrowser.search.GetLineText(0) # Make sure we do not count wildcards as search symbol realsearch = search.replace('*', '').replace('?', '') # Re-select market group if search query has zero length if len(realsearch) == 0: self.selectionMade('search') return self.marketBrowser.mode = 'search' self.sMkt.searchItems(search, self.populateSearch, 'market') def clearSearch(self, event=None): # Wipe item store and update everything to accomodate with it # If clearSearch was generated by SearchCtrl's Cancel button, clear the content also if event: self.marketBrowser.search.Clear() if self.marketBrowser.mode == 'search': self.marketBrowser.mode = 'normal' self.updateItemStore(set()) self.setToggles() self.filterItemStore() def populateSearch(self, itemIDs): # If we're no longer searching, dump the results if self.marketBrowser.mode != 'search': return items = Market.getItems(itemIDs) self.updateItemStore(items) self.setToggles() self.filterItemStore() def contextMenu(self, event): clickedPos = self.getRowByAbs(event.Position) self.ensureSelection(clickedPos) # Check if something is selected, if so, spawn the menu for it if clickedPos == -1: return item = self.active[clickedPos] sMkt = self.sMkt sourceContext = "marketItemMisc" if self.marketBrowser.mode in ("search", "recent") else "marketItemGroup" itemContext = sMkt.getCategoryByItem(item).displayName menu = ContextMenu.getMenu(self, item, (item,), (sourceContext, itemContext)) self.PopupMenu(menu) def populate(self, items): if len(items) > 0: # Clear selection self.unselectAll() # Perform sorting, using item's meta levels besides other stuff if self.marketBrowser.mode != 'recent': items.sort(key=self.sMkt.itemSort) # Mark current item list as active self.active = items # Show them Display.populate(self, items) def refresh(self, items): if len(items) > 1: # Re-sort stuff if self.marketBrowser.mode != 'recent': items.sort(key=self.sMkt.itemSort) for i, item in enumerate(items[:9]): # set shortcut info for first 9 modules item.marketShortcut = i + 1 Display.refresh(self, items) def columnBackground(self, colItem, item): if self.sFit.serviceFittingOptions["colorFitBySlot"]: colorMap = get_slotColourMapDark() if isDark() else get_slotColourMap() return colorMap.get(Module.calculateSlot(item)) or self.GetBackgroundColour() else: return self.GetBackgroundColour()