Merge branch 'charImplants' into singularity

# Conflicts:
#	eos/db/saveddata/fit.py
#	gui/builtinContextMenus/itemStats.py
This commit is contained in:
blitzmann
2016-04-12 20:16:53 -04:00
66 changed files with 1598 additions and 871 deletions

View File

@@ -4,6 +4,9 @@ import time
import re
import os
import migrations
import logging
logger = logging.getLogger(__name__)
def getVersion(db):
cursor = db.execute('PRAGMA user_version')
@@ -30,10 +33,9 @@ def update(saveddata_engine):
shutil.copyfile(config.saveDB, toFile)
for version in xrange(dbVersion, appVersion):
func = migrations.updates[version+1]
if func:
print "applying update",version+1
logger.info("Applying database update: %d", version+1)
func(saveddata_engine)
# when all is said and done, set version to current

View File

@@ -0,0 +1,15 @@
"""
Migration 13
- Alters fits table to introduce implant location attribute
"""
import sqlalchemy
def upgrade(saveddata_engine):
# Update fits schema to include implant location attribute
try:
saveddata_engine.execute("SELECT implantLocation FROM fits LIMIT 1")
except sqlalchemy.exc.DatabaseError:
saveddata_engine.execute("ALTER TABLE fits ADD COLUMN implantLocation INTEGER;")
saveddata_engine.execute("UPDATE fits SET implantLocation = 0")

View File

@@ -13,6 +13,7 @@ __all__ = [
"miscData",
"targetResists",
"override",
"crest"
"crest",
"implantSet"
]

View File

@@ -36,9 +36,23 @@ characters_table = Table("characters", saveddata_meta,
Column("ownerID", ForeignKey("users.ID"), nullable = True))
mapper(Character, characters_table,
properties = {"_Character__owner" : relation(User, backref = "characters"),
"_Character__skills" : relation(Skill, backref="character", cascade = "all,delete-orphan"),
"_Character__implants" : relation(Implant, collection_class = HandledImplantBoosterList, cascade='all,delete-orphan', single_parent=True,
primaryjoin = charImplants_table.c.charID == characters_table.c.ID,
secondaryjoin = charImplants_table.c.implantID == Implant.ID,
secondary = charImplants_table),})
properties = {
"savedName": characters_table.c.name,
"_Character__owner": relation(
User,
backref = "characters"),
"_Character__skills": relation(
Skill,
backref="character",
cascade = "all,delete-orphan"),
"_Character__implants": relation(
Implant,
collection_class = HandledImplantBoosterList,
cascade='all,delete-orphan',
backref='character',
single_parent=True,
primaryjoin = charImplants_table.c.charID == characters_table.c.ID,
secondaryjoin = charImplants_table.c.implantID == Implant.ID,
secondary = charImplants_table),
}
)

View File

@@ -29,7 +29,7 @@ from eos.db.saveddata.drone import drones_table
from eos.db.saveddata.fighter import fighters_table
from eos.db.saveddata.cargo import cargo_table
from eos.db.saveddata.implant import fitImplants_table
from eos.types import Fit, Module, User, Booster, Drone, Fighter, Cargo, Implant, Character, DamagePattern, TargetResists
from eos.types import Fit, Module, User, Booster, Drone, Fighter, Cargo, Implant, Character, DamagePattern, TargetResists, ImplantLocation
from eos.effectHandlerHelpers import *
fits_table = Table("fits", saveddata_meta,
@@ -43,6 +43,7 @@ fits_table = Table("fits", saveddata_meta,
Column("booster", Boolean, nullable = False, index = True, default = 0),
Column("targetResistsID", ForeignKey("targetResists.ID"), nullable=True),
Column("modeID", Integer, nullable=True),
Column("implantLocation", Integer, nullable=False, default=ImplantLocation.FIT),
)
projectedFits_table = Table("projectedFits", saveddata_meta,

View File

@@ -36,4 +36,8 @@ charImplants_table = Table("charImplants", saveddata_meta,
Column("charID", ForeignKey("characters.ID"), index = True),
Column("implantID", ForeignKey("implants.ID"), primary_key = True))
implantsSetMap_table = Table("implantSetMap", saveddata_meta,
Column("setID", ForeignKey("implantSets.ID"), index = True),
Column("implantID", ForeignKey("implants.ID"), primary_key = True))
mapper(Implant, implants_table)

View File

@@ -0,0 +1,45 @@
#===============================================================================
# Copyright (C) 2016 Ryan Holmes
#
# This file is part of eos.
#
# eos is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 2 of the License, or
# (at your option) any later version.
#
# eos 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 Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with eos. If not, see <http://www.gnu.org/licenses/>.
#===============================================================================
from sqlalchemy import Table, Column, Integer, ForeignKey, String
from sqlalchemy.orm import relation, mapper
from eos.db import saveddata_meta
from eos.db.saveddata.implant import implantsSetMap_table
from eos.types import Implant, ImplantSet
from eos.effectHandlerHelpers import HandledImplantBoosterList
implant_set_table = Table("implantSets", saveddata_meta,
Column("ID", Integer, primary_key = True),
Column("name", String, nullable = False),
)
mapper(ImplantSet, implant_set_table,
properties = {
"_ImplantSet__implants": relation(
Implant,
collection_class = HandledImplantBoosterList,
cascade='all, delete, delete-orphan',
backref='set',
single_parent=True,
primaryjoin = implantsSetMap_table.c.setID == implant_set_table.c.ID,
secondaryjoin = implantsSetMap_table.c.implantID == Implant.ID,
secondary = implantsSetMap_table),
}
)

View File

@@ -20,7 +20,7 @@
from eos.db.util import processEager, processWhere
from eos.db import saveddata_session, sd_lock
from eos.types import User, Character, Fit, Price, DamagePattern, Fleet, MiscData, Wing, Squad, TargetResists, Override, CrestChar
from eos.types import *
from eos.db.saveddata.fleet import squadmembers_table
from eos.db.saveddata.fit import projectedFits_table
from sqlalchemy.sql import and_
@@ -154,7 +154,7 @@ def getCharacter(lookfor, eager=None):
elif isinstance(lookfor, basestring):
eager = processEager(eager)
with sd_lock:
character = saveddata_session.query(Character).options(*eager).filter(Character.name == lookfor).first()
character = saveddata_session.query(Character).options(*eager).filter(Character.savedName == lookfor).first()
else:
raise TypeError("Need integer or string as argument")
return character
@@ -349,6 +349,12 @@ def getTargetResistsList(eager=None):
patterns = saveddata_session.query(TargetResists).options(*eager).all()
return patterns
def getImplantSetList(eager=None):
eager = processEager(eager)
with sd_lock:
sets = saveddata_session.query(ImplantSet).options(*eager).all()
return sets
@cachedQuery(DamagePattern, 1, "lookfor")
def getDamagePattern(lookfor, eager=None):
if isinstance(lookfor, int):
@@ -385,6 +391,24 @@ def getTargetResists(lookfor, eager=None):
raise TypeError("Need integer or string as argument")
return pattern
@cachedQuery(ImplantSet, 1, "lookfor")
def getImplantSet(lookfor, eager=None):
if isinstance(lookfor, int):
if eager is None:
with sd_lock:
pattern = saveddata_session.query(ImplantSet).get(lookfor)
else:
eager = processEager(eager)
with sd_lock:
pattern = saveddata_session.query(ImplantSet).options(*eager).filter(TargetResists.ID == lookfor).first()
elif isinstance(lookfor, basestring):
eager = processEager(eager)
with sd_lock:
pattern = saveddata_session.query(ImplantSet).options(*eager).filter(TargetResists.name == lookfor).first()
else:
raise TypeError("Improper argument")
return pattern
def searchFits(nameLike, where=None, eager=None):
if not isinstance(nameLike, basestring):
raise TypeError("Need string as argument")

View File

@@ -5,7 +5,7 @@
runTime = "early"
type = "passive"
def handler(fit, implant, context):
fit.implants.filteredItemMultiply(
fit.appliedImplants.filteredItemMultiply(
lambda implant: "signatureRadiusBonus" in implant.itemModifiedAttributes and "implantSetAngel" in implant.itemModifiedAttributes,
"signatureRadiusBonus",
implant.getModifiedItemAttr("implantSetAngel"))

View File

@@ -5,5 +5,5 @@
runTime = "early"
type = "passive"
def handler(fit, implant, context):
fit.implants.filteredItemMultiply(lambda target: target.item.requiresSkill("Cybernetics"),
fit.appliedImplants.filteredItemMultiply(lambda target: target.item.requiresSkill("Cybernetics"),
"scanGravimetricStrengthPercent", implant.getModifiedItemAttr("implantSetCaldariNavy"))

View File

@@ -5,5 +5,5 @@
runTime = "early"
type = "passive"
def handler(fit, implant, context):
fit.implants.filteredItemMultiply(lambda target: target.item.requiresSkill("Cybernetics"),
fit.appliedImplants.filteredItemMultiply(lambda target: target.item.requiresSkill("Cybernetics"),
"scanGravimetricStrengthModifier", implant.getModifiedItemAttr("implantSetLGCaldariNavy"))

View File

@@ -5,5 +5,5 @@
type = "passive"
runTime = "early"
def handler(fit, implant, context):
fit.implants.filteredItemMultiply(lambda target: target.item.requiresSkill("Cybernetics"),
fit.appliedImplants.filteredItemMultiply(lambda target: target.item.requiresSkill("Cybernetics"),
"scanMagnetometricStrengthPercent", implant.getModifiedItemAttr("implantSetFederationNavy"))

View File

@@ -5,5 +5,5 @@
type = "passive"
runTime = "early"
def handler(fit, implant, context):
fit.implants.filteredItemMultiply(lambda target: target.item.requiresSkill("Cybernetics"),
fit.appliedImplants.filteredItemMultiply(lambda target: target.item.requiresSkill("Cybernetics"),
"scanMagnetometricStrengthModifier", implant.getModifiedItemAttr("implantSetLGFederationNavy"))

View File

@@ -5,5 +5,5 @@
type = "passive"
runTime = "early"
def handler(fit, implant, context):
fit.implants.filteredItemMultiply(lambda target: target.item.requiresSkill("Cybernetics"),
fit.appliedImplants.filteredItemMultiply(lambda target: target.item.requiresSkill("Cybernetics"),
"scanRadarStrengthPercent", implant.getModifiedItemAttr("implantSetImperialNavy"))

View File

@@ -5,5 +5,5 @@
type = "passive"
runTime = "early"
def handler(fit, implant, context):
fit.implants.filteredItemMultiply(lambda target: target.item.requiresSkill("Cybernetics"),
fit.appliedImplants.filteredItemMultiply(lambda target: target.item.requiresSkill("Cybernetics"),
"scanRadarStrengthModifier", implant.getModifiedItemAttr("implantSetLGImperialNavy"))

View File

@@ -5,5 +5,5 @@
runTime = "early"
type = "passive"
def handler(fit, implant, context):
fit.implants.filteredItemMultiply(lambda mod: mod.item.group.name == "Cyberimplant",
fit.appliedImplants.filteredItemMultiply(lambda mod: mod.item.group.name == "Cyberimplant",
"WarpSBonus", implant.getModifiedItemAttr("implantSetWarpSpeed"))

View File

@@ -5,5 +5,5 @@
runTime = "early"
type = "passive"
def handler(fit, implant, context):
fit.implants.filteredItemMultiply(lambda target: target.item.requiresSkill("Cybernetics"),
fit.appliedImplants.filteredItemMultiply(lambda target: target.item.requiresSkill("Cybernetics"),
"scanLadarStrengthPercent", implant.getModifiedItemAttr("implantSetRepublicFleet"))

View File

@@ -5,5 +5,5 @@
runTime = "early"
type = "passive"
def handler(fit, implant, context):
fit.implants.filteredItemMultiply(lambda target: target.item.requiresSkill("Cybernetics"),
fit.appliedImplants.filteredItemMultiply(lambda target: target.item.requiresSkill("Cybernetics"),
"scanLadarStrengthModifier", implant.getModifiedItemAttr("implantSetLGRepublicFleet"))

View File

@@ -5,5 +5,5 @@
runTime = "early"
type = "passive"
def handler(fit, implant, context):
fit.implants.filteredItemMultiply(lambda mod: mod.item.group.name == "Cyberimplant",
fit.appliedImplants.filteredItemMultiply(lambda mod: mod.item.group.name == "Cyberimplant",
"durationBonus", implant.getModifiedItemAttr("implantSetBloodraider"))

View File

@@ -5,5 +5,5 @@
runTime = "early"
type = "passive"
def handler(fit, implant, context):
fit.implants.filteredItemMultiply(lambda mod: mod.item.group.name == "Cyberimplant",
fit.appliedImplants.filteredItemMultiply(lambda mod: mod.item.group.name == "Cyberimplant",
"agilityBonus", implant.getModifiedItemAttr("implantSetChristmas"))

View File

@@ -5,5 +5,5 @@
runTime = "early"
type = "passive"
def handler(fit, implant, context):
fit.implants.filteredItemMultiply(lambda mod: mod.item.group.name == "Cyberimplant",
fit.appliedImplants.filteredItemMultiply(lambda mod: mod.item.group.name == "Cyberimplant",
"armorHpBonus2", implant.getModifiedItemAttr("implantSetChristmas"))

View File

@@ -5,5 +5,5 @@
runTime = "early"
type = "passive"
def handler(fit, implant, context):
fit.implants.filteredItemMultiply(lambda mod: mod.item.group.name == "Cyberimplant",
fit.appliedImplants.filteredItemMultiply(lambda mod: mod.item.group.name == "Cyberimplant",
"implantBonusVelocity", implant.getModifiedItemAttr("implantSetChristmas"))

View File

@@ -5,5 +5,5 @@
runTime = "early"
type = "passive"
def handler(fit, implant, context):
fit.implants.filteredItemMultiply(lambda mod: mod.item.group.name == "Cyberimplant",
fit.appliedImplants.filteredItemMultiply(lambda mod: mod.item.group.name == "Cyberimplant",
"capacitorCapacityBonus", implant.getModifiedItemAttr("implantSetChristmas"))

View File

@@ -5,5 +5,5 @@
runTime = "early"
type = "passive"
def handler(fit, implant, context):
fit.implants.filteredItemMultiply(lambda mod: mod.item.group.name == "Cyberimplant",
fit.appliedImplants.filteredItemMultiply(lambda mod: mod.item.group.name == "Cyberimplant",
"capRechargeBonus", implant.getModifiedItemAttr("implantSetChristmas"))

View File

@@ -5,5 +5,5 @@
runTime = "early"
type = "passive"
def handler(fit, implant, context):
fit.implants.filteredItemMultiply(lambda mod: mod.item.group.name == "Cyberimplant",
fit.appliedImplants.filteredItemMultiply(lambda mod: mod.item.group.name == "Cyberimplant",
"cpuOutputBonus2", implant.getModifiedItemAttr("implantSetChristmas"))

View File

@@ -5,5 +5,5 @@
runTime = "early"
type = "passive"
def handler(fit, implant, context):
fit.implants.filteredItemMultiply(lambda mod: mod.item.group.name == "Cyberimplant",
fit.appliedImplants.filteredItemMultiply(lambda mod: mod.item.group.name == "Cyberimplant",
"powerEngineeringOutputBonus", implant.getModifiedItemAttr("implantSetChristmas"))

View File

@@ -5,5 +5,5 @@
runTime = "early"
type = "passive"
def handler(fit, implant, context):
fit.implants.filteredItemMultiply(lambda mod: mod.item.group.name == "Cyberimplant",
fit.appliedImplants.filteredItemMultiply(lambda mod: mod.item.group.name == "Cyberimplant",
"shieldCapacityBonus", implant.getModifiedItemAttr("implantSetChristmas"))

View File

@@ -5,5 +5,5 @@
runTime = "early"
type = "passive"
def handler(fit, implant, context):
fit.implants.filteredItemMultiply(lambda mod: mod.item.group.name == "Cyberimplant",
fit.appliedImplants.filteredItemMultiply(lambda mod: mod.item.group.name == "Cyberimplant",
"shieldBoostMultiplier", implant.getModifiedItemAttr("implantSetGuristas"))

View File

@@ -5,5 +5,5 @@
runTime = "early"
type = "passive"
def handler(fit, implant, context):
fit.implants.filteredItemMultiply(lambda mod: mod.item.group.name == "Cyberimplant",
fit.appliedImplants.filteredItemMultiply(lambda mod: mod.item.group.name == "Cyberimplant",
"rangeSkillBonus", implant.getModifiedItemAttr("implantSetMordus"))

View File

@@ -5,5 +5,5 @@
runTime = "early"
type = "passive"
def handler(fit, implant, context):
fit.implants.filteredItemMultiply(lambda mod: mod.item.group.name == "Cyberimplant",
fit.appliedImplants.filteredItemMultiply(lambda mod: mod.item.group.name == "Cyberimplant",
"maxRangeBonus", implant.getModifiedItemAttr("implantSetORE"))

View File

@@ -6,5 +6,5 @@
runTime = "early"
type = "passive"
def handler(fit, implant, context):
fit.implants.filteredItemMultiply(lambda target: target.item.requiresSkill("Cybernetics"),
fit.appliedImplants.filteredItemMultiply(lambda target: target.item.requiresSkill("Cybernetics"),
"armorHpBonus", implant.getModifiedItemAttr("implantSetSansha") or 1)

View File

@@ -5,5 +5,5 @@
runTime = "early"
type = "passive"
def handler(fit, implant, context):
fit.implants.filteredItemMultiply(lambda mod: mod.item.group.name == "Cyberimplant",
fit.appliedImplants.filteredItemMultiply(lambda mod: mod.item.group.name == "Cyberimplant",
"velocityBonus", implant.getModifiedItemAttr("implantSetSerpentis"))

View File

@@ -5,5 +5,5 @@
runTime = "early"
type = "passive"
def handler(fit, implant, context):
fit.implants.filteredItemMultiply(lambda mod: mod.item.group.name == "Cyberimplant",
fit.appliedImplants.filteredItemMultiply(lambda mod: mod.item.group.name == "Cyberimplant",
"scanStrengthBonus", implant.getModifiedItemAttr("implantSetSisters"))

View File

@@ -5,5 +5,5 @@
runTime = "early"
type = "passive"
def handler(fit, implant, context):
fit.implants.filteredItemMultiply(lambda mod: mod.item.group.name == "Cyberimplant",
fit.appliedImplants.filteredItemMultiply(lambda mod: mod.item.group.name == "Cyberimplant",
"boosterAttributeModifier", implant.getModifiedItemAttr("implantSetSyndicate"))

View File

@@ -5,5 +5,5 @@
runTime = "early"
type = "passive"
def handler(fit, implant, context):
fit.implants.filteredItemMultiply(lambda mod: mod.item.group.name == "Cyberimplant",
fit.appliedImplants.filteredItemMultiply(lambda mod: mod.item.group.name == "Cyberimplant",
"agilityBonus", implant.getModifiedItemAttr("implantSetThukker"))

View File

@@ -19,8 +19,9 @@
from sqlalchemy.orm import validates, reconstructor
from itertools import chain
from eos.effectHandlerHelpers import HandledItem
from eos.effectHandlerHelpers import HandledItem, HandledImplantBoosterList
import eos.db
import eos
import logging
@@ -89,7 +90,7 @@ class Character(object):
return all0
def __init__(self, name, defaultLevel=None, initSkills=True):
self.name = name
self.savedName = name
self.__owner = None
self.defaultLevel = defaultLevel
self.__skills = []
@@ -100,7 +101,7 @@ class Character(object):
for item in self.getSkillList():
self.addSkill(Skill(item.ID, self.defaultLevel))
self.__implants = eos.saveddata.fit.HandledImplantBoosterList()
self.__implants = HandledImplantBoosterList()
self.apiKey = None
@reconstructor
@@ -128,6 +129,14 @@ class Character(object):
def owner(self, owner):
self.__owner = owner
@property
def name(self):
return self.savedName if not self.isDirty else "{} *".format(self.savedName)
@name.setter
def name(self, name):
self.savedName = name
@property
def skills(self):
return self.__skills
@@ -200,8 +209,13 @@ class Character(object):
skill.calculateModifiedAttributes(fit, runTime)
def clear(self):
for skill in self.skills:
skill.clear()
c = chain(
self.skills,
self.implants
)
for stuff in c:
if stuff is not None and stuff != self:
stuff.clear()
def __deepcopy__(self, memo):
copy = Character("%s copy" % self.name, initSkills=False)

View File

@@ -31,6 +31,8 @@ import eos.db
import time
import copy
from utils.timer import Timer
from eos.enum import Enum
import logging
@@ -41,6 +43,10 @@ try:
except ImportError:
from utils.compat import OrderedDict
class ImplantLocation(Enum):
FIT = 0
CHARACTER = 1
class Fit(object):
"""Represents a fitting, with modules, ship, implants, etc."""
@@ -323,17 +329,20 @@ class Fit(object):
return -log(0.25) * agility * mass / 1000000
@property
def implantSource(self):
return self.implantLocation
@implantSource.setter
def implantSource(self, source):
self.implantLocation = source
@property
def appliedImplants(self):
implantsBySlot = {}
if self.character:
for implant in self.character.implants:
implantsBySlot[implant.slot] = implant
for implant in self.implants:
implantsBySlot[implant.slot] = implant
return implantsBySlot.values()
if self.implantLocation == ImplantLocation.CHARACTER:
return self.character.implants
else:
return self.implants
@validates("ID", "ownerID", "shipID")
def validator(self, key, val):

View File

@@ -0,0 +1,58 @@
#===============================================================================
# Copyright (C) 2016 Ryan Holmes
#
# This file is part of eos.
#
# eos is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 2 of the License, or
# (at your option) any later version.
#
# eos 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 Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with eos. If not, see <http://www.gnu.org/licenses/>.
#===============================================================================
from eos.effectHandlerHelpers import HandledImplantBoosterList
from copy import deepcopy
class ImplantSet(object):
def __init__(self, name=None):
self.name = name
self.__implants = HandledImplantBoosterList()
@property
def implants(self):
return self.__implants
@classmethod
def exportSets(cls, *sets):
out = "# Exported from pyfa\n#\n" \
"# Values are in following format:\n" \
"# [Implant Set name]\n" \
"# [Implant name]\n" \
"# [Implant name]\n" \
"# ...\n\n"
for set in sets:
out += "[{}]\n".format(set.name)
for implant in set.implants:
out += "{}\n".format(implant.item.name)
out += "\n"
return out.strip()
def __deepcopy__(self, memo):
copy = ImplantSet(self.name)
copy.name = "%s copy" % self.name
orig = getattr(self, 'implants')
c = getattr(copy, 'implants')
for i in orig:
c.append(deepcopy(i, memo))
return copy

View File

@@ -30,10 +30,11 @@ from eos.saveddata.drone import Drone
from eos.saveddata.fighter import Fighter
from eos.saveddata.cargo import Cargo
from eos.saveddata.implant import Implant
from eos.saveddata.implantSet import ImplantSet
from eos.saveddata.booster import SideEffect
from eos.saveddata.booster import Booster
from eos.saveddata.ship import Ship
from eos.saveddata.fit import Fit
from eos.saveddata.fit import Fit, ImplantLocation
from eos.saveddata.mode import Mode
from eos.saveddata.fleet import Fleet, Wing, Squad
from eos.saveddata.miscData import MiscData

View File

@@ -20,4 +20,5 @@ __all__ = [
"priceClear",
"amount",
"metaSwap",
"implantSets",
]

View File

@@ -0,0 +1,78 @@
from gui.contextMenu import ContextMenu
import gui.mainFrame
import service
import gui.globalEvents as GE
import wx
class ImplantSets(ContextMenu):
def __init__(self):
self.mainFrame = gui.mainFrame.MainFrame.getInstance()
def display(self, srcContext, selection):
return srcContext in ("implantView", "implantEditor")
def getText(self, itmContext, selection):
return "Add Implant Set"
def getSubMenu(self, context, selection, rootMenu, i, pitem):
"""
A note on the selection here: Most context menus act on a fit, so it's easy enough to get the active fit from
the MainFrame instance. There's never been a reason to get info from another window, so there's not common
way of doing this. However, we use this context menu within the Character Editor to apply implant sets to a
character, so we need to access the character editor.
It is for these reasons that I hijack the selection parameter when calling the menu and pass a pointer to the
Character Editor. This way we can use it to get current editing character ID and apply the implants.
It would probably be better to have a function on the MainFrame to get the currently open Character Editor (as
we do with the item stats window). Eventually... Until then, this long ass note will remain to remind me why
stupid shit like this is even happening.
"""
m = wx.Menu()
bindmenu = rootMenu if "wxMSW" in wx.PlatformInfo else m
sIS = service.ImplantSets.getInstance()
implantSets = sIS.getImplantSetList()
self.context = context
if len(selection) == 1:
self.selection = selection[0] # dirty hack here
self.idmap = {}
for set in implantSets:
id = wx.NewId()
mitem = wx.MenuItem(rootMenu, id, set.name)
bindmenu.Bind(wx.EVT_MENU, self.handleSelection, mitem)
self.idmap[id] = set
m.AppendItem(mitem)
return m
def handleSelection(self, event):
set = self.idmap.get(event.Id, None)
if set is None:
event.Skip()
return
if self.context == "implantEditor":
# we are calling from character editor, the implant source is different
sChar = service.Character.getInstance()
charID = self.selection.getActiveCharacter()
for implant in set.implants:
sChar.addImplant(charID, implant.item.ID)
wx.PostEvent(self.selection, GE.CharChanged())
else:
sFit = service.Fit.getInstance()
fitID = self.mainFrame.getActiveFit()
for implant in set.implants:
sFit.addImplant(fitID, implant.item.ID, recalc=implant == set.implants[-1])
wx.PostEvent(self.mainFrame, GE.FitChanged(fitID=fitID))
ImplantSets.register()

View File

@@ -9,6 +9,7 @@ class ItemStats(ContextMenu):
self.mainFrame = gui.mainFrame.MainFrame.getInstance()
def display(self, srcContext, selection):
return srcContext in ("marketItemGroup", "marketItemMisc",
"fittingModule", "fittingCharge",
"fittingShip", "baseShip",
@@ -16,7 +17,7 @@ class ItemStats(ContextMenu):
"implantItem", "boosterItem",
"skillItem", "projectedModule",
"projectedDrone", "projectedCharge",
"itemStats", "fighterItem")
"itemStats", "fighterItem", "implantItemChar")
def getText(self, itmContext, selection):
return "{0} Stats".format(itmContext if itmContext is not None else "Item")

View File

@@ -12,7 +12,8 @@ class MarketJump(ContextMenu):
"fittingCharge", "droneItem",
"implantItem", "boosterItem",
"projectedModule", "projectedDrone",
"projectedCharge", "cargoItem")
"projectedCharge", "cargoItem",
"implantItemChar")
if not srcContext in validContexts or selection is None or len(selection) < 1:
return False
@@ -33,12 +34,10 @@ class MarketJump(ContextMenu):
def activate(self, fullContext, selection, i):
srcContext = fullContext[0]
if srcContext in ("fittingModule", "droneItem", "implantItem",
"boosterItem", "projectedModule", "projectedDrone",
"cargoItem"):
item = selection[0].item
elif srcContext in ("fittingCharge", "projectedCharge"):
if srcContext in ("fittingCharge", "projectedCharge"):
item = selection[0].charge
elif hasattr(selection[0], "item"):
item = selection[0].item
else:
item = selection[0]

View File

@@ -2,7 +2,7 @@ from gui import builtinViewColumns
from gui.viewColumn import ViewColumn
from gui.bitmapLoader import BitmapLoader
import wx
from eos.types import Drone, Fit, Module, Slot, Rack
from eos.types import Drone, Fit, Module, Slot, Rack, Implant
class BaseIcon(ViewColumn):
name = "Base Icon"
@@ -21,6 +21,11 @@ class BaseIcon(ViewColumn):
return self.shipImage
if isinstance(stuff, Rack):
return -1
if isinstance(stuff, Implant):
if stuff.character: # if it has a character as it's parent
return self.fittingView.imageList.GetImageIndex("character_small", "gui")
else:
return self.shipImage
if isinstance(stuff, Module):
if stuff.isEmpty:
return self.fittingView.imageList.GetImageIndex("slot_%s_small" % Slot.getName(stuff.slot).lower(), "gui")

View File

@@ -22,7 +22,7 @@ from gui.viewColumn import ViewColumn
import gui.mainFrame
import wx
from eos.types import Drone, Cargo, Fit, Module, Slot, Rack
from eos.types import Drone, Cargo, Fit, Module, Slot, Rack, Implant
import service
class BaseName(ViewColumn):
@@ -61,6 +61,8 @@ class BaseName(ViewColumn):
return "%s Slot" % Slot.getName(stuff.slot).capitalize()
else:
return stuff.item.name
elif isinstance(stuff, Implant):
return stuff.item.name
else:
item = getattr(stuff, "item", stuff)

View File

@@ -22,7 +22,7 @@ from gui.bitmapLoader import BitmapLoader
import gui.mainFrame
import wx
from eos.types import Drone, Module, Rack, Fit
from eos.types import Drone, Module, Rack, Fit, Implant
from eos.types import State as State_
class State(ViewColumn):
@@ -67,6 +67,9 @@ class State(ViewColumn):
if projectionInfo.active:
return generic_active
return generic_inactive
elif isinstance(stuff, Implant) and stuff.character:
# if we're showing character implants, show an "online" state, which should not be changed
return self.fittingView.imageList.GetImageIndex("state_%s_small" % State_.getName(0).lower(), "gui")
else:
active = getattr(stuff, "active", None)
if active is None:

View File

@@ -1 +1 @@
__all__ = ["fittingView", "fleetView"]
__all__ = ["fittingView", "fleetView", "implantEditor"]

View File

@@ -0,0 +1,173 @@
import wx
from gui.bitmapLoader import BitmapLoader
import service
class BaseValidator(wx.PyValidator):
def __init__(self):
wx.PyValidator.__init__(self)
def Validate(self, win):
raise NotImplementedError()
def TransferToWindow(self):
return True
def TransferFromWindow(self):
return True
class TextEntryValidatedDialog(wx.TextEntryDialog):
def __init__(self, parent, validator=None, *args, **kargs):
wx.TextEntryDialog.__init__(self, parent, *args, **kargs)
self.parent = parent
self.txtctrl = self.FindWindowById(3000)
if validator:
self.txtctrl.SetValidator(validator())
class EntityEditor (wx.Panel):
"""
Entity Editor is a panel that takes some sort of list as a source and populates a drop down with options to add/
rename/clone/delete an entity. Comes with dialogs that take user input. Classes that derive this class must override
functions that get the list from the source, what to do when user does an action, and how to validate the input.
"""
def __init__(self, parent, entityName):
wx.Panel.__init__(self, parent, id=wx.ID_ANY, style=wx.TAB_TRAVERSAL)
self.entityName = entityName
self.validator = None
self.navSizer = wx.BoxSizer(wx.HORIZONTAL)
self.choices = []
self.choices.sort(key=lambda p: p.name)
self.entityChoices = wx.Choice(self, choices=map(lambda p: p.name, self.choices))
self.navSizer.Add(self.entityChoices, 1, wx.ALL, 5)
buttons = (("new", wx.ART_NEW, self.OnNew),
("rename", BitmapLoader.getBitmap("rename", "gui"), self.OnRename),
("copy", wx.ART_COPY, self.OnCopy),
("delete", wx.ART_DELETE, self.OnDelete))
size = None
for name, art, func in buttons:
bitmap = wx.ArtProvider.GetBitmap(art, wx.ART_BUTTON) if name != "rename" else art
btn = wx.BitmapButton(self, wx.ID_ANY, bitmap)
if size is None:
size = btn.GetSize()
btn.SetMinSize(size)
btn.SetMaxSize(size)
btn.SetToolTipString("{} {}".format(name.capitalize(), self.entityName))
btn.Bind(wx.EVT_BUTTON, func)
setattr(self, "btn%s" % name.capitalize(), btn)
self.navSizer.Add(btn, 0, wx.ALL | wx.ALIGN_CENTER_VERTICAL, 2)
self.SetSizer(self.navSizer)
self.Layout()
self.refreshEntityList()
def SetEditorValidator(self, validator=None):
""" Sets validator class (not an instance of the class) """
self.validator = validator
def getEntitiesFromContext(self):
""" Gets list of entities from current context """
raise NotImplementedError()
def DoNew(self, name):
"""Override method to do new entity logic. Must return the new entity"""
raise NotImplementedError()
def DoCopy(self, entity, name):
"""Override method to copy entity. Must return the copy"""
raise NotImplementedError()
def DoRename(self, entity, name):
"""Override method to rename an entity"""
raise NotImplementedError()
def DoDelete(self, entity):
"""Override method to delete entity"""
raise NotImplementedError()
def OnNew(self, event):
dlg = TextEntryValidatedDialog(self, self.validator,
"Enter a name for your new {}:".format(self.entityName),
"New {}".format(self.entityName))
dlg.CenterOnParent()
if dlg.ShowModal() == wx.ID_OK:
new = self.DoNew(dlg.GetValue().strip())
self.refreshEntityList(new)
wx.PostEvent(self.entityChoices, wx.CommandEvent(wx.wxEVT_COMMAND_CHOICE_SELECTED))
else:
return False
def OnCopy(self, event):
dlg = TextEntryValidatedDialog(self, self.validator,
"Enter a name for your {} copy:".format(self.entityName),
"Copy {}".format(self.entityName))
active = self.getActiveEntity()
dlg.SetValue("{} Copy".format(active.name))
dlg.txtctrl.SetInsertionPointEnd()
dlg.CenterOnParent()
if dlg.ShowModal() == wx.ID_OK:
copy = self.DoCopy(active, dlg.GetValue().strip())
self.refreshEntityList(copy)
wx.PostEvent(self.entityChoices, wx.CommandEvent(wx.wxEVT_COMMAND_CHOICE_SELECTED))
def OnRename(self, event):
dlg = TextEntryValidatedDialog(self, self.validator,
"Enter a new name for your {}:".format(self.entityName),
"Rename {}".format(self.entityName))
active = self.getActiveEntity()
dlg.SetValue(active.name)
dlg.txtctrl.SetInsertionPointEnd()
dlg.CenterOnParent()
if dlg.ShowModal() == wx.ID_OK:
self.DoRename(active, dlg.GetValue().strip())
self.refreshEntityList(active)
wx.PostEvent(self.entityChoices, wx.CommandEvent(wx.wxEVT_COMMAND_CHOICE_SELECTED))
def OnDelete(self, event):
dlg = wx.MessageDialog(self,
"Do you really want to delete the {} {}?".format(self.getActiveEntity().name, self.entityName),
"Confirm Delete", wx.YES | wx.NO | wx.ICON_QUESTION)
dlg.CenterOnParent()
if dlg.ShowModal() == wx.ID_YES:
self.DoDelete(self.getActiveEntity())
self.refreshEntityList()
wx.PostEvent(self.entityChoices, wx.CommandEvent(wx.wxEVT_COMMAND_CHOICE_SELECTED))
def refreshEntityList(self, selected=None):
self.choices = self.getEntitiesFromContext()
self.entityChoices.Clear()
self.entityChoices.AppendItems(map(lambda p: p.name, self.choices))
if selected:
idx = self.choices.index(selected)
self.entityChoices.SetSelection(idx)
else:
self.entityChoices.SetSelection(0)
def getActiveEntity(self):
if len(self.choices) == 0:
return None
return self.choices[self.entityChoices.GetSelection()]
def setActiveEntity(self, entity):
self.entityChoices.SetSelection(self.choices.index(entity))
def checkEntitiesExist(self):
if len(self.choices) == 0:
self.Parent.Hide()
if self.OnNew(None) is False:
return False
self.Parent.Show()
return True

View File

@@ -0,0 +1,258 @@
import wx
import service
import gui.display as d
from gui.bitmapLoader import BitmapLoader
import gui.PFSearchBox as SBox
from gui.marketBrowser import SearchBox
from wx.lib.buttons import GenBitmapButton
class BaseImplantEditorView (wx.Panel):
def addMarketViewImage(self, iconFile):
if iconFile is None:
return -1
bitmap = BitmapLoader.getBitmap(iconFile, "icons")
if bitmap is None:
return -1
else:
return self.availableImplantsImageList.Add(bitmap)
def __init__(self, parent):
wx.Panel.__init__ (self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition, size=wx.DefaultSize, style=wx.TAB_TRAVERSAL)
self.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
pmainSizer = wx.BoxSizer(wx.HORIZONTAL)
availableSizer = wx.BoxSizer(wx.VERTICAL)
self.searchBox = SearchBox(self)
self.itemView = ItemView(self)
self.itemView.Hide()
availableSizer.Add(self.searchBox, 0, wx.EXPAND)
availableSizer.Add(self.itemView, 1, wx.EXPAND)
'''
self.availableImplantsSearch = wx.SearchCtrl(self, wx.ID_ANY, style=wx.TE_PROCESS_ENTER)
self.availableImplantsSearch.ShowCancelButton(True)
availableSizer.Add(self.availableImplantsSearch, 0, wx.BOTTOM | wx.EXPAND, 2)
'''
self.availableImplantsTree = wx.TreeCtrl(self, wx.ID_ANY, style=wx.TR_DEFAULT_STYLE | wx.TR_HIDE_ROOT)
root = self.availableRoot = self.availableImplantsTree.AddRoot("Available")
self.availableImplantsImageList = wx.ImageList(16, 16)
self.availableImplantsTree.SetImageList(self.availableImplantsImageList)
availableSizer.Add(self.availableImplantsTree, 1, wx.EXPAND)
pmainSizer.Add(availableSizer, 1, wx.ALL | wx.EXPAND, 5)
buttonSizer = wx.BoxSizer(wx.VERTICAL)
buttonSizer.AddSpacer(( 0, 0), 1)
self.btnAdd = GenBitmapButton(self, wx.ID_ADD, BitmapLoader.getBitmap("fit_add_small", "gui"), style = wx.BORDER_NONE)
buttonSizer.Add(self.btnAdd, 0)
self.btnRemove = GenBitmapButton(self, wx.ID_REMOVE, BitmapLoader.getBitmap("fit_delete_small", "gui"), style = wx.BORDER_NONE)
buttonSizer.Add(self.btnRemove, 0)
buttonSizer.AddSpacer(( 0, 0), 1)
pmainSizer.Add(buttonSizer, 0, wx.EXPAND, 0)
characterImplantSizer = wx.BoxSizer(wx.VERTICAL)
self.pluggedImplantsTree = AvailableImplantsView(self)
characterImplantSizer.Add(self.pluggedImplantsTree, 1, wx.ALL|wx.EXPAND, 5)
pmainSizer.Add(characterImplantSizer, 1, wx.EXPAND, 5)
self.SetSizer(pmainSizer)
# Populate the market tree
sMkt = service.Market.getInstance()
for mktGrp in sMkt.getImplantTree():
iconId = self.addMarketViewImage(sMkt.getIconByMarketGroup(mktGrp))
childId = self.availableImplantsTree.AppendItem(root, mktGrp.name, iconId, data=wx.TreeItemData(mktGrp.ID))
if sMkt.marketGroupHasTypesCheck(mktGrp) is False:
self.availableImplantsTree.AppendItem(childId, "dummy")
self.availableImplantsTree.SortChildren(self.availableRoot)
#Bind the event to replace dummies by real data
self.availableImplantsTree.Bind(wx.EVT_TREE_ITEM_EXPANDING, self.expandLookup)
self.availableImplantsTree.Bind(wx.EVT_TREE_ITEM_ACTIVATED, self.itemSelected)
self.itemView.Bind(wx.EVT_LIST_ITEM_ACTIVATED, self.itemSelected)
#Bind add & remove buttons
self.btnAdd.Bind(wx.EVT_BUTTON, self.itemSelected)
self.btnRemove.Bind(wx.EVT_BUTTON, self.removeItem)
# We update with an empty list first to set the initial size for Layout(), then update later with actual
# implants for character. This helps with sizing issues.
self.pluggedImplantsTree.update([])
self.bindContext()
self.Layout()
self.update()
def bindContext(self):
# Binds self.contextChanged to whatever changes the context
raise NotImplementedError()
def getImplantsFromContext(self):
""" Gets list of implants from current context """
raise NotImplementedError()
def addImplantToContext(self, item):
""" Adds implant to the current context"""
raise NotImplementedError()
def removeImplantFromContext(self, implant):
""" Removes implant from the current context"""
raise NotImplementedError()
def update(self):
"""Updates implant list based off the current context"""
self.implants = self.getImplantsFromContext()
self.implants.sort(key=lambda i: int(i.getModifiedItemAttr("implantness")))
self.pluggedImplantsTree.update(self.implants)
def contextChanged(self, event):
self.update()
event.Skip()
def expandLookup(self, event):
tree = self.availableImplantsTree
sMkt = service.Market.getInstance()
parent = event.Item
child, _ = tree.GetFirstChild(parent)
text = tree.GetItemText(child)
if text == "dummy" or text == "itemdummy":
tree.Delete(child)
# if the dummy item is a market group, replace with actual market groups
if text == "dummy":
#Add 'real stoof!' instead
currentMktGrp = sMkt.getMarketGroup(tree.GetPyData(parent), eager="children")
for childMktGrp in sMkt.getMarketGroupChildren(currentMktGrp):
iconId = self.addMarketViewImage(sMkt.getIconByMarketGroup(childMktGrp))
childId = tree.AppendItem(parent, childMktGrp.name, iconId, data=wx.TreeItemData(childMktGrp.ID))
if sMkt.marketGroupHasTypesCheck(childMktGrp) is False:
tree.AppendItem(childId, "dummy")
else:
tree.AppendItem(childId, "itemdummy")
# replace dummy with actual items
if text == "itemdummy":
currentMktGrp = sMkt.getMarketGroup(tree.GetPyData(parent))
items = sMkt.getItemsByMarketGroup(currentMktGrp)
for item in items:
iconId = self.addMarketViewImage(item.icon.iconFile)
tree.AppendItem(parent, item.name, iconId, data=wx.TreeItemData(item))
tree.SortChildren(parent)
def itemSelected(self, event):
if event.EventObject is self.btnAdd:
# janky fix that sets EventObject so that we don't have similar code elsewhere.
if self.itemView.IsShown():
event.EventObject = self.itemView
else:
event.EventObject = self.availableImplantsTree
if event.EventObject is self.itemView:
curr = event.EventObject.GetFirstSelected()
while curr != -1:
item = self.itemView.items[curr]
self.addImplantToContext(item)
curr = event.EventObject.GetNextSelected(curr)
else:
root = self.availableImplantsTree.GetSelection()
if not root.IsOk():
return
nchilds = self.availableImplantsTree.GetChildrenCount(root)
if nchilds == 0:
item = self.availableImplantsTree.GetPyData(root)
self.addImplantToContext(item)
else:
event.Skip()
return
self.update()
def removeItem(self, event):
pos = self.pluggedImplantsTree.GetFirstSelected()
if pos != -1:
self.removeImplantFromContext(self.implants[pos])
self.update()
class AvailableImplantsView(d.Display):
DEFAULT_COLS = ["attr:implantness",
"Base Icon",
"Base Name"]
def __init__(self, parent):
d.Display.__init__(self, parent, style=wx.LC_SINGLE_SEL)
self.Bind(wx.EVT_LEFT_DCLICK, parent.removeItem)
class ItemView(d.Display):
DEFAULT_COLS = ["Base Icon",
"Base Name",
"attr:power,,,True",
"attr:cpu,,,True"]
def __init__(self, parent):
d.Display.__init__(self, parent)
self.parent = parent
self.searchBox = parent.searchBox
self.items = []
# Bind search actions
self.searchBox.Bind(SBox.EVT_TEXT_ENTER, self.scheduleSearch)
self.searchBox.Bind(SBox.EVT_SEARCH_BTN, self.scheduleSearch)
self.searchBox.Bind(SBox.EVT_CANCEL_BTN, self.clearSearch)
self.searchBox.Bind(SBox.EVT_TEXT, self.scheduleSearch)
def clearSearch(self, event=None):
if self.IsShown():
self.parent.availableImplantsTree.Show()
self.Hide()
self.parent.Layout()
if event:
self.searchBox.Clear()
self.items = []
self.update(self.items)
def scheduleSearch(self, event=None):
sMkt = service.Market.getInstance()
search = self.searchBox.GetLineText(0)
# Make sure we do not count wildcard as search symbol
realsearch = search.replace("*", "")
# Show nothing if query is too short
if len(realsearch) < 3:
self.clearSearch()
return
sMkt.searchItems(search, self.populateSearch, ["Implant"])
def populateSearch(self, items):
if not self.IsShown():
self.parent.availableImplantsTree.Hide()
self.Show()
self.parent.Layout()
self.items = sorted(list(items), key=lambda i: i.name)
self.update(self.items)

View File

@@ -19,15 +19,80 @@
import wx
import gui.mainFrame
import wx.lib.newevent
import wx.gizmos
from gui.bitmapLoader import BitmapLoader
import service
import gui.display as d
from gui.contextMenu import ContextMenu
from wx.lib.buttons import GenBitmapButton
import gui.globalEvents as GE
from gui.builtinViews.implantEditor import BaseImplantEditorView
from gui.builtinViews.entityEditor import EntityEditor, BaseValidator
class CharacterTextValidor(BaseValidator):
def __init__(self):
BaseValidator.__init__(self)
def Clone(self):
return CharacterTextValidor()
def Validate(self, win):
profileEditor = win.Parent
textCtrl = self.GetWindow()
text = textCtrl.GetValue().strip()
try:
if len(text) == 0:
raise ValueError("You must supply a name for the Character!")
elif text in [x.name for x in profileEditor.entityEditor.choices]:
raise ValueError("Character name already in use, please choose another.")
return True
except ValueError, e:
wx.MessageBox(u"{}".format(e), "Error")
textCtrl.SetFocus()
return False
class CharacterEntityEditor(EntityEditor):
def __init__(self, parent):
EntityEditor.__init__(self, parent, "Character")
self.SetEditorValidator(CharacterTextValidor)
def getEntitiesFromContext(self):
sChar = service.Character.getInstance()
charList = sorted(sChar.getCharacterList(), key=lambda c: c.name)
# Do some processing to ensure that we have All 0 and All 5 at the top
all5 = sChar.all5()
all0 = sChar.all0()
charList.remove(all5)
charList.remove(all0)
charList.insert(0, all5)
charList.insert(0, all0)
return charList
def DoNew(self, name):
sChar = service.Character.getInstance()
return sChar.new(name)
def DoRename(self, entity, name):
sChar = service.Character.getInstance()
sChar.rename(entity, name)
def DoCopy(self, entity, name):
sChar = service.Character.getInstance()
copy = sChar.copy(entity)
sChar.rename(copy, name)
return copy
def DoDelete(self, entity):
sChar = service.Character.getInstance()
sChar.delete(entity)
class CharacterEditor(wx.Frame):
def __init__(self, parent):
@@ -38,75 +103,26 @@ class CharacterEditor(wx.Frame):
self.SetIcon(i)
self.mainFrame = parent
#self.disableWin = wx.WindowDisabler(self)
self.SetBackgroundColour( wx.SystemSettings.GetColour( wx.SYS_COLOUR_BTNFACE ) )
sFit = service.Fit.getInstance()
self.SetBackgroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_BTNFACE))
mainSizer = wx.BoxSizer(wx.VERTICAL)
self.navSizer = wx.BoxSizer(wx.HORIZONTAL)
sChar = service.Character.getInstance()
self.btnSave = wx.Button(self, wx.ID_SAVE)
self.btnSave.Hide()
self.btnSave.Bind(wx.EVT_BUTTON, self.processRename)
self.characterRename = wx.TextCtrl(self, wx.ID_ANY, style=wx.TE_PROCESS_ENTER)
self.characterRename.Hide()
self.characterRename.Bind(wx.EVT_TEXT_ENTER, self.processRename)
self.charChoice = wx.Choice(self, wx.ID_ANY, style=0)
self.navSizer.Add(self.charChoice, 1, wx.ALL | wx.EXPAND, 5)
charList = sChar.getCharacterList()
for id, name, active in charList:
i = self.charChoice.Append(name, id)
if active:
self.charChoice.SetSelection(i)
self.navSizer.Add(self.btnSave, 0, wx.ALL , 5)
buttons = (("new", wx.ART_NEW),
("rename", BitmapLoader.getBitmap("rename", "gui")),
("copy", wx.ART_COPY),
("delete", wx.ART_DELETE))
size = None
for name, art in buttons:
bitmap = wx.ArtProvider.GetBitmap(art, wx.ART_BUTTON) if name != "rename" else art
btn = wx.BitmapButton(self, wx.ID_ANY, bitmap)
if size is None:
size = btn.GetSize()
btn.SetMinSize(size)
btn.SetMaxSize(size)
btn.SetToolTipString("%s character" % name.capitalize())
btn.Bind(wx.EVT_BUTTON, getattr(self, name))
setattr(self, "btn%s" % name.capitalize(), btn)
self.navSizer.Add(btn, 0, wx.ALL | wx.ALIGN_CENTER_VERTICAL, 2)
mainSizer.Add(self.navSizer, 0, wx.ALL | wx.EXPAND, 5)
self.entityEditor = CharacterEntityEditor(self)
mainSizer.Add(self.entityEditor, 0, wx.ALL | wx.EXPAND, 2)
# Default drop down to current fit's character
self.entityEditor.setActiveEntity(sFit.character)
self.viewsNBContainer = wx.Notebook(self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, 0)
self.sview = SkillTreeView(self.viewsNBContainer)
#self.iview = ImplantsTreeView(self.viewsNBContainer)
#=======================================================================
# RC2
#self.iview.Show(False)
#=======================================================================
self.iview = ImplantEditorView(self.viewsNBContainer)
self.aview = APIView(self.viewsNBContainer)
self.viewsNBContainer.AddPage(self.sview, "Skills")
#=======================================================================
# Disabled for RC2
# self.viewsNBContainer.AddPage(self.iview, "Implants")
#=======================================================================
self.viewsNBContainer.AddPage(self.iview, "Implants")
self.viewsNBContainer.AddPage(self.aview, "API")
mainSizer.Add(self.viewsNBContainer, 1, wx.EXPAND | wx.ALL, 5)
@@ -124,7 +140,6 @@ class CharacterEditor(wx.Frame):
bSizerButtons.AddStretchSpacer()
bSizerButtons.Add(self.btnOK, 0, wx.ALL, 5)
self.btnSaveChar.Bind(wx.EVT_BUTTON, self.saveChar)
self.btnSaveAs.Bind(wx.EVT_BUTTON, self.saveCharAs)
self.btnRevert.Bind(wx.EVT_BUTTON, self.revertChar)
@@ -139,16 +154,12 @@ class CharacterEditor(wx.Frame):
self.Centre(wx.BOTH)
charID = self.getActiveCharacter()
if sChar.getCharName(charID) in ("All 0", "All 5"):
self.restrict()
self.registerEvents()
self.Bind(wx.EVT_CLOSE, self.closeEvent)
self.Bind(GE.CHAR_LIST_UPDATED, self.refreshCharacterList)
self.entityEditor.Bind(wx.EVT_CHOICE, self.charChanged)
def btnRestrict(self):
sChar = service.Character.getInstance()
charID = self.getActiveCharacter()
char = sChar.getCharacter(charID)
char = self.entityEditor.getActiveEntity()
# enable/disable character saving stuff
self.btnSaveChar.Enable(not char.ro and char.isDirty)
@@ -156,46 +167,34 @@ class CharacterEditor(wx.Frame):
self.btnRevert.Enable(char.isDirty)
def refreshCharacterList(self, event=None):
sChar = service.Character.getInstance()
charList = sChar.getCharacterList()
active = self.getActiveCharacter()
self.charChoice.Clear()
for id, name, _ in charList:
i = self.charChoice.Append(name, id)
if active == id:
self.charChoice.SetSelection(i)
"""This is only called when we save a modified character"""
active = self.entityEditor.getActiveEntity()
self.entityEditor.refreshEntityList(active)
self.btnRestrict()
if event:
event.Skip()
def editingFinished(self, event):
#del self.disableWin
wx.PostEvent(self.mainFrame, GE.CharListUpdated())
self.Destroy()
def registerEvents(self):
self.Bind(wx.EVT_CLOSE, self.closeEvent)
self.Bind(GE.CHAR_LIST_UPDATED, self.refreshCharacterList)
self.charChoice.Bind(wx.EVT_CHOICE, self.charChanged)
def saveChar(self, event):
sChr = service.Character.getInstance()
charID = self.getActiveCharacter()
sChr.saveCharacter(charID)
self.sview.populateSkillTree()
char = self.entityEditor.getActiveEntity()
sChr.saveCharacter(char.ID)
wx.PostEvent(self, GE.CharListUpdated())
def saveCharAs(self, event):
charID = self.getActiveCharacter()
dlg = SaveCharacterAs(self, charID)
char = self.entityEditor.getActiveEntity()
dlg = SaveCharacterAs(self, char.ID)
dlg.ShowModal()
self.sview.populateSkillTree()
def revertChar(self, event):
sChr = service.Character.getInstance()
charID = self.getActiveCharacter()
sChr.revertCharacter(charID)
self.sview.populateSkillTree()
char = self.entityEditor.getActiveEntity()
sChr.revertCharacter(char.ID)
wx.PostEvent(self, GE.CharListUpdated())
def closeEvent(self, event):
@@ -204,120 +203,25 @@ class CharacterEditor(wx.Frame):
self.Destroy()
def restrict(self):
self.btnRename.Enable(False)
self.btnDelete.Enable(False)
self.aview.stDisabledTip.Show()
self.aview.inputID.Enable(False)
self.aview.inputKey.Enable(False)
self.aview.charChoice.Enable(False)
self.aview.btnFetchCharList.Enable(False)
self.aview.btnFetchSkills.Enable(False)
self.aview.stStatus.SetLabel("")
self.aview.Layout()
self.entityEditor.btnRename.Enable(False)
self.entityEditor.btnDelete.Enable(False)
def unrestrict(self):
self.btnRename.Enable(True)
self.btnDelete.Enable(True)
self.aview.stDisabledTip.Hide()
self.aview.inputID.Enable(True)
self.aview.inputKey.Enable(True)
self.aview.btnFetchCharList.Enable(True)
self.aview.btnFetchSkills.Enable(True)
self.aview.stStatus.SetLabel("")
self.aview.Layout()
self.entityEditor.btnRename.Enable()
self.entityEditor.btnDelete.Enable()
def charChanged(self, event):
self.sview.populateSkillTree()
sChar = service.Character.getInstance()
charID = self.getActiveCharacter()
if sChar.getCharName(charID) in ("All 0", "All 5"):
char = self.entityEditor.getActiveEntity()
if char.name in ("All 0", "All 5"):
self.restrict()
else:
self.unrestrict()
wx.PostEvent(self, GE.CharChanged())
self.btnRestrict()
if event is not None:
event.Skip()
def getActiveCharacter(self):
selection = self.charChoice.GetCurrentSelection()
return self.charChoice.GetClientData(selection) if selection is not None else None
def new(self, event):
sChar = service.Character.getInstance()
charID = sChar.new()
id = self.charChoice.Append(sChar.getCharName(charID), charID)
self.charChoice.SetSelection(id)
self.unrestrict()
self.btnSave.SetLabel("Create")
self.rename(None)
self.charChanged(None)
def rename(self, event):
if event is not None:
self.btnSave.SetLabel("Rename")
self.charChoice.Hide()
self.characterRename.Show()
self.navSizer.Replace(self.charChoice, self.characterRename)
self.characterRename.SetFocus()
for btn in (self.btnNew, self.btnCopy, self.btnRename, self.btnDelete):
btn.Hide()
self.btnSave.Show()
self.navSizer.Layout()
sChar = service.Character.getInstance()
currName = sChar.getCharName(self.getActiveCharacter())
self.characterRename.SetValue(currName)
self.characterRename.SetSelection(0, len(currName))
def processRename(self, event):
sChar = service.Character.getInstance()
newName = self.characterRename.GetLineText(0)
if newName == "All 0" or newName == "All 5":
newName = newName + " bases are belong to us"
charID = self.getActiveCharacter()
sChar.rename(charID, newName)
self.charChoice.Show()
self.characterRename.Hide()
self.navSizer.Replace(self.characterRename, self.charChoice)
for btn in (self.btnNew, self.btnCopy, self.btnRename, self.btnDelete):
btn.Show()
self.btnSave.Hide()
self.navSizer.Layout()
self.refreshCharacterList()
def copy(self, event):
sChar = service.Character.getInstance()
charID = sChar.copy(self.getActiveCharacter())
id = self.charChoice.Append(sChar.getCharName(charID), charID)
self.charChoice.SetSelection(id)
self.unrestrict()
self.btnSave.SetLabel("Copy")
self.rename(None)
wx.PostEvent(self, GE.CharChanged())
def delete(self, event):
dlg = wx.MessageDialog(self,
"Do you really want to delete this character?",
"Confirm Delete", wx.YES | wx.NO | wx.ICON_QUESTION)
if dlg.ShowModal() == wx.ID_YES:
sChar = service.Character.getInstance()
sChar.delete(self.getActiveCharacter())
sel = self.charChoice.GetSelection()
self.charChoice.Delete(sel)
self.charChoice.SetSelection(sel - 1)
newSelection = self.getActiveCharacter()
if sChar.getCharName(newSelection) in ("All 0", "All 5"):
self.restrict()
wx.PostEvent(self, GE.CharChanged())
def Destroy(self):
sFit = service.Fit.getInstance()
fitID = self.mainFrame.getActiveFit()
@@ -329,7 +233,8 @@ class CharacterEditor(wx.Frame):
class SkillTreeView (wx.Panel):
def __init__(self, parent):
wx.Panel.__init__ (self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition, size=wx.Size(500, 300), style=wx.TAB_TRAVERSAL)
wx.Panel.__init__ (self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition, size=wx.DefaultSize, style=wx.TAB_TRAVERSAL)
self.charEditor = self.Parent.Parent # first parent is Notebook, second is Character Editor
self.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
pmainSizer = wx.BoxSizer(wx.VERTICAL)
@@ -356,6 +261,10 @@ class SkillTreeView (wx.Panel):
tree.Bind(wx.EVT_TREE_ITEM_EXPANDING, self.expandLookup)
tree.Bind(wx.EVT_TREE_ITEM_RIGHT_CLICK, self.scheduleMenu)
# bind the Character selection event
self.charEditor.entityEditor.Bind(wx.EVT_CHOICE, self.populateSkillTree)
self.charEditor.Bind(GE.CHAR_LIST_UPDATED, self.populateSkillTree)
srcContext = "skillItem"
itemContext = "Skill"
context = (srcContext, itemContext)
@@ -386,11 +295,10 @@ class SkillTreeView (wx.Panel):
self.Layout()
def populateSkillTree(self):
def populateSkillTree(self, event=None):
sChar = service.Character.getInstance()
charID = self.Parent.Parent.getActiveCharacter()
dirtySkills = sChar.getDirtySkills(charID)
dirtyGroups = set([skill.item.group.ID for skill in dirtySkills])
char = self.charEditor.entityEditor.getActiveEntity()
dirtyGroups = set([skill.item.group.ID for skill in char.dirtySkills])
groups = sChar.getSkillGroups()
imageId = self.skillBookImageId
@@ -407,6 +315,9 @@ class SkillTreeView (wx.Panel):
tree.SortChildren(root)
if event:
event.Skip()
def expandLookup(self, event):
root = event.Item
tree = self.skillTreeListCtrl
@@ -416,11 +327,11 @@ class SkillTreeView (wx.Panel):
#Get the real intrestin' stuff
sChar = service.Character.getInstance()
char = self.Parent.Parent.getActiveCharacter()
char = self.charEditor.entityEditor.getActiveEntity()
for id, name in sChar.getSkills(tree.GetPyData(root)):
iconId = self.skillBookImageId
childId = tree.AppendItem(root, name, iconId, data=wx.TreeItemData(id))
level, dirty = sChar.getSkillLevel(char, id)
level, dirty = sChar.getSkillLevel(char.ID, id)
tree.SetItemText(childId, "Level %d" % level if isinstance(level, int) else level, 1)
if dirty:
tree.SetItemTextColour(childId, wx.BLUE)
@@ -436,10 +347,9 @@ class SkillTreeView (wx.Panel):
if self.skillTreeListCtrl.GetChildrenCount(item) > 0:
return
sChar = service.Character.getInstance()
charID = self.Parent.Parent.getActiveCharacter()
char = self.charEditor.entityEditor.getActiveEntity()
sMkt = service.Market.getInstance()
if sChar.getCharName(charID) not in ("All 0", "All 5"):
if char.name not in ("All 0", "All 5"):
self.levelChangeMenu.selection = sMkt.getItem(self.skillTreeListCtrl.GetPyData(item))
self.PopupMenu(self.levelChangeMenu)
else:
@@ -450,21 +360,21 @@ class SkillTreeView (wx.Panel):
level = self.levelIds.get(event.Id)
sChar = service.Character.getInstance()
charID = self.Parent.Parent.getActiveCharacter()
char = self.charEditor.entityEditor.getActiveEntity()
selection = self.skillTreeListCtrl.GetSelection()
skillID = self.skillTreeListCtrl.GetPyData(selection)
if level is not None:
self.skillTreeListCtrl.SetItemText(selection, "Level %d" % level if isinstance(level, int) else level, 1)
sChar.changeLevel(charID, skillID, level, persist=True)
sChar.changeLevel(char.ID, skillID, level, persist=True)
elif event.Id == self.revertID:
sChar.revertLevel(charID, skillID)
sChar.revertLevel(char.ID, skillID)
elif event.Id == self.saveID:
sChar.saveSkill(charID, skillID)
sChar.saveSkill(char.ID, skillID)
self.skillTreeListCtrl.SetItemTextColour(selection, None)
dirtySkills = sChar.getDirtySkills(charID)
dirtySkills = sChar.getDirtySkills(char.ID)
dirtyGroups = set([skill.item.group.ID for skill in dirtySkills])
parentID = self.skillTreeListCtrl.GetItemParent(selection)
@@ -473,156 +383,69 @@ class SkillTreeView (wx.Panel):
if groupID not in dirtyGroups:
self.skillTreeListCtrl.SetItemTextColour(parentID, None)
wx.PostEvent(self.Parent.Parent, GE.CharListUpdated())
event.Skip()
class ImplantsTreeView (wx.Panel):
def addMarketViewImage(self, iconFile):
if iconFile is None:
return -1
bitmap = BitmapLoader.getBitmap(iconFile, "icons")
if bitmap is None:
return -1
else:
return self.availableImplantsImageList.Add(bitmap)
class ImplantEditorView(BaseImplantEditorView):
def __init__(self, parent):
wx.Panel.__init__ (self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition, size=wx.Size(500, 300), style=wx.TAB_TRAVERSAL)
BaseImplantEditorView.__init__ (self, parent)
pmainSizer = wx.BoxSizer(wx.HORIZONTAL)
self.determineEnabled()
availableSizer = wx.BoxSizer(wx.VERTICAL)
pmainSizer.Add(availableSizer, 1, wx.ALL | wx.EXPAND, 5)
if "__WXGTK__" in wx.PlatformInfo:
self.pluggedImplantsTree.Bind(wx.EVT_RIGHT_UP, self.scheduleMenu)
else:
self.pluggedImplantsTree.Bind(wx.EVT_RIGHT_DOWN, self.scheduleMenu)
self.availableImplantsSearch = wx.SearchCtrl(self, wx.ID_ANY, style=wx.TE_PROCESS_ENTER)
self.availableImplantsSearch.ShowCancelButton(True)
availableSizer.Add(self.availableImplantsSearch, 0, wx.BOTTOM | wx.EXPAND, 2)
def bindContext(self):
self.Parent.Parent.entityEditor.Bind(wx.EVT_CHOICE, self.contextChanged)
self.availableImplantsTree = wx.TreeCtrl(self, wx.ID_ANY, style=wx.TR_DEFAULT_STYLE | wx.TR_HIDE_ROOT)
root = self.availableRoot = self.availableImplantsTree.AddRoot("Available")
self.availableImplantsImageList = wx.ImageList(16, 16)
self.availableImplantsTree.SetImageList(self.availableImplantsImageList)
def contextChanged(self, event):
BaseImplantEditorView.contextChanged(self, event)
self.determineEnabled()
availableSizer.Add(self.availableImplantsTree, 1, wx.EXPAND)
buttonSizer = wx.BoxSizer(wx.VERTICAL)
pmainSizer.Add(buttonSizer, 0, wx.TOP, 5)
self.btnAdd = GenBitmapButton(self, wx.ID_ADD, BitmapLoader.getBitmap("fit_add_small", "gui"), style = wx.BORDER_NONE)
buttonSizer.Add(self.btnAdd, 0)
self.btnRemove = GenBitmapButton(self, wx.ID_REMOVE, BitmapLoader.getBitmap("fit_delete_small", "gui"), style = wx.BORDER_NONE)
buttonSizer.Add(self.btnRemove, 0)
self.pluggedImplantsTree = AvailableImplantsView(self, style=wx.LC_SINGLE_SEL)
pmainSizer.Add(self.pluggedImplantsTree, 1, wx.ALL | wx.EXPAND, 5)
self.SetSizer(pmainSizer)
# Populate the market tree
sMkt = service.Market.getInstance()
for mktGrp in sMkt.getImplantTree():
iconId = self.addMarketViewImage(sMkt.getIconByMarketGroup(mktGrp))
childId = self.availableImplantsTree.AppendItem(root, mktGrp.name, iconId, data=wx.TreeItemData(mktGrp.ID))
if sMkt.marketGroupHasTypesCheck(mktGrp) is False:
self.availableImplantsTree.AppendItem(childId, "dummy")
self.availableImplantsTree.SortChildren(self.availableRoot)
#Bind the event to replace dummies by real data
self.availableImplantsTree.Bind(wx.EVT_TREE_ITEM_EXPANDING, self.expandLookup)
#Bind add & remove buttons
self.btnAdd.Bind(wx.EVT_BUTTON, self.addImplant)
self.btnRemove.Bind(wx.EVT_BUTTON, self.removeImplant)
#Bind the change of a character*
self.Parent.Parent.Bind(GE.CHAR_CHANGED, self.charChanged)
self.Enable(False)
self.Layout()
def update(self, implants):
self.implants = implants[:]
self.implants.sort(key=lambda i: int(i.getModifiedItemAttr("implantness")))
self.pluggedImplantsTree.update(self.implants)
def charChanged(self, event):
def getImplantsFromContext(self):
sChar = service.Character.getInstance()
charID = self.Parent.Parent.getActiveCharacter()
name = sChar.getCharName(charID)
if name == "All 0" or name == "All 5":
char = self.Parent.Parent.entityEditor.getActiveEntity()
return sChar.getImplants(char.ID)
def addImplantToContext(self, item):
sChar = service.Character.getInstance()
char = self.Parent.Parent.entityEditor.getActiveEntity()
sChar.addImplant(char.ID, item.ID)
def removeImplantFromContext(self, implant):
sChar = service.Character.getInstance()
char = self.Parent.Parent.entityEditor.getActiveEntity()
sChar.removeImplant(char.ID, implant)
def scheduleMenu(self, event):
event.Skip()
wx.CallAfter(self.spawnMenu)
def spawnMenu(self):
context = (("implantEditor",),)
# fuck good coding practices, passing a pointer to the character editor here for [reasons] =D
# (see implantSets context class for info)
menu = ContextMenu.getMenu((self.Parent.Parent,), *context)
self.PopupMenu(menu)
def determineEnabled(self):
char = self.Parent.Parent.entityEditor.getActiveEntity()
if char.name in ("All 0", "All 5"):
self.Enable(False)
else:
self.Enable(True)
self.Enable()
self.update(sChar.getImplants(charID))
event.Skip()
def expandLookup(self, event):
tree = self.availableImplantsTree
root = event.Item
child, cookie = tree.GetFirstChild(root)
text = tree.GetItemText(child)
if text == "dummy" or text == "itemdummy":
sMkt = service.Market.getInstance()
#A DUMMY! Keeeel!!! EBUL DUMMY MUST DIAF!
tree.Delete(child)
if text == "dummy":
#Add 'real stoof!' instead
for id, name, iconFile, more in sMkt.getChildren(tree.GetPyData(root)):
iconId = self.addMarketViewImage(iconFile)
childId = tree.AppendItem(root, name, iconId, data=wx.TreeItemData(id))
if more:
tree.AppendItem(childId, "dummy")
else:
tree.AppendItem(childId, "itemdummy")
if text == "itemdummy":
sMkt = service.Market.getInstance()
data, usedMetas = sMkt.getVariations(tree.GetPyData(root))
for item in data:
id = item.ID
name = item.name
iconFile = item.icon.iconFile
iconId = self.addMarketViewImage(iconFile)
tree.AppendItem(root, name, iconId, data=wx.TreeItemData(id))
tree.SortChildren(root)
def addImplant(self, event):
root = self.availableImplantsTree.GetSelection()
if not root.IsOk():
return
nchilds = self.availableImplantsTree.GetChildrenCount(root)
sChar = service.Character.getInstance()
charID = self.Parent.Parent.getActiveCharacter()
if nchilds == 0:
itemID = self.availableImplantsTree.GetPyData(root)
sChar.addImplant(charID, itemID)
self.update(sChar.getImplants(charID))
def removeImplant(self, event):
pos = self.pluggedImplantsTree.GetFirstSelected()
if pos != -1:
sChar = service.Character.getInstance()
charID = self.Parent.Parent.getActiveCharacter()
sChar.removeImplant(charID, self.implants[pos].slot)
self.update(sChar.getImplants(charID))
class AvailableImplantsView(d.Display):
DEFAULT_COLS = ["Base Name",
"attr:implantness"]
def __init__(self, parent, style):
d.Display.__init__(self, parent, style=style)
class APIView (wx.Panel):
def __init__(self, parent):
wx.Panel.__init__ (self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition, size=wx.Size(500, 300), style=wx.TAB_TRAVERSAL)
self.Parent.Parent.Bind(GE.CHAR_CHANGED, self.charChanged)
self.charEditor = self.Parent.Parent # first parent is Notebook, second is Character Editor
self.SetBackgroundColour(wx.SystemSettings_GetColour(wx.SYS_COLOUR_WINDOW))
self.apiUrlCreatePredefined = u"https://community.eveonline.com/support/api-key/CreatePredefined?accessMask=8"
@@ -706,13 +529,17 @@ class APIView (wx.Panel):
self.hlEveAPI2 = wx.HyperlinkCtrl( self, wx.ID_ANY, self.apiUrlKeyList, self.apiUrlKeyList, wx.DefaultPosition, wx.DefaultSize, wx.HL_DEFAULT_STYLE )
pmainSizer.Add( self.hlEveAPI2, 0, wx.ALL, 2 )
self.charEditor.entityEditor.Bind(wx.EVT_CHOICE, self.charChanged)
self.SetSizer(pmainSizer)
self.Layout()
self.charChanged(None)
def charChanged(self, event):
sChar = service.Character.getInstance()
ID, key, char, chars = sChar.getApiDetails(self.Parent.Parent.getActiveCharacter())
activeChar = self.charEditor.entityEditor.getActiveEntity()
ID, key, char, chars = sChar.getApiDetails(activeChar.ID)
self.inputID.SetValue(str(ID))
self.inputKey.SetValue(key)
@@ -730,6 +557,14 @@ class APIView (wx.Panel):
self.charChoice.Enable(False)
self.btnFetchSkills.Enable(False)
if activeChar.name in ("All 0", "All 5"):
self.Enable(False)
self.stDisabledTip.Show()
self.Layout()
else:
self.Enable()
self.stDisabledTip.Hide()
self.Layout()
if event is not None:
event.Skip()

View File

@@ -83,9 +83,9 @@ class CharacterSelection(wx.Panel):
charList = sChar.getCharacterList()
picked = False
for id, name, active in charList:
currId = choice.Append(name, id)
if id == activeChar:
for char in charList:
currId = choice.Append(char.name, char.ID)
if char.ID == activeChar:
choice.SetSelection(currId)
self.charChanged(None)
picked = True

View File

@@ -346,9 +346,8 @@ class GangView ( ScrolledPanel ):
choice.Clear()
currSelFound = False
for char in charList:
id,name,_ = char
choice.Append(name, id)
if chCurrData == id:
choice.Append(char.name, char.ID)
if chCurrData == char.ID:
currSelFound = True
if chCurrSelection == -1:

View File

@@ -21,12 +21,62 @@ import wx
import service
import gui.display as d
import gui.marketBrowser as mb
import gui.mainFrame
from gui.builtinViewColumns.state import State
from gui.contextMenu import ContextMenu
import globalEvents as GE
class ImplantView(d.Display):
from eos.types import ImplantLocation
class ImplantView(wx.Panel):
def __init__(self, parent):
wx.Panel.__init__(self, parent, id=wx.ID_ANY, pos=wx.DefaultPosition, style=wx.TAB_TRAVERSAL )
self.mainFrame = gui.mainFrame.MainFrame.getInstance()
mainSizer = wx.BoxSizer(wx.VERTICAL)
self.implantDisplay = ImplantDisplay(self)
mainSizer.Add(self.implantDisplay, 1, wx.EXPAND, 0 )
radioSizer = wx.BoxSizer(wx.HORIZONTAL)
radioSizer.AddSpacer(( 0, 0), 1, wx.EXPAND, 5)
self.rbFit = wx.RadioButton(self, id=wx.ID_ANY, label="Use Fit-specific Implants", style=wx.RB_GROUP)
self.rbChar = wx.RadioButton(self, id=wx.ID_ANY, label="Use Character Implants")
radioSizer.Add(self.rbFit, 0, wx.ALL, 5)
radioSizer.Add(self.rbChar, 0, wx.ALL, 5)
radioSizer.AddSpacer((0, 0), 1, wx.EXPAND, 5)
mainSizer.Add(radioSizer, 0, wx.EXPAND, 5)
self.SetSizer( mainSizer )
self.SetAutoLayout(True)
self.Bind(wx.EVT_RADIOBUTTON, self.OnRadioSelect, self.rbFit)
self.Bind(wx.EVT_RADIOBUTTON, self.OnRadioSelect, self.rbChar)
self.mainFrame.Bind(GE.FIT_CHANGED, self.fitChanged)
def fitChanged(self, event):
sFit = service.Fit.getInstance()
activeFitID = self.mainFrame.getActiveFit()
fit = sFit.getFit(activeFitID)
if fit:
if fit.implantSource == ImplantLocation.FIT:
self.rbFit.SetValue(True)
else:
self.rbChar.SetValue(True)
def OnRadioSelect(self, event):
fitID = self.mainFrame.getActiveFit()
sFit = service.Fit.getInstance()
sFit.toggleImplantSource(fitID, ImplantLocation.FIT if self.rbFit.GetValue() else ImplantLocation.CHARACTER)
wx.PostEvent(self.mainFrame, GE.FitChanged(fitID=fitID))
class ImplantDisplay(d.Display):
DEFAULT_COLS = ["State",
"attr:implantness",
"Base Icon",
"Base Name"]
def __init__(self, parent):
@@ -65,7 +115,7 @@ class ImplantView(d.Display):
fit = sFit.getFit(event.fitID)
self.original = fit.implants if fit is not None else None
self.implants = stuff = fit.implants if fit is not None else None
self.implants = stuff = fit.appliedImplants if fit is not None else None
if stuff is not None: stuff.sort(key=lambda implant: implant.slot)
if event.fitID != self.lastFitId:
@@ -78,8 +128,7 @@ class ImplantView(d.Display):
self.deselectItems()
self.populate(stuff)
self.refresh(stuff)
self.update(stuff)
event.Skip()
def addItem(self, event):
@@ -123,14 +172,27 @@ class ImplantView(d.Display):
def spawnMenu(self):
sel = self.GetFirstSelected()
menu = None
sFit = service.Fit.getInstance()
fit = sFit.getFit(self.mainFrame.getActiveFit())
if not fit:
return
if sel != -1:
sFit = service.Fit.getInstance()
fit = sFit.getFit(self.mainFrame.getActiveFit())
implant = fit.implants[sel]
implant = fit.appliedImplants[sel]
sMkt = service.Market.getInstance()
sourceContext = "implantItem"
sourceContext = "implantItem" if fit.implantSource == ImplantLocation.FIT else "implantItemChar"
itemContext = sMkt.getCategoryByItem(implant.item).name
menu = ContextMenu.getMenu((implant,), (sourceContext, itemContext))
elif sel == -1 and fit.implantSource == ImplantLocation.FIT:
fitID = self.mainFrame.getActiveFit()
if fitID is None:
return
context = (("implantView",),)
menu = ContextMenu.getMenu([], *context)
if menu is not None:
self.PopupMenu(menu)

View File

@@ -50,6 +50,7 @@ from gui.characterEditor import CharacterEditor, SaveCharacterAs
from gui.characterSelection import CharacterSelection
from gui.patternEditor import DmgPatternEditorDlg
from gui.resistsEditor import ResistsEditorDlg
from gui.setEditor import ImplantSetEditorDlg
from gui.preferenceDialog import PreferenceDialog
from gui.graphFrame import GraphFrame
from gui.copySelectDialog import CopySelectDialog
@@ -358,15 +359,16 @@ class MainFrame(wx.Frame):
dlg.Show()
def showTargetResistsEditor(self, event):
dlg=ResistsEditorDlg(self)
dlg.ShowModal()
dlg.Destroy()
ResistsEditorDlg(self)
def showDamagePatternEditor(self, event):
dlg=DmgPatternEditorDlg(self)
dlg.ShowModal()
dlg.Destroy()
def showImplantSetEditor(self, event):
ImplantSetEditorDlg(self)
def showExportDialog(self, event):
""" Export active fit """
sFit = service.Fit.getInstance()
@@ -418,6 +420,8 @@ class MainFrame(wx.Frame):
self.Bind(wx.EVT_MENU, self.showDamagePatternEditor, id=menuBar.damagePatternEditorId)
# Target Resists editor
self.Bind(wx.EVT_MENU, self.showTargetResistsEditor, id=menuBar.targetResistsEditorId)
# Implant Set editor
self.Bind(wx.EVT_MENU, self.showImplantSetEditor, id=menuBar.implantSetEditorId)
# Import dialog
self.Bind(wx.EVT_MENU, self.fileImportDialog, id=wx.ID_OPEN)
# Export dialog

View File

@@ -33,6 +33,7 @@ class MainMenuBar(wx.MenuBar):
self.characterEditorId = wx.NewId()
self.damagePatternEditorId = wx.NewId()
self.targetResistsEditorId = wx.NewId()
self.implantSetEditorId = wx.NewId()
self.graphFrameId = wx.NewId()
self.backupFitsId = wx.NewId()
self.exportSkillsNeededId = wx.NewId()
@@ -101,9 +102,13 @@ class MainMenuBar(wx.MenuBar):
windowMenu.AppendItem(damagePatternEditItem)
targetResistsEditItem = wx.MenuItem(windowMenu, self.targetResistsEditorId, "Target Resists Editor\tCTRL+R")
targetResistsEditItem.SetBitmap(BitmapLoader.getBitmap("explosive_big", "gui"))
targetResistsEditItem.SetBitmap(BitmapLoader.getBitmap("explosive_small", "gui"))
windowMenu.AppendItem(targetResistsEditItem)
implantSetEditItem = wx.MenuItem(windowMenu, self.implantSetEditorId, "Implant Set Editor\tCTRL+I")
implantSetEditItem.SetBitmap(BitmapLoader.getBitmap("hardwire_small", "gui"))
windowMenu.AppendItem(implantSetEditItem)
graphFrameItem = wx.MenuItem(windowMenu, self.graphFrameId, "Graphs\tCTRL+G")
graphFrameItem.SetBitmap(BitmapLoader.getBitmap("graphs_small", "gui"))
windowMenu.AppendItem(graphFrameItem)

View File

@@ -23,11 +23,64 @@ import service
from wx.lib.intctrl import IntCtrl
from gui.utils.clipboard import toClipboard, fromClipboard
from service.damagePattern import ImportError
from gui.builtinViews.entityEditor import EntityEditor, BaseValidator
###########################################################################
## Class DmgPatternEditorDlg
###########################################################################
class DmgPatternTextValidor(BaseValidator):
def __init__(self):
BaseValidator.__init__(self)
def Clone(self):
return DmgPatternTextValidor()
def Validate(self, win):
profileEditor = win.Parent
textCtrl = self.GetWindow()
text = textCtrl.GetValue().strip()
try:
if len(text) == 0:
raise ValueError("You must supply a name for your Damage Profile!")
elif text in [x.name for x in profileEditor.entityEditor.choices]:
raise ValueError("Damage Profile name already in use, please choose another.")
return True
except ValueError, e:
wx.MessageBox(u"{}".format(e), "Error")
textCtrl.SetFocus()
return False
class DmgPatternEntityEditor(EntityEditor):
def __init__(self, parent):
EntityEditor.__init__(self, parent, "Damage Profile")
self.SetEditorValidator(DmgPatternTextValidor)
def getEntitiesFromContext(self):
sDP = service.DamagePattern.getInstance()
choices = sorted(sDP.getDamagePatternList(), key=lambda p: p.name)
return [c for c in choices if c.name != "Selected Ammo"]
def DoNew(self, name):
sDP = service.DamagePattern.getInstance()
return sDP.newPattern(name)
def DoRename(self, entity, name):
sDP = service.DamagePattern.getInstance()
sDP.renamePattern(entity, name)
def DoCopy(self, entity, name):
sDP = service.DamagePattern.getInstance()
copy = sDP.copyPattern(entity)
sDP.renamePattern(copy, name)
return copy
def DoDelete(self, entity):
sDP = service.DamagePattern.getInstance()
sDP.deletePattern(entity)
class DmgPatternEditorDlg(wx.Dialog):
DAMAGE_TYPES = ("em", "thermal", "kinetic", "explosive")
@@ -39,52 +92,8 @@ class DmgPatternEditorDlg(wx.Dialog):
mainSizer = wx.BoxSizer(wx.VERTICAL)
self.headerSizer = headerSizer = wx.BoxSizer(wx.HORIZONTAL)
sDP = service.DamagePattern.getInstance()
self.choices = sDP.getDamagePatternList()
# Remove "Selected Ammo" Damage Pattern
for dp in self.choices:
if dp.name == "Selected Ammo":
self.choices.remove(dp)
# Sort the remaining list and continue on
self.choices.sort(key=lambda p: p.name)
self.ccDmgPattern = wx.Choice(self, choices=map(lambda p: p.name, self.choices))
self.ccDmgPattern.Bind(wx.EVT_CHOICE, self.patternChanged)
self.ccDmgPattern.SetSelection(0)
self.namePicker = wx.TextCtrl(self, style=wx.TE_PROCESS_ENTER)
self.namePicker.Bind(wx.EVT_TEXT_ENTER, self.processRename)
self.namePicker.Hide()
size = None
headerSizer.Add(self.ccDmgPattern, 1, wx.ALIGN_CENTER_VERTICAL | wx.RIGHT|wx.LEFT, 3)
buttons = (("new", wx.ART_NEW),
("rename", BitmapLoader.getBitmap("rename", "gui")),
("copy", wx.ART_COPY),
("delete", wx.ART_DELETE))
for name, art in buttons:
bitmap = wx.ArtProvider.GetBitmap(art, wx.ART_BUTTON) if name != "rename" else art
btn = wx.BitmapButton(self, wx.ID_ANY, bitmap)
if size is None:
size = btn.GetSize()
btn.SetMinSize(size)
btn.SetMaxSize(size)
btn.Layout()
setattr(self, name, btn)
btn.Enable(True)
btn.SetToolTipString("%s pattern" % name.capitalize())
headerSizer.Add(btn, 0, wx.ALIGN_CENTER_VERTICAL)
self.btnSave = wx.Button(self, wx.ID_SAVE)
self.btnSave.Hide()
self.btnSave.Bind(wx.EVT_BUTTON, self.processRename)
self.headerSizer.Add(self.btnSave, 0, wx.ALIGN_CENTER)
mainSizer.Add(headerSizer, 0, wx.EXPAND | wx.ALL, 2)
self.entityEditor = DmgPatternEntityEditor(self)
mainSizer.Add(self.entityEditor, 0, wx.ALL | wx.EXPAND, 2)
self.sl = wx.StaticLine(self)
mainSizer.Add(self.sl, 0, wx.EXPAND | wx.TOP | wx.BOTTOM, 5)
@@ -108,7 +117,7 @@ class DmgPatternEditorDlg(wx.Dialog):
bmp = wx.StaticBitmap(self, wx.ID_ANY, BitmapLoader.getBitmap("%s_big"%type, "gui"))
if i%2:
style = wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_RIGHT | wx.LEFT
border = 10
border = 20
else:
style = wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_RIGHT
border = 5
@@ -155,6 +164,7 @@ class DmgPatternEditorDlg(wx.Dialog):
importExport = (("Import", wx.ART_FILE_OPEN, "from"),
("Export", wx.ART_FILE_SAVE_AS, "to"))
for name, art, direction in importExport:
bitmap = wx.ArtProvider.GetBitmap(art, wx.ART_BUTTON)
btn = wx.BitmapButton(self, wx.ID_ANY, bitmap)
@@ -170,14 +180,10 @@ class DmgPatternEditorDlg(wx.Dialog):
self.Layout()
bsize = self.GetBestSize()
self.SetSize((-1,bsize.height))
self.SetSize((-1, bsize.height))
self.CenterOnParent()
self.new.Bind(wx.EVT_BUTTON, self.newPattern)
self.rename.Bind(wx.EVT_BUTTON, self.renamePattern)
self.copy.Bind(wx.EVT_BUTTON, self.copyPattern)
self.delete.Bind(wx.EVT_BUTTON, self.deletePattern)
self.Import.Bind(wx.EVT_BUTTON, self.importPatterns)
self.Export.Bind(wx.EVT_BUTTON, self.exportPatterns)
self.Bind(wx.EVT_CHOICE, self.patternChanged)
self.patternChanged()
@@ -188,7 +194,7 @@ class DmgPatternEditorDlg(wx.Dialog):
if self.block:
return
p = self.getActivePattern()
p = self.entityEditor.getActiveEntity()
total = sum(map(lambda attr: getattr(self, "%sEdit"%attr).GetValue(), self.DAMAGE_TYPES))
for type in self.DAMAGE_TYPES:
editObj = getattr(self, "%sEdit"%type)
@@ -207,24 +213,18 @@ class DmgPatternEditorDlg(wx.Dialog):
for type in self.DAMAGE_TYPES:
editObj = getattr(self, "%sEdit"%type)
editObj.Enable(False)
self.rename.Enable(False)
self.delete.Enable(False)
self.entityEditor.btnRename.Enable(False)
self.entityEditor.btnDelete.Enable(False)
def unrestrict(self):
for type in self.DAMAGE_TYPES:
editObj = getattr(self, "%sEdit"%type)
editObj.Enable()
self.rename.Enable()
self.delete.Enable()
def getActivePattern(self):
if len(self.choices) == 0:
return None
return self.choices[self.ccDmgPattern.GetSelection()]
self.entityEditor.btnRename.Enable()
self.entityEditor.btnDelete.Enable()
def patternChanged(self, event=None):
p = self.getActivePattern()
p = self.entityEditor.getActiveEntity()
if p is None:
return
@@ -244,126 +244,9 @@ class DmgPatternEditorDlg(wx.Dialog):
self.block = False
self.ValuesUpdated()
def newPattern(self, event):
self.restrict()
self.block = True
# reset values
for type in self.DAMAGE_TYPES:
editObj = getattr(self, "%sEdit"%type)
editObj.SetValue(0)
self.block = False
self.btnSave.SetLabel("Create")
self.Refresh()
self.renamePattern()
def renamePattern(self, event=None):
if event is not None:
self.btnSave.SetLabel("Rename")
self.ccDmgPattern.Hide()
self.namePicker.Show()
self.headerSizer.Replace(self.ccDmgPattern, self.namePicker)
self.namePicker.SetFocus()
if event is not None: # Rename mode
self.btnSave.SetLabel("Rename")
self.namePicker.SetValue(self.getActivePattern().name)
else: # Create mode
self.namePicker.SetValue("")
for btn in (self.new, self.rename, self.delete, self.copy):
btn.Hide()
self.btnSave.Show()
self.headerSizer.Layout()
if event is not None:
event.Skip()
def processRename(self, event):
newName = self.namePicker.GetLineText(0)
self.stNotice.SetLabel("")
if newName == "":
self.stNotice.SetLabel("Invalid name.")
return
sDP = service.DamagePattern.getInstance()
if self.btnSave.Label == "Create":
p = sDP.newPattern()
else:
# we are renaming, so get the current selection
p = self.getActivePattern()
for pattern in self.choices:
if pattern.name == newName and p != pattern:
self.stNotice.SetLabel("Name already used, please choose another")
return
sDP.renamePattern(p, newName)
self.updateChoices(newName)
self.headerSizer.Replace(self.namePicker, self.ccDmgPattern)
self.ccDmgPattern.Show()
self.namePicker.Hide()
self.btnSave.Hide()
for btn in (self.new, self.rename, self.delete, self.copy):
btn.Show()
sel = self.ccDmgPattern.GetSelection()
self.ccDmgPattern.Delete(sel)
self.ccDmgPattern.Insert(newName, sel)
self.ccDmgPattern.SetSelection(sel)
self.ValuesUpdated()
self.unrestrict()
def copyPattern(self,event):
sDP = service.DamagePattern.getInstance()
p = sDP.copyPattern(self.getActivePattern())
self.choices.append(p)
id = self.ccDmgPattern.Append(p.name)
self.ccDmgPattern.SetSelection(id)
self.btnSave.SetLabel("Copy")
self.renamePattern()
self.patternChanged()
def deletePattern(self,event):
sDP = service.DamagePattern.getInstance()
sel = self.ccDmgPattern.GetSelection()
sDP.deletePattern(self.getActivePattern())
self.ccDmgPattern.Delete(sel)
self.ccDmgPattern.SetSelection(max(0, sel - 1))
del self.choices[sel]
self.patternChanged()
def __del__( self ):
def __del__(self):
pass
def updateChoices(self, select=None):
"Gathers list of patterns and updates choice selections"
sDP = service.DamagePattern.getInstance()
self.choices = sDP.getDamagePatternList()
for dp in self.choices:
if dp.name == "Selected Ammo": # don't include this special butterfly
self.choices.remove(dp)
# Sort the remaining list and continue on
self.choices.sort(key=lambda p: p.name)
self.ccDmgPattern.Clear()
for i, choice in enumerate(map(lambda p: p.name, self.choices)):
self.ccDmgPattern.Append(choice)
if select is not None and choice == select:
self.ccDmgPattern.SetSelection(i)
if select is None:
self.ccDmgPattern.SetSelection(0)
self.patternChanged()
def importPatterns(self, event):
text = fromClipboard()
if text:
@@ -384,3 +267,6 @@ class DmgPatternEditorDlg(wx.Dialog):
sDP = service.DamagePattern.getInstance()
toClipboard( sDP.exportPatterns() )
self.stNotice.SetLabel("Patterns exported to clipboard")
def contextChanged(self, event):
print "lol"

View File

@@ -22,6 +22,61 @@ from gui.bitmapLoader import BitmapLoader
import service
from gui.utils.clipboard import toClipboard, fromClipboard
from service.targetResists import ImportError
from gui.builtinViews.entityEditor import EntityEditor, BaseValidator
class TargetResistsTextValidor(BaseValidator):
def __init__(self):
BaseValidator.__init__(self)
def Clone(self):
return TargetResistsTextValidor()
def Validate(self, win):
profileEditor = win.parent.Parent
textCtrl = self.GetWindow()
text = textCtrl.GetValue().strip()
try:
if len(text) == 0:
raise ValueError("You must supply a name for your Target Resist Profile!")
elif text in [x.name for x in profileEditor.entityEditor.choices]:
raise ValueError("Target Resist Profile name already in use, please choose another.")
return True
except ValueError, e:
wx.MessageBox(u"{}".format(e), "Error")
textCtrl.SetFocus()
return False
class TargetResistsEntityEditor(EntityEditor):
def __init__(self, parent):
EntityEditor.__init__(self, parent, "Target Resist Profile")
self.SetEditorValidator(TargetResistsTextValidor)
def getEntitiesFromContext(self):
sTR = service.TargetResists.getInstance()
choices = sorted(sTR.getTargetResistsList(), key=lambda p: p.name)
return choices
def DoNew(self, name):
sTR = service.TargetResists.getInstance()
return sTR.newPattern(name)
def DoRename(self, entity, name):
sTR = service.TargetResists.getInstance()
sTR.renamePattern(entity, name)
def DoCopy(self, entity, name):
sTR = service.TargetResists.getInstance()
copy = sTR.copyPattern(entity)
sTR.renamePattern(copy, name)
return copy
def DoDelete(self, entity):
sTR = service.TargetResists.getInstance()
sTR.deletePattern(entity)
class ResistsEditorDlg(wx.Dialog):
@@ -35,51 +90,8 @@ class ResistsEditorDlg(wx.Dialog):
mainSizer = wx.BoxSizer(wx.VERTICAL)
self.headerSizer = headerSizer = wx.BoxSizer(wx.HORIZONTAL)
sTR = service.TargetResists.getInstance()
self.choices = sTR.getTargetResistsList()
# Sort the remaining list and continue on
self.choices.sort(key=lambda p: p.name)
self.ccResists = wx.Choice(self, choices=map(lambda p: p.name, self.choices))
self.ccResists.Bind(wx.EVT_CHOICE, self.patternChanged)
self.ccResists.SetSelection(0)
self.namePicker = wx.TextCtrl(self, style=wx.TE_PROCESS_ENTER)
self.namePicker.Bind(wx.EVT_TEXT_ENTER, self.processRename)
self.namePicker.Hide()
size = None
headerSizer.Add(self.ccResists, 1, wx.ALIGN_CENTER_VERTICAL | wx.RIGHT | wx.LEFT, 3)
buttons = (("new", wx.ART_NEW),
("rename", BitmapLoader.getBitmap("rename", "gui")),
("copy", wx.ART_COPY),
("delete", wx.ART_DELETE))
for name, art in buttons:
bitmap = wx.ArtProvider.GetBitmap(art, wx.ART_BUTTON) if name != "rename" else art
btn = wx.BitmapButton(self, wx.ID_ANY, bitmap)
if size is None:
size = btn.GetSize()
btn.SetMinSize(size)
btn.SetMaxSize(size)
btn.Layout()
setattr(self, name, btn)
btn.Enable(True)
btn.SetToolTipString("%s resist profile" % name.capitalize())
headerSizer.Add(btn, 0, wx.ALIGN_CENTER_VERTICAL)
self.btnSave = wx.Button(self, wx.ID_SAVE)
self.btnSave.Hide()
self.btnSave.Bind(wx.EVT_BUTTON, self.processRename)
headerSizer.Add(self.btnSave, 0, wx.ALIGN_CENTER)
mainSizer.Add(headerSizer, 0, wx.EXPAND | wx.ALL, 2)
self.entityEditor = TargetResistsEntityEditor(self)
mainSizer.Add(self.entityEditor, 0, wx.ALL | wx.EXPAND, 2)
self.sl = wx.StaticLine(self)
mainSizer.Add(self.sl, 0, wx.EXPAND | wx.TOP | wx.BOTTOM, 5)
@@ -157,19 +169,21 @@ class ResistsEditorDlg(wx.Dialog):
btn.SetToolTipString("%s patterns %s clipboard" % (name, direction) )
footerSizer.Add(btn, 0, wx.ALIGN_CENTER_HORIZONTAL | wx.ALIGN_RIGHT)
if not self.entityEditor.checkEntitiesExist():
self.Destroy()
return
self.Layout()
bsize = self.GetBestSize()
self.SetSize((-1,bsize.height))
self.SetSize((-1, bsize.height))
self.CenterOnParent()
self.new.Bind(wx.EVT_BUTTON, self.newPattern)
self.rename.Bind(wx.EVT_BUTTON, self.renamePattern)
self.copy.Bind(wx.EVT_BUTTON, self.copyPattern)
self.delete.Bind(wx.EVT_BUTTON, self.deletePattern)
self.Import.Bind(wx.EVT_BUTTON, self.importPatterns)
self.Export.Bind(wx.EVT_BUTTON, self.exportPatterns)
self.Bind(wx.EVT_CHOICE, self.patternChanged)
self.patternChanged()
self.ShowModal()
def closeEvent(self, event):
self.Destroy()
@@ -184,7 +198,7 @@ class ResistsEditorDlg(wx.Dialog):
return
try:
p = self.getActivePattern()
p = self.entityEditor.getActiveEntity()
for type in self.DAMAGE_TYPES:
editObj = getattr(self, "%sEdit"%type)
@@ -220,33 +234,15 @@ class ResistsEditorDlg(wx.Dialog):
finally: # Refresh for color changes to take effect immediately
self.Refresh()
def restrict(self):
for type in self.DAMAGE_TYPES:
editObj = getattr(self, "%sEdit"%type)
editObj.Enable(False)
self.rename.Enable(False)
self.delete.Enable(False)
def unrestrict(self):
for type in self.DAMAGE_TYPES:
editObj = getattr(self, "%sEdit"%type)
editObj.Enable()
self.rename.Enable()
self.delete.Enable()
def getActivePattern(self):
if len(self.choices) == 0:
return None
return self.choices[self.ccResists.GetSelection()]
def patternChanged(self, event=None):
"Event fired when user selects pattern. Can also be called from script"
p = self.getActivePattern()
if not self.entityEditor.checkEntitiesExist():
self.Destroy()
return
p = self.entityEditor.getActiveEntity()
if p is None:
# This happens when there are no patterns in the DB. As such, force
# user to create one first or exit dlg.
self.newPattern(None)
return
self.block = True
@@ -259,142 +255,9 @@ class ResistsEditorDlg(wx.Dialog):
self.block = False
self.ValuesUpdated()
def newPattern(self, event):
'''
Simply does new-pattern specifics: replaces label on button, restricts,
and resets values to default. Hands off to the rename function for
further handling.
'''
self.btnSave.SetLabel("Create")
self.restrict()
# reset values
for type in self.DAMAGE_TYPES:
editObj = getattr(self, "%sEdit"%type)
editObj.ChangeValue("0.0")
editObj.SetForegroundColour(self.colorReset)
self.Refresh()
self.renamePattern()
def renamePattern(self, event=None):
"Changes layout to facilitate naming a pattern"
self.showInput(True)
if event is not None: # Rename mode
self.btnSave.SetLabel("Rename")
self.namePicker.SetValue(self.getActivePattern().name)
else: # Create mode
self.namePicker.SetValue("")
if event is not None:
event.Skip()
def processRename(self, event):
'''
Processes rename event (which can be new or old patterns). If new
pattern, creates it; if old, selects it. if checks are valid, rename
saves pattern to DB.
Also resets to default layout and unrestricts.
'''
newName = self.namePicker.GetLineText(0)
self.stNotice.SetLabel("")
if newName == "":
self.stNotice.SetLabel("Invalid name")
return
sTR = service.TargetResists.getInstance()
if self.btnSave.Label == "Create":
p = sTR.newPattern()
else:
# we are renaming, so get the current selection
p = self.getActivePattern()
# test for patterns of the same name
for pattern in self.choices:
if pattern.name == newName and p != pattern:
self.stNotice.SetLabel("Name already used, please choose another")
return
# rename regardless of new or rename
sTR.renamePattern(p, newName)
self.updateChoices(newName)
self.showInput(False)
sel = self.ccResists.GetSelection()
self.ValuesUpdated()
self.unrestrict()
def copyPattern(self,event):
sTR = service.TargetResists.getInstance()
p = sTR.copyPattern(self.getActivePattern())
self.choices.append(p)
id = self.ccResists.Append(p.name)
self.ccResists.SetSelection(id)
self.btnSave.SetLabel("Copy")
self.renamePattern()
self.patternChanged()
def deletePattern(self,event):
sTR = service.TargetResists.getInstance()
sel = self.ccResists.GetSelection()
sTR.deletePattern(self.getActivePattern())
self.ccResists.Delete(sel)
self.ccResists.SetSelection(max(0, sel - 1))
del self.choices[sel]
self.patternChanged()
def showInput(self, bool):
if bool and not self.namePicker.IsShown():
self.ccResists.Hide()
self.namePicker.Show()
self.headerSizer.Replace(self.ccResists, self.namePicker)
self.namePicker.SetFocus()
for btn in (self.new, self.rename, self.delete, self.copy):
btn.Hide()
self.btnSave.Show()
self.restrict()
self.headerSizer.Layout()
elif not bool and self.namePicker.IsShown():
self.headerSizer.Replace(self.namePicker, self.ccResists)
self.ccResists.Show()
self.namePicker.Hide()
self.btnSave.Hide()
for btn in (self.new, self.rename, self.delete, self.copy):
btn.Show()
self.unrestrict()
self.headerSizer.Layout()
def __del__( self ):
pass
def updateChoices(self, select=None):
"Gathers list of patterns and updates choice selections"
sTR = service.TargetResists.getInstance()
self.choices = sTR.getTargetResistsList()
if len(self.choices) == 0:
#self.newPattern(None)
return
# Sort the remaining list and continue on
self.choices.sort(key=lambda p: p.name)
self.ccResists.Clear()
for i, choice in enumerate(map(lambda p: p.name, self.choices)):
self.ccResists.Append(choice)
if select is not None and choice == select:
self.ccResists.SetSelection(i)
if select is None:
self.ccResists.SetSelection(0)
self.patternChanged()
def importPatterns(self, event):
"Event fired when import from clipboard button is clicked"

214
gui/setEditor.py Normal file
View File

@@ -0,0 +1,214 @@
#===============================================================================
# Copyright (C) 2016 Ryan Holmes
#
# 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
from gui.bitmapLoader import BitmapLoader
from gui.builtinViews.implantEditor import BaseImplantEditorView
import service
from gui.utils.clipboard import toClipboard, fromClipboard
from service.implantSet import ImportError
import logging
from gui.builtinViews.entityEditor import EntityEditor, BaseValidator
logger = logging.getLogger(__name__)
class ImplantTextValidor(BaseValidator):
def __init__(self):
BaseValidator.__init__(self)
def Clone(self):
return ImplantTextValidor()
def Validate(self, win):
profileEditor = win.parent.Parent
textCtrl = self.GetWindow()
text = textCtrl.GetValue().strip()
try:
if len(text) == 0:
raise ValueError("You must supply a name for the Implant Set!")
elif text in [x.name for x in profileEditor.entityEditor.choices]:
raise ValueError("Imlplant Set name already in use, please choose another.")
return True
except ValueError, e:
wx.MessageBox(u"{}".format(e), "Error")
textCtrl.SetFocus()
return False
class ImplantSetEntityEditor(EntityEditor):
def __init__(self, parent):
EntityEditor.__init__(self, parent, "Implant Set")
self.SetEditorValidator(ImplantTextValidor)
def getEntitiesFromContext(self):
sIS = service.ImplantSets.getInstance()
return sorted(sIS.getImplantSetList(), key=lambda c: c.name)
def DoNew(self, name):
sIS = service.ImplantSets.getInstance()
return sIS.newSet(name)
def DoRename(self, entity, name):
sIS = service.ImplantSets.getInstance()
sIS.renameSet(entity, name)
def DoCopy(self, entity, name):
sIS = service.ImplantSets.getInstance()
copy = sIS.copySet(entity)
sIS.renameSet(copy, name)
return copy
def DoDelete(self, entity):
sIS = service.ImplantSets.getInstance()
sIS.deleteSet(entity)
class ImplantSetEditor(BaseImplantEditorView):
def __init__(self, parent):
BaseImplantEditorView.__init__(self, parent)
if 'wxMSW' in wx.PlatformInfo:
self.SetBackgroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_BTNFACE))
def bindContext(self):
self.Parent.entityEditor.Bind(wx.EVT_CHOICE, self.contextChanged)
def getImplantsFromContext(self):
sIS = service.ImplantSets.getInstance()
set = self.Parent.entityEditor.getActiveEntity()
if set:
return sIS.getImplants(set.ID)
return []
def addImplantToContext(self, item):
sIS = service.ImplantSets.getInstance()
set = self.Parent.entityEditor.getActiveEntity()
sIS.addImplant(set.ID, item.ID)
def removeImplantFromContext(self, implant):
sIS = service.ImplantSets.getInstance()
set = self.Parent.entityEditor.getActiveEntity()
sIS.removeImplant(set.ID, implant)
class ImplantSetEditorDlg(wx.Dialog):
def __init__(self, parent):
wx.Dialog.__init__(self, parent, id = wx.ID_ANY, title = u"Implant Set Editor", size = wx.Size(640, 600))
self.block = False
self.SetSizeHintsSz(wx.DefaultSize, wx.DefaultSize)
mainSizer = wx.BoxSizer(wx.VERTICAL)
self.entityEditor = ImplantSetEntityEditor(self)
mainSizer.Add(self.entityEditor, 0, wx.ALL | wx.EXPAND, 2)
self.sl = wx.StaticLine(self)
mainSizer.Add(self.sl, 0, wx.EXPAND | wx.TOP | wx.BOTTOM, 5)
self.iview = ImplantSetEditor(self)
mainSizer.Add(self.iview, 1, wx.ALL | wx.EXPAND, 5)
self.slfooter = wx.StaticLine(self)
mainSizer.Add(self.slfooter, 0, wx.EXPAND | wx.TOP, 5)
footerSizer = wx.BoxSizer(wx.HORIZONTAL)
self.stNotice = wx.StaticText(self, wx.ID_ANY, u"")
self.stNotice.Wrap(-1)
footerSizer.Add(self.stNotice, 1, wx.BOTTOM | wx.TOP | wx.LEFT, 5)
if "wxGTK" in wx.PlatformInfo:
self.closeBtn = wx.Button( self, wx.ID_ANY, u"Close", wx.DefaultPosition, wx.DefaultSize, 0 )
mainSizer.Add( self.closeBtn, 0, wx.ALL|wx.ALIGN_RIGHT, 5 )
self.closeBtn.Bind(wx.EVT_BUTTON, self.closeEvent)
importExport = (("Import", wx.ART_FILE_OPEN, "from"),
("Export", wx.ART_FILE_SAVE_AS, "to"))
for name, art, direction in importExport:
bitmap = wx.ArtProvider.GetBitmap(art, wx.ART_BUTTON)
btn = wx.BitmapButton(self, wx.ID_ANY, bitmap)
btn.SetMinSize( btn.GetSize() )
btn.SetMaxSize( btn.GetSize() )
btn.Layout()
setattr(self, name, btn)
btn.Enable(True)
btn.SetToolTipString("%s implant sets %s clipboard" % (name, direction) )
footerSizer.Add(btn, 0, wx.ALIGN_CENTER_HORIZONTAL | wx.ALIGN_RIGHT)
mainSizer.Add(footerSizer, 0, wx.ALL | wx.EXPAND, 5)
self.SetSizer(mainSizer)
self.Layout()
if not self.entityEditor.checkEntitiesExist():
self.Destroy()
return
self.Bind(wx.EVT_CHOICE, self.entityChanged)
self.Import.Bind(wx.EVT_BUTTON, self.importPatterns)
self.Export.Bind(wx.EVT_BUTTON, self.exportPatterns)
self.CenterOnParent()
self.ShowModal()
def entityChanged(self, event):
if not self.entityEditor.checkEntitiesExist():
self.Destroy()
return
def closeEvent(self, event):
self.Destroy()
def __del__( self ):
pass
def importPatterns(self, event):
"Event fired when import from clipboard button is clicked"
text = fromClipboard()
if text:
sIS = service.ImplantSets.getInstance()
try:
sIS.importSets(text)
self.stNotice.SetLabel("Patterns successfully imported from clipboard")
self.showInput(False)
except ImportError, e:
self.stNotice.SetLabel(str(e))
except Exception, e:
logging.exception("Unhandled Exception")
self.stNotice.SetLabel("Could not import from clipboard: unknown errors")
finally:
self.updateChoices()
else:
self.stNotice.SetLabel("Could not import from clipboard")
def exportPatterns(self, event):
"Event fired when export to clipboard button is clicked"
sIS = service.ImplantSets.getInstance()
toClipboard(sIS.exportSets())
self.stNotice.SetLabel("Sets exported to clipboard")

BIN
imgs/gui/hardwire_small.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 762 B

View File

@@ -10,6 +10,7 @@ from service.update import Update
from service.price import Price
from service.network import Network
from service.eveapi import EVEAPIConnection, ParseXML
from service.implantSet import ImplantSets
import wx
if not 'wxMac' in wx.PlatformInfo or ('wxMac' in wx.PlatformInfo and wx.VERSION >= (3,0)):

View File

@@ -32,7 +32,9 @@ import eos.db
import eos.types
import service
import config
import logging
logger = logging.getLogger(__name__)
class CharacterImportThread(threading.Thread):
def __init__(self, paths, callback):
@@ -48,9 +50,8 @@ class CharacterImportThread(threading.Thread):
# we try to parse api XML data first
with open(path, mode='r') as charFile:
sheet = service.ParseXML(charFile)
charID = sCharacter.new()
sCharacter.rename(charID, sheet.name+" (imported)")
sCharacter.apiUpdateCharSheet(charID, sheet.skills)
char = sCharacter.new(sheet.name+" (imported)")
sCharacter.apiUpdateCharSheet(char.ID, sheet.skills)
except:
# if it's not api XML data, try this
# this is a horrible logic flow, but whatever
@@ -67,9 +68,8 @@ class CharacterImportThread(threading.Thread):
"typeID": int(skill.getAttribute("typeID")),
"level": int(skill.getAttribute("level")),
})
charID = sCharacter.new()
sCharacter.rename(charID, name+" (EVEMon)")
sCharacter.apiUpdateCharSheet(charID, skills)
char = sCharacter.new(name+" (EVEMon)")
sCharacter.apiUpdateCharSheet(char.ID, skills)
except:
continue
@@ -114,6 +114,11 @@ class Character(object):
return cls.instance
def __init__(self):
# Simply initializes default characters in case they aren't in the database yet
self.all0()
self.all5()
def exportText(self):
data = "Pyfa exported plan for \""+self.skillReqsDict['charname']+"\"\n"
data += "=" * 79 + "\n"
@@ -183,10 +188,7 @@ class Character(object):
return self.all5().ID
def getCharacterList(self):
baseChars = [eos.types.Character.getAll0(), eos.types.Character.getAll5()]
sFit = service.Fit.getInstance()
return map(lambda c: (c.ID, c.name if not c.isDirty else "{} *".format(c.name), c == sFit.character), eos.db.getCharacterList())
return eos.db.getCharacterList()
def getCharacter(self, charID):
char = eos.db.getCharacter(charID)
@@ -246,25 +248,21 @@ class Character(object):
def getCharName(self, charID):
return eos.db.getCharacter(charID).name
def new(self):
char = eos.types.Character("New Character")
def new(self, name="New Character"):
char = eos.types.Character(name)
eos.db.save(char)
return char.ID
return char
def rename(self, charID, newName):
char = eos.db.getCharacter(charID)
def rename(self, char, newName):
char.name = newName
eos.db.commit()
def copy(self, charID):
char = eos.db.getCharacter(charID)
def copy(self, char):
newChar = copy.deepcopy(char)
eos.db.save(newChar)
return newChar.ID
return newChar
def delete(self, charID):
char = eos.db.getCharacter(charID)
eos.db.commit()
def delete(self, char):
eos.db.remove(char)
def getApiDetails(self, charID):
@@ -343,13 +341,18 @@ class Character(object):
def addImplant(self, charID, itemID):
char = eos.db.getCharacter(charID)
implant = eos.types.Implant(eos.db.getItem(itemID))
char.implants.freeSlot(implant.slot)
char.implants.append(implant)
if char.ro:
logger.error("Trying to add implant to read-only character")
return
def removeImplant(self, charID, slot):
implant = eos.types.Implant(eos.db.getItem(itemID))
char.implants.append(implant)
eos.db.commit()
def removeImplant(self, charID, implant):
char = eos.db.getCharacter(charID)
char.implants.freeSlot(slot)
char.implants.remove(implant)
eos.db.commit()
def getImplants(self, charID):
char = eos.db.getCharacter(charID)

View File

@@ -46,9 +46,10 @@ class DamagePattern():
def getDamagePattern(self, name):
return eos.db.getDamagePattern(name)
def newPattern(self):
def newPattern(self, name):
p = eos.types.DamagePattern(0, 0, 0, 0)
p.name = ""
p.name = name
eos.db.save(p)
return p
def renamePattern(self, p, newName):

View File

@@ -277,7 +277,7 @@ class Fit(object):
fit.timestamp))
return fits
def addImplant(self, fitID, itemID):
def addImplant(self, fitID, itemID, recalc=True):
if fitID is None:
return False
@@ -289,7 +289,8 @@ class Fit(object):
return False
fit.implants.append(implant)
self.recalc(fit)
if recalc:
self.recalc(fit)
return True
def removeImplant(self, fitID, position):
@@ -760,6 +761,14 @@ class Fit(object):
self.recalc(fit)
return True
def toggleImplantSource(self, fitID, source):
fit = eos.db.getFit(fitID)
fit.implantSource = source
eos.db.commit()
self.recalc(fit)
return True
def toggleBooster(self, fitID, i):
fit = eos.db.getFit(fitID)
booster = fit.boosters[i]

125
service/implantSet.py Normal file
View File

@@ -0,0 +1,125 @@
#===============================================================================
# Copyright (C) 2016 Ryan Holmes
#
# 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 eos.db
import eos.types
import copy
import service.market
class ImportError(Exception):
pass
class ImplantSets():
instance = None
@classmethod
def getInstance(cls):
if cls.instance is None:
cls.instance = ImplantSets()
return cls.instance
def getImplantSetList(self):
return eos.db.getImplantSetList(None)
def getImplantSet(self, name):
return eos.db.getImplantSet(name)
def getImplants(self, setID):
set = eos.db.getImplantSet(setID)
return set.implants
def addImplant(self, setID, itemID):
set = eos.db.getImplantSet(setID)
implant = eos.types.Implant(eos.db.getItem(itemID))
set.implants.append(implant)
eos.db.commit()
def removeImplant(self, setID, implant):
set = eos.db.getImplantSet(setID)
set.implants.remove(implant)
eos.db.commit()
def newSet(self, name):
s = eos.types.ImplantSet()
s.name = name
eos.db.save(s)
return s
def renameSet(self, s, newName):
s.name = newName
eos.db.save(s)
def deleteSet(self, s):
eos.db.remove(s)
def copySet(self, s):
newS = copy.deepcopy(s)
eos.db.save(newS)
return newS
def saveChanges(self, s):
eos.db.save(s)
def importSets(self, text):
sMkt = service.Market.getInstance()
lines = text.splitlines()
newSets = []
errors = 0
current = None
lookup = {}
for i, line in enumerate(lines):
line = line.strip()
try:
if line == '' or line[0] == "#": # comments / empty string
continue
if line[:1] == "[" and line[-1:] == "]":
current = eos.types.ImplantSet(line[1:-1])
newSets.append(current)
else:
item = sMkt.getItem(line)
current.implants.append(eos.types.Implant(item))
except:
errors += 1
continue
for set in self.getImplantSetList():
lookup[set.name] = set
for set in newSets:
if set.name in lookup:
match = lookup[set.name]
for implant in set.implants:
match.implants.append(eos.types.Implant(implant.item))
else:
eos.db.save(set)
eos.db.commit()
lenImports = len(newSets)
if lenImports == 0:
raise ImportError("No patterns found for import")
if errors > 0:
raise ImportError("%d sets imported from clipboard; %d errors"%(lenImports, errors))
def exportSets(self):
patterns = self.getImplantSetList()
patterns.sort(key=lambda p: p.name)
return eos.types.ImplantSet.exportSets(*patterns)

View File

@@ -29,12 +29,15 @@ import eos.types
from service.settings import SettingsProvider, NetworkSettings
import service
import service.conversions as conversions
import logging
try:
from collections import OrderedDict
except ImportError:
from utils.compat import OrderedDict
logger = logging.getLogger(__name__)
# Event which tells threads dependent on Market that it's initialized
mktRdy = threading.Event()
@@ -120,11 +123,14 @@ class SearchWorkerThread(threading.Thread):
self.searchRequest = None
cv.release()
sMkt = Market.getInstance()
if filterOn:
if filterOn is True:
# Rely on category data provided by eos as we don't hardcode them much in service
filter = eos.types.Category.name.in_(sMkt.SEARCH_CATEGORIES)
elif filterOn: # filter by selected categories
filter = eos.types.Category.name.in_(filterOn)
else:
filter=None
results = eos.db.searchItems(request, where=filter,
join=(eos.types.Item.group, eos.types.Group.category),
eager=("icon", "group.category", "metaGroup", "metaGroup.parent"))
@@ -346,20 +352,25 @@ class Market():
def getItem(self, identity, *args, **kwargs):
"""Get item by its ID or name"""
if isinstance(identity, eos.types.Item):
item = identity
elif isinstance(identity, int):
item = eos.db.getItem(identity, *args, **kwargs)
elif isinstance(identity, basestring):
# We normally lookup with string when we are using import/export
# features. Check against overrides
identity = conversions.all.get(identity, identity)
item = eos.db.getItem(identity, *args, **kwargs)
elif isinstance(identity, float):
id = int(identity)
item = eos.db.getItem(id, *args, **kwargs)
else:
raise TypeError("Need Item object, integer, float or string as argument")
try:
if isinstance(identity, eos.types.Item):
item = identity
elif isinstance(identity, int):
item = eos.db.getItem(identity, *args, **kwargs)
elif isinstance(identity, basestring):
# We normally lookup with string when we are using import/export
# features. Check against overrides
identity = conversions.all.get(identity, identity)
item = eos.db.getItem(identity, *args, **kwargs)
elif isinstance(identity, float):
id = int(identity)
item = eos.db.getItem(id, *args, **kwargs)
else:
raise TypeError("Need Item object, integer, float or string as argument")
except:
logger.error("Could not get item: %s", identity)
raise
return item
def getGroup(self, identity, *args, **kwargs):

View File

@@ -39,9 +39,10 @@ class TargetResists():
def getTargetResists(self, name):
return eos.db.getTargetResists(name)
def newPattern(self):
def newPattern(self, name):
p = eos.types.TargetResists(0.0, 0.0, 0.0, 0.0)
p.name = ""
p.name = name
eos.db.save(p)
return p
def renamePattern(self, p, newName):