diff --git a/eos/db/migration.py b/eos/db/migration.py
index c99be1ffcf..a0824f8125 100644
--- a/eos/db/migration.py
+++ b/eos/db/migration.py
@@ -4,6 +4,9 @@
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
diff --git a/eos/db/migrations/upgrade13.py b/eos/db/migrations/upgrade13.py
new file mode 100644
index 0000000000..047dc71293
--- /dev/null
+++ b/eos/db/migrations/upgrade13.py
@@ -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")
\ No newline at end of file
diff --git a/eos/db/saveddata/__init__.py b/eos/db/saveddata/__init__.py
index 683fb499d5..e43c6e6010 100644
--- a/eos/db/saveddata/__init__.py
+++ b/eos/db/saveddata/__init__.py
@@ -13,6 +13,7 @@
"miscData",
"targetResists",
"override",
- "crest"
+ "crest",
+ "implantSet"
]
diff --git a/eos/db/saveddata/character.py b/eos/db/saveddata/character.py
index 6600d975ef..e6be52535f 100644
--- a/eos/db/saveddata/character.py
+++ b/eos/db/saveddata/character.py
@@ -36,9 +36,23 @@
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),
+ }
+)
diff --git a/eos/db/saveddata/fit.py b/eos/db/saveddata/fit.py
index 77e6eb04be..29f51ef6c4 100644
--- a/eos/db/saveddata/fit.py
+++ b/eos/db/saveddata/fit.py
@@ -29,7 +29,7 @@
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 @@
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,
diff --git a/eos/db/saveddata/implant.py b/eos/db/saveddata/implant.py
index 60e40bff7f..c74def1576 100644
--- a/eos/db/saveddata/implant.py
+++ b/eos/db/saveddata/implant.py
@@ -36,4 +36,8 @@
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)
diff --git a/eos/db/saveddata/implantSet.py b/eos/db/saveddata/implantSet.py
new file mode 100644
index 0000000000..73036de91c
--- /dev/null
+++ b/eos/db/saveddata/implantSet.py
@@ -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 .
+#===============================================================================
+
+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),
+ }
+)
diff --git a/eos/db/saveddata/queries.py b/eos/db/saveddata/queries.py
index a305994cd9..5a6dd1bfcc 100644
--- a/eos/db/saveddata/queries.py
+++ b/eos/db/saveddata/queries.py
@@ -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")
diff --git a/eos/effects/angelsetbonus.py b/eos/effects/angelsetbonus.py
index b2e5f449bc..711d99c9d6 100644
--- a/eos/effects/angelsetbonus.py
+++ b/eos/effects/angelsetbonus.py
@@ -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"))
\ No newline at end of file
diff --git a/eos/effects/caldarisetbonus3.py b/eos/effects/caldarisetbonus3.py
index 40aade2acd..a31e831e68 100644
--- a/eos/effects/caldarisetbonus3.py
+++ b/eos/effects/caldarisetbonus3.py
@@ -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"))
diff --git a/eos/effects/caldarisetlgbonus.py b/eos/effects/caldarisetlgbonus.py
index abf7ecd8ef..04d1c6f746 100644
--- a/eos/effects/caldarisetlgbonus.py
+++ b/eos/effects/caldarisetlgbonus.py
@@ -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"))
diff --git a/eos/effects/federationsetbonus3.py b/eos/effects/federationsetbonus3.py
index aa25dfcc4d..b97317b82d 100644
--- a/eos/effects/federationsetbonus3.py
+++ b/eos/effects/federationsetbonus3.py
@@ -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"))
diff --git a/eos/effects/federationsetlgbonus.py b/eos/effects/federationsetlgbonus.py
index 27c320be94..4328417cd1 100644
--- a/eos/effects/federationsetlgbonus.py
+++ b/eos/effects/federationsetlgbonus.py
@@ -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"))
diff --git a/eos/effects/imperialsetbonus3.py b/eos/effects/imperialsetbonus3.py
index 7c764dcad7..d239dcc35a 100644
--- a/eos/effects/imperialsetbonus3.py
+++ b/eos/effects/imperialsetbonus3.py
@@ -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"))
diff --git a/eos/effects/imperialsetlgbonus.py b/eos/effects/imperialsetlgbonus.py
index 6495b9b81d..56369bf227 100644
--- a/eos/effects/imperialsetlgbonus.py
+++ b/eos/effects/imperialsetlgbonus.py
@@ -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"))
diff --git a/eos/effects/implantsetwarpspeed.py b/eos/effects/implantsetwarpspeed.py
index a6410ed77d..29fd50e49a 100644
--- a/eos/effects/implantsetwarpspeed.py
+++ b/eos/effects/implantsetwarpspeed.py
@@ -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"))
diff --git a/eos/effects/republicsetbonus3.py b/eos/effects/republicsetbonus3.py
index 1f3cad903f..5709959ae0 100644
--- a/eos/effects/republicsetbonus3.py
+++ b/eos/effects/republicsetbonus3.py
@@ -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"))
diff --git a/eos/effects/republicsetlgbonus.py b/eos/effects/republicsetlgbonus.py
index a80ae38e77..bf79499ea6 100644
--- a/eos/effects/republicsetlgbonus.py
+++ b/eos/effects/republicsetlgbonus.py
@@ -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"))
diff --git a/eos/effects/setbonusbloodraider.py b/eos/effects/setbonusbloodraider.py
index 718be78e68..73fb305ec0 100644
--- a/eos/effects/setbonusbloodraider.py
+++ b/eos/effects/setbonusbloodraider.py
@@ -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"))
diff --git a/eos/effects/setbonuschristmasagilitybonus.py b/eos/effects/setbonuschristmasagilitybonus.py
index 857cad3555..f0ca5c03fe 100644
--- a/eos/effects/setbonuschristmasagilitybonus.py
+++ b/eos/effects/setbonuschristmasagilitybonus.py
@@ -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"))
diff --git a/eos/effects/setbonuschristmasarmorhpbonus2.py b/eos/effects/setbonuschristmasarmorhpbonus2.py
index 971644d029..9f5c52fd4c 100644
--- a/eos/effects/setbonuschristmasarmorhpbonus2.py
+++ b/eos/effects/setbonuschristmasarmorhpbonus2.py
@@ -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"))
diff --git a/eos/effects/setbonuschristmasbonusvelocity.py b/eos/effects/setbonuschristmasbonusvelocity.py
index 1a0d7ffcd1..09b1ef21f0 100644
--- a/eos/effects/setbonuschristmasbonusvelocity.py
+++ b/eos/effects/setbonuschristmasbonusvelocity.py
@@ -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"))
diff --git a/eos/effects/setbonuschristmascapacitorcapacity.py b/eos/effects/setbonuschristmascapacitorcapacity.py
index a0ba4bdd05..cc949b255e 100644
--- a/eos/effects/setbonuschristmascapacitorcapacity.py
+++ b/eos/effects/setbonuschristmascapacitorcapacity.py
@@ -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"))
diff --git a/eos/effects/setbonuschristmascapacitorrecharge2.py b/eos/effects/setbonuschristmascapacitorrecharge2.py
index 7f31e18184..a526176e4f 100644
--- a/eos/effects/setbonuschristmascapacitorrecharge2.py
+++ b/eos/effects/setbonuschristmascapacitorrecharge2.py
@@ -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"))
diff --git a/eos/effects/setbonuschristmascpuoutput.py b/eos/effects/setbonuschristmascpuoutput.py
index 5ba1d538a0..e264c8dd52 100644
--- a/eos/effects/setbonuschristmascpuoutput.py
+++ b/eos/effects/setbonuschristmascpuoutput.py
@@ -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"))
diff --git a/eos/effects/setbonuschristmaspowergrid.py b/eos/effects/setbonuschristmaspowergrid.py
index b0fc46366f..9c39919be4 100644
--- a/eos/effects/setbonuschristmaspowergrid.py
+++ b/eos/effects/setbonuschristmaspowergrid.py
@@ -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"))
diff --git a/eos/effects/setbonuschristmasshieldcapacitybonus.py b/eos/effects/setbonuschristmasshieldcapacitybonus.py
index 5841a565e5..c604d02812 100644
--- a/eos/effects/setbonuschristmasshieldcapacitybonus.py
+++ b/eos/effects/setbonuschristmasshieldcapacitybonus.py
@@ -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"))
diff --git a/eos/effects/setbonusguristas.py b/eos/effects/setbonusguristas.py
index e0e6a14bff..34bbfacc26 100644
--- a/eos/effects/setbonusguristas.py
+++ b/eos/effects/setbonusguristas.py
@@ -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"))
diff --git a/eos/effects/setbonusmordus.py b/eos/effects/setbonusmordus.py
index 3bcdb2c07b..eef3675f98 100644
--- a/eos/effects/setbonusmordus.py
+++ b/eos/effects/setbonusmordus.py
@@ -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"))
diff --git a/eos/effects/setbonusore.py b/eos/effects/setbonusore.py
index 8e540b81cb..a7342a8fa2 100644
--- a/eos/effects/setbonusore.py
+++ b/eos/effects/setbonusore.py
@@ -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"))
diff --git a/eos/effects/setbonussansha.py b/eos/effects/setbonussansha.py
index e7e0d261c2..df7ae7be3e 100644
--- a/eos/effects/setbonussansha.py
+++ b/eos/effects/setbonussansha.py
@@ -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)
diff --git a/eos/effects/setbonusserpentis.py b/eos/effects/setbonusserpentis.py
index 4775993c03..f4de04dcc2 100644
--- a/eos/effects/setbonusserpentis.py
+++ b/eos/effects/setbonusserpentis.py
@@ -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"))
diff --git a/eos/effects/setbonussisters.py b/eos/effects/setbonussisters.py
index cf67fda489..a43478d52d 100644
--- a/eos/effects/setbonussisters.py
+++ b/eos/effects/setbonussisters.py
@@ -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"))
diff --git a/eos/effects/setbonussyndicate.py b/eos/effects/setbonussyndicate.py
index 594c4e90a5..61fc59591e 100644
--- a/eos/effects/setbonussyndicate.py
+++ b/eos/effects/setbonussyndicate.py
@@ -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"))
diff --git a/eos/effects/setbonusthukker.py b/eos/effects/setbonusthukker.py
index 073b46c96a..8594ddcfec 100644
--- a/eos/effects/setbonusthukker.py
+++ b/eos/effects/setbonusthukker.py
@@ -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"))
diff --git a/eos/saveddata/character.py b/eos/saveddata/character.py
index 522ad590ad..24fa086941 100644
--- a/eos/saveddata/character.py
+++ b/eos/saveddata/character.py
@@ -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 @@ def getAll0(cls):
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 @@ def __init__(self, name, defaultLevel=None, initSkills=True):
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 @@ def owner(self):
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 @@ def calculateModifiedAttributes(self, fit, runTime, forceProjected = False):
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)
diff --git a/eos/saveddata/fit.py b/eos/saveddata/fit.py
index 1bda5ee17b..7e742a9393 100644
--- a/eos/saveddata/fit.py
+++ b/eos/saveddata/fit.py
@@ -31,6 +31,8 @@
import time
import copy
from utils.timer import Timer
+from eos.enum import Enum
+
import logging
@@ -41,6 +43,10 @@
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."""
@@ -324,16 +330,19 @@ def alignTime(self):
return -log(0.25) * agility * mass / 1000000
@property
- def appliedImplants(self):
- implantsBySlot = {}
- if self.character:
- for implant in self.character.implants:
- implantsBySlot[implant.slot] = implant
+ def implantSource(self):
+ return self.implantLocation
- for implant in self.implants:
- implantsBySlot[implant.slot] = implant
+ @implantSource.setter
+ def implantSource(self, source):
+ self.implantLocation = source
- return implantsBySlot.values()
+ @property
+ def appliedImplants(self):
+ if self.implantLocation == ImplantLocation.CHARACTER:
+ return self.character.implants
+ else:
+ return self.implants
@validates("ID", "ownerID", "shipID")
def validator(self, key, val):
diff --git a/eos/saveddata/implantSet.py b/eos/saveddata/implantSet.py
new file mode 100644
index 0000000000..8e495c5ea8
--- /dev/null
+++ b/eos/saveddata/implantSet.py
@@ -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 .
+#===============================================================================
+
+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
diff --git a/eos/types.py b/eos/types.py
index 26e36ef904..90e0974c05 100644
--- a/eos/types.py
+++ b/eos/types.py
@@ -30,10 +30,11 @@
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
diff --git a/gui/builtinContextMenus/__init__.py b/gui/builtinContextMenus/__init__.py
index 785e3eebf9..c2a39b1792 100644
--- a/gui/builtinContextMenus/__init__.py
+++ b/gui/builtinContextMenus/__init__.py
@@ -20,4 +20,5 @@
"priceClear",
"amount",
"metaSwap",
+ "implantSets",
]
diff --git a/gui/builtinContextMenus/implantSets.py b/gui/builtinContextMenus/implantSets.py
new file mode 100644
index 0000000000..6ae43039be
--- /dev/null
+++ b/gui/builtinContextMenus/implantSets.py
@@ -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()
diff --git a/gui/builtinContextMenus/itemStats.py b/gui/builtinContextMenus/itemStats.py
index a05064d249..7669b1f2f0 100644
--- a/gui/builtinContextMenus/itemStats.py
+++ b/gui/builtinContextMenus/itemStats.py
@@ -9,6 +9,7 @@ def __init__(self):
self.mainFrame = gui.mainFrame.MainFrame.getInstance()
def display(self, srcContext, selection):
+
return srcContext in ("marketItemGroup", "marketItemMisc",
"fittingModule", "fittingCharge",
"fittingShip", "baseShip",
@@ -16,7 +17,7 @@ def display(self, srcContext, selection):
"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")
diff --git a/gui/builtinContextMenus/marketJump.py b/gui/builtinContextMenus/marketJump.py
index 4980bf61fc..d38fd9f1c9 100644
--- a/gui/builtinContextMenus/marketJump.py
+++ b/gui/builtinContextMenus/marketJump.py
@@ -12,7 +12,8 @@ def display(self, srcContext, selection):
"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 @@ def getText(self, itmContext, selection):
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]
diff --git a/gui/builtinViewColumns/baseIcon.py b/gui/builtinViewColumns/baseIcon.py
index 30cbf6fb23..868c73a213 100644
--- a/gui/builtinViewColumns/baseIcon.py
+++ b/gui/builtinViewColumns/baseIcon.py
@@ -2,7 +2,7 @@
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 @@ def getImageId(self, stuff):
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")
diff --git a/gui/builtinViewColumns/baseName.py b/gui/builtinViewColumns/baseName.py
index e5f59a9e22..edb45044e8 100644
--- a/gui/builtinViewColumns/baseName.py
+++ b/gui/builtinViewColumns/baseName.py
@@ -22,7 +22,7 @@
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 @@ def getText(self, stuff):
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)
diff --git a/gui/builtinViewColumns/state.py b/gui/builtinViewColumns/state.py
index c12db54dfb..cc3dee59b4 100644
--- a/gui/builtinViewColumns/state.py
+++ b/gui/builtinViewColumns/state.py
@@ -22,7 +22,7 @@
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 @@ def getImageId(self, stuff):
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:
diff --git a/gui/builtinViews/__init__.py b/gui/builtinViews/__init__.py
index 4d1ed63c69..46a500f7ba 100644
--- a/gui/builtinViews/__init__.py
+++ b/gui/builtinViews/__init__.py
@@ -1 +1 @@
-__all__ = ["fittingView", "fleetView"]
+__all__ = ["fittingView", "fleetView", "implantEditor"]
diff --git a/gui/builtinViews/entityEditor.py b/gui/builtinViews/entityEditor.py
new file mode 100644
index 0000000000..112c1c63e9
--- /dev/null
+++ b/gui/builtinViews/entityEditor.py
@@ -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
\ No newline at end of file
diff --git a/gui/builtinViews/implantEditor.py b/gui/builtinViews/implantEditor.py
new file mode 100644
index 0000000000..bcdb96c7bb
--- /dev/null
+++ b/gui/builtinViews/implantEditor.py
@@ -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)
\ No newline at end of file
diff --git a/gui/characterEditor.py b/gui/characterEditor.py
index 7d45dae3d2..72fc0a333f 100644
--- a/gui/characterEditor.py
+++ b/gui/characterEditor.py
@@ -19,94 +19,110 @@
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 CharacterEditor(wx.Frame):
- def __init__(self, parent):
- wx.Frame.__init__ (self, parent, id=wx.ID_ANY, title=u"pyfa: Character Editor", pos=wx.DefaultPosition,
- size=wx.Size(640, 600), style=wx.DEFAULT_FRAME_STYLE ^ wx.RESIZE_BORDER)
- i = wx.IconFromBitmap(BitmapLoader.getBitmap("character_small", "gui"))
- self.SetIcon(i)
+class CharacterTextValidor(BaseValidator):
+ def __init__(self):
+ BaseValidator.__init__(self)
- self.mainFrame = parent
-
- #self.disableWin = wx.WindowDisabler(self)
- self.SetBackgroundColour( wx.SystemSettings.GetColour( wx.SYS_COLOUR_BTNFACE ) )
+ def Clone(self):
+ return CharacterTextValidor()
- mainSizer = wx.BoxSizer(wx.VERTICAL)
- self.navSizer = wx.BoxSizer(wx.HORIZONTAL)
+ 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)
- self.btnSave = wx.Button(self, wx.ID_SAVE)
- self.btnSave.Hide()
- self.btnSave.Bind(wx.EVT_BUTTON, self.processRename)
+ # Do some processing to ensure that we have All 0 and All 5 at the top
+ all5 = sChar.all5()
+ all0 = sChar.all0()
- 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)
+ charList.remove(all5)
+ charList.remove(all0)
- self.charChoice = wx.Choice(self, wx.ID_ANY, style=0)
- self.navSizer.Add(self.charChoice, 1, wx.ALL | wx.EXPAND, 5)
+ charList.insert(0, all5)
+ charList.insert(0, all0)
- charList = sChar.getCharacterList()
+ return charList
- for id, name, active in charList:
- i = self.charChoice.Append(name, id)
- if active:
- self.charChoice.SetSelection(i)
+ def DoNew(self, name):
+ sChar = service.Character.getInstance()
+ return sChar.new(name)
- self.navSizer.Add(self.btnSave, 0, wx.ALL , 5)
+ 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
- buttons = (("new", wx.ART_NEW),
- ("rename", BitmapLoader.getBitmap("rename", "gui")),
- ("copy", wx.ART_COPY),
- ("delete", wx.ART_DELETE))
+ def DoDelete(self, entity):
+ sChar = service.Character.getInstance()
+ sChar.delete(entity)
- 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)
+class CharacterEditor(wx.Frame):
+ def __init__(self, parent):
+ wx.Frame.__init__ (self, parent, id=wx.ID_ANY, title=u"pyfa: Character Editor", pos=wx.DefaultPosition,
+ size=wx.Size(640, 600), style=wx.DEFAULT_FRAME_STYLE ^ wx.RESIZE_BORDER)
- 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)
+ i = wx.IconFromBitmap(BitmapLoader.getBitmap("character_small", "gui"))
+ self.SetIcon(i)
+ self.mainFrame = parent
+ #self.disableWin = wx.WindowDisabler(self)
+ sFit = service.Fit.getInstance()
- mainSizer.Add(self.navSizer, 0, wx.ALL | wx.EXPAND, 5)
+ self.SetBackgroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_BTNFACE))
+
+ mainSizer = wx.BoxSizer(wx.VERTICAL)
+
+ 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 @@ def __init__(self, parent):
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 @@ def __init__(self, parent):
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 @@ def btnRestrict(self):
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,119 +203,24 @@ def closeEvent(self, event):
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())
- 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)
+ self.btnRestrict()
- 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())
+ event.Skip()
def Destroy(self):
sFit = service.Fit.getInstance()
@@ -329,7 +233,8 @@ def Destroy(self):
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 @@ def __init__(self, parent):
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 @@ def __init__(self, parent):
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 @@ def populateSkillTree(self):
tree.SortChildren(root)
+ if event:
+ event.Skip()
+
def expandLookup(self, event):
root = event.Item
tree = self.skillTreeListCtrl
@@ -416,11 +327,11 @@ def expandLookup(self, event):
#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 @@ def spawnMenu(self, item):
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 @@ def changeLevel(self, event):
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 @@ def changeLevel(self, event):
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)
-
- pmainSizer = wx.BoxSizer(wx.HORIZONTAL)
-
- availableSizer = wx.BoxSizer(wx.VERTICAL)
- pmainSizer.Add(availableSizer, 1, wx.ALL | wx.EXPAND, 5)
-
- 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)
+ BaseImplantEditorView.__init__ (self, parent)
- 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)
+ self.determineEnabled()
- availableSizer.Add(self.availableImplantsTree, 1, wx.EXPAND)
+ if "__WXGTK__" in wx.PlatformInfo:
+ self.pluggedImplantsTree.Bind(wx.EVT_RIGHT_UP, self.scheduleMenu)
+ else:
+ self.pluggedImplantsTree.Bind(wx.EVT_RIGHT_DOWN, self.scheduleMenu)
- buttonSizer = wx.BoxSizer(wx.VERTICAL)
- pmainSizer.Add(buttonSizer, 0, wx.TOP, 5)
+ def bindContext(self):
+ self.Parent.Parent.entityEditor.Bind(wx.EVT_CHOICE, self.contextChanged)
- 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)
+ def contextChanged(self, event):
+ BaseImplantEditorView.contextChanged(self, event)
+ self.determineEnabled()
- self.pluggedImplantsTree = AvailableImplantsView(self, style=wx.LC_SINGLE_SEL)
+ def getImplantsFromContext(self):
+ sChar = service.Character.getInstance()
+ char = self.Parent.Parent.entityEditor.getActiveEntity()
- pmainSizer.Add(self.pluggedImplantsTree, 1, wx.ALL | wx.EXPAND, 5)
+ return sChar.getImplants(char.ID)
- self.SetSizer(pmainSizer)
+ def addImplantToContext(self, item):
+ sChar = service.Character.getInstance()
+ char = self.Parent.Parent.entityEditor.getActiveEntity()
- # 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")
+ sChar.addImplant(char.ID, item.ID)
- self.availableImplantsTree.SortChildren(self.availableRoot)
+ def removeImplantFromContext(self, implant):
+ sChar = service.Character.getInstance()
+ char = self.Parent.Parent.entityEditor.getActiveEntity()
- #Bind the event to replace dummies by real data
- self.availableImplantsTree.Bind(wx.EVT_TREE_ITEM_EXPANDING, self.expandLookup)
+ sChar.removeImplant(char.ID, implant)
- #Bind add & remove buttons
- self.btnAdd.Bind(wx.EVT_BUTTON, self.addImplant)
- self.btnRemove.Bind(wx.EVT_BUTTON, self.removeImplant)
+ def scheduleMenu(self, event):
+ event.Skip()
+ wx.CallAfter(self.spawnMenu)
- #Bind the change of a character*
- self.Parent.Parent.Bind(GE.CHAR_CHANGED, self.charChanged)
- self.Enable(False)
- self.Layout()
+ 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 update(self, implants):
- self.implants = implants[:]
- self.implants.sort(key=lambda i: int(i.getModifiedItemAttr("implantness")))
- self.pluggedImplantsTree.update(self.implants)
+ def determineEnabled(self):
+ char = self.Parent.Parent.entityEditor.getActiveEntity()
- def charChanged(self, event):
- sChar = service.Character.getInstance()
- charID = self.Parent.Parent.getActiveCharacter()
- name = sChar.getCharName(charID)
- if name == "All 0" or name == "All 5":
+ if char.name in ("All 0", "All 5"):
self.Enable(False)
else:
- self.Enable(True)
-
- self.update(sChar.getImplants(charID))
- event.Skip()
+ self.Enable()
- 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 @@ def __init__(self, parent):
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 @@ def charChanged(self, event):
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()
diff --git a/gui/characterSelection.py b/gui/characterSelection.py
index 990dc8bf56..47df7e0183 100644
--- a/gui/characterSelection.py
+++ b/gui/characterSelection.py
@@ -83,9 +83,9 @@ def refreshCharacterList(self, event=None):
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
diff --git a/gui/gangView.py b/gui/gangView.py
index 2e6d7b1dae..25a457301f 100644
--- a/gui/gangView.py
+++ b/gui/gangView.py
@@ -346,9 +346,8 @@ def RefreshCharacterList(self, event = None):
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:
diff --git a/gui/implantView.py b/gui/implantView.py
index d878381137..4b47552527 100644
--- a/gui/implantView.py
+++ b/gui/implantView.py
@@ -21,12 +21,62 @@
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 @@ def fitChanged(self, event):
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 @@ def fitChanged(self, event):
self.deselectItems()
- self.populate(stuff)
- self.refresh(stuff)
+ self.update(stuff)
event.Skip()
def addItem(self, event):
@@ -123,14 +172,27 @@ def scheduleMenu(self, event):
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)
diff --git a/gui/mainFrame.py b/gui/mainFrame.py
index 4c0d39ba73..7557813db7 100644
--- a/gui/mainFrame.py
+++ b/gui/mainFrame.py
@@ -50,6 +50,7 @@
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 @@ def showAttrEditor(self, event):
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 @@ def registerMenu(self):
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
diff --git a/gui/mainMenuBar.py b/gui/mainMenuBar.py
index 78376fde7d..e8c3cb614f 100644
--- a/gui/mainMenuBar.py
+++ b/gui/mainMenuBar.py
@@ -33,6 +33,7 @@ def __init__(self):
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 @@ def __init__(self):
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)
diff --git a/gui/patternEditor.py b/gui/patternEditor.py
index 40b8e45aae..92454d750b 100644
--- a/gui/patternEditor.py
+++ b/gui/patternEditor.py
@@ -23,68 +23,77 @@
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 DmgPatternEditorDlg(wx.Dialog):
- DAMAGE_TYPES = ("em", "thermal", "kinetic", "explosive")
+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):
- wx.Dialog.__init__(self, parent, id = wx.ID_ANY, title = u"Damage Pattern Editor", size = wx.Size( 400,240 ))
+ EntityEditor.__init__(self, parent, "Damage Profile")
+ self.SetEditorValidator(DmgPatternTextValidor)
- self.block = False
- self.SetSizeHintsSz(wx.DefaultSize, wx.DefaultSize)
+ 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"]
- mainSizer = wx.BoxSizer(wx.VERTICAL)
+ def DoNew(self, name):
+ sDP = service.DamagePattern.getInstance()
+ return sDP.newPattern(name)
- self.headerSizer = headerSizer = wx.BoxSizer(wx.HORIZONTAL)
+ 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
- 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()
+ def DoDelete(self, entity):
+ sDP = service.DamagePattern.getInstance()
+ sDP.deletePattern(entity)
- btn.SetMinSize(size)
- btn.SetMaxSize(size)
+class DmgPatternEditorDlg(wx.Dialog):
+ DAMAGE_TYPES = ("em", "thermal", "kinetic", "explosive")
- btn.Layout()
- setattr(self, name, btn)
- btn.Enable(True)
- btn.SetToolTipString("%s pattern" % name.capitalize())
- headerSizer.Add(btn, 0, wx.ALIGN_CENTER_VERTICAL)
+ def __init__(self, parent):
+ wx.Dialog.__init__(self, parent, id = wx.ID_ANY, title = u"Damage Pattern Editor", size = wx.Size( 400,240 ))
- 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)
+ self.block = False
+ self.SetSizeHintsSz(wx.DefaultSize, wx.DefaultSize)
- mainSizer.Add(headerSizer, 0, wx.EXPAND | wx.ALL, 2)
+ mainSizer = wx.BoxSizer(wx.VERTICAL)
+
+ 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 @@ def __init__(self, parent):
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 @@ def __init__(self, parent):
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 @@ def __init__(self, parent):
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 @@ def ValuesUpdated(self, event=None):
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 @@ 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)
+ 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 @@ def patternChanged(self, event=None):
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 @@ def exportPatterns(self, event):
sDP = service.DamagePattern.getInstance()
toClipboard( sDP.exportPatterns() )
self.stNotice.SetLabel("Patterns exported to clipboard")
+
+ def contextChanged(self, event):
+ print "lol"
\ No newline at end of file
diff --git a/gui/resistsEditor.py b/gui/resistsEditor.py
index 3d8ac6ef22..374f9c1fc0 100644
--- a/gui/resistsEditor.py
+++ b/gui/resistsEditor.py
@@ -22,64 +22,76 @@
import service
from gui.utils.clipboard import toClipboard, fromClipboard
from service.targetResists import ImportError
+from gui.builtinViews.entityEditor import EntityEditor, BaseValidator
-class ResistsEditorDlg(wx.Dialog):
- DAMAGE_TYPES = ("em", "thermal", "kinetic", "explosive")
+class TargetResistsTextValidor(BaseValidator):
+ def __init__(self):
+ BaseValidator.__init__(self)
- def __init__(self, parent):
- wx.Dialog.__init__(self, parent, id = wx.ID_ANY, title = u"Target Resists Editor", size = wx.Size( 350,240 ))
+ def Clone(self):
+ return TargetResistsTextValidor()
- self.block = False
- self.SetSizeHintsSz(wx.DefaultSize, wx.DefaultSize)
+ 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
- mainSizer = wx.BoxSizer(wx.VERTICAL)
- self.headerSizer = headerSizer = wx.BoxSizer(wx.HORIZONTAL)
+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
- self.choices = sTR.getTargetResistsList()
+ def DoNew(self, name):
+ sTR = service.TargetResists.getInstance()
+ return sTR.newPattern(name)
- # 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)
+ def DoRename(self, entity, name):
+ sTR = service.TargetResists.getInstance()
+ sTR.renamePattern(entity, name)
- self.namePicker = wx.TextCtrl(self, style=wx.TE_PROCESS_ENTER)
- self.namePicker.Bind(wx.EVT_TEXT_ENTER, self.processRename)
- self.namePicker.Hide()
+ def DoCopy(self, entity, name):
+ sTR = service.TargetResists.getInstance()
+ copy = sTR.copyPattern(entity)
+ sTR.renamePattern(copy, name)
+ return copy
- size = None
- headerSizer.Add(self.ccResists, 1, wx.ALIGN_CENTER_VERTICAL | wx.RIGHT | wx.LEFT, 3)
+ def DoDelete(self, entity):
+ sTR = service.TargetResists.getInstance()
+ sTR.deletePattern(entity)
- 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()
+class ResistsEditorDlg(wx.Dialog):
- btn.SetMinSize(size)
- btn.SetMaxSize(size)
+ DAMAGE_TYPES = ("em", "thermal", "kinetic", "explosive")
- btn.Layout()
- setattr(self, name, btn)
- btn.Enable(True)
- btn.SetToolTipString("%s resist profile" % name.capitalize())
- headerSizer.Add(btn, 0, wx.ALIGN_CENTER_VERTICAL)
+ def __init__(self, parent):
+ wx.Dialog.__init__(self, parent, id = wx.ID_ANY, title = u"Target Resists Editor", size = wx.Size( 350,240 ))
+ self.block = False
+ self.SetSizeHintsSz(wx.DefaultSize, wx.DefaultSize)
- 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 = wx.BoxSizer(wx.VERTICAL)
- 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 @@ def __init__(self, parent):
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 @@ def ValuesUpdated(self, event=None):
return
try:
- p = self.getActivePattern()
+ p = self.entityEditor.getActiveEntity()
for type in self.DAMAGE_TYPES:
editObj = getattr(self, "%sEdit"%type)
@@ -220,33 +234,15 @@ def ValuesUpdated(self, event=None):
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 @@ def patternChanged(self, event=None):
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"
diff --git a/gui/setEditor.py b/gui/setEditor.py
new file mode 100644
index 0000000000..4e8baa9eda
--- /dev/null
+++ b/gui/setEditor.py
@@ -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 .
+#===============================================================================
+
+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")
diff --git a/imgs/gui/hardwire_small.png b/imgs/gui/hardwire_small.png
new file mode 100644
index 0000000000..58b63b045d
Binary files /dev/null and b/imgs/gui/hardwire_small.png differ
diff --git a/service/__init__.py b/service/__init__.py
index e7e0286d47..b19c6db17a 100644
--- a/service/__init__.py
+++ b/service/__init__.py
@@ -10,6 +10,7 @@
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)):
diff --git a/service/character.py b/service/character.py
index 83e50bbaa4..a4d68c98ac 100644
--- a/service/character.py
+++ b/service/character.py
@@ -32,7 +32,9 @@
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 @@ def run(self):
# 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 @@ def run(self):
"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 @@ def getInstance(cls):
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 @@ def all5ID(self):
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 @@ def getDirtySkills(self, charID):
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 @@ def saveSkill(self, charID, skillID):
def addImplant(self, charID, itemID):
char = eos.db.getCharacter(charID)
+ if char.ro:
+ logger.error("Trying to add implant to read-only character")
+ return
+
implant = eos.types.Implant(eos.db.getItem(itemID))
- char.implants.freeSlot(implant.slot)
char.implants.append(implant)
+ eos.db.commit()
- def removeImplant(self, charID, slot):
+ 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)
diff --git a/service/damagePattern.py b/service/damagePattern.py
index 374940e0ca..b67b109564 100644
--- a/service/damagePattern.py
+++ b/service/damagePattern.py
@@ -46,9 +46,10 @@ def getDamagePatternList(self):
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):
diff --git a/service/fit.py b/service/fit.py
index e15faaf8a4..a931061f1d 100644
--- a/service/fit.py
+++ b/service/fit.py
@@ -277,7 +277,7 @@ def searchFits(self, name):
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 @@ def addImplant(self, fitID, itemID):
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 @@ def toggleImplant(self, fitID, i):
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]
diff --git a/service/implantSet.py b/service/implantSet.py
new file mode 100644
index 0000000000..0d0df58084
--- /dev/null
+++ b/service/implantSet.py
@@ -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 .
+#===============================================================================
+
+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)
+
diff --git a/service/market.py b/service/market.py
index 00323b78b8..0d8538732a 100644
--- a/service/market.py
+++ b/service/market.py
@@ -29,12 +29,15 @@
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 @@ def processSearches(self):
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 @@ def __makeRevDict(self, orig):
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):
diff --git a/service/targetResists.py b/service/targetResists.py
index a8f61b9f92..2640551f45 100644
--- a/service/targetResists.py
+++ b/service/targetResists.py
@@ -39,9 +39,10 @@ def getTargetResistsList(self):
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):