diff --git a/gui/builtinItemStatsViews/__init__.py b/gui/builtinItemStatsViews/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/gui/builtinItemStatsViews/helpers.py b/gui/builtinItemStatsViews/helpers.py new file mode 100644 index 000000000..74ceee8cb --- /dev/null +++ b/gui/builtinItemStatsViews/helpers.py @@ -0,0 +1,18 @@ +# noinspection PyPackageRequirements +import wx + +# noinspection PyPackageRequirements +import wx.lib.mixins.listctrl as listmix + + +class AutoListCtrl(wx.ListCtrl, listmix.ListCtrlAutoWidthMixin, listmix.ListRowHighlighter): + def __init__(self, parent, ID, pos=wx.DefaultPosition, size=wx.DefaultSize, style=0): + wx.ListCtrl.__init__(self, parent, ID, pos, size, style) + listmix.ListCtrlAutoWidthMixin.__init__(self) + listmix.ListRowHighlighter.__init__(self) + + +class AutoListCtrlNoHighlight(wx.ListCtrl, listmix.ListCtrlAutoWidthMixin, listmix.ListRowHighlighter): + def __init__(self, parent, ID, pos=wx.DefaultPosition, size=wx.DefaultSize, style=0): + wx.ListCtrl.__init__(self, parent, ID, pos, size, style) + listmix.ListCtrlAutoWidthMixin.__init__(self) diff --git a/gui/builtinItemStatsViews/itemAffectedBy.py b/gui/builtinItemStatsViews/itemAffectedBy.py new file mode 100644 index 000000000..7a852b2ca --- /dev/null +++ b/gui/builtinItemStatsViews/itemAffectedBy.py @@ -0,0 +1,447 @@ +# noinspection PyPackageRequirements +import wx + +from eos.saveddata.mode import Mode +from eos.saveddata.character import Skill +from eos.saveddata.implant import Implant +from eos.saveddata.booster import Booster +from eos.saveddata.drone import Drone +from eos.saveddata.fighter import Fighter +from eos.saveddata.module import Module +from eos.saveddata.ship import Ship +from eos.saveddata.citadel import Citadel +from eos.saveddata.fit import Fit + +import gui.mainFrame +from gui.contextMenu import ContextMenu +from gui.bitmapLoader import BitmapLoader + + +class ItemAffectedBy(wx.Panel): + ORDER = [Fit, Ship, Citadel, Mode, Module, Drone, Fighter, Implant, Booster, Skill] + + def __init__(self, parent, stuff, item): + wx.Panel.__init__(self, parent) + self.stuff = stuff + self.item = item + + self.activeFit = gui.mainFrame.MainFrame.getInstance().getActiveFit() + + self.showRealNames = False + self.showAttrView = False + self.expand = -1 + + self.treeItems = [] + + mainSizer = wx.BoxSizer(wx.VERTICAL) + + self.affectedBy = wx.TreeCtrl(self, style=wx.TR_DEFAULT_STYLE | wx.TR_HIDE_ROOT | wx.NO_BORDER) + mainSizer.Add(self.affectedBy, 1, wx.ALL | wx.EXPAND, 0) + + self.m_staticline = wx.StaticLine(self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.LI_HORIZONTAL) + + mainSizer.Add(self.m_staticline, 0, wx.EXPAND) + bSizer = wx.BoxSizer(wx.HORIZONTAL) + + self.toggleExpandBtn = wx.ToggleButton(self, wx.ID_ANY, u"Expand All", wx.DefaultPosition, wx.DefaultSize, 0) + bSizer.Add(self.toggleExpandBtn, 0, wx.ALIGN_CENTER_VERTICAL) + + self.toggleNameBtn = wx.ToggleButton(self, wx.ID_ANY, u"Toggle Names", wx.DefaultPosition, wx.DefaultSize, 0) + bSizer.Add(self.toggleNameBtn, 0, wx.ALIGN_CENTER_VERTICAL) + + self.toggleViewBtn = wx.ToggleButton(self, wx.ID_ANY, u"Toggle View", wx.DefaultPosition, wx.DefaultSize, 0) + bSizer.Add(self.toggleViewBtn, 0, wx.ALIGN_CENTER_VERTICAL) + + if stuff is not None: + self.refreshBtn = wx.Button(self, wx.ID_ANY, u"Refresh", wx.DefaultPosition, wx.DefaultSize, wx.BU_EXACTFIT) + bSizer.Add(self.refreshBtn, 0, wx.ALIGN_CENTER_VERTICAL) + self.refreshBtn.Bind(wx.EVT_BUTTON, self.RefreshTree) + + self.toggleNameBtn.Bind(wx.EVT_TOGGLEBUTTON, self.ToggleNameMode) + self.toggleExpandBtn.Bind(wx.EVT_TOGGLEBUTTON, self.ToggleExpand) + self.toggleViewBtn.Bind(wx.EVT_TOGGLEBUTTON, self.ToggleViewMode) + + mainSizer.Add(bSizer, 0, wx.ALIGN_RIGHT) + self.SetSizer(mainSizer) + self.PopulateTree() + self.Layout() + self.affectedBy.Bind(wx.EVT_TREE_ITEM_RIGHT_CLICK, self.scheduleMenu) + + def scheduleMenu(self, event): + event.Skip() + wx.CallAfter(self.spawnMenu, event.Item) + + def spawnMenu(self, item): + self.affectedBy.SelectItem(item) + + stuff = self.affectedBy.GetPyData(item) + # String is set as data when we are dealing with attributes, not stuff containers + if stuff is None or isinstance(stuff, basestring): + return + contexts = [] + + # Skills are different in that they don't have itemModifiedAttributes, + # which is needed if we send the container to itemStats dialog. So + # instead, we send the item. + type_ = stuff.__class__.__name__ + contexts.append(("itemStats", type_)) + menu = ContextMenu.getMenu(stuff if type_ != "Skill" else stuff.item, *contexts) + self.PopupMenu(menu) + + def ExpandCollapseTree(self): + + self.Freeze() + if self.expand == 1: + self.affectedBy.ExpandAll() + else: + try: + self.affectedBy.CollapseAll() + except: + pass + + self.Thaw() + + def ToggleExpand(self, event): + self.expand *= -1 + self.ExpandCollapseTree() + + def ToggleViewTree(self): + self.Freeze() + + for item in self.treeItems: + change = self.affectedBy.GetPyData(item) + display = self.affectedBy.GetItemText(item) + self.affectedBy.SetItemText(item, change) + self.affectedBy.SetPyData(item, display) + + self.Thaw() + + def UpdateTree(self): + self.Freeze() + self.affectedBy.DeleteAllItems() + self.PopulateTree() + self.Thaw() + + def RefreshTree(self, event): + self.UpdateTree() + event.Skip() + + def ToggleViewMode(self, event): + self.showAttrView = not self.showAttrView + self.affectedBy.DeleteAllItems() + self.PopulateTree() + event.Skip() + + def ToggleNameMode(self, event): + self.showRealNames = not self.showRealNames + self.ToggleViewTree() + event.Skip() + + def PopulateTree(self): + # sheri was here + del self.treeItems[:] + root = self.affectedBy.AddRoot("WINPWNZ0R") + self.affectedBy.SetPyData(root, None) + + self.imageList = wx.ImageList(16, 16) + self.affectedBy.SetImageList(self.imageList) + + if self.showAttrView: + self.buildAttributeView(root) + else: + self.buildModuleView(root) + + self.ExpandCollapseTree() + + def sortAttrDisplayName(self, attr): + info = self.stuff.item.attributes.get(attr) + if info and info.displayName != "": + return info.displayName + + return attr + + def buildAttributeView(self, root): + """ + We first build a usable dictionary of items. The key is either a fit + if the afflictions stem from a projected fit, or self.stuff if they + are local afflictions (everything else, even gang boosts at this time) + The value of this is yet another dictionary in the following format: + + "attribute name": { + "Module Name": [ + class of affliction, + affliction item (required due to GH issue #335) + modifier type + amount of modification + whether this affliction was projected + ] + } + """ + + attributes = self.stuff.itemModifiedAttributes if self.item == self.stuff.item else self.stuff.chargeModifiedAttributes + container = {} + for attrName in attributes.iterAfflictions(): + # if value is 0 or there has been no change from original to modified, return + if attributes[attrName] == (attributes.getOriginal(attrName, 0)): + continue + + for fit, afflictors in attributes.getAfflictions(attrName).iteritems(): + for afflictor, modifier, amount, used in afflictors: + + if not used or afflictor.item is None: + continue + + if fit.ID != self.activeFit: + # affliction fit does not match our fit + if fit not in container: + container[fit] = {} + items = container[fit] + else: + # local afflictions + if self.stuff not in container: + container[self.stuff] = {} + items = container[self.stuff] + + # items hold our module: info mappings + if attrName not in items: + items[attrName] = [] + + if afflictor == self.stuff and getattr(afflictor, 'charge', None): + # we are showing a charges modifications, see #335 + item = afflictor.charge + else: + item = afflictor.item + + items[attrName].append( + (type(afflictor), afflictor, item, modifier, amount, getattr(afflictor, "projected", False))) + + # Make sure projected fits are on top + rootOrder = container.keys() + rootOrder.sort(key=lambda x: self.ORDER.index(type(x))) + + # Now, we take our created dictionary and start adding stuff to our tree + for thing in rootOrder: + # This block simply directs which parent we are adding to (root or projected fit) + if thing == self.stuff: + parent = root + else: # projected fit + icon = self.imageList.Add(BitmapLoader.getBitmap("ship_small", "gui")) + child = self.affectedBy.AppendItem(root, "{} ({})".format(thing.name, thing.ship.item.name), icon) + parent = child + + attributes = container[thing] + attrOrder = sorted(attributes.keys(), key=self.sortAttrDisplayName) + + for attrName in attrOrder: + attrInfo = self.stuff.item.attributes.get(attrName) + displayName = attrInfo.displayName if attrInfo and attrInfo.displayName != "" else attrName + + if attrInfo: + if attrInfo.icon is not None: + iconFile = attrInfo.icon.iconFile + icon = BitmapLoader.getBitmap(iconFile, "icons") + if icon is None: + icon = BitmapLoader.getBitmap("transparent16x16", "gui") + attrIcon = self.imageList.Add(icon) + else: + attrIcon = self.imageList.Add(BitmapLoader.getBitmap("7_15", "icons")) + else: + attrIcon = self.imageList.Add(BitmapLoader.getBitmap("7_15", "icons")) + + if self.showRealNames: + display = attrName + saved = displayName + else: + display = displayName + saved = attrName + + # this is the attribute node + child = self.affectedBy.AppendItem(parent, display, attrIcon) + self.affectedBy.SetPyData(child, saved) + self.treeItems.append(child) + + items = attributes[attrName] + items.sort(key=lambda x: self.ORDER.index(x[0])) + for itemInfo in items: + afflictorType, afflictor, item, attrModifier, attrAmount, projected = itemInfo + + if afflictorType == Ship: + itemIcon = self.imageList.Add(BitmapLoader.getBitmap("ship_small", "gui")) + elif item.icon: + bitmap = BitmapLoader.getBitmap(item.icon.iconFile, "icons") + itemIcon = self.imageList.Add(bitmap) if bitmap else -1 + else: + itemIcon = -1 + + displayStr = item.name + + if projected: + displayStr += " (projected)" + + penalized = "" + if '*' in attrModifier: + if 's' in attrModifier: + penalized += "(penalized)" + if 'r' in attrModifier: + penalized += "(resisted)" + attrModifier = "*" + + # this is the Module node, the attribute will be attached to this + display = "%s %s %.2f %s" % (displayStr, attrModifier, attrAmount, penalized) + treeItem = self.affectedBy.AppendItem(child, display, itemIcon) + self.affectedBy.SetPyData(treeItem, afflictor) + + def buildModuleView(self, root): + """ + We first build a usable dictionary of items. The key is either a fit + if the afflictions stem from a projected fit, or self.stuff if they + are local afflictions (everything else, even gang boosts at this time) + The value of this is yet another dictionary in the following format: + + "Module Name": [ + class of affliction, + set of afflictors (such as 2 of the same module), + info on affliction (attribute name, modifier, and modification amount), + item that will be used to determine icon (required due to GH issue #335) + whether this affliction is actually used (unlearned skills are not used) + ] + """ + + attributes = self.stuff.itemModifiedAttributes if self.item == self.stuff.item else self.stuff.chargeModifiedAttributes + container = {} + for attrName in attributes.iterAfflictions(): + # if value is 0 or there has been no change from original to modified, return + if attributes[attrName] == (attributes.getOriginal(attrName, 0)): + continue + + for fit, afflictors in attributes.getAfflictions(attrName).iteritems(): + for afflictor, modifier, amount, used in afflictors: + if not used or getattr(afflictor, 'item', None) is None: + continue + + if fit.ID != self.activeFit: + # affliction fit does not match our fit + if fit not in container: + container[fit] = {} + items = container[fit] + else: + # local afflictions + if self.stuff not in container: + container[self.stuff] = {} + items = container[self.stuff] + + if afflictor == self.stuff and getattr(afflictor, 'charge', None): + # we are showing a charges modifications, see #335 + item = afflictor.charge + else: + item = afflictor.item + + # items hold our module: info mappings + if item.name not in items: + items[item.name] = [type(afflictor), set(), [], item, getattr(afflictor, "projected", False)] + + info = items[item.name] + info[1].add(afflictor) + # If info[1] > 1, there are two separate modules working. + # Check to make sure we only include the modifier once + # See GH issue 154 + if len(info[1]) > 1 and (attrName, modifier, amount) in info[2]: + continue + info[2].append((attrName, modifier, amount)) + + # Make sure projected fits are on top + rootOrder = container.keys() + rootOrder.sort(key=lambda x: self.ORDER.index(type(x))) + + # Now, we take our created dictionary and start adding stuff to our tree + for thing in rootOrder: + # This block simply directs which parent we are adding to (root or projected fit) + if thing == self.stuff: + parent = root + else: # projected fit + icon = self.imageList.Add(BitmapLoader.getBitmap("ship_small", "gui")) + child = self.affectedBy.AppendItem(root, "{} ({})".format(thing.name, thing.ship.item.name), icon) + parent = child + + items = container[thing] + order = items.keys() + order.sort(key=lambda x: (self.ORDER.index(items[x][0]), x)) + + for itemName in order: + info = items[itemName] + afflictorType, afflictors, attrData, item, projected = info + counter = len(afflictors) + if afflictorType == Ship: + itemIcon = self.imageList.Add(BitmapLoader.getBitmap("ship_small", "gui")) + elif item.icon: + bitmap = BitmapLoader.getBitmap(item.icon.iconFile, "icons") + itemIcon = self.imageList.Add(bitmap) if bitmap else -1 + else: + itemIcon = -1 + + displayStr = itemName + + if counter > 1: + displayStr += " x {}".format(counter) + + if projected: + displayStr += " (projected)" + + # this is the Module node, the attribute will be attached to this + child = self.affectedBy.AppendItem(parent, displayStr, itemIcon) + self.affectedBy.SetPyData(child, afflictors.pop()) + + if counter > 0: + attributes = [] + for attrName, attrModifier, attrAmount in attrData: + attrInfo = self.stuff.item.attributes.get(attrName) + displayName = attrInfo.displayName if attrInfo else "" + + if attrInfo: + if attrInfo.icon is not None: + iconFile = attrInfo.icon.iconFile + icon = BitmapLoader.getBitmap(iconFile, "icons") + if icon is None: + icon = BitmapLoader.getBitmap("transparent16x16", "gui") + + attrIcon = self.imageList.Add(icon) + else: + attrIcon = self.imageList.Add(BitmapLoader.getBitmap("7_15", "icons")) + else: + attrIcon = self.imageList.Add(BitmapLoader.getBitmap("7_15", "icons")) + + penalized = "" + if '*' in attrModifier: + if 's' in attrModifier: + penalized += "(penalized)" + if 'r' in attrModifier: + penalized += "(resisted)" + attrModifier = "*" + + attributes.append((attrName, (displayName if displayName != "" else attrName), attrModifier, + attrAmount, penalized, attrIcon)) + + attrSorted = sorted(attributes, key=lambda attribName: attribName[0]) + for attr in attrSorted: + attrName, displayName, attrModifier, attrAmount, penalized, attrIcon = attr + + if self.showRealNames: + display = "%s %s %.2f %s" % (attrName, attrModifier, attrAmount, penalized) + saved = "%s %s %.2f %s" % ( + displayName if displayName != "" else attrName, + attrModifier, + attrAmount, + penalized + ) + else: + display = "%s %s %.2f %s" % ( + displayName if displayName != "" else attrName, + attrModifier, + attrAmount, + penalized + ) + saved = "%s %s %.2f %s" % (attrName, attrModifier, attrAmount, penalized) + + treeitem = self.affectedBy.AppendItem(child, display, attrIcon) + self.affectedBy.SetPyData(treeitem, saved) + self.treeItems.append(treeitem) \ No newline at end of file diff --git a/gui/builtinItemStatsViews/itemAttributes.py b/gui/builtinItemStatsViews/itemAttributes.py new file mode 100644 index 000000000..1e1804d1d --- /dev/null +++ b/gui/builtinItemStatsViews/itemAttributes.py @@ -0,0 +1,273 @@ +import sys +import csv +import config + +# noinspection PyPackageRequirements +import wx + +from helpers import AutoListCtrl + +from gui.bitmapLoader import BitmapLoader +from service.market import Market +from service.attribute import Attribute +from gui.utils.numberFormatter import formatAmount + + +class ItemParams(wx.Panel): + def __init__(self, parent, stuff, item, context=None): + wx.Panel.__init__(self, parent) + mainSizer = wx.BoxSizer(wx.VERTICAL) + + self.paramList = AutoListCtrl(self, wx.ID_ANY, + style=wx.LC_REPORT | wx.LC_SINGLE_SEL | wx.LC_VRULES | wx.NO_BORDER) + mainSizer.Add(self.paramList, 1, wx.ALL | wx.EXPAND, 0) + self.SetSizer(mainSizer) + + self.toggleView = 1 + self.stuff = stuff + self.item = item + self.attrInfo = {} + self.attrValues = {} + self._fetchValues() + + self.m_staticline = wx.StaticLine(self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.LI_HORIZONTAL) + mainSizer.Add(self.m_staticline, 0, wx.EXPAND) + bSizer = wx.BoxSizer(wx.HORIZONTAL) + + self.totalAttrsLabel = wx.StaticText(self, wx.ID_ANY, u" ", wx.DefaultPosition, wx.DefaultSize, 0) + bSizer.Add(self.totalAttrsLabel, 0, wx.ALIGN_CENTER_VERTICAL | wx.RIGHT) + + self.toggleViewBtn = wx.ToggleButton(self, wx.ID_ANY, u"Toggle view mode", wx.DefaultPosition, wx.DefaultSize, + 0) + bSizer.Add(self.toggleViewBtn, 0, wx.ALIGN_CENTER_VERTICAL) + + self.exportStatsBtn = wx.ToggleButton(self, wx.ID_ANY, u"Export Item Stats", wx.DefaultPosition, wx.DefaultSize, + 0) + bSizer.Add(self.exportStatsBtn, 0, wx.ALIGN_CENTER_VERTICAL) + + if stuff is not None: + self.refreshBtn = wx.Button(self, wx.ID_ANY, u"Refresh", wx.DefaultPosition, wx.DefaultSize, wx.BU_EXACTFIT) + bSizer.Add(self.refreshBtn, 0, wx.ALIGN_CENTER_VERTICAL) + self.refreshBtn.Bind(wx.EVT_BUTTON, self.RefreshValues) + + mainSizer.Add(bSizer, 0, wx.ALIGN_RIGHT) + + self.PopulateList() + + self.toggleViewBtn.Bind(wx.EVT_TOGGLEBUTTON, self.ToggleViewMode) + self.exportStatsBtn.Bind(wx.EVT_TOGGLEBUTTON, self.ExportItemStats) + + def _fetchValues(self): + if self.stuff is None: + self.attrInfo.clear() + self.attrValues.clear() + self.attrInfo.update(self.item.attributes) + self.attrValues.update(self.item.attributes) + elif self.stuff.item == self.item: + self.attrInfo.clear() + self.attrValues.clear() + self.attrInfo.update(self.stuff.item.attributes) + self.attrValues.update(self.stuff.itemModifiedAttributes) + elif self.stuff.charge == self.item: + self.attrInfo.clear() + self.attrValues.clear() + self.attrInfo.update(self.stuff.charge.attributes) + self.attrValues.update(self.stuff.chargeModifiedAttributes) + # When item for stats window no longer exists, don't change anything + else: + return + + def UpdateList(self): + self.Freeze() + self.paramList.ClearAll() + self.PopulateList() + self.Thaw() + self.paramList.resizeLastColumn(100) + + def RefreshValues(self, event): + self._fetchValues() + self.UpdateList() + event.Skip() + + def ToggleViewMode(self, event): + self.toggleView *= -1 + self.UpdateList() + event.Skip() + + def ExportItemStats(self, event): + exportFileName = self.item.name + " (" + str(self.item.ID) + ").csv" + + saveFileDialog = wx.FileDialog(self, "Save CSV file", "", exportFileName, + "CSV files (*.csv)|*.csv", wx.FD_SAVE | wx.FD_OVERWRITE_PROMPT) + + if saveFileDialog.ShowModal() == wx.ID_CANCEL: + return # the user hit cancel... + + with open(saveFileDialog.GetPath(), "wb") as exportFile: + writer = csv.writer(exportFile, delimiter=',') + + writer.writerow( + [ + "ID", + "Internal Name", + "Friendly Name", + "Modified Value", + "Base Value", + ] + ) + + for attribute in self.attrValues: + + try: + attribute_id = self.attrInfo[attribute].ID + except (KeyError, AttributeError): + attribute_id = '' + + try: + attribute_name = self.attrInfo[attribute].name + except (KeyError, AttributeError): + attribute_name = attribute + + try: + attribute_displayname = self.attrInfo[attribute].displayName + except (KeyError, AttributeError): + attribute_displayname = '' + + try: + attribute_value = self.attrInfo[attribute].value + except (KeyError, AttributeError): + attribute_value = '' + + try: + attribute_modified_value = self.attrValues[attribute].value + except (KeyError, AttributeError): + attribute_modified_value = self.attrValues[attribute] + + writer.writerow( + [ + attribute_id, + attribute_name, + attribute_displayname, + attribute_modified_value, + attribute_value, + ] + ) + + def PopulateList(self): + self.paramList.InsertColumn(0, "Attribute") + self.paramList.InsertColumn(1, "Current Value") + if self.stuff is not None: + self.paramList.InsertColumn(2, "Base Value") + self.paramList.SetColumnWidth(0, 110) + self.paramList.SetColumnWidth(1, 90) + if self.stuff is not None: + self.paramList.SetColumnWidth(2, 90) + self.paramList.setResizeColumn(0) + self.imageList = wx.ImageList(16, 16) + self.paramList.SetImageList(self.imageList, wx.IMAGE_LIST_SMALL) + + names = list(self.attrValues.iterkeys()) + names.sort() + + idNameMap = {} + idCount = 0 + for name in names: + info = self.attrInfo.get(name) + att = self.attrValues[name] + + valDefault = getattr(info, "value", None) + valueDefault = valDefault if valDefault is not None else att + + val = getattr(att, "value", None) + value = val if val is not None else att + + if info and info.displayName and self.toggleView == 1: + attrName = info.displayName + else: + attrName = name + + if info and config.debug: + attrName += " ({})".format(info.ID) + + if info: + if info.icon is not None: + iconFile = info.icon.iconFile + icon = BitmapLoader.getBitmap(iconFile, "icons") + + if icon is None: + icon = BitmapLoader.getBitmap("transparent16x16", "gui") + + attrIcon = self.imageList.Add(icon) + else: + attrIcon = self.imageList.Add(BitmapLoader.getBitmap("7_15", "icons")) + else: + attrIcon = self.imageList.Add(BitmapLoader.getBitmap("7_15", "icons")) + + index = self.paramList.InsertImageStringItem(sys.maxint, attrName, attrIcon) + idNameMap[idCount] = attrName + self.paramList.SetItemData(index, idCount) + idCount += 1 + + if self.toggleView != 1: + valueUnit = str(value) + elif info and info.unit: + valueUnit = self.TranslateValueUnit(value, info.unit.displayName, info.unit.name) + else: + valueUnit = formatAmount(value, 3, 0, 0) + + if self.toggleView != 1: + valueUnitDefault = str(valueDefault) + elif info and info.unit: + valueUnitDefault = self.TranslateValueUnit(valueDefault, info.unit.displayName, info.unit.name) + else: + valueUnitDefault = formatAmount(valueDefault, 3, 0, 0) + + self.paramList.SetStringItem(index, 1, valueUnit) + if self.stuff is not None: + self.paramList.SetStringItem(index, 2, valueUnitDefault) + + self.paramList.SortItems(lambda id1, id2: cmp(idNameMap[id1], idNameMap[id2])) + self.paramList.RefreshRows() + self.totalAttrsLabel.SetLabel("%d attributes. " % idCount) + self.Layout() + + @staticmethod + def TranslateValueUnit(value, unitName, unitDisplayName): + def itemIDCallback(): + item = Market.getInstance().getItem(value) + return "%s (%d)" % (item.name, value) if item is not None else str(value) + + def groupIDCallback(): + group = Market.getInstance().getGroup(value) + return "%s (%d)" % (group.name, value) if group is not None else str(value) + + def attributeIDCallback(): + attribute = Attribute.getInstance().getAttributeInfo(value) + return "%s (%d)" % (attribute.name.capitalize(), value) + + trans = { + "Inverse Absolute Percent" : (lambda: (1 - value) * 100, unitName), + "Inversed Modifier Percent": (lambda: (1 - value) * 100, unitName), + "Modifier Percent" : ( + lambda: ("%+.2f" if ((value - 1) * 100) % 1 else "%+d") % ((value - 1) * 100), unitName), + "Volume" : (lambda: value, u"m\u00B3"), + "Sizeclass" : (lambda: value, ""), + "Absolute Percent" : (lambda: (value * 100), unitName), + "Milliseconds" : (lambda: value / 1000.0, unitName), + "typeID" : (itemIDCallback, ""), + "groupID" : (groupIDCallback, ""), + "attributeID" : (attributeIDCallback, "") + } + + override = trans.get(unitDisplayName) + if override is not None: + v = override[0]() + if isinstance(v, str): + fvalue = v + elif isinstance(v, (int, float, long)): + fvalue = formatAmount(v, 3, 0, 0) + else: + fvalue = v + return "%s %s" % (fvalue, override[1]) + else: + return "%s %s" % (formatAmount(value, 3, 0), unitName) diff --git a/gui/builtinItemStatsViews/itemCompare.py b/gui/builtinItemStatsViews/itemCompare.py new file mode 100644 index 000000000..7690bdc78 --- /dev/null +++ b/gui/builtinItemStatsViews/itemCompare.py @@ -0,0 +1,207 @@ +import sys + +# noinspection PyPackageRequirements +import wx + +from helpers import AutoListCtrl +from service.price import Price as ServicePrice +from service.market import Market +from service.attribute import Attribute +from gui.utils.numberFormatter import formatAmount + +class ItemCompare(wx.Panel): + def __init__(self, parent, stuff, item, items, context=None): + # Start dealing with Price stuff to get that thread going + sPrice = ServicePrice.getInstance() + sPrice.getPrices(items, self.UpdateList) + + wx.Panel.__init__(self, parent) + mainSizer = wx.BoxSizer(wx.VERTICAL) + + self.paramList = AutoListCtrl(self, wx.ID_ANY, + style=wx.LC_REPORT | wx.LC_SINGLE_SEL | wx.LC_VRULES | wx.NO_BORDER) + mainSizer.Add(self.paramList, 1, wx.ALL | wx.EXPAND, 0) + self.SetSizer(mainSizer) + + self.toggleView = 1 + self.stuff = stuff + self.currentSort = None + self.sortReverse = False + self.item = item + self.items = sorted(items, + key=lambda x: x.attributes['metaLevel'].value if 'metaLevel' in x.attributes else None) + self.attrs = {} + + # get a dict of attrName: attrInfo of all unique attributes across all items + for item in self.items: + for attr in item.attributes.keys(): + if item.attributes[attr].info.displayName: + self.attrs[attr] = item.attributes[attr].info + + # Process attributes for items and find ones that differ + for attr in self.attrs.keys(): + value = None + + for item in self.items: + # we can automatically break here if this item doesn't have the attribute, + # as that means at least one item did + if attr not in item.attributes: + break + + # this is the first attribute for the item set, set the initial value + if value is None: + value = item.attributes[attr].value + continue + + if attr not in item.attributes or item.attributes[attr].value != value: + break + else: + # attribute values were all the same, delete + del self.attrs[attr] + + self.m_staticline = wx.StaticLine(self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, + wx.LI_HORIZONTAL) + mainSizer.Add(self.m_staticline, 0, wx.EXPAND) + bSizer = wx.BoxSizer(wx.HORIZONTAL) + + self.totalAttrsLabel = wx.StaticText(self, wx.ID_ANY, u" ", wx.DefaultPosition, wx.DefaultSize, 0) + bSizer.Add(self.totalAttrsLabel, 0, wx.ALIGN_CENTER_VERTICAL | wx.RIGHT) + + self.toggleViewBtn = wx.ToggleButton(self, wx.ID_ANY, u"Toggle view mode", wx.DefaultPosition, + wx.DefaultSize, 0) + bSizer.Add(self.toggleViewBtn, 0, wx.ALIGN_CENTER_VERTICAL) + + self.refreshBtn = wx.Button(self, wx.ID_ANY, u"Refresh", wx.DefaultPosition, wx.DefaultSize, + wx.BU_EXACTFIT) + bSizer.Add(self.refreshBtn, 0, wx.ALIGN_CENTER_VERTICAL) + self.refreshBtn.Bind(wx.EVT_BUTTON, self.RefreshValues) + + mainSizer.Add(bSizer, 0, wx.ALIGN_RIGHT) + + self.PopulateList() + + self.toggleViewBtn.Bind(wx.EVT_TOGGLEBUTTON, self.ToggleViewMode) + self.Bind(wx.EVT_LIST_COL_CLICK, self.SortCompareCols) + + def SortCompareCols(self, event): + self.Freeze() + self.paramList.ClearAll() + self.PopulateList(event.Column) + self.Thaw() + + def UpdateList(self, items=None): + # We do nothing with `items`, but it gets returned by the price service thread + self.Freeze() + self.paramList.ClearAll() + self.PopulateList() + self.Thaw() + self.paramList.resizeLastColumn(100) + + def RefreshValues(self, event): + self.UpdateList() + event.Skip() + + def ToggleViewMode(self, event): + self.toggleView *= -1 + self.UpdateList() + event.Skip() + + def processPrices(self, prices): + for i, price in enumerate(prices): + self.paramList.SetStringItem(i, len(self.attrs) + 1, formatAmount(price.value, 3, 3, 9, currency=True)) + + def PopulateList(self, sort=None): + + if sort is not None and self.currentSort == sort: + self.sortReverse = not self.sortReverse + else: + self.currentSort = sort + self.sortReverse = False + + if sort is not None: + if sort == 0: # Name sort + func = lambda _val: _val.name + else: + try: + # Remember to reduce by 1, because the attrs array + # starts at 0 while the list has the item name as column 0. + attr = str(self.attrs.keys()[sort - 1]) + func = lambda _val: _val.attributes[attr].value if attr in _val.attributes else None + except IndexError: + # Clicked on a column that's not part of our array (price most likely) + self.sortReverse = False + func = lambda _val: _val.attributes['metaLevel'].value if 'metaLevel' in _val.attributes else None + + self.items = sorted(self.items, key=func, reverse=self.sortReverse) + + self.paramList.InsertColumn(0, "Item") + self.paramList.SetColumnWidth(0, 200) + + for i, attr in enumerate(self.attrs.keys()): + name = self.attrs[attr].displayName if self.attrs[attr].displayName else attr + self.paramList.InsertColumn(i + 1, name) + self.paramList.SetColumnWidth(i + 1, 120) + + self.paramList.InsertColumn(len(self.attrs) + 1, "Price") + self.paramList.SetColumnWidth(len(self.attrs) + 1, 60) + + for item in self.items: + i = self.paramList.InsertStringItem(sys.maxint, item.name) + for x, attr in enumerate(self.attrs.keys()): + if attr in item.attributes: + info = self.attrs[attr] + value = item.attributes[attr].value + if self.toggleView != 1: + valueUnit = str(value) + elif info and info.unit and self.toggleView == 1: + valueUnit = self.TranslateValueUnit(value, info.unit.displayName, info.unit.name) + else: + valueUnit = formatAmount(value, 3, 0, 0) + + self.paramList.SetStringItem(i, x + 1, valueUnit) + + # Add prices + self.paramList.SetStringItem(i, len(self.attrs) + 1, formatAmount(item.price.price, 3, 3, 9, currency=True)) + + self.paramList.RefreshRows() + self.Layout() + + @staticmethod + def TranslateValueUnit(value, unitName, unitDisplayName): + def itemIDCallback(): + item = Market.getInstance().getItem(value) + return "%s (%d)" % (item.name, value) if item is not None else str(value) + + def groupIDCallback(): + group = Market.getInstance().getGroup(value) + return "%s (%d)" % (group.name, value) if group is not None else str(value) + + def attributeIDCallback(): + attribute = Attribute.getInstance().getAttributeInfo(value) + return "%s (%d)" % (attribute.name.capitalize(), value) + + trans = { + "Inverse Absolute Percent" : (lambda: (1 - value) * 100, unitName), + "Inversed Modifier Percent": (lambda: (1 - value) * 100, unitName), + "Modifier Percent" : (lambda: ("%+.2f" if ((value - 1) * 100) % 1 else "%+d") % ((value - 1) * 100), unitName), + "Volume" : (lambda: value, u"m\u00B3"), + "Sizeclass" : (lambda: value, ""), + "Absolute Percent" : (lambda: (value * 100), unitName), + "Milliseconds" : (lambda: value / 1000.0, unitName), + "typeID" : (itemIDCallback, ""), + "groupID" : (groupIDCallback, ""), + "attributeID" : (attributeIDCallback, "") + } + + override = trans.get(unitDisplayName) + if override is not None: + v = override[0]() + if isinstance(v, str): + fvalue = v + elif isinstance(v, (int, float, long)): + fvalue = formatAmount(v, 3, 0, 0) + else: + fvalue = v + return "%s %s" % (fvalue, override[1]) + else: + return "%s %s" % (formatAmount(value, 3, 0), unitName) diff --git a/gui/builtinItemStatsViews/itemDependants.py b/gui/builtinItemStatsViews/itemDependants.py new file mode 100644 index 000000000..3a8110b55 --- /dev/null +++ b/gui/builtinItemStatsViews/itemDependants.py @@ -0,0 +1,53 @@ +# noinspection PyPackageRequirements +import wx + +from gui.bitmapLoader import BitmapLoader + + +class ItemDependents(wx.Panel): + def __init__(self, parent, stuff, item): + wx.Panel.__init__(self, parent, style=wx.TAB_TRAVERSAL) + + # itemId is set by the parent. + self.romanNb = ["0", "I", "II", "III", "IV", "V", "VI", "VII", "VIII", "IX", "X"] + self.skillIdHistory = [] + mainSizer = wx.BoxSizer(wx.VERTICAL) + + self.reqTree = wx.TreeCtrl(self, style=wx.TR_DEFAULT_STYLE | wx.TR_HIDE_ROOT | wx.NO_BORDER) + + mainSizer.Add(self.reqTree, 1, wx.ALL | wx.EXPAND, 0) + + self.SetSizer(mainSizer) + self.root = self.reqTree.AddRoot("WINRARZOR") + self.reqTree.SetPyData(self.root, None) + + self.imageList = wx.ImageList(16, 16) + self.reqTree.SetImageList(self.imageList) + skillBookId = self.imageList.Add(BitmapLoader.getBitmap("skill_small", "gui")) + + self.getFullSkillTree(item, self.root, skillBookId) + + self.Layout() + + def getFullSkillTree(self, parentSkill, parent, sbIconId): + levelToItems = {} + + for item, level in parentSkill.requiredFor.iteritems(): + if level not in levelToItems: + levelToItems[level] = [] + levelToItems[level].append(item) + + for x in sorted(levelToItems.keys()): + items = levelToItems[x] + items.sort(key=lambda x: x.name) + + child = self.reqTree.AppendItem(parent, "Level {}".format(self.romanNb[int(x)]), sbIconId) + for item in items: + + if item.icon: + bitmap = BitmapLoader.getBitmap(item.icon.iconFile, "icons") + itemIcon = self.imageList.Add(bitmap) if bitmap else -1 + else: + itemIcon = -1 + + self.reqTree.AppendItem(child, "{}".format(item.name), itemIcon) diff --git a/gui/builtinItemStatsViews/itemDescription.py b/gui/builtinItemStatsViews/itemDescription.py new file mode 100644 index 000000000..c94bd2b66 --- /dev/null +++ b/gui/builtinItemStatsViews/itemDescription.py @@ -0,0 +1,33 @@ +# noinspection PyPackageRequirements +import wx +# noinspection PyPackageRequirements +import wx.html +import re + + +class ItemDescription(wx.Panel): + def __init__(self, parent, stuff, item): + wx.Panel.__init__(self, parent) + mainSizer = wx.BoxSizer(wx.VERTICAL) + self.SetSizer(mainSizer) + + bgcolor = wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW) + fgcolor = wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOWTEXT) + + self.description = wx.html.HtmlWindow(self) + + if not item.description: + return + + desc = item.description.replace("\n", "
") + # Strip font tags + desc = re.sub("<( *)font( *)color( *)=(.*?)>(?P.*?)<( *)/( *)font( *)>", "\g", desc) + # Strip URLs + desc = re.sub("<( *)a(.*?)>(?P.*?)<( *)/( *)a( *)>", "\g", desc) + desc = "" + desc + "" + + self.description.SetPage(desc) + + mainSizer.Add(self.description, 1, wx.ALL | wx.EXPAND, 0) + self.Layout() diff --git a/gui/builtinItemStatsViews/itemEffects.py b/gui/builtinItemStatsViews/itemEffects.py new file mode 100644 index 000000000..ae4caacbb --- /dev/null +++ b/gui/builtinItemStatsViews/itemEffects.py @@ -0,0 +1,130 @@ +import sys +import os +import subprocess +import config + +# noinspection PyPackageRequirements +import wx + +from helpers import AutoListCtrl + + +class ItemEffects(wx.Panel): + def __init__(self, parent, stuff, item): + wx.Panel.__init__(self, parent) + self.item = item + + mainSizer = wx.BoxSizer(wx.VERTICAL) + + self.effectList = AutoListCtrl(self, wx.ID_ANY, + style=wx.LC_REPORT | wx.LC_SINGLE_SEL | wx.LC_VRULES | wx.NO_BORDER) + mainSizer.Add(self.effectList, 1, wx.ALL | wx.EXPAND, 0) + self.SetSizer(mainSizer) + + self.Bind(wx.EVT_LIST_ITEM_ACTIVATED, self.OnClick, self.effectList) + if config.debug: + self.Bind(wx.EVT_LIST_ITEM_RIGHT_CLICK, self.OnRightClick, self.effectList) + + self.PopulateList() + + def PopulateList(self): + + self.effectList.InsertColumn(0, "Name") + self.effectList.InsertColumn(1, "Active") + self.effectList.InsertColumn(2, "Type") + if config.debug: + self.effectList.InsertColumn(3, "Run Time") + self.effectList.InsertColumn(4, "ID") + + # self.effectList.SetColumnWidth(0,385) + + self.effectList.setResizeColumn(0) + self.effectList.SetColumnWidth(1, 50) + self.effectList.SetColumnWidth(2, 80) + if config.debug: + self.effectList.SetColumnWidth(3, 65) + self.effectList.SetColumnWidth(4, 40) + + item = self.item + effects = item.effects + names = list(effects.iterkeys()) + names.sort() + + for name in names: + index = self.effectList.InsertStringItem(sys.maxint, name) + + if effects[name].isImplemented: + if effects[name].activeByDefault: + activeByDefault = "Yes" + else: + activeByDefault = "No" + else: + activeByDefault = "" + + effectTypeText = "" + if effects[name].type: + for effectType in effects[name].type: + effectTypeText += effectType + " " + pass + + if effects[name].runTime and effects[name].isImplemented: + effectRunTime = str(effects[name].runTime) + else: + effectRunTime = "" + + self.effectList.SetStringItem(index, 1, activeByDefault) + self.effectList.SetStringItem(index, 2, effectTypeText) + if config.debug: + self.effectList.SetStringItem(index, 3, effectRunTime) + self.effectList.SetStringItem(index, 4, str(effects[name].ID)) + + self.effectList.RefreshRows() + self.Layout() + + def OnClick(self, event): + """ + Debug use: toggle effects on/off. + Affects *ALL* items that use that effect. + Is not stateful. Will reset if Pyfa is closed and reopened. + """ + + try: + activeByDefault = getattr(self.item.effects[event.GetText()], "activeByDefault") + if activeByDefault: + setattr(self.item.effects[event.GetText()], "activeByDefault", False) + else: + setattr(self.item.effects[event.GetText()], "activeByDefault", True) + + except AttributeError: + # Attribute doesn't exist, do nothing + pass + + self.RefreshValues(event) + + @staticmethod + def OnRightClick(event): + """ + Debug use: open effect file with default application. + If effect file does not exist, create it + """ + + file_ = os.path.join(config.pyfaPath, "eos", "effects", "%s.py" % event.GetText().lower()) + + if not os.path.isfile(file_): + open(file_, 'a').close() + + if 'wxMSW' in wx.PlatformInfo: + os.startfile(file_) + elif 'wxMac' in wx.PlatformInfo: + os.system("open " + file_) + else: + subprocess.call(["xdg-open", file_]) + + def RefreshValues(self, event): + self.Freeze() + self.effectList.ClearAll() + self.PopulateList() + self.effectList.RefreshRows() + self.Layout() + self.Thaw() + event.Skip() \ No newline at end of file diff --git a/gui/builtinItemStatsViews/itemProperties.py b/gui/builtinItemStatsViews/itemProperties.py new file mode 100644 index 000000000..b8ce86dd5 --- /dev/null +++ b/gui/builtinItemStatsViews/itemProperties.py @@ -0,0 +1,99 @@ +import sys + +# noinspection PyPackageRequirements +import wx + +from helpers import AutoListCtrl + + +class ItemProperties(wx.Panel): + def __init__(self, parent, stuff, item, context=None): + wx.Panel.__init__(self, parent) + mainSizer = wx.BoxSizer(wx.VERTICAL) + + self.paramList = AutoListCtrl(self, wx.ID_ANY, + style=wx.LC_REPORT | wx.LC_SINGLE_SEL | wx.LC_VRULES | wx.NO_BORDER) + mainSizer.Add(self.paramList, 1, wx.ALL | wx.EXPAND, 0) + self.SetSizer(mainSizer) + + self.toggleView = 1 + self.stuff = stuff + self.item = item + self.attrInfo = {} + self.attrValues = {} + self._fetchValues() + + self.m_staticline = wx.StaticLine(self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.LI_HORIZONTAL) + mainSizer.Add(self.m_staticline, 0, wx.EXPAND) + bSizer = wx.BoxSizer(wx.HORIZONTAL) + + self.totalAttrsLabel = wx.StaticText(self, wx.ID_ANY, u" ", wx.DefaultPosition, wx.DefaultSize, 0) + bSizer.Add(self.totalAttrsLabel, 0, wx.ALIGN_CENTER_VERTICAL | wx.RIGHT) + + mainSizer.Add(bSizer, 0, wx.ALIGN_RIGHT) + + self.PopulateList() + + def _fetchValues(self): + if self.stuff is None: + self.attrInfo.clear() + self.attrValues.clear() + self.attrInfo.update(self.item.attributes) + self.attrValues.update(self.item.attributes) + elif self.stuff.item == self.item: + self.attrInfo.clear() + self.attrValues.clear() + self.attrInfo.update(self.stuff.item.attributes) + self.attrValues.update(self.stuff.itemModifiedAttributes) + elif self.stuff.charge == self.item: + self.attrInfo.clear() + self.attrValues.clear() + self.attrInfo.update(self.stuff.charge.attributes) + self.attrValues.update(self.stuff.chargeModifiedAttributes) + # When item for stats window no longer exists, don't change anything + else: + return + + def PopulateList(self): + self.paramList.InsertColumn(0, "Attribute") + self.paramList.InsertColumn(1, "Current Value") + self.paramList.SetColumnWidth(0, 110) + self.paramList.SetColumnWidth(1, 1500) + self.paramList.setResizeColumn(0) + + if self.stuff: + names = dir(self.stuff) + else: + names = dir(self.item) + + names = [a for a in names if not (a.startswith('__') and a.endswith('__'))] + + idNameMap = {} + idCount = 0 + for name in names: + try: + if self.stuff: + attrName = name.title() + value = getattr(self.stuff, name) + else: + attrName = name.title() + value = getattr(self.item, name) + + index = self.paramList.InsertStringItem(sys.maxint, attrName) + # index = self.paramList.InsertImageStringItem(sys.maxint, attrName) + idNameMap[idCount] = attrName + self.paramList.SetItemData(index, idCount) + idCount += 1 + + valueUnit = str(value) + + self.paramList.SetStringItem(index, 1, valueUnit) + except: + # TODO: Add logging to this. + # We couldn't get a property for some reason. Skip it for now. + continue + + self.paramList.SortItems(lambda id1, id2: cmp(idNameMap[id1], idNameMap[id2])) + self.paramList.RefreshRows() + self.totalAttrsLabel.SetLabel("%d attributes. " % idCount) + self.Layout() diff --git a/gui/builtinItemStatsViews/itemRequirements.py b/gui/builtinItemStatsViews/itemRequirements.py new file mode 100644 index 000000000..ff493bcab --- /dev/null +++ b/gui/builtinItemStatsViews/itemRequirements.py @@ -0,0 +1,39 @@ +# noinspection PyPackageRequirements +import wx + +from gui.bitmapLoader import BitmapLoader + + +class ItemRequirements(wx.Panel): + def __init__(self, parent, stuff, item): + wx.Panel.__init__(self, parent, style=wx.TAB_TRAVERSAL) + + # itemId is set by the parent. + self.romanNb = ["0", "I", "II", "III", "IV", "V", "VI", "VII", "VIII", "IX", "X"] + self.skillIdHistory = [] + mainSizer = wx.BoxSizer(wx.VERTICAL) + + self.reqTree = wx.TreeCtrl(self, style=wx.TR_DEFAULT_STYLE | wx.TR_HIDE_ROOT | wx.NO_BORDER) + + mainSizer.Add(self.reqTree, 1, wx.ALL | wx.EXPAND, 0) + + self.SetSizer(mainSizer) + self.root = self.reqTree.AddRoot("WINRARZOR") + self.reqTree.SetPyData(self.root, None) + + self.imageList = wx.ImageList(16, 16) + self.reqTree.SetImageList(self.imageList) + skillBookId = self.imageList.Add(BitmapLoader.getBitmap("skill_small", "gui")) + + self.getFullSkillTree(item, self.root, skillBookId) + + self.reqTree.ExpandAll() + + self.Layout() + + def getFullSkillTree(self, parentSkill, parent, sbIconId): + for skill, level in parentSkill.requiredSkills.iteritems(): + child = self.reqTree.AppendItem(parent, "%s %s" % (skill.name, self.romanNb[int(level)]), sbIconId) + if skill.ID not in self.skillIdHistory: + self.getFullSkillTree(skill, child, sbIconId) + self.skillIdHistory.append(skill.ID) diff --git a/gui/builtinItemStatsViews/itemTraits.py b/gui/builtinItemStatsViews/itemTraits.py new file mode 100644 index 000000000..ef6a4283b --- /dev/null +++ b/gui/builtinItemStatsViews/itemTraits.py @@ -0,0 +1,17 @@ +# noinspection PyPackageRequirements +import wx +# noinspection PyPackageRequirements +import wx.html + + +class ItemTraits(wx.Panel): + def __init__(self, parent, stuff, item): + wx.Panel.__init__(self, parent) + mainSizer = wx.BoxSizer(wx.VERTICAL) + self.SetSizer(mainSizer) + + self.traits = wx.html.HtmlWindow(self) + self.traits.SetPage(item.traits.traitText) + + mainSizer.Add(self.traits, 1, wx.ALL | wx.EXPAND, 0) + self.Layout() \ No newline at end of file diff --git a/gui/itemStats.py b/gui/itemStats.py index e8f4c85a5..7a2fd178e 100644 --- a/gui/itemStats.py +++ b/gui/itemStats.py @@ -17,38 +17,23 @@ # along with pyfa. If not, see . # ============================================================================= -import re -import os -import csv -import sys -import subprocess - # noinspection PyPackageRequirements import wx -# noinspection PyPackageRequirements -import wx.html -# noinspection PyPackageRequirements -import wx.lib.mixins.listctrl as listmix import config -from eos.saveddata.mode import Mode -from eos.saveddata.character import Skill -from eos.saveddata.implant import Implant -from eos.saveddata.booster import Booster -from eos.saveddata.drone import Drone -from eos.saveddata.fighter import Fighter -from eos.saveddata.module import Module -from eos.saveddata.ship import Ship -from eos.saveddata.citadel import Citadel -from eos.saveddata.fit import Fit from service.market import Market -from service.attribute import Attribute -from service.price import Price as ServicePrice import gui.mainFrame from gui.bitmapLoader import BitmapLoader -from gui.utils.numberFormatter import formatAmount -from gui.contextMenu import ContextMenu +from gui.builtinItemStatsViews.itemTraits import ItemTraits +from gui.builtinItemStatsViews.itemDescription import ItemDescription +from gui.builtinItemStatsViews.itemAttributes import ItemParams +from gui.builtinItemStatsViews.itemCompare import ItemCompare +from gui.builtinItemStatsViews.itemRequirements import ItemRequirements +from gui.builtinItemStatsViews.itemDependants import ItemDependents +from gui.builtinItemStatsViews.itemEffects import ItemEffects +from gui.builtinItemStatsViews.itemAffectedBy import ItemAffectedBy +from gui.builtinItemStatsViews.itemProperties import ItemProperties class ItemStatsDialog(wx.Dialog): counter = 0 @@ -217,1243 +202,3 @@ class ItemStatsContainer(wx.Panel): tab, _ = self.nbContainer.HitTest(event.Position) if tab != -1: self.nbContainer.SetSelection(tab) - - -class AutoListCtrl(wx.ListCtrl, listmix.ListCtrlAutoWidthMixin, listmix.ListRowHighlighter): - def __init__(self, parent, ID, pos=wx.DefaultPosition, size=wx.DefaultSize, style=0): - wx.ListCtrl.__init__(self, parent, ID, pos, size, style) - listmix.ListCtrlAutoWidthMixin.__init__(self) - listmix.ListRowHighlighter.__init__(self) - - -class AutoListCtrlNoHighlight(wx.ListCtrl, listmix.ListCtrlAutoWidthMixin, listmix.ListRowHighlighter): - def __init__(self, parent, ID, pos=wx.DefaultPosition, size=wx.DefaultSize, style=0): - wx.ListCtrl.__init__(self, parent, ID, pos, size, style) - listmix.ListCtrlAutoWidthMixin.__init__(self) - - -class ItemTraits(wx.Panel): - def __init__(self, parent, stuff, item): - wx.Panel.__init__(self, parent) - mainSizer = wx.BoxSizer(wx.VERTICAL) - self.SetSizer(mainSizer) - - self.traits = wx.html.HtmlWindow(self) - self.traits.SetPage(item.traits.traitText) - - mainSizer.Add(self.traits, 1, wx.ALL | wx.EXPAND, 0) - self.Layout() - - -class ItemDescription(wx.Panel): - def __init__(self, parent, stuff, item): - wx.Panel.__init__(self, parent) - mainSizer = wx.BoxSizer(wx.VERTICAL) - self.SetSizer(mainSizer) - - bgcolor = wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW) - fgcolor = wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOWTEXT) - - self.description = wx.html.HtmlWindow(self) - - if not item.description: - return - - desc = item.description.replace("\n", "
") - # Strip font tags - desc = re.sub("<( *)font( *)color( *)=(.*?)>(?P.*?)<( *)/( *)font( *)>", "\g", desc) - # Strip URLs - desc = re.sub("<( *)a(.*?)>(?P.*?)<( *)/( *)a( *)>", "\g", desc) - desc = "" + desc + "" - - self.description.SetPage(desc) - - mainSizer.Add(self.description, 1, wx.ALL | wx.EXPAND, 0) - self.Layout() - - -class ItemParams(wx.Panel): - def __init__(self, parent, stuff, item, context=None): - wx.Panel.__init__(self, parent) - mainSizer = wx.BoxSizer(wx.VERTICAL) - - self.paramList = AutoListCtrl(self, wx.ID_ANY, - style=wx.LC_REPORT | wx.LC_SINGLE_SEL | wx.LC_VRULES | wx.NO_BORDER) - mainSizer.Add(self.paramList, 1, wx.ALL | wx.EXPAND, 0) - self.SetSizer(mainSizer) - - self.toggleView = 1 - self.stuff = stuff - self.item = item - self.attrInfo = {} - self.attrValues = {} - self._fetchValues() - - self.m_staticline = wx.StaticLine(self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.LI_HORIZONTAL) - mainSizer.Add(self.m_staticline, 0, wx.EXPAND) - bSizer = wx.BoxSizer(wx.HORIZONTAL) - - self.totalAttrsLabel = wx.StaticText(self, wx.ID_ANY, u" ", wx.DefaultPosition, wx.DefaultSize, 0) - bSizer.Add(self.totalAttrsLabel, 0, wx.ALIGN_CENTER_VERTICAL | wx.RIGHT) - - self.toggleViewBtn = wx.ToggleButton(self, wx.ID_ANY, u"Toggle view mode", wx.DefaultPosition, wx.DefaultSize, - 0) - bSizer.Add(self.toggleViewBtn, 0, wx.ALIGN_CENTER_VERTICAL) - - self.exportStatsBtn = wx.ToggleButton(self, wx.ID_ANY, u"Export Item Stats", wx.DefaultPosition, wx.DefaultSize, - 0) - bSizer.Add(self.exportStatsBtn, 0, wx.ALIGN_CENTER_VERTICAL) - - if stuff is not None: - self.refreshBtn = wx.Button(self, wx.ID_ANY, u"Refresh", wx.DefaultPosition, wx.DefaultSize, wx.BU_EXACTFIT) - bSizer.Add(self.refreshBtn, 0, wx.ALIGN_CENTER_VERTICAL) - self.refreshBtn.Bind(wx.EVT_BUTTON, self.RefreshValues) - - mainSizer.Add(bSizer, 0, wx.ALIGN_RIGHT) - - self.PopulateList() - - self.toggleViewBtn.Bind(wx.EVT_TOGGLEBUTTON, self.ToggleViewMode) - self.exportStatsBtn.Bind(wx.EVT_TOGGLEBUTTON, self.ExportItemStats) - - def _fetchValues(self): - if self.stuff is None: - self.attrInfo.clear() - self.attrValues.clear() - self.attrInfo.update(self.item.attributes) - self.attrValues.update(self.item.attributes) - elif self.stuff.item == self.item: - self.attrInfo.clear() - self.attrValues.clear() - self.attrInfo.update(self.stuff.item.attributes) - self.attrValues.update(self.stuff.itemModifiedAttributes) - elif self.stuff.charge == self.item: - self.attrInfo.clear() - self.attrValues.clear() - self.attrInfo.update(self.stuff.charge.attributes) - self.attrValues.update(self.stuff.chargeModifiedAttributes) - # When item for stats window no longer exists, don't change anything - else: - return - - def UpdateList(self): - self.Freeze() - self.paramList.ClearAll() - self.PopulateList() - self.Thaw() - self.paramList.resizeLastColumn(100) - - def RefreshValues(self, event): - self._fetchValues() - self.UpdateList() - event.Skip() - - def ToggleViewMode(self, event): - self.toggleView *= -1 - self.UpdateList() - event.Skip() - - def ExportItemStats(self, event): - exportFileName = self.item.name + " (" + str(self.item.ID) + ").csv" - - saveFileDialog = wx.FileDialog(self, "Save CSV file", "", exportFileName, - "CSV files (*.csv)|*.csv", wx.FD_SAVE | wx.FD_OVERWRITE_PROMPT) - - if saveFileDialog.ShowModal() == wx.ID_CANCEL: - return # the user hit cancel... - - with open(saveFileDialog.GetPath(), "wb") as exportFile: - writer = csv.writer(exportFile, delimiter=',') - - writer.writerow( - [ - "ID", - "Internal Name", - "Friendly Name", - "Modified Value", - "Base Value", - ] - ) - - for attribute in self.attrValues: - - try: - attribute_id = self.attrInfo[attribute].ID - except (KeyError, AttributeError): - attribute_id = '' - - try: - attribute_name = self.attrInfo[attribute].name - except (KeyError, AttributeError): - attribute_name = attribute - - try: - attribute_displayname = self.attrInfo[attribute].displayName - except (KeyError, AttributeError): - attribute_displayname = '' - - try: - attribute_value = self.attrInfo[attribute].value - except (KeyError, AttributeError): - attribute_value = '' - - try: - attribute_modified_value = self.attrValues[attribute].value - except (KeyError, AttributeError): - attribute_modified_value = self.attrValues[attribute] - - writer.writerow( - [ - attribute_id, - attribute_name, - attribute_displayname, - attribute_modified_value, - attribute_value, - ] - ) - - def PopulateList(self): - self.paramList.InsertColumn(0, "Attribute") - self.paramList.InsertColumn(1, "Current Value") - if self.stuff is not None: - self.paramList.InsertColumn(2, "Base Value") - self.paramList.SetColumnWidth(0, 110) - self.paramList.SetColumnWidth(1, 90) - if self.stuff is not None: - self.paramList.SetColumnWidth(2, 90) - self.paramList.setResizeColumn(0) - self.imageList = wx.ImageList(16, 16) - self.paramList.SetImageList(self.imageList, wx.IMAGE_LIST_SMALL) - - names = list(self.attrValues.iterkeys()) - names.sort() - - idNameMap = {} - idCount = 0 - for name in names: - info = self.attrInfo.get(name) - att = self.attrValues[name] - - valDefault = getattr(info, "value", None) - valueDefault = valDefault if valDefault is not None else att - - val = getattr(att, "value", None) - value = val if val is not None else att - - if info and info.displayName and self.toggleView == 1: - attrName = info.displayName - else: - attrName = name - - if info and config.debug: - attrName += " ({})".format(info.ID) - - if info: - if info.icon is not None: - iconFile = info.icon.iconFile - icon = BitmapLoader.getBitmap(iconFile, "icons") - - if icon is None: - icon = BitmapLoader.getBitmap("transparent16x16", "gui") - - attrIcon = self.imageList.Add(icon) - else: - attrIcon = self.imageList.Add(BitmapLoader.getBitmap("7_15", "icons")) - else: - attrIcon = self.imageList.Add(BitmapLoader.getBitmap("7_15", "icons")) - - index = self.paramList.InsertImageStringItem(sys.maxint, attrName, attrIcon) - idNameMap[idCount] = attrName - self.paramList.SetItemData(index, idCount) - idCount += 1 - - if self.toggleView != 1: - valueUnit = str(value) - elif info and info.unit: - valueUnit = self.TranslateValueUnit(value, info.unit.displayName, info.unit.name) - else: - valueUnit = formatAmount(value, 3, 0, 0) - - if self.toggleView != 1: - valueUnitDefault = str(valueDefault) - elif info and info.unit: - valueUnitDefault = self.TranslateValueUnit(valueDefault, info.unit.displayName, info.unit.name) - else: - valueUnitDefault = formatAmount(valueDefault, 3, 0, 0) - - self.paramList.SetStringItem(index, 1, valueUnit) - if self.stuff is not None: - self.paramList.SetStringItem(index, 2, valueUnitDefault) - - self.paramList.SortItems(lambda id1, id2: cmp(idNameMap[id1], idNameMap[id2])) - self.paramList.RefreshRows() - self.totalAttrsLabel.SetLabel("%d attributes. " % idCount) - self.Layout() - - @staticmethod - def TranslateValueUnit(value, unitName, unitDisplayName): - def itemIDCallback(): - item = Market.getInstance().getItem(value) - return "%s (%d)" % (item.name, value) if item is not None else str(value) - - def groupIDCallback(): - group = Market.getInstance().getGroup(value) - return "%s (%d)" % (group.name, value) if group is not None else str(value) - - def attributeIDCallback(): - attribute = Attribute.getInstance().getAttributeInfo(value) - return "%s (%d)" % (attribute.name.capitalize(), value) - - trans = { - "Inverse Absolute Percent" : (lambda: (1 - value) * 100, unitName), - "Inversed Modifier Percent": (lambda: (1 - value) * 100, unitName), - "Modifier Percent" : ( - lambda: ("%+.2f" if ((value - 1) * 100) % 1 else "%+d") % ((value - 1) * 100), unitName), - "Volume" : (lambda: value, u"m\u00B3"), - "Sizeclass" : (lambda: value, ""), - "Absolute Percent" : (lambda: (value * 100), unitName), - "Milliseconds" : (lambda: value / 1000.0, unitName), - "typeID" : (itemIDCallback, ""), - "groupID" : (groupIDCallback, ""), - "attributeID" : (attributeIDCallback, "") - } - - override = trans.get(unitDisplayName) - if override is not None: - v = override[0]() - if isinstance(v, str): - fvalue = v - elif isinstance(v, (int, float, long)): - fvalue = formatAmount(v, 3, 0, 0) - else: - fvalue = v - return "%s %s" % (fvalue, override[1]) - else: - return "%s %s" % (formatAmount(value, 3, 0), unitName) - - -class ItemCompare(wx.Panel): - def __init__(self, parent, stuff, item, items, context=None): - # Start dealing with Price stuff to get that thread going - sPrice = ServicePrice.getInstance() - sPrice.getPrices(items, self.UpdateList) - - wx.Panel.__init__(self, parent) - mainSizer = wx.BoxSizer(wx.VERTICAL) - - self.paramList = AutoListCtrl(self, wx.ID_ANY, - style=wx.LC_REPORT | wx.LC_SINGLE_SEL | wx.LC_VRULES | wx.NO_BORDER) - mainSizer.Add(self.paramList, 1, wx.ALL | wx.EXPAND, 0) - self.SetSizer(mainSizer) - - self.toggleView = 1 - self.stuff = stuff - self.currentSort = None - self.sortReverse = False - self.item = item - self.items = sorted(items, - key=lambda x: x.attributes['metaLevel'].value if 'metaLevel' in x.attributes else None) - self.attrs = {} - - # get a dict of attrName: attrInfo of all unique attributes across all items - for item in self.items: - for attr in item.attributes.keys(): - if item.attributes[attr].info.displayName: - self.attrs[attr] = item.attributes[attr].info - - # Process attributes for items and find ones that differ - for attr in self.attrs.keys(): - value = None - - for item in self.items: - # we can automatically break here if this item doesn't have the attribute, - # as that means at least one item did - if attr not in item.attributes: - break - - # this is the first attribute for the item set, set the initial value - if value is None: - value = item.attributes[attr].value - continue - - if attr not in item.attributes or item.attributes[attr].value != value: - break - else: - # attribute values were all the same, delete - del self.attrs[attr] - - self.m_staticline = wx.StaticLine(self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, - wx.LI_HORIZONTAL) - mainSizer.Add(self.m_staticline, 0, wx.EXPAND) - bSizer = wx.BoxSizer(wx.HORIZONTAL) - - self.totalAttrsLabel = wx.StaticText(self, wx.ID_ANY, u" ", wx.DefaultPosition, wx.DefaultSize, 0) - bSizer.Add(self.totalAttrsLabel, 0, wx.ALIGN_CENTER_VERTICAL | wx.RIGHT) - - self.toggleViewBtn = wx.ToggleButton(self, wx.ID_ANY, u"Toggle view mode", wx.DefaultPosition, - wx.DefaultSize, 0) - bSizer.Add(self.toggleViewBtn, 0, wx.ALIGN_CENTER_VERTICAL) - - self.refreshBtn = wx.Button(self, wx.ID_ANY, u"Refresh", wx.DefaultPosition, wx.DefaultSize, - wx.BU_EXACTFIT) - bSizer.Add(self.refreshBtn, 0, wx.ALIGN_CENTER_VERTICAL) - self.refreshBtn.Bind(wx.EVT_BUTTON, self.RefreshValues) - - mainSizer.Add(bSizer, 0, wx.ALIGN_RIGHT) - - self.PopulateList() - - self.toggleViewBtn.Bind(wx.EVT_TOGGLEBUTTON, self.ToggleViewMode) - self.Bind(wx.EVT_LIST_COL_CLICK, self.SortCompareCols) - - def SortCompareCols(self, event): - self.Freeze() - self.paramList.ClearAll() - self.PopulateList(event.Column) - self.Thaw() - - def UpdateList(self, items=None): - # We do nothing with `items`, but it gets returned by the price service thread - self.Freeze() - self.paramList.ClearAll() - self.PopulateList() - self.Thaw() - self.paramList.resizeLastColumn(100) - - def RefreshValues(self, event): - self.UpdateList() - event.Skip() - - def ToggleViewMode(self, event): - self.toggleView *= -1 - self.UpdateList() - event.Skip() - - def processPrices(self, prices): - for i, price in enumerate(prices): - self.paramList.SetStringItem(i, len(self.attrs) + 1, formatAmount(price.value, 3, 3, 9, currency=True)) - - def PopulateList(self, sort=None): - - if sort is not None and self.currentSort == sort: - self.sortReverse = not self.sortReverse - else: - self.currentSort = sort - self.sortReverse = False - - if sort is not None: - if sort == 0: # Name sort - func = lambda _val: _val.name - else: - try: - # Remember to reduce by 1, because the attrs array - # starts at 0 while the list has the item name as column 0. - attr = str(self.attrs.keys()[sort - 1]) - func = lambda _val: _val.attributes[attr].value if attr in _val.attributes else None - except IndexError: - # Clicked on a column that's not part of our array (price most likely) - self.sortReverse = False - func = lambda _val: _val.attributes['metaLevel'].value if 'metaLevel' in _val.attributes else None - - self.items = sorted(self.items, key=func, reverse=self.sortReverse) - - self.paramList.InsertColumn(0, "Item") - self.paramList.SetColumnWidth(0, 200) - - for i, attr in enumerate(self.attrs.keys()): - name = self.attrs[attr].displayName if self.attrs[attr].displayName else attr - self.paramList.InsertColumn(i + 1, name) - self.paramList.SetColumnWidth(i + 1, 120) - - self.paramList.InsertColumn(len(self.attrs) + 1, "Price") - self.paramList.SetColumnWidth(len(self.attrs) + 1, 60) - - for item in self.items: - i = self.paramList.InsertStringItem(sys.maxint, item.name) - for x, attr in enumerate(self.attrs.keys()): - if attr in item.attributes: - info = self.attrs[attr] - value = item.attributes[attr].value - if self.toggleView != 1: - valueUnit = str(value) - elif info and info.unit and self.toggleView == 1: - valueUnit = self.TranslateValueUnit(value, info.unit.displayName, info.unit.name) - else: - valueUnit = formatAmount(value, 3, 0, 0) - - self.paramList.SetStringItem(i, x + 1, valueUnit) - - # Add prices - self.paramList.SetStringItem(i, len(self.attrs) + 1, formatAmount(item.price.price, 3, 3, 9, currency=True)) - - self.paramList.RefreshRows() - self.Layout() - - @staticmethod - def TranslateValueUnit(value, unitName, unitDisplayName): - def itemIDCallback(): - item = Market.getInstance().getItem(value) - return "%s (%d)" % (item.name, value) if item is not None else str(value) - - def groupIDCallback(): - group = Market.getInstance().getGroup(value) - return "%s (%d)" % (group.name, value) if group is not None else str(value) - - def attributeIDCallback(): - attribute = Attribute.getInstance().getAttributeInfo(value) - return "%s (%d)" % (attribute.name.capitalize(), value) - - trans = { - "Inverse Absolute Percent" : (lambda: (1 - value) * 100, unitName), - "Inversed Modifier Percent": (lambda: (1 - value) * 100, unitName), - "Modifier Percent" : (lambda: ("%+.2f" if ((value - 1) * 100) % 1 else "%+d") % ((value - 1) * 100), unitName), - "Volume" : (lambda: value, u"m\u00B3"), - "Sizeclass" : (lambda: value, ""), - "Absolute Percent" : (lambda: (value * 100), unitName), - "Milliseconds" : (lambda: value / 1000.0, unitName), - "typeID" : (itemIDCallback, ""), - "groupID" : (groupIDCallback, ""), - "attributeID" : (attributeIDCallback, "") - } - - override = trans.get(unitDisplayName) - if override is not None: - v = override[0]() - if isinstance(v, str): - fvalue = v - elif isinstance(v, (int, float, long)): - fvalue = formatAmount(v, 3, 0, 0) - else: - fvalue = v - return "%s %s" % (fvalue, override[1]) - else: - return "%s %s" % (formatAmount(value, 3, 0), unitName) - - -class ItemRequirements(wx.Panel): - def __init__(self, parent, stuff, item): - wx.Panel.__init__(self, parent, style=wx.TAB_TRAVERSAL) - - # itemId is set by the parent. - self.romanNb = ["0", "I", "II", "III", "IV", "V", "VI", "VII", "VIII", "IX", "X"] - self.skillIdHistory = [] - mainSizer = wx.BoxSizer(wx.VERTICAL) - - self.reqTree = wx.TreeCtrl(self, style=wx.TR_DEFAULT_STYLE | wx.TR_HIDE_ROOT | wx.NO_BORDER) - - mainSizer.Add(self.reqTree, 1, wx.ALL | wx.EXPAND, 0) - - self.SetSizer(mainSizer) - self.root = self.reqTree.AddRoot("WINRARZOR") - self.reqTree.SetPyData(self.root, None) - - self.imageList = wx.ImageList(16, 16) - self.reqTree.SetImageList(self.imageList) - skillBookId = self.imageList.Add(BitmapLoader.getBitmap("skill_small", "gui")) - - self.getFullSkillTree(item, self.root, skillBookId) - - self.reqTree.ExpandAll() - - self.Layout() - - def getFullSkillTree(self, parentSkill, parent, sbIconId): - for skill, level in parentSkill.requiredSkills.iteritems(): - child = self.reqTree.AppendItem(parent, "%s %s" % (skill.name, self.romanNb[int(level)]), sbIconId) - if skill.ID not in self.skillIdHistory: - self.getFullSkillTree(skill, child, sbIconId) - self.skillIdHistory.append(skill.ID) - - -class ItemDependents(wx.Panel): - def __init__(self, parent, stuff, item): - wx.Panel.__init__(self, parent, style=wx.TAB_TRAVERSAL) - - # itemId is set by the parent. - self.romanNb = ["0", "I", "II", "III", "IV", "V", "VI", "VII", "VIII", "IX", "X"] - self.skillIdHistory = [] - mainSizer = wx.BoxSizer(wx.VERTICAL) - - self.reqTree = wx.TreeCtrl(self, style=wx.TR_DEFAULT_STYLE | wx.TR_HIDE_ROOT | wx.NO_BORDER) - - mainSizer.Add(self.reqTree, 1, wx.ALL | wx.EXPAND, 0) - - self.SetSizer(mainSizer) - self.root = self.reqTree.AddRoot("WINRARZOR") - self.reqTree.SetPyData(self.root, None) - - self.imageList = wx.ImageList(16, 16) - self.reqTree.SetImageList(self.imageList) - skillBookId = self.imageList.Add(BitmapLoader.getBitmap("skill_small", "gui")) - - self.getFullSkillTree(item, self.root, skillBookId) - - self.Layout() - - def getFullSkillTree(self, parentSkill, parent, sbIconId): - levelToItems = {} - - for item, level in parentSkill.requiredFor.iteritems(): - if level not in levelToItems: - levelToItems[level] = [] - levelToItems[level].append(item) - - for x in sorted(levelToItems.keys()): - items = levelToItems[x] - items.sort(key=lambda x: x.name) - - child = self.reqTree.AppendItem(parent, "Level {}".format(self.romanNb[int(x)]), sbIconId) - for item in items: - - if item.icon: - bitmap = BitmapLoader.getBitmap(item.icon.iconFile, "icons") - itemIcon = self.imageList.Add(bitmap) if bitmap else -1 - else: - itemIcon = -1 - - self.reqTree.AppendItem(child, "{}".format(item.name), itemIcon) - - -class ItemEffects(wx.Panel): - def __init__(self, parent, stuff, item): - wx.Panel.__init__(self, parent) - self.item = item - - mainSizer = wx.BoxSizer(wx.VERTICAL) - - self.effectList = AutoListCtrl(self, wx.ID_ANY, - style=wx.LC_REPORT | wx.LC_SINGLE_SEL | wx.LC_VRULES | wx.NO_BORDER) - mainSizer.Add(self.effectList, 1, wx.ALL | wx.EXPAND, 0) - self.SetSizer(mainSizer) - - self.Bind(wx.EVT_LIST_ITEM_ACTIVATED, self.OnClick, self.effectList) - if config.debug: - self.Bind(wx.EVT_LIST_ITEM_RIGHT_CLICK, self.OnRightClick, self.effectList) - - self.PopulateList() - - def PopulateList(self): - - self.effectList.InsertColumn(0, "Name") - self.effectList.InsertColumn(1, "Active") - self.effectList.InsertColumn(2, "Type") - if config.debug: - self.effectList.InsertColumn(3, "Run Time") - self.effectList.InsertColumn(4, "ID") - - # self.effectList.SetColumnWidth(0,385) - - self.effectList.setResizeColumn(0) - self.effectList.SetColumnWidth(1, 50) - self.effectList.SetColumnWidth(2, 80) - if config.debug: - self.effectList.SetColumnWidth(3, 65) - self.effectList.SetColumnWidth(4, 40) - - item = self.item - effects = item.effects - names = list(effects.iterkeys()) - names.sort() - - for name in names: - index = self.effectList.InsertStringItem(sys.maxint, name) - - if effects[name].isImplemented: - if effects[name].activeByDefault: - activeByDefault = "Yes" - else: - activeByDefault = "No" - else: - activeByDefault = "" - - effectTypeText = "" - if effects[name].type: - for effectType in effects[name].type: - effectTypeText += effectType + " " - pass - - if effects[name].runTime and effects[name].isImplemented: - effectRunTime = str(effects[name].runTime) - else: - effectRunTime = "" - - self.effectList.SetStringItem(index, 1, activeByDefault) - self.effectList.SetStringItem(index, 2, effectTypeText) - if config.debug: - self.effectList.SetStringItem(index, 3, effectRunTime) - self.effectList.SetStringItem(index, 4, str(effects[name].ID)) - - self.effectList.RefreshRows() - self.Layout() - - def OnClick(self, event): - """ - Debug use: toggle effects on/off. - Affects *ALL* items that use that effect. - Is not stateful. Will reset if Pyfa is closed and reopened. - """ - - try: - activeByDefault = getattr(self.item.effects[event.GetText()], "activeByDefault") - if activeByDefault: - setattr(self.item.effects[event.GetText()], "activeByDefault", False) - else: - setattr(self.item.effects[event.GetText()], "activeByDefault", True) - - except AttributeError: - # Attribute doesn't exist, do nothing - pass - - self.RefreshValues(event) - - @staticmethod - def OnRightClick(event): - """ - Debug use: open effect file with default application. - If effect file does not exist, create it - """ - - file_ = os.path.join(config.pyfaPath, "eos", "effects", "%s.py" % event.GetText().lower()) - - if not os.path.isfile(file_): - open(file_, 'a').close() - - if 'wxMSW' in wx.PlatformInfo: - os.startfile(file_) - elif 'wxMac' in wx.PlatformInfo: - os.system("open " + file_) - else: - subprocess.call(["xdg-open", file_]) - - def RefreshValues(self, event): - self.Freeze() - self.effectList.ClearAll() - self.PopulateList() - self.effectList.RefreshRows() - self.Layout() - self.Thaw() - event.Skip() - - -class ItemAffectedBy(wx.Panel): - ORDER = [Fit, Ship, Citadel, Mode, Module, Drone, Fighter, Implant, Booster, Skill] - - def __init__(self, parent, stuff, item): - wx.Panel.__init__(self, parent) - self.stuff = stuff - self.item = item - - self.activeFit = gui.mainFrame.MainFrame.getInstance().getActiveFit() - - self.showRealNames = False - self.showAttrView = False - self.expand = -1 - - self.treeItems = [] - - mainSizer = wx.BoxSizer(wx.VERTICAL) - - self.affectedBy = wx.TreeCtrl(self, style=wx.TR_DEFAULT_STYLE | wx.TR_HIDE_ROOT | wx.NO_BORDER) - mainSizer.Add(self.affectedBy, 1, wx.ALL | wx.EXPAND, 0) - - self.m_staticline = wx.StaticLine(self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.LI_HORIZONTAL) - - mainSizer.Add(self.m_staticline, 0, wx.EXPAND) - bSizer = wx.BoxSizer(wx.HORIZONTAL) - - self.toggleExpandBtn = wx.ToggleButton(self, wx.ID_ANY, u"Expand All", wx.DefaultPosition, wx.DefaultSize, 0) - bSizer.Add(self.toggleExpandBtn, 0, wx.ALIGN_CENTER_VERTICAL) - - self.toggleNameBtn = wx.ToggleButton(self, wx.ID_ANY, u"Toggle Names", wx.DefaultPosition, wx.DefaultSize, 0) - bSizer.Add(self.toggleNameBtn, 0, wx.ALIGN_CENTER_VERTICAL) - - self.toggleViewBtn = wx.ToggleButton(self, wx.ID_ANY, u"Toggle View", wx.DefaultPosition, wx.DefaultSize, 0) - bSizer.Add(self.toggleViewBtn, 0, wx.ALIGN_CENTER_VERTICAL) - - if stuff is not None: - self.refreshBtn = wx.Button(self, wx.ID_ANY, u"Refresh", wx.DefaultPosition, wx.DefaultSize, wx.BU_EXACTFIT) - bSizer.Add(self.refreshBtn, 0, wx.ALIGN_CENTER_VERTICAL) - self.refreshBtn.Bind(wx.EVT_BUTTON, self.RefreshTree) - - self.toggleNameBtn.Bind(wx.EVT_TOGGLEBUTTON, self.ToggleNameMode) - self.toggleExpandBtn.Bind(wx.EVT_TOGGLEBUTTON, self.ToggleExpand) - self.toggleViewBtn.Bind(wx.EVT_TOGGLEBUTTON, self.ToggleViewMode) - - mainSizer.Add(bSizer, 0, wx.ALIGN_RIGHT) - self.SetSizer(mainSizer) - self.PopulateTree() - self.Layout() - self.affectedBy.Bind(wx.EVT_TREE_ITEM_RIGHT_CLICK, self.scheduleMenu) - - def scheduleMenu(self, event): - event.Skip() - wx.CallAfter(self.spawnMenu, event.Item) - - def spawnMenu(self, item): - self.affectedBy.SelectItem(item) - - stuff = self.affectedBy.GetPyData(item) - # String is set as data when we are dealing with attributes, not stuff containers - if stuff is None or isinstance(stuff, basestring): - return - contexts = [] - - # Skills are different in that they don't have itemModifiedAttributes, - # which is needed if we send the container to itemStats dialog. So - # instead, we send the item. - type_ = stuff.__class__.__name__ - contexts.append(("itemStats", type_)) - menu = ContextMenu.getMenu(stuff if type_ != "Skill" else stuff.item, *contexts) - self.PopupMenu(menu) - - def ExpandCollapseTree(self): - - self.Freeze() - if self.expand == 1: - self.affectedBy.ExpandAll() - else: - try: - self.affectedBy.CollapseAll() - except: - pass - - self.Thaw() - - def ToggleExpand(self, event): - self.expand *= -1 - self.ExpandCollapseTree() - - def ToggleViewTree(self): - self.Freeze() - - for item in self.treeItems: - change = self.affectedBy.GetPyData(item) - display = self.affectedBy.GetItemText(item) - self.affectedBy.SetItemText(item, change) - self.affectedBy.SetPyData(item, display) - - self.Thaw() - - def UpdateTree(self): - self.Freeze() - self.affectedBy.DeleteAllItems() - self.PopulateTree() - self.Thaw() - - def RefreshTree(self, event): - self.UpdateTree() - event.Skip() - - def ToggleViewMode(self, event): - self.showAttrView = not self.showAttrView - self.affectedBy.DeleteAllItems() - self.PopulateTree() - event.Skip() - - def ToggleNameMode(self, event): - self.showRealNames = not self.showRealNames - self.ToggleViewTree() - event.Skip() - - def PopulateTree(self): - # sheri was here - del self.treeItems[:] - root = self.affectedBy.AddRoot("WINPWNZ0R") - self.affectedBy.SetPyData(root, None) - - self.imageList = wx.ImageList(16, 16) - self.affectedBy.SetImageList(self.imageList) - - if self.showAttrView: - self.buildAttributeView(root) - else: - self.buildModuleView(root) - - self.ExpandCollapseTree() - - def sortAttrDisplayName(self, attr): - info = self.stuff.item.attributes.get(attr) - if info and info.displayName != "": - return info.displayName - - return attr - - def buildAttributeView(self, root): - """ - We first build a usable dictionary of items. The key is either a fit - if the afflictions stem from a projected fit, or self.stuff if they - are local afflictions (everything else, even gang boosts at this time) - The value of this is yet another dictionary in the following format: - - "attribute name": { - "Module Name": [ - class of affliction, - affliction item (required due to GH issue #335) - modifier type - amount of modification - whether this affliction was projected - ] - } - """ - - attributes = self.stuff.itemModifiedAttributes if self.item == self.stuff.item else self.stuff.chargeModifiedAttributes - container = {} - for attrName in attributes.iterAfflictions(): - # if value is 0 or there has been no change from original to modified, return - if attributes[attrName] == (attributes.getOriginal(attrName, 0)): - continue - - for fit, afflictors in attributes.getAfflictions(attrName).iteritems(): - for afflictor, modifier, amount, used in afflictors: - - if not used or afflictor.item is None: - continue - - if fit.ID != self.activeFit: - # affliction fit does not match our fit - if fit not in container: - container[fit] = {} - items = container[fit] - else: - # local afflictions - if self.stuff not in container: - container[self.stuff] = {} - items = container[self.stuff] - - # items hold our module: info mappings - if attrName not in items: - items[attrName] = [] - - if afflictor == self.stuff and getattr(afflictor, 'charge', None): - # we are showing a charges modifications, see #335 - item = afflictor.charge - else: - item = afflictor.item - - items[attrName].append( - (type(afflictor), afflictor, item, modifier, amount, getattr(afflictor, "projected", False))) - - # Make sure projected fits are on top - rootOrder = container.keys() - rootOrder.sort(key=lambda x: self.ORDER.index(type(x))) - - # Now, we take our created dictionary and start adding stuff to our tree - for thing in rootOrder: - # This block simply directs which parent we are adding to (root or projected fit) - if thing == self.stuff: - parent = root - else: # projected fit - icon = self.imageList.Add(BitmapLoader.getBitmap("ship_small", "gui")) - child = self.affectedBy.AppendItem(root, "{} ({})".format(thing.name, thing.ship.item.name), icon) - parent = child - - attributes = container[thing] - attrOrder = sorted(attributes.keys(), key=self.sortAttrDisplayName) - - for attrName in attrOrder: - attrInfo = self.stuff.item.attributes.get(attrName) - displayName = attrInfo.displayName if attrInfo and attrInfo.displayName != "" else attrName - - if attrInfo: - if attrInfo.icon is not None: - iconFile = attrInfo.icon.iconFile - icon = BitmapLoader.getBitmap(iconFile, "icons") - if icon is None: - icon = BitmapLoader.getBitmap("transparent16x16", "gui") - attrIcon = self.imageList.Add(icon) - else: - attrIcon = self.imageList.Add(BitmapLoader.getBitmap("7_15", "icons")) - else: - attrIcon = self.imageList.Add(BitmapLoader.getBitmap("7_15", "icons")) - - if self.showRealNames: - display = attrName - saved = displayName - else: - display = displayName - saved = attrName - - # this is the attribute node - child = self.affectedBy.AppendItem(parent, display, attrIcon) - self.affectedBy.SetPyData(child, saved) - self.treeItems.append(child) - - items = attributes[attrName] - items.sort(key=lambda x: self.ORDER.index(x[0])) - for itemInfo in items: - afflictorType, afflictor, item, attrModifier, attrAmount, projected = itemInfo - - if afflictorType == Ship: - itemIcon = self.imageList.Add(BitmapLoader.getBitmap("ship_small", "gui")) - elif item.icon: - bitmap = BitmapLoader.getBitmap(item.icon.iconFile, "icons") - itemIcon = self.imageList.Add(bitmap) if bitmap else -1 - else: - itemIcon = -1 - - displayStr = item.name - - if projected: - displayStr += " (projected)" - - penalized = "" - if '*' in attrModifier: - if 's' in attrModifier: - penalized += "(penalized)" - if 'r' in attrModifier: - penalized += "(resisted)" - attrModifier = "*" - - # this is the Module node, the attribute will be attached to this - display = "%s %s %.2f %s" % (displayStr, attrModifier, attrAmount, penalized) - treeItem = self.affectedBy.AppendItem(child, display, itemIcon) - self.affectedBy.SetPyData(treeItem, afflictor) - - def buildModuleView(self, root): - """ - We first build a usable dictionary of items. The key is either a fit - if the afflictions stem from a projected fit, or self.stuff if they - are local afflictions (everything else, even gang boosts at this time) - The value of this is yet another dictionary in the following format: - - "Module Name": [ - class of affliction, - set of afflictors (such as 2 of the same module), - info on affliction (attribute name, modifier, and modification amount), - item that will be used to determine icon (required due to GH issue #335) - whether this affliction is actually used (unlearned skills are not used) - ] - """ - - attributes = self.stuff.itemModifiedAttributes if self.item == self.stuff.item else self.stuff.chargeModifiedAttributes - container = {} - for attrName in attributes.iterAfflictions(): - # if value is 0 or there has been no change from original to modified, return - if attributes[attrName] == (attributes.getOriginal(attrName, 0)): - continue - - for fit, afflictors in attributes.getAfflictions(attrName).iteritems(): - for afflictor, modifier, amount, used in afflictors: - if not used or getattr(afflictor, 'item', None) is None: - continue - - if fit.ID != self.activeFit: - # affliction fit does not match our fit - if fit not in container: - container[fit] = {} - items = container[fit] - else: - # local afflictions - if self.stuff not in container: - container[self.stuff] = {} - items = container[self.stuff] - - if afflictor == self.stuff and getattr(afflictor, 'charge', None): - # we are showing a charges modifications, see #335 - item = afflictor.charge - else: - item = afflictor.item - - # items hold our module: info mappings - if item.name not in items: - items[item.name] = [type(afflictor), set(), [], item, getattr(afflictor, "projected", False)] - - info = items[item.name] - info[1].add(afflictor) - # If info[1] > 1, there are two separate modules working. - # Check to make sure we only include the modifier once - # See GH issue 154 - if len(info[1]) > 1 and (attrName, modifier, amount) in info[2]: - continue - info[2].append((attrName, modifier, amount)) - - # Make sure projected fits are on top - rootOrder = container.keys() - rootOrder.sort(key=lambda x: self.ORDER.index(type(x))) - - # Now, we take our created dictionary and start adding stuff to our tree - for thing in rootOrder: - # This block simply directs which parent we are adding to (root or projected fit) - if thing == self.stuff: - parent = root - else: # projected fit - icon = self.imageList.Add(BitmapLoader.getBitmap("ship_small", "gui")) - child = self.affectedBy.AppendItem(root, "{} ({})".format(thing.name, thing.ship.item.name), icon) - parent = child - - items = container[thing] - order = items.keys() - order.sort(key=lambda x: (self.ORDER.index(items[x][0]), x)) - - for itemName in order: - info = items[itemName] - afflictorType, afflictors, attrData, item, projected = info - counter = len(afflictors) - if afflictorType == Ship: - itemIcon = self.imageList.Add(BitmapLoader.getBitmap("ship_small", "gui")) - elif item.icon: - bitmap = BitmapLoader.getBitmap(item.icon.iconFile, "icons") - itemIcon = self.imageList.Add(bitmap) if bitmap else -1 - else: - itemIcon = -1 - - displayStr = itemName - - if counter > 1: - displayStr += " x {}".format(counter) - - if projected: - displayStr += " (projected)" - - # this is the Module node, the attribute will be attached to this - child = self.affectedBy.AppendItem(parent, displayStr, itemIcon) - self.affectedBy.SetPyData(child, afflictors.pop()) - - if counter > 0: - attributes = [] - for attrName, attrModifier, attrAmount in attrData: - attrInfo = self.stuff.item.attributes.get(attrName) - displayName = attrInfo.displayName if attrInfo else "" - - if attrInfo: - if attrInfo.icon is not None: - iconFile = attrInfo.icon.iconFile - icon = BitmapLoader.getBitmap(iconFile, "icons") - if icon is None: - icon = BitmapLoader.getBitmap("transparent16x16", "gui") - - attrIcon = self.imageList.Add(icon) - else: - attrIcon = self.imageList.Add(BitmapLoader.getBitmap("7_15", "icons")) - else: - attrIcon = self.imageList.Add(BitmapLoader.getBitmap("7_15", "icons")) - - penalized = "" - if '*' in attrModifier: - if 's' in attrModifier: - penalized += "(penalized)" - if 'r' in attrModifier: - penalized += "(resisted)" - attrModifier = "*" - - attributes.append((attrName, (displayName if displayName != "" else attrName), attrModifier, - attrAmount, penalized, attrIcon)) - - attrSorted = sorted(attributes, key=lambda attribName: attribName[0]) - for attr in attrSorted: - attrName, displayName, attrModifier, attrAmount, penalized, attrIcon = attr - - if self.showRealNames: - display = "%s %s %.2f %s" % (attrName, attrModifier, attrAmount, penalized) - saved = "%s %s %.2f %s" % ( - displayName if displayName != "" else attrName, - attrModifier, - attrAmount, - penalized - ) - else: - display = "%s %s %.2f %s" % ( - displayName if displayName != "" else attrName, - attrModifier, - attrAmount, - penalized - ) - saved = "%s %s %.2f %s" % (attrName, attrModifier, attrAmount, penalized) - - treeitem = self.affectedBy.AppendItem(child, display, attrIcon) - self.affectedBy.SetPyData(treeitem, saved) - self.treeItems.append(treeitem) - - -class ItemProperties(wx.Panel): - def __init__(self, parent, stuff, item, context=None): - wx.Panel.__init__(self, parent) - mainSizer = wx.BoxSizer(wx.VERTICAL) - - self.paramList = AutoListCtrl(self, wx.ID_ANY, - style=wx.LC_REPORT | wx.LC_SINGLE_SEL | wx.LC_VRULES | wx.NO_BORDER) - mainSizer.Add(self.paramList, 1, wx.ALL | wx.EXPAND, 0) - self.SetSizer(mainSizer) - - self.toggleView = 1 - self.stuff = stuff - self.item = item - self.attrInfo = {} - self.attrValues = {} - self._fetchValues() - - self.m_staticline = wx.StaticLine(self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.LI_HORIZONTAL) - mainSizer.Add(self.m_staticline, 0, wx.EXPAND) - bSizer = wx.BoxSizer(wx.HORIZONTAL) - - self.totalAttrsLabel = wx.StaticText(self, wx.ID_ANY, u" ", wx.DefaultPosition, wx.DefaultSize, 0) - bSizer.Add(self.totalAttrsLabel, 0, wx.ALIGN_CENTER_VERTICAL | wx.RIGHT) - - mainSizer.Add(bSizer, 0, wx.ALIGN_RIGHT) - - self.PopulateList() - - def _fetchValues(self): - if self.stuff is None: - self.attrInfo.clear() - self.attrValues.clear() - self.attrInfo.update(self.item.attributes) - self.attrValues.update(self.item.attributes) - elif self.stuff.item == self.item: - self.attrInfo.clear() - self.attrValues.clear() - self.attrInfo.update(self.stuff.item.attributes) - self.attrValues.update(self.stuff.itemModifiedAttributes) - elif self.stuff.charge == self.item: - self.attrInfo.clear() - self.attrValues.clear() - self.attrInfo.update(self.stuff.charge.attributes) - self.attrValues.update(self.stuff.chargeModifiedAttributes) - # When item for stats window no longer exists, don't change anything - else: - return - - def PopulateList(self): - self.paramList.InsertColumn(0, "Attribute") - self.paramList.InsertColumn(1, "Current Value") - self.paramList.SetColumnWidth(0, 110) - self.paramList.SetColumnWidth(1, 1500) - self.paramList.setResizeColumn(0) - - if self.stuff: - names = dir(self.stuff) - else: - names = dir(self.item) - - names = [a for a in names if not (a.startswith('__') and a.endswith('__'))] - - idNameMap = {} - idCount = 0 - for name in names: - try: - if self.stuff: - attrName = name.title() - value = getattr(self.stuff, name) - else: - attrName = name.title() - value = getattr(self.item, name) - - index = self.paramList.InsertStringItem(sys.maxint, attrName) - # index = self.paramList.InsertImageStringItem(sys.maxint, attrName) - idNameMap[idCount] = attrName - self.paramList.SetItemData(index, idCount) - idCount += 1 - - valueUnit = str(value) - - self.paramList.SetStringItem(index, 1, valueUnit) - except: - # TODO: Add logging to this. - # We couldn't get a property for some reason. Skip it for now. - continue - - self.paramList.SortItems(lambda id1, id2: cmp(idNameMap[id1], idNameMap[id2])) - self.paramList.RefreshRows() - self.totalAttrsLabel.SetLabel("%d attributes. " % idCount) - self.Layout()