diff --git a/eos/db/saveddata/__init__.py b/eos/db/saveddata/__init__.py
index 683fb499d..e43c6e601 100644
--- a/eos/db/saveddata/__init__.py
+++ b/eos/db/saveddata/__init__.py
@@ -13,6 +13,7 @@ __all__ = [
"miscData",
"targetResists",
"override",
- "crest"
+ "crest",
+ "implantSet"
]
diff --git a/eos/db/saveddata/implant.py b/eos/db/saveddata/implant.py
index 60e40bff7..c74def157 100644
--- a/eos/db/saveddata/implant.py
+++ b/eos/db/saveddata/implant.py
@@ -36,4 +36,8 @@ charImplants_table = Table("charImplants", saveddata_meta,
Column("charID", ForeignKey("characters.ID"), index = True),
Column("implantID", ForeignKey("implants.ID"), primary_key = True))
+implantsSetMap_table = Table("implantSetMap", saveddata_meta,
+ Column("setID", ForeignKey("implantSets.ID"), index = True),
+ Column("implantID", ForeignKey("implants.ID"), primary_key = True))
+
mapper(Implant, implants_table)
diff --git a/eos/db/saveddata/implantSet.py b/eos/db/saveddata/implantSet.py
new file mode 100644
index 000000000..d72b9097a
--- /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-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 a305994cd..264e3c0da 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_
@@ -385,6 +385,29 @@ 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()
+ elif lookfor is None:
+ eager = processEager(eager)
+ with sd_lock:
+ patterns = saveddata_session.query(ImplantSet).options(*eager).all()
+ return patterns
+ 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/saveddata/implantSet.py b/eos/saveddata/implantSet.py
new file mode 100644
index 000000000..00df48536
--- /dev/null
+++ b/eos/saveddata/implantSet.py
@@ -0,0 +1,48 @@
+#===============================================================================
+# 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
+
+class ImplantSet(object):
+ DAMAGE_TYPES = ("em", "thermal", "kinetic", "explosive")
+
+ def __init__(self, name=None):
+ self.name = name
+ self.__implants = HandledImplantBoosterList()
+
+ @property
+ def implants(self):
+ return self.__implants
+
+
+ EXPORT_FORMAT = "ImplantSet = %s,%d,%d,%d,%d\n"
+ @classmethod
+ def exportPatterns(cls, *patterns):
+ out = "# Exported from pyfa\n#\n"
+ out += "# Values are in following format:\n"
+ out += "# DamageProfile = [name],[EM amount],[Thermal amount],[Kinetic amount],[Explosive amount]\n\n"
+ for dp in patterns:
+ out += cls.EXPORT_FORMAT % (dp.name, dp.emAmount, dp.thermalAmount, dp.kineticAmount, dp.explosiveAmount)
+
+ return out.strip()
+
+ def __deepcopy__(self, memo):
+ p = ImplantSet(self.name)
+ p.name = "%s copy" % self.name
+ return p
diff --git a/eos/types.py b/eos/types.py
index d1628b4eb..7b03d74a2 100644
--- a/eos/types.py
+++ b/eos/types.py
@@ -29,6 +29,7 @@ from eos.saveddata.module import Module, State, Slot, Hardpoint, Rack
from eos.saveddata.drone import Drone
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
diff --git a/gui/mainFrame.py b/gui/mainFrame.py
index 4c0d39ba7..3b76a13c9 100644
--- a/gui/mainFrame.py
+++ b/gui/mainFrame.py
@@ -50,6 +50,7 @@ from gui.characterEditor import CharacterEditor, SaveCharacterAs
from gui.characterSelection import CharacterSelection
from gui.patternEditor import DmgPatternEditorDlg
from gui.resistsEditor import ResistsEditorDlg
+from gui.setEditor import ImplantSetEditorDlg
from gui.preferenceDialog import PreferenceDialog
from gui.graphFrame import GraphFrame
from gui.copySelectDialog import CopySelectDialog
@@ -367,6 +368,11 @@ class MainFrame(wx.Frame):
dlg.ShowModal()
dlg.Destroy()
+ def showImplantSetEditor(self, event):
+ dlg=ImplantSetEditorDlg(self)
+ dlg.ShowModal()
+ dlg.Destroy()
+
def showExportDialog(self, event):
""" Export active fit """
sFit = service.Fit.getInstance()
@@ -418,6 +424,8 @@ class MainFrame(wx.Frame):
self.Bind(wx.EVT_MENU, self.showDamagePatternEditor, id=menuBar.damagePatternEditorId)
# Target Resists editor
self.Bind(wx.EVT_MENU, self.showTargetResistsEditor, id=menuBar.targetResistsEditorId)
+ # Implant Set editor
+ self.Bind(wx.EVT_MENU, self.showImplantSetEditor, id=menuBar.implantSetEditorId)
# Import dialog
self.Bind(wx.EVT_MENU, self.fileImportDialog, id=wx.ID_OPEN)
# Export dialog
diff --git a/gui/mainMenuBar.py b/gui/mainMenuBar.py
index 78376fde7..fda1955d9 100644
--- a/gui/mainMenuBar.py
+++ b/gui/mainMenuBar.py
@@ -33,6 +33,7 @@ class MainMenuBar(wx.MenuBar):
self.characterEditorId = wx.NewId()
self.damagePatternEditorId = wx.NewId()
self.targetResistsEditorId = wx.NewId()
+ self.implantSetEditorId = wx.NewId()
self.graphFrameId = wx.NewId()
self.backupFitsId = wx.NewId()
self.exportSkillsNeededId = wx.NewId()
@@ -104,6 +105,10 @@ class MainMenuBar(wx.MenuBar):
targetResistsEditItem.SetBitmap(BitmapLoader.getBitmap("explosive_big", "gui"))
windowMenu.AppendItem(targetResistsEditItem)
+ implantSetEditItem = wx.MenuItem(windowMenu, self.implantSetEditorId, "Implant Set Editor\tCTRL+I")
+ implantSetEditItem.SetBitmap(BitmapLoader.getBitmap("damagePattern_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/setEditor.py b/gui/setEditor.py
new file mode 100644
index 000000000..5af848f42
--- /dev/null
+++ b/gui/setEditor.py
@@ -0,0 +1,380 @@
+#===============================================================================
+# 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
+import service
+from gui.utils.clipboard import toClipboard, fromClipboard
+from service.targetResists import ImportError
+
+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( 350,240 ))
+
+ self.block = False
+ self.SetSizeHintsSz(wx.DefaultSize, wx.DefaultSize)
+
+ mainSizer = wx.BoxSizer(wx.VERTICAL)
+
+ self.headerSizer = headerSizer = wx.BoxSizer(wx.HORIZONTAL)
+
+ sIS = service.ImplantSets.getInstance()
+
+ self.choices = sIS.getImplantSetList()
+
+ # Sort the remaining list and continue on
+ self.choices.sort(key=lambda s: s.name)
+ self.ccSets = wx.Choice(self, choices=map(lambda s: s.name, self.choices))
+ self.ccSets.Bind(wx.EVT_CHOICE, self.setChanged)
+ self.ccSets.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.ccSets, 1, wx.ALIGN_CENTER_VERTICAL | wx.RIGHT | wx.LEFT, 3)
+
+ buttons = (("new", wx.ART_NEW),
+ ("rename", BitmapLoader.getBitmap("rename", "gui")),
+ ("copy", wx.ART_COPY),
+ ("delete", wx.ART_DELETE))
+ for name, art in buttons:
+ bitmap = wx.ArtProvider.GetBitmap(art, wx.ART_BUTTON) if name != "rename" else art
+ btn = wx.BitmapButton(self, wx.ID_ANY, bitmap)
+ if size is None:
+ size = btn.GetSize()
+
+ btn.SetMinSize(size)
+ btn.SetMaxSize(size)
+
+ btn.Layout()
+ setattr(self, name, btn)
+ btn.Enable(True)
+ #btn.SetToolTipString("%s resist profile" % name.capitalize())
+ headerSizer.Add(btn, 0, wx.ALIGN_CENTER_VERTICAL)
+
+
+ self.btnSave = wx.Button(self, wx.ID_SAVE)
+ self.btnSave.Hide()
+ self.btnSave.Bind(wx.EVT_BUTTON, self.processRename)
+ headerSizer.Add(self.btnSave, 0, wx.ALIGN_CENTER)
+
+ mainSizer.Add(headerSizer, 0, wx.EXPAND | wx.ALL, 2)
+
+ self.sl = wx.StaticLine(self)
+ mainSizer.Add(self.sl, 0, wx.EXPAND | wx.TOP | wx.BOTTOM, 5)
+
+ contentSizer = wx.BoxSizer(wx.VERTICAL)
+
+ self.slfooter = wx.StaticLine(self)
+ contentSizer.Add(self.slfooter, 0, wx.EXPAND | wx.TOP, 5)
+
+ footerSizer = wx.BoxSizer(wx.HORIZONTAL)
+ perSizer = wx.BoxSizer(wx.VERTICAL)
+
+ self.stNotice = wx.StaticText(self, wx.ID_ANY, u"")
+ self.stNotice.Wrap(-1)
+ perSizer.Add(self.stNotice, 0, wx.BOTTOM | wx.TOP | wx.LEFT, 5)
+
+ footerSizer.Add(perSizer, 1, wx.ALIGN_CENTER_VERTICAL, 5)
+
+ self.totSizer = wx.BoxSizer(wx.VERTICAL)
+
+ contentSizer.Add(footerSizer, 0, wx.EXPAND, 5)
+
+ mainSizer.Add(contentSizer, 1, wx.EXPAND, 0)
+
+ 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)
+
+ self.SetSizer(mainSizer)
+
+ 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 patterns %s clipboard" % (name, direction) )
+ footerSizer.Add(btn, 0, wx.ALIGN_CENTER_HORIZONTAL | wx.ALIGN_RIGHT)
+
+ self.Layout()
+ bsize = self.GetBestSize()
+ self.SetSize((-1,bsize.height))
+
+ 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)
+
+ def closeEvent(self, event):
+ self.Destroy()
+
+ def ValuesUpdated(self, event=None):
+ '''
+ Event that is fired when resists values change. Iterates through all
+ resist edit fields. If blank, sets it to 0.0. If it is not a proper
+ decimal value, sets text color to red and refuses to save changes until
+ issue is resolved
+ '''
+ if self.block:
+ return
+
+ try:
+ p = self.getActivePattern()
+
+ for type in self.DAMAGE_TYPES:
+ editObj = getattr(self, "%sEdit"%type)
+
+ if editObj.GetValue() == "":
+ # if we are blank, overwrite with 0
+ editObj.ChangeValue("0.0")
+ editObj.SetInsertionPointEnd()
+
+ value = float(editObj.GetValue())
+
+ # assertion, because they're easy
+ assert 0 <= value <= 100
+
+ # if everything checks out, set resist attribute
+ setattr(p, "%sAmount"%type, value/100)
+ editObj.SetForegroundColour(self.colorReset)
+
+ self.stNotice.SetLabel("")
+ self.totSizer.Layout()
+
+ if event is not None:
+ event.Skip()
+
+ service.TargetResists.getInstance().saveChanges(p)
+
+ except ValueError:
+ editObj.SetForegroundColour(wx.RED)
+ self.stNotice.SetLabel("Incorrect Formatting (decimals only)")
+ except AssertionError:
+ editObj.SetForegroundColour(wx.RED)
+ self.stNotice.SetLabel("Incorrect Range (must be 0-100)")
+ 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.ccSets.GetSelection()]
+
+ def setChanged(self, event=None):
+ "Event fired when user selects pattern. Can also be called from script"
+ p = self.getActivePattern()
+ 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
+
+ #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.ccSets.GetSelection()
+ self.ValuesUpdated()
+ self.unrestrict()
+
+ def copyPattern(self,event):
+ sTR = service.TargetResists.getInstance()
+ p = sTR.copyPattern(self.getActivePattern())
+ self.choices.append(p)
+ id = self.ccSets.Append(p.name)
+ self.ccSets.SetSelection(id)
+ self.btnSave.SetLabel("Copy")
+ self.renamePattern()
+ self.setChanged()
+
+ def deletePattern(self,event):
+ sTR = service.TargetResists.getInstance()
+ sel = self.ccSets.GetSelection()
+ sTR.deletePattern(self.getActivePattern())
+ self.ccSets.Delete(sel)
+ self.ccSets.SetSelection(max(0, sel - 1))
+ del self.choices[sel]
+ self.setChanged()
+
+ def showInput(self, bool):
+ if bool and not self.namePicker.IsShown():
+ self.ccSets.Hide()
+ self.namePicker.Show()
+ self.headerSizer.Replace(self.ccSets, 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.ccSets)
+ self.ccSets.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.ccSets.Clear()
+
+ for i, choice in enumerate(map(lambda p: p.name, self.choices)):
+ self.ccSets.Append(choice)
+
+ if select is not None and choice == select:
+ self.ccSets.SetSelection(i)
+
+ if select is None:
+ self.ccSets.SetSelection(0)
+
+ self.setChanged()
+
+ def importPatterns(self, event):
+ "Event fired when import from clipboard button is clicked"
+
+ text = fromClipboard()
+ if text:
+ sTR = service.TargetResists.getInstance()
+ try:
+ sTR.importPatterns(text)
+ self.stNotice.SetLabel("Patterns successfully imported from clipboard")
+ self.showInput(False)
+ except service.targetResists.ImportError, e:
+ self.stNotice.SetLabel(str(e))
+ except Exception, e:
+ 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"
+
+ sTR = service.TargetResists.getInstance()
+ toClipboard( sTR.exportPatterns() )
+ self.stNotice.SetLabel("Patterns exported to clipboard")
diff --git a/service/__init__.py b/service/__init__.py
index e7e0286d4..b19c6db17 100644
--- a/service/__init__.py
+++ b/service/__init__.py
@@ -10,6 +10,7 @@ from service.update import Update
from service.price import Price
from service.network import Network
from service.eveapi import EVEAPIConnection, ParseXML
+from service.implantSet import ImplantSets
import wx
if not 'wxMac' in wx.PlatformInfo or ('wxMac' in wx.PlatformInfo and wx.VERSION >= (3,0)):
diff --git a/service/implantSet.py b/service/implantSet.py
new file mode 100644
index 000000000..ff7469536
--- /dev/null
+++ b/service/implantSet.py
@@ -0,0 +1,60 @@
+#===============================================================================
+# 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
+
+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.getImplantSet(None)
+
+ def getImplantSet(self, name):
+ return eos.db.getImplantSet(name)
+
+ def newSet(self):
+ p = eos.types.ImplantSet()
+ p.name = ""
+ return p
+
+ 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)