diff --git a/eos/db/gamedata/queries.py b/eos/db/gamedata/queries.py index db333fdbdf..d0ca8a5657 100644 --- a/eos/db/gamedata/queries.py +++ b/eos/db/gamedata/queries.py @@ -249,6 +249,22 @@ def searchItems(nameLike, where=None, join=None, eager=None): return items +@cachedQuery(3, "where", "nameLike", "join") +def searchSkills(nameLike, where=None, eager=None): + if not isinstance(nameLike, basestring): + raise TypeError("Need string as argument") + + items = gamedata_session.query(Item).options(*processEager(eager)).join(Item.group, Group.category) + for token in nameLike.split(' '): + token_safe = u"%{0}%".format(sqlizeString(token)) + if where is not None: + items = items.filter(and_(Item.name.like(token_safe, escape="\\"), Category.ID == 16, where)) + else: + items = items.filter(and_(Item.name.like(token_safe, escape="\\"), Category.ID == 16)) + items = items.limit(100).all() + return items + + @cachedQuery(2, "where", "itemids") def getVariations(itemids, groupIDs=None, where=None, eager=None): for itemid in itemids: diff --git a/gui/characterEditor.py b/gui/characterEditor.py index ab9874fe78..529ee3577b 100644 --- a/gui/characterEditor.py +++ b/gui/characterEditor.py @@ -64,6 +64,28 @@ def Validate(self, win): return False +class PlaceholderTextCtrl(wx.TextCtrl): + def __init__(self, *args, **kwargs): + self.default_text = kwargs.pop("placeholder", "") + kwargs["value"] = self.default_text + wx.TextCtrl.__init__(self, *args, **kwargs) + self.Bind(wx.EVT_SET_FOCUS, self.OnFocus) + self.Bind(wx.EVT_KILL_FOCUS, self.OnKillFocus) + + def OnFocus(self, evt): + if self.GetValue() == self.default_text: + self.SetValue("") + evt.Skip() + + def OnKillFocus(self, evt): + if self.GetValue().strip() == "": + self.SetValue(self.default_text) + evt.Skip() + + def Reset(self): + self.SetValue(self.default_text) + + class CharacterEntityEditor(EntityEditor): def __init__(self, parent): EntityEditor.__init__(self, parent, "Character") @@ -253,10 +275,16 @@ def __init__(self, parent): pmainSizer = wx.BoxSizer(wx.VERTICAL) + hSizer = wx.BoxSizer(wx.HORIZONTAL) + self.clonesChoice = wx.Choice(self, wx.ID_ANY, style=0) i = self.clonesChoice.Append("Omega Clone", None) self.clonesChoice.SetSelection(i) - pmainSizer.Add(self.clonesChoice, 0, wx.ALL | wx.EXPAND, 5) + hSizer.Add(self.clonesChoice, 5, wx.ALL | wx.EXPAND, 5) + + self.searchInput = PlaceholderTextCtrl(self, wx.ID_ANY, placeholder="Search...") + hSizer.Add(self.searchInput, 1, wx.ALL | wx.EXPAND, 5) + self.searchInput.Bind(wx.EVT_TEXT, self.delaySearch) sChar = Character.getInstance() self.alphaClones = sChar.getAlphaCloneList() @@ -269,6 +297,12 @@ def __init__(self, parent): self.clonesChoice.Bind(wx.EVT_CHOICE, self.cloneChanged) + pmainSizer.Add(hSizer, 0, wx.EXPAND | wx.ALL, 5) + + # Set up timer for skill search + self.searchTimer = wx.Timer(self) + self.Bind(wx.EVT_TIMER, self.populateSkillTreeSkillSearch, self.searchTimer) + tree = self.skillTreeListCtrl = wx.gizmos.TreeListCtrl(self, wx.ID_ANY, style=wx.TR_DEFAULT_STYLE | wx.TR_HIDE_ROOT) pmainSizer.Add(tree, 1, wx.EXPAND | wx.ALL, 5) @@ -342,12 +376,20 @@ def onSecStatus(self, event): self.btnSecStatus.SetLabel("Sec Status: {0:.2f}".format(value)) myDlg.Destroy() + def delaySearch(self, evt): + if self.searchInput.GetValue() == "" or self.searchInput.GetValue() == self.searchInput.default_text: + self.populateSkillTree() + else: + self.searchTimer.Stop() + self.searchTimer.Start(150, True) # 150ms + def cloneChanged(self, event): sChar = Character.getInstance() sChar.setAlphaClone(self.charEditor.entityEditor.getActiveEntity(), event.ClientData) self.populateSkillTree() def charChanged(self, event=None): + self.searchInput.Reset() char = self.charEditor.entityEditor.getActiveEntity() for i in range(self.clonesChoice.GetCount()): cloneID = self.clonesChoice.GetClientData(i) @@ -358,6 +400,23 @@ def charChanged(self, event=None): self.populateSkillTree(event) + def populateSkillTreeSkillSearch(self, event=None): + sChar = Character.getInstance() + char = self.charEditor.entityEditor.getActiveEntity() + search = self.searchInput.GetLineText(0) + + root = self.root + tree = self.skillTreeListCtrl + tree.DeleteChildren(root) + + for id, name in sChar.getSkillsByName(search): + iconId = self.skillBookImageId + childId = tree.AppendItem(root, name, iconId, data=wx.TreeItemData(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) + def populateSkillTree(self, event=None): sChar = Character.getInstance() char = self.charEditor.entityEditor.getActiveEntity() @@ -444,6 +503,7 @@ def changeLevel(self, event): # After saving the skill, we need to update not just the selected skill, but all open skills due to strict skill # level setting. We don't want to refresh tree, as that will lose all expanded categories and users location # within the tree. Thus, we loop through the tree and refresh the info. + # @todo: when collapsing branch, remove the data. This will make this loop more performant child, cookie = self.skillTreeListCtrl.GetFirstChild(self.root) while child.IsOk(): # child = Skill category diff --git a/service/character.py b/service/character.py index f1bdfb8d38..ab8736c35f 100644 --- a/service/character.py +++ b/service/character.py @@ -277,6 +277,15 @@ def getSkills(groupID): skills.append((skill.ID, skill.name)) return skills + @staticmethod + def getSkillsByName(text): + items = eos.db.searchSkills(text) + skills = [] + for skill in items: + if skill.published is True: + skills.append((skill.ID, skill.name)) + return skills + @staticmethod def setAlphaClone(char, cloneID): char.alphaCloneID = cloneID