From a7c89f7b401c75da268c836c6c4c0ffb67c24b29 Mon Sep 17 00:00:00 2001 From: Ryan Holmes Date: Sun, 11 Jun 2017 17:54:36 -0400 Subject: [PATCH] break out market browser to individual class files --- gui/boosterView.py | 6 +- gui/builtinMarketBrowser/__init__.py | 0 gui/builtinMarketBrowser/events.py | 7 + gui/builtinMarketBrowser/itemView.py | 273 ++++++++++++ gui/builtinMarketBrowser/marketTree.py | 99 +++++ gui/builtinMarketBrowser/metaButton.py | 24 ++ .../pfSearchBox.py} | 0 gui/builtinMarketBrowser/searchBox.py | 13 + gui/builtinViews/fittingView.py | 8 +- gui/builtinViews/implantEditor.py | 4 +- gui/droneView.py | 2 +- gui/fighterView.py | 6 +- gui/mainFrame.py | 3 +- gui/marketBrowser.py | 400 +----------------- gui/propertyEditor.py | 4 +- 15 files changed, 441 insertions(+), 408 deletions(-) create mode 100644 gui/builtinMarketBrowser/__init__.py create mode 100644 gui/builtinMarketBrowser/events.py create mode 100644 gui/builtinMarketBrowser/itemView.py create mode 100644 gui/builtinMarketBrowser/marketTree.py create mode 100644 gui/builtinMarketBrowser/metaButton.py rename gui/{PFSearchBox.py => builtinMarketBrowser/pfSearchBox.py} (100%) create mode 100644 gui/builtinMarketBrowser/searchBox.py diff --git a/gui/boosterView.py b/gui/boosterView.py index 5f43aca04..c1b4748c8 100644 --- a/gui/boosterView.py +++ b/gui/boosterView.py @@ -21,7 +21,7 @@ import wx import gui.display as d import gui.globalEvents as GE -import gui.marketBrowser as marketBrowser +from gui.builtinMarketBrowser.events import ItemSelected, ITEM_SELECTED from gui.builtinViewColumns.state import State from gui.contextMenu import ContextMenu from gui.utils.staticHelpers import DragDropHelper @@ -58,7 +58,7 @@ class BoosterView(d.Display): self.lastFitId = None self.mainFrame.Bind(GE.FIT_CHANGED, self.fitChanged) - self.mainFrame.Bind(marketBrowser.ITEM_SELECTED, self.addItem) + self.mainFrame.Bind(ITEM_SELECTED, self.addItem) self.Bind(wx.EVT_LEFT_DCLICK, self.removeItem) self.Bind(wx.EVT_LEFT_DOWN, self.click) @@ -81,7 +81,7 @@ class BoosterView(d.Display): """ if data[0] == "market": - wx.PostEvent(self.mainFrame, marketBrowser.ItemSelected(itemID=int(data[1]))) + wx.PostEvent(self.mainFrame, ItemSelected(itemID=int(data[1]))) def kbEvent(self, event): keycode = event.GetKeyCode() diff --git a/gui/builtinMarketBrowser/__init__.py b/gui/builtinMarketBrowser/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/gui/builtinMarketBrowser/events.py b/gui/builtinMarketBrowser/events.py new file mode 100644 index 000000000..f2e78cf93 --- /dev/null +++ b/gui/builtinMarketBrowser/events.py @@ -0,0 +1,7 @@ +# noinspection PyPackageRequirements +import wx.lib.newevent + +ItemSelected, ITEM_SELECTED = wx.lib.newevent.NewEvent() + +RECENTLY_USED_MODULES = -2 +MAX_RECENTLY_USED_MODULES = 20 \ No newline at end of file diff --git a/gui/builtinMarketBrowser/itemView.py b/gui/builtinMarketBrowser/itemView.py new file mode 100644 index 000000000..382b5fd02 --- /dev/null +++ b/gui/builtinMarketBrowser/itemView.py @@ -0,0 +1,273 @@ +import wx + +import gui.builtinMarketBrowser.pfSearchBox as SBox +from gui.contextMenu import ContextMenu +from gui.display import Display +from service.attribute import Attribute +from service.fit import Fit +from gui.utils.staticHelpers import DragDropHelper + +from logbook import Logger + +from gui.builtinMarketBrowser.events import * + +pyfalog = Logger(__name__) + + +class ItemView(Display): + DEFAULT_COLS = ["Base Icon", + "Base Name", + "attr:power,,,True", + "attr:cpu,,,True"] + + def __init__(self, parent, marketBrowser): + Display.__init__(self, parent) + pyfalog.debug("Initialize ItemView") + marketBrowser.Bind(wx.EVT_TREE_SEL_CHANGED, self.selectionMade) + + self.unfilteredStore = set() + self.filteredStore = set() + self.recentlyUsedModules = set() + self.sMkt = marketBrowser.sMkt + self.searchMode = marketBrowser.searchMode + + 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) + + # Make reverse map, used by sorter + self.metaMap = self.makeReverseMetaMap() + + # Fill up recently used modules set + pyfalog.debug("Fill up recently used modules set") + for itemID in self.sMkt.serviceMarketRecentlyUsedModules["pyfaMarketRecentlyUsedModules"]: + self.recentlyUsedModules.add(self.sMkt.getItem(itemID)) + + def delaySearch(self, evt): + sFit = Fit.getInstance() + self.searchTimer.Stop() + self.searchTimer.Start(sFit.serviceFittingOptions["marketSearchDelay"], True) # 150ms + + def startDrag(self, event): + row = self.GetFirstSelected() + + if row != -1: + data = wx.PyTextDataObject() + 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(): + + self.storeRecentlyUsedMarketItem(self.active[sel].ID) + self.recentlyUsedModules = set() + for itemID in self.sMkt.serviceMarketRecentlyUsedModules["pyfaMarketRecentlyUsedModules"]: + self.recentlyUsedModules.add(self.sMkt.getItem(itemID)) + + wx.PostEvent(self.mainFrame, ItemSelected(itemID=self.active[sel].ID)) + + def storeRecentlyUsedMarketItem(self, itemID): + if len(self.sMkt.serviceMarketRecentlyUsedModules["pyfaMarketRecentlyUsedModules"]) > MAX_RECENTLY_USED_MODULES: + self.sMkt.serviceMarketRecentlyUsedModules["pyfaMarketRecentlyUsedModules"].pop(0) + + self.sMkt.serviceMarketRecentlyUsedModules["pyfaMarketRecentlyUsedModules"].append(itemID) + + def selectionMade(self, event=None): + self.marketBrowser.searchMode = False + # 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.GetPyData(sel) + if seldata is not None and seldata != RECENTLY_USED_MODULES: + # 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: + # If method was called but selection wasn't actually made or we have a hit on recently used modules + if seldata == RECENTLY_USED_MODULES: + items = self.recentlyUsedModules + 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 is not RECENTLY_USED_MODULES: + self.setToggles() + else: + self.marketBrowser.searchMode = True + self.setToggles() + + # Update filtered items + self.filterItemStore() + + def updateItemStore(self, items): + self.unfilteredStore = items + + def filterItemStore(self): + sMkt = self.sMkt + selectedMetas = set() + for btn in self.marketBrowser.metaButtons: + if btn.GetValue(): + selectedMetas.update(sMkt.META_MAP[btn.metaName]) + self.filteredStore = sMkt.filterItemsByMeta(self.unfilteredStore, selectedMetas) + self.update(list(self.filteredStore)) + + def setToggles(self): + metaIDs = set() + sMkt = self.sMkt + for item in self.unfilteredStore: + metaIDs.add(sMkt.getMetaGroupIdByItem(item)) + + 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) + + def scheduleSearch(self, event=None): + self.searchTimer.Stop() # Cancel any pending timers + search = self.marketBrowser.search.GetLineText(0) + # Make sure we do not count wildcard as search symbol + realsearch = search.replace("*", "") + # Re-select market group if search query has zero length + if len(realsearch) == 0: + self.selectionMade() + return + # Show nothing if query is too short + elif len(realsearch) < 3: + self.clearSearch() + return + + self.marketBrowser.searchMode = True + self.sMkt.searchItems(search, self.populateSearch) + + 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() + + self.marketBrowser.searchMode = False + self.updateItemStore(set()) + self.setToggles() + self.filterItemStore() + + def populateSearch(self, items): + # If we're no longer searching, dump the results + if self.marketBrowser.searchMode is False: + return + self.updateItemStore(items) + self.setToggles() + self.filterItemStore() + + def itemSort(self, item): + sMkt = self.sMkt + catname = sMkt.getCategoryByItem(item).name + try: + mktgrpid = sMkt.getMarketGroupByItem(item).ID + except AttributeError: + mktgrpid = None + print("unable to find market group for", item.name) + parentname = sMkt.getParentItemByItem(item).name + # Get position of market group + metagrpid = sMkt.getMetaGroupIdByItem(item) + metatab = self.metaMap.get(metagrpid) + metalvl = self.metalvls.get(item.ID, 0) + return catname, mktgrpid, parentname, metatab, metalvl, item.name + + def contextMenu(self, event): + # Check if something is selected, if so, spawn the menu for it + sel = self.GetFirstSelected() + if sel == -1: + return + + item = self.active[sel] + + sMkt = self.sMkt + sourceContext = "marketItemGroup" if self.marketBrowser.searchMode is False else "marketItemMisc" + itemContext = sMkt.getCategoryByItem(item).name + + menu = ContextMenu.getMenu((item,), (sourceContext, itemContext)) + self.PopupMenu(menu) + + def populate(self, items): + if len(items) > 0: + # Get dictionary with meta level attribute + sAttr = Attribute.getInstance() + attrs = sAttr.getAttributeInfo("metaLevel") + sMkt = self.sMkt + self.metalvls = sMkt.directAttrRequest(items, attrs) + # Clear selection + self.deselectItems() + # Perform sorting, using item's meta levels besides other stuff + items.sort(key=self.itemSort) + # Mark current item list as active + self.active = items + # Show them + Display.populate(self, items) + + def refresh(self, items): + if len(items) > 1: + # Get dictionary with meta level attribute + sAttr = Attribute.getInstance() + attrs = sAttr.getAttributeInfo("metaLevel") + sMkt = self.sMkt + self.metalvls = sMkt.directAttrRequest(items, attrs) + # Re-sort stuff + items.sort(key=self.itemSort) + + for i, item in enumerate(items[:9]): + # set shortcut info for first 9 modules + item.marketShortcut = i + 1 + + Display.refresh(self, items) + + def makeReverseMetaMap(self): + """ + Form map which tells in which tab items of given metagroup are located + """ + revmap = {} + i = 0 + for mgids in self.sMkt.META_MAP.itervalues(): + for mgid in mgids: + revmap[mgid] = i + i += 1 + return revmap diff --git a/gui/builtinMarketBrowser/marketTree.py b/gui/builtinMarketBrowser/marketTree.py new file mode 100644 index 000000000..1658da2c5 --- /dev/null +++ b/gui/builtinMarketBrowser/marketTree.py @@ -0,0 +1,99 @@ +import wx + +from gui.cachingImageList import CachingImageList +from gui.builtinMarketBrowser.events import * + +from logbook import Logger + +pyfalog = Logger(__name__) + + +class MarketTree(wx.TreeCtrl): + def __init__(self, parent, marketBrowser): + wx.TreeCtrl.__init__(self, parent, style=wx.TR_DEFAULT_STYLE | wx.TR_HIDE_ROOT) + pyfalog.debug("Initialize marketTree") + self.root = self.AddRoot("root") + + self.imageList = CachingImageList(16, 16) + self.SetImageList(self.imageList) + + self.sMkt = marketBrowser.sMkt + self.marketBrowser = marketBrowser + + # Form market tree root + sMkt = self.sMkt + for mktGrp in sMkt.getMarketRoot(): + iconId = self.addImage(sMkt.getIconByMarketGroup(mktGrp)) + childId = self.AppendItem(self.root, mktGrp.name, iconId, data=wx.TreeItemData(mktGrp.ID)) + # All market groups which were never expanded are dummies, here we assume + # that all root market groups are expandable + self.AppendItem(childId, "dummy") + self.SortChildren(self.root) + + # Add recently used modules node + rumIconId = self.addImage("market_small", "gui") + self.AppendItem(self.root, "Recently Used Modules", rumIconId, data=wx.TreeItemData(RECENTLY_USED_MODULES)) + + # Bind our lookup method to when the tree gets expanded + self.Bind(wx.EVT_TREE_ITEM_EXPANDING, self.expandLookup) + + def addImage(self, iconFile, location="icons"): + if iconFile is None: + return -1 + return self.imageList.GetImageIndex(iconFile, location) + + def expandLookup(self, event): + """Process market tree expands""" + root = event.Item + child = self.GetFirstChild(root)[0] + # If child of given market group is a dummy + if self.GetItemText(child) == "dummy": + # Delete it + self.Delete(child) + # And add real market group contents + sMkt = self.sMkt + currentMktGrp = sMkt.getMarketGroup(self.GetPyData(root), eager="children") + for childMktGrp in sMkt.getMarketGroupChildren(currentMktGrp): + # If market should have items but it doesn't, do not show it + if sMkt.marketGroupValidityCheck(childMktGrp) is False: + continue + iconId = self.addImage(sMkt.getIconByMarketGroup(childMktGrp)) + try: + childId = self.AppendItem(root, childMktGrp.name, iconId, data=wx.TreeItemData(childMktGrp.ID)) + except Exception as e: + pyfalog.debug("Error appending item.") + pyfalog.debug(e) + continue + if sMkt.marketGroupHasTypesCheck(childMktGrp) is False: + self.AppendItem(childId, "dummy") + + self.SortChildren(root) + + def jump(self, item): + """Open market group and meta tab of given item""" + self.marketBrowser.searchMode = False + sMkt = self.sMkt + mg = sMkt.getMarketGroupByItem(item) + + jumpList = [] + while mg is not None: + jumpList.append(mg.ID) + mg = mg.parent + + for id in sMkt.ROOT_MARKET_GROUPS: + if id in jumpList: + jumpList = jumpList[:jumpList.index(id) + 1] + + item = self.root + for i in range(len(jumpList) - 1, -1, -1): + target = jumpList[i] + child, cookie = self.GetFirstChild(item) + while self.GetItemPyData(child) != target: + child, cookie = self.GetNextChild(item, cookie) + + item = child + self.Expand(item) + + self.SelectItem(item) + self.marketBrowser.itemView.selectionMade() + diff --git a/gui/builtinMarketBrowser/metaButton.py b/gui/builtinMarketBrowser/metaButton.py new file mode 100644 index 000000000..ce939a436 --- /dev/null +++ b/gui/builtinMarketBrowser/metaButton.py @@ -0,0 +1,24 @@ +from gui.builtinMarketBrowser.events import * +from logbook import Logger + +pyfalog = Logger(__name__) + + +class MetaButton(wx.ToggleButton): + def __init__(self, *args, **kwargs): + super(MetaButton, self).__init__(*args, **kwargs) + self.setUserSelection(True) + + def setUserSelection(self, isSelected): + self.userSelected = isSelected + self.SetValue(isSelected) + + def setMetaAvailable(self, isAvailable): + self.Enable(isAvailable) + # need to also SetValue(False) for windows because Enabled=False AND SetValue(True) looks enabled. + if not isAvailable: + self.SetValue(False) + + def reset(self): + self.Enable(True) + self.SetValue(self.userSelected) diff --git a/gui/PFSearchBox.py b/gui/builtinMarketBrowser/pfSearchBox.py similarity index 100% rename from gui/PFSearchBox.py rename to gui/builtinMarketBrowser/pfSearchBox.py diff --git a/gui/builtinMarketBrowser/searchBox.py b/gui/builtinMarketBrowser/searchBox.py new file mode 100644 index 000000000..4fd899111 --- /dev/null +++ b/gui/builtinMarketBrowser/searchBox.py @@ -0,0 +1,13 @@ +from gui.bitmapLoader import BitmapLoader +from pfSearchBox import PFSearchBox + + +class SearchBox(PFSearchBox): + def __init__(self, parent, **kwargs): + PFSearchBox.__init__(self, parent, **kwargs) + cancelBitmap = BitmapLoader.getBitmap("fit_delete_small", "gui") + searchBitmap = BitmapLoader.getBitmap("fsearch_small", "gui") + self.SetSearchBitmap(searchBitmap) + self.SetCancelBitmap(cancelBitmap) + self.ShowSearchButton() + self.ShowCancelButton() \ No newline at end of file diff --git a/gui/builtinViews/fittingView.py b/gui/builtinViews/fittingView.py index 8358576ce..d2c4feaf4 100644 --- a/gui/builtinViews/fittingView.py +++ b/gui/builtinViews/fittingView.py @@ -22,7 +22,7 @@ import wx # noinspection PyPackageRequirements import wx.lib.newevent import gui.mainFrame -import gui.marketBrowser +from gui.builtinMarketBrowser.events import ItemSelected, ITEM_SELECTED import gui.display as d from gui.contextMenu import ContextMenu from gui.builtinShipBrowser.events import * @@ -140,7 +140,7 @@ class FittingView(d.Display): self.mainFrame.Bind(GE.FIT_CHANGED, self.fitChanged) self.mainFrame.Bind(EVT_FIT_RENAMED, self.fitRenamed) self.mainFrame.Bind(EVT_FIT_REMOVED, self.fitRemoved) - self.mainFrame.Bind(gui.marketBrowser.ITEM_SELECTED, self.appendItem) + self.mainFrame.Bind(ITEM_SELECTED, self.appendItem) self.Bind(wx.EVT_LEFT_DCLICK, self.removeItem) self.Bind(wx.EVT_LIST_BEGIN_DRAG, self.startDrag) @@ -217,7 +217,7 @@ class FittingView(d.Display): self.mainFrame.Unbind(GE.FIT_CHANGED, handler=self.fitChanged) self.mainFrame.Unbind(EVT_FIT_RENAMED, handler=self.fitRenamed) self.mainFrame.Unbind(EVT_FIT_REMOVED, handler=self.fitRemoved) - self.mainFrame.Unbind(gui.marketBrowser.ITEM_SELECTED, handler=self.appendItem) + self.mainFrame.Unbind(ITEM_SELECTED, handler=self.appendItem) d.Display.Destroy(self) @@ -394,7 +394,7 @@ class FittingView(d.Display): moduleChanged = sFit.changeModule(fitID, self.mods[dstRow].modPosition, srcIdx) if moduleChanged is None: # the new module doesn't fit in specified slot, try to simply append it - wx.PostEvent(self.mainFrame, gui.marketBrowser.ItemSelected(itemID=srcIdx)) + wx.PostEvent(self.mainFrame, ItemSelected(itemID=srcIdx)) wx.PostEvent(self.mainFrame, GE.FitChanged(fitID=self.mainFrame.getActiveFit(), action="modadd", typeID=srcIdx)) diff --git a/gui/builtinViews/implantEditor.py b/gui/builtinViews/implantEditor.py index bb9c125e7..827d514a2 100644 --- a/gui/builtinViews/implantEditor.py +++ b/gui/builtinViews/implantEditor.py @@ -3,11 +3,11 @@ import wx # noinspection PyPackageRequirements from wx.lib.buttons import GenBitmapButton -from service.market import Market +import gui.builtinMarketBrowser.pfSearchBox as SBox import gui.display as d -import gui.PFSearchBox as SBox from gui.bitmapLoader import BitmapLoader from gui.marketBrowser import SearchBox +from service.market import Market class BaseImplantEditorView(wx.Panel): diff --git a/gui/droneView.py b/gui/droneView.py index e4316059c..7fde0a105 100644 --- a/gui/droneView.py +++ b/gui/droneView.py @@ -21,7 +21,7 @@ import wx import gui.globalEvents as GE -from gui.marketBrowser import ITEM_SELECTED, ItemSelected +from gui.builtinMarketBrowser.events import ItemSelected, ITEM_SELECTED from gui.display import Display from gui.builtinViewColumns.state import State from gui.contextMenu import ContextMenu diff --git a/gui/fighterView.py b/gui/fighterView.py index e5fdfd1c2..f3b4aea1f 100644 --- a/gui/fighterView.py +++ b/gui/fighterView.py @@ -21,7 +21,7 @@ import wx import gui.globalEvents as GE -import gui.marketBrowser as marketBrowser +from gui.builtinMarketBrowser.events import ItemSelected, ITEM_SELECTED import gui.mainFrame import gui.display as d from gui.builtinViewColumns.state import State @@ -129,7 +129,7 @@ class FighterDisplay(d.Display): self.hoveredColumn = None self.mainFrame.Bind(GE.FIT_CHANGED, self.fitChanged) - self.mainFrame.Bind(marketBrowser.ITEM_SELECTED, self.addItem) + self.mainFrame.Bind(ITEM_SELECTED, self.addItem) self.Bind(wx.EVT_LEFT_DCLICK, self.removeItem) self.Bind(wx.EVT_LEFT_DOWN, self.click) self.Bind(wx.EVT_KEY_UP, self.kbEvent) @@ -208,7 +208,7 @@ class FighterDisplay(d.Display): if srcRow != -1 and dstRow != -1: self._merge(srcRow, dstRow) elif data[0] == "market": - wx.PostEvent(self.mainFrame, marketBrowser.ItemSelected(itemID=int(data[1]))) + wx.PostEvent(self.mainFrame, ItemSelected(itemID=int(data[1]))) @staticmethod def _merge(src, dst): diff --git a/gui/mainFrame.py b/gui/mainFrame.py index d61a833f3..b5d859149 100644 --- a/gui/mainFrame.py +++ b/gui/mainFrame.py @@ -45,7 +45,8 @@ import gui.globalEvents as GE from gui.bitmapLoader import BitmapLoader from gui.mainMenuBar import MainMenuBar from gui.additionsPane import AdditionsPane -from gui.marketBrowser import MarketBrowser, ItemSelected +from gui.marketBrowser import MarketBrowser +from gui.builtinMarketBrowser.events import ItemSelected from gui.multiSwitch import MultiSwitch from gui.statsPane import StatsPane from gui.shipBrowser import ShipBrowser diff --git a/gui/marketBrowser.py b/gui/marketBrowser.py index d914069b7..b67950503 100644 --- a/gui/marketBrowser.py +++ b/gui/marketBrowser.py @@ -19,44 +19,19 @@ # noinspection PyPackageRequirements import wx + from service.market import Market -from service.fit import Fit -from service.attribute import Attribute -from gui.display import Display -import gui.PFSearchBox as SBox -from gui.cachingImageList import CachingImageList -from gui.contextMenu import ContextMenu -from gui.bitmapLoader import BitmapLoader + +from gui.builtinMarketBrowser.searchBox import SearchBox +from gui.builtinMarketBrowser.itemView import ItemView +from gui.builtinMarketBrowser.metaButton import MetaButton +from gui.builtinMarketBrowser.marketTree import MarketTree +from gui.builtinMarketBrowser.events import * + from logbook import Logger -from utils.staticHelpers import DragDropHelper pyfalog = Logger(__name__) -ItemSelected, ITEM_SELECTED = wx.lib.newevent.NewEvent() - -RECENTLY_USED_MODULES = -2 -MAX_RECENTLY_USED_MODULES = 20 - - -class MetaButton(wx.ToggleButton): - def __init__(self, *args, **kwargs): - super(MetaButton, self).__init__(*args, **kwargs) - self.setUserSelection(True) - - def setUserSelection(self, isSelected): - self.userSelected = isSelected - self.SetValue(isSelected) - - def setMetaAvailable(self, isAvailable): - self.Enable(isAvailable) - # need to also SetValue(False) for windows because Enabled=False AND SetValue(True) looks enabled. - if not isAvailable: - self.SetValue(False) - - def reset(self): - self.Enable(True) - self.SetValue(self.userSelected) - class MarketBrowser(wx.Panel): def __init__(self, parent): @@ -125,362 +100,3 @@ class MarketBrowser(wx.Panel): def jump(self, item): self.marketView.jump(item) - -class SearchBox(SBox.PFSearchBox): - def __init__(self, parent, **kwargs): - SBox.PFSearchBox.__init__(self, parent, **kwargs) - cancelBitmap = BitmapLoader.getBitmap("fit_delete_small", "gui") - searchBitmap = BitmapLoader.getBitmap("fsearch_small", "gui") - self.SetSearchBitmap(searchBitmap) - self.SetCancelBitmap(cancelBitmap) - self.ShowSearchButton() - self.ShowCancelButton() - - -class MarketTree(wx.TreeCtrl): - def __init__(self, parent, marketBrowser): - wx.TreeCtrl.__init__(self, parent, style=wx.TR_DEFAULT_STYLE | wx.TR_HIDE_ROOT) - pyfalog.debug("Initialize marketTree") - self.root = self.AddRoot("root") - - self.imageList = CachingImageList(16, 16) - self.SetImageList(self.imageList) - - self.sMkt = marketBrowser.sMkt - self.marketBrowser = marketBrowser - - # Form market tree root - sMkt = self.sMkt - for mktGrp in sMkt.getMarketRoot(): - iconId = self.addImage(sMkt.getIconByMarketGroup(mktGrp)) - childId = self.AppendItem(self.root, mktGrp.name, iconId, data=wx.TreeItemData(mktGrp.ID)) - # All market groups which were never expanded are dummies, here we assume - # that all root market groups are expandable - self.AppendItem(childId, "dummy") - self.SortChildren(self.root) - - # Add recently used modules node - rumIconId = self.addImage("market_small", "gui") - self.AppendItem(self.root, "Recently Used Modules", rumIconId, data=wx.TreeItemData(RECENTLY_USED_MODULES)) - - # Bind our lookup method to when the tree gets expanded - self.Bind(wx.EVT_TREE_ITEM_EXPANDING, self.expandLookup) - - def addImage(self, iconFile, location="icons"): - if iconFile is None: - return -1 - return self.imageList.GetImageIndex(iconFile, location) - - def expandLookup(self, event): - """Process market tree expands""" - root = event.Item - child = self.GetFirstChild(root)[0] - # If child of given market group is a dummy - if self.GetItemText(child) == "dummy": - # Delete it - self.Delete(child) - # And add real market group contents - sMkt = self.sMkt - currentMktGrp = sMkt.getMarketGroup(self.GetPyData(root), eager="children") - for childMktGrp in sMkt.getMarketGroupChildren(currentMktGrp): - # If market should have items but it doesn't, do not show it - if sMkt.marketGroupValidityCheck(childMktGrp) is False: - continue - iconId = self.addImage(sMkt.getIconByMarketGroup(childMktGrp)) - try: - childId = self.AppendItem(root, childMktGrp.name, iconId, data=wx.TreeItemData(childMktGrp.ID)) - except Exception as e: - pyfalog.debug("Error appending item.") - pyfalog.debug(e) - continue - if sMkt.marketGroupHasTypesCheck(childMktGrp) is False: - self.AppendItem(childId, "dummy") - - self.SortChildren(root) - - def jump(self, item): - """Open market group and meta tab of given item""" - self.marketBrowser.searchMode = False - sMkt = self.sMkt - mg = sMkt.getMarketGroupByItem(item) - - jumpList = [] - while mg is not None: - jumpList.append(mg.ID) - mg = mg.parent - - for id in sMkt.ROOT_MARKET_GROUPS: - if id in jumpList: - jumpList = jumpList[:jumpList.index(id) + 1] - - item = self.root - for i in range(len(jumpList) - 1, -1, -1): - target = jumpList[i] - child, cookie = self.GetFirstChild(item) - while self.GetItemPyData(child) != target: - child, cookie = self.GetNextChild(item, cookie) - - item = child - self.Expand(item) - - self.SelectItem(item) - self.marketBrowser.itemView.selectionMade() - - -class ItemView(Display): - DEFAULT_COLS = ["Base Icon", - "Base Name", - "attr:power,,,True", - "attr:cpu,,,True"] - - def __init__(self, parent, marketBrowser): - Display.__init__(self, parent) - pyfalog.debug("Initialize ItemView") - marketBrowser.Bind(wx.EVT_TREE_SEL_CHANGED, self.selectionMade) - - self.unfilteredStore = set() - self.filteredStore = set() - self.recentlyUsedModules = set() - self.sMkt = marketBrowser.sMkt - self.searchMode = marketBrowser.searchMode - - 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) - - # Make reverse map, used by sorter - self.metaMap = self.makeReverseMetaMap() - - # Fill up recently used modules set - pyfalog.debug("Fill up recently used modules set") - for itemID in self.sMkt.serviceMarketRecentlyUsedModules["pyfaMarketRecentlyUsedModules"]: - self.recentlyUsedModules.add(self.sMkt.getItem(itemID)) - - def delaySearch(self, evt): - sFit = Fit.getInstance() - self.searchTimer.Stop() - self.searchTimer.Start(sFit.serviceFittingOptions["marketSearchDelay"], True) # 150ms - - def startDrag(self, event): - row = self.GetFirstSelected() - - if row != -1: - data = wx.PyTextDataObject() - 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(): - - self.storeRecentlyUsedMarketItem(self.active[sel].ID) - self.recentlyUsedModules = set() - for itemID in self.sMkt.serviceMarketRecentlyUsedModules["pyfaMarketRecentlyUsedModules"]: - self.recentlyUsedModules.add(self.sMkt.getItem(itemID)) - - wx.PostEvent(self.mainFrame, ItemSelected(itemID=self.active[sel].ID)) - - def storeRecentlyUsedMarketItem(self, itemID): - if len(self.sMkt.serviceMarketRecentlyUsedModules["pyfaMarketRecentlyUsedModules"]) > MAX_RECENTLY_USED_MODULES: - self.sMkt.serviceMarketRecentlyUsedModules["pyfaMarketRecentlyUsedModules"].pop(0) - - self.sMkt.serviceMarketRecentlyUsedModules["pyfaMarketRecentlyUsedModules"].append(itemID) - - def selectionMade(self, event=None): - self.marketBrowser.searchMode = False - # 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.GetPyData(sel) - if seldata is not None and seldata != RECENTLY_USED_MODULES: - # 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: - # If method was called but selection wasn't actually made or we have a hit on recently used modules - if seldata == RECENTLY_USED_MODULES: - items = self.recentlyUsedModules - 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 is not RECENTLY_USED_MODULES: - self.setToggles() - else: - self.marketBrowser.searchMode = True - self.setToggles() - - # Update filtered items - self.filterItemStore() - - def updateItemStore(self, items): - self.unfilteredStore = items - - def filterItemStore(self): - sMkt = self.sMkt - selectedMetas = set() - for btn in self.marketBrowser.metaButtons: - if btn.GetValue(): - selectedMetas.update(sMkt.META_MAP[btn.metaName]) - self.filteredStore = sMkt.filterItemsByMeta(self.unfilteredStore, selectedMetas) - self.update(list(self.filteredStore)) - - def setToggles(self): - metaIDs = set() - sMkt = self.sMkt - for item in self.unfilteredStore: - metaIDs.add(sMkt.getMetaGroupIdByItem(item)) - - 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) - - def scheduleSearch(self, event=None): - self.searchTimer.Stop() # Cancel any pending timers - search = self.marketBrowser.search.GetLineText(0) - # Make sure we do not count wildcard as search symbol - realsearch = search.replace("*", "") - # Re-select market group if search query has zero length - if len(realsearch) == 0: - self.selectionMade() - return - # Show nothing if query is too short - elif len(realsearch) < 3: - self.clearSearch() - return - - self.marketBrowser.searchMode = True - self.sMkt.searchItems(search, self.populateSearch) - - 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() - - self.marketBrowser.searchMode = False - self.updateItemStore(set()) - self.setToggles() - self.filterItemStore() - - def populateSearch(self, items): - # If we're no longer searching, dump the results - if self.marketBrowser.searchMode is False: - return - self.updateItemStore(items) - self.setToggles() - self.filterItemStore() - - def itemSort(self, item): - sMkt = self.sMkt - catname = sMkt.getCategoryByItem(item).name - try: - mktgrpid = sMkt.getMarketGroupByItem(item).ID - except AttributeError: - mktgrpid = None - print("unable to find market group for", item.name) - parentname = sMkt.getParentItemByItem(item).name - # Get position of market group - metagrpid = sMkt.getMetaGroupIdByItem(item) - metatab = self.metaMap.get(metagrpid) - metalvl = self.metalvls.get(item.ID, 0) - return catname, mktgrpid, parentname, metatab, metalvl, item.name - - def contextMenu(self, event): - # Check if something is selected, if so, spawn the menu for it - sel = self.GetFirstSelected() - if sel == -1: - return - - item = self.active[sel] - - sMkt = self.sMkt - sourceContext = "marketItemGroup" if self.marketBrowser.searchMode is False else "marketItemMisc" - itemContext = sMkt.getCategoryByItem(item).name - - menu = ContextMenu.getMenu((item,), (sourceContext, itemContext)) - self.PopupMenu(menu) - - def populate(self, items): - if len(items) > 0: - # Get dictionary with meta level attribute - sAttr = Attribute.getInstance() - attrs = sAttr.getAttributeInfo("metaLevel") - sMkt = self.sMkt - self.metalvls = sMkt.directAttrRequest(items, attrs) - # Clear selection - self.deselectItems() - # Perform sorting, using item's meta levels besides other stuff - items.sort(key=self.itemSort) - # Mark current item list as active - self.active = items - # Show them - Display.populate(self, items) - - def refresh(self, items): - if len(items) > 1: - # Get dictionary with meta level attribute - sAttr = Attribute.getInstance() - attrs = sAttr.getAttributeInfo("metaLevel") - sMkt = self.sMkt - self.metalvls = sMkt.directAttrRequest(items, attrs) - # Re-sort stuff - items.sort(key=self.itemSort) - - for i, item in enumerate(items[:9]): - # set shortcut info for first 9 modules - item.marketShortcut = i + 1 - - Display.refresh(self, items) - - def makeReverseMetaMap(self): - """ - Form map which tells in which tab items of given metagroup are located - """ - revmap = {} - i = 0 - for mgids in self.sMkt.META_MAP.itervalues(): - for mgid in mgids: - revmap[mgid] = i - i += 1 - return revmap diff --git a/gui/propertyEditor.py b/gui/propertyEditor.py index 3416d591a..b6f4ab1a1 100644 --- a/gui/propertyEditor.py +++ b/gui/propertyEditor.py @@ -1,8 +1,8 @@ import csv -from logbook import Logger # noinspection PyPackageRequirements import wx +from logbook import Logger try: # noinspection PyPackageRequirements @@ -17,7 +17,7 @@ from eos.db.gamedata.queries import getItem, getAttributeInfo from service.market import Market import gui.display as d import gui.globalEvents as GE -import gui.PFSearchBox as SBox +import gui.builtinMarketBrowser.pfSearchBox as SBox from gui.marketBrowser import SearchBox from gui.bitmapLoader import BitmapLoader