getIconsByMarketGroup() was badly broken. I've added various error checking routines and fixed a runaway memory allocation freeze in the event that no icons can be found. The cause of that problem remains, however. Still working on it.
441 lines
18 KiB
Python
441 lines
18 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/>.
|
|
#===============================================================================
|
|
|
|
import wx
|
|
import service
|
|
import gui.display as d
|
|
from gui.cachingImageList import CachingImageList
|
|
from gui.contextMenu import ContextMenu
|
|
import gui.PFSearchBox as SBox
|
|
|
|
from gui import bitmapLoader
|
|
|
|
ItemSelected, ITEM_SELECTED = wx.lib.newevent.NewEvent()
|
|
|
|
RECENTLY_USED_MODULES = -2
|
|
MAX_RECENTLY_USED_MODULES = 20
|
|
|
|
class MarketBrowser(wx.Panel):
|
|
def __init__(self, parent):
|
|
wx.Panel.__init__(self, parent)
|
|
vbox = wx.BoxSizer(wx.VERTICAL)
|
|
self.SetSizer(vbox)
|
|
|
|
# Add a search box on top
|
|
self.search = SearchBox(self)
|
|
vbox.Add(self.search, 0, wx.EXPAND)
|
|
|
|
self.splitter = wx.SplitterWindow(self, style = wx.SP_LIVE_UPDATE)
|
|
vbox.Add(self.splitter, 1, wx.EXPAND)
|
|
|
|
# Grab market service instance and create child objects
|
|
self.sMarket = service.Market.getInstance()
|
|
self.searchMode = False
|
|
self.marketView = MarketTree(self.splitter, self)
|
|
self.itemView = ItemView(self.splitter, self)
|
|
|
|
self.splitter.SplitHorizontally(self.marketView, self.itemView)
|
|
self.splitter.SetMinimumPaneSize(250)
|
|
|
|
# Setup our buttons for metaGroup selection
|
|
# Same fix as for search box on macs,
|
|
# need some pixels of extra space or everything clips and is ugly
|
|
p = wx.Panel(self)
|
|
box = wx.BoxSizer(wx.HORIZONTAL)
|
|
p.SetSizer(box)
|
|
vbox.Add(p, 0, wx.EXPAND)
|
|
self.metaButtons = []
|
|
for name in self.sMarket.META_MAP.keys():
|
|
btn = wx.ToggleButton(p, wx.ID_ANY, name.capitalize(), style=wx.BU_EXACTFIT)
|
|
setattr(self, name, btn)
|
|
box.Add(btn, 1, wx.ALIGN_CENTER)
|
|
btn.Bind(wx.EVT_TOGGLEBUTTON, self.toggleMetaButton)
|
|
btn.metaName = name
|
|
self.metaButtons.append(btn)
|
|
# Make itemview to set toggles according to list contents
|
|
self.itemView.setToggles()
|
|
|
|
p.SetMinSize((wx.SIZE_AUTO_WIDTH, btn.GetSize()[1] + 5))
|
|
|
|
def toggleMetaButton(self, event):
|
|
"""Process clicks on toggle buttons"""
|
|
ctrl = wx.GetMouseState().CmdDown()
|
|
ebtn = event.EventObject
|
|
if not ctrl:
|
|
for btn in self.metaButtons:
|
|
if btn.Enabled:
|
|
if btn == ebtn:
|
|
btn.SetValue(True)
|
|
else:
|
|
btn.SetValue(False)
|
|
else:
|
|
# Note: using the 'wrong' value for clicked button might seem weird,
|
|
# But the button is toggled by wx and we should deal with it
|
|
activeBtns = set()
|
|
for btn in self.metaButtons:
|
|
if (btn.GetValue() is True and btn != ebtn) or (btn.GetValue() is False and btn == ebtn):
|
|
activeBtns.add(btn)
|
|
# Do 'nothing' if we're trying to turn last active button off
|
|
if len(activeBtns) == 1 and activeBtns.pop() == ebtn:
|
|
# Keep button in the same state
|
|
ebtn.SetValue(True)
|
|
return
|
|
# Leave old unfiltered list contents, just re-filter them and show
|
|
self.itemView.filterItemStore()
|
|
|
|
def jump(self, item):
|
|
self.marketView.jump(item)
|
|
|
|
class SearchBox(SBox.PFSearchBox):
|
|
def __init__(self, parent):
|
|
SBox.PFSearchBox.__init__(self, parent)
|
|
cancelBitmap = bitmapLoader.getBitmap("fit_delete_small","icons")
|
|
searchBitmap = bitmapLoader.getBitmap("fsearch_small","icons")
|
|
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)
|
|
self.root = self.AddRoot("root")
|
|
|
|
self.imageList = CachingImageList(16, 16)
|
|
self.SetImageList(self.imageList)
|
|
|
|
self.sMarket = marketBrowser.sMarket
|
|
self.marketBrowser = marketBrowser
|
|
|
|
# Form market tree root
|
|
sMkt = self.sMarket
|
|
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", "icons")
|
|
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 = "pack"):
|
|
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.sMarket
|
|
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:
|
|
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.sMarket
|
|
mg = sMkt.getMarketGroupByItem(item)
|
|
metaId = sMkt.getMetaGroupIdByItem(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(forcedMetaSelect=metaId)
|
|
|
|
class ItemView(d.Display):
|
|
DEFAULT_COLS = ["Base Icon",
|
|
"Base Name",
|
|
"attr:power,,,True",
|
|
"attr:cpu,,,True"]
|
|
|
|
def __init__(self, parent, marketBrowser):
|
|
d.Display.__init__(self, parent)
|
|
marketBrowser.Bind(wx.EVT_TREE_SEL_CHANGED, self.selectionMade)
|
|
|
|
self.unfilteredStore = set()
|
|
self.filteredStore = set()
|
|
self.recentlyUsedModules = set()
|
|
self.sMarket = marketBrowser.sMarket
|
|
self.searchMode = marketBrowser.searchMode
|
|
|
|
self.marketBrowser = marketBrowser
|
|
self.marketView = marketBrowser.marketView
|
|
|
|
# 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.scheduleSearch)
|
|
|
|
# Make sure WE do interesting stuff too
|
|
self.Bind(wx.EVT_CONTEXT_MENU, self.contextMenu)
|
|
self.Bind(wx.EVT_LIST_ITEM_ACTIVATED, self.itemActivated)
|
|
|
|
# Make reverse map, used by sorter
|
|
self.metaMap = self.makeReverseMetaMap()
|
|
|
|
# Fill up recently used modules set
|
|
for itemID in self.sMarket.serviceMarketRecentlyUsedModules["pyfaMarketRecentlyUsedModules"]:
|
|
self.recentlyUsedModules.add(self.sMarket.getItem(itemID))
|
|
|
|
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.sMarket.serviceMarketRecentlyUsedModules["pyfaMarketRecentlyUsedModules"]:
|
|
self.recentlyUsedModules.add(self.sMarket.getItem(itemID))
|
|
|
|
wx.PostEvent(self.mainFrame, ItemSelected(itemID=self.active[sel].ID))
|
|
|
|
def storeRecentlyUsedMarketItem(self, itemID):
|
|
if len(self.sMarket.serviceMarketRecentlyUsedModules["pyfaMarketRecentlyUsedModules"]) > MAX_RECENTLY_USED_MODULES:
|
|
self.sMarket.serviceMarketRecentlyUsedModules["pyfaMarketRecentlyUsedModules"].pop(0)
|
|
|
|
self.sMarket.serviceMarketRecentlyUsedModules["pyfaMarketRecentlyUsedModules"].append(itemID)
|
|
|
|
def selectionMade(self, event=None, forcedMetaSelect=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.sMarket
|
|
# 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(forcedMetaSelect=forcedMetaSelect)
|
|
else:
|
|
self.marketBrowser.searchMode = True
|
|
self.setToggles()
|
|
|
|
# Update filtered items
|
|
self.filterItemStore()
|
|
|
|
def updateItemStore(self, items):
|
|
self.unfilteredStore = items
|
|
|
|
def filterItemStore(self):
|
|
sMkt = self.sMarket
|
|
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, forcedMetaSelect=None):
|
|
metaIDs = set()
|
|
sMkt = self.sMarket
|
|
for item in self.unfilteredStore:
|
|
metaIDs.add(sMkt.getMetaGroupIdByItem(item))
|
|
anySelection = False
|
|
for btn in self.marketBrowser.metaButtons:
|
|
btnMetas = sMkt.META_MAP[btn.metaName]
|
|
if len(metaIDs.intersection(btnMetas)) > 0:
|
|
btn.Enable(True)
|
|
# Select all available buttons if we're searching
|
|
if self.marketBrowser.searchMode is True:
|
|
btn.SetValue(True)
|
|
# Select explicitly requested button
|
|
if forcedMetaSelect is not None:
|
|
btn.SetValue(True if forcedMetaSelect in btnMetas else False)
|
|
else:
|
|
btn.Enable(False)
|
|
btn.SetValue(False)
|
|
if btn.GetValue():
|
|
anySelection = True
|
|
# If no buttons are pressed, press first active
|
|
if anySelection is False:
|
|
for btn in self.marketBrowser.metaButtons:
|
|
if btn.Enabled:
|
|
btn.SetValue(True)
|
|
break
|
|
|
|
def scheduleSearch(self, event=None):
|
|
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.sMarket.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.sMarket
|
|
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.sMarket
|
|
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 = service.Attribute.getInstance()
|
|
attrs = sAttr.getAttributeInfo("metaLevel")
|
|
sMkt = self.sMarket
|
|
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
|
|
d.Display.populate(self, items)
|
|
|
|
def refresh(self, items):
|
|
if len(items) > 1:
|
|
# Get dictionary with meta level attribute
|
|
sAttr = service.Attribute.getInstance()
|
|
attrs = sAttr.getAttributeInfo("metaLevel")
|
|
sMkt = self.sMarket
|
|
self.metalvls = sMkt.directAttrRequest(items, attrs)
|
|
# Re-sort stuff
|
|
items.sort(key=self.itemSort)
|
|
d.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.sMarket.META_MAP.itervalues():
|
|
for mgid in mgids:
|
|
revmap[mgid] = i
|
|
i += 1
|
|
return revmap
|