From e533048f753947c59dc84ce58ade5dc27e816f6a Mon Sep 17 00:00:00 2001 From: Peter Eliasson Date: Mon, 19 Jan 2015 18:19:43 +0100 Subject: [PATCH] Added basic filtering to the GUI (possible to filter by player name and kill date). --- src/gui.lua | 347 +++++++++++++++++++++++++++++++++++++++-------------------- 1 file changed, 233 insertions(+), 114 deletions(-) diff --git a/src/gui.lua b/src/gui.lua index 60fad70..d7adbb4 100644 --- a/src/gui.lua +++ b/src/gui.lua @@ -12,11 +12,10 @@ addon.gui = gui; -- AceGUI local AceGUI = LibStub("AceGUI-3.0"); +-- Constants +local RAID_TIME_FORMAT = "%m/%d/%y %H:%M"; -local classIdToClassName = {}; -FillLocalizedClassList(classIdToClassName); - - +-- Formats a long number as a more human-readable version. function gui:FormatNumber(number) if Skada and Skada.FormatNumber then return Skada:FormatNumber(number) @@ -30,58 +29,28 @@ function gui:FormatNumber(number) end end -function gui:CreateHighScoreParseEntry(parse, role, rank) - local entryWidget = AceGUI:Create("SimpleGroup"); - entryWidget:SetFullWidth(true); - entryWidget:SetLayout("Flow"); - entryWidget:SetHeight(30); - - local classId = parse.class; - local classColor = RAID_CLASS_COLORS[classId]; - local className = classIdToClassName[classId]; - - local relativeWidth = floor((1/6)*100)/100; - - local rankLabel = AceGUI:Create("Label"); - rankLabel:SetText(rank); - rankLabel:SetFontObject(GameFontHighlightSmall); - rankLabel:SetRelativeWidth(relativeWidth); - - local dpsHpsLabel = AceGUI:Create("Label"); - local dpsHps = self:FormatNumber((role == "HEALER") and parse.hps or parse.dps); - dpsHpsLabel:SetText(dpsHps); - dpsHpsLabel:SetFontObject(GameFontHighlightSmall); - dpsHpsLabel:SetRelativeWidth(relativeWidth); - - local nameLabel = AceGUI:Create("Label"); - nameLabel:SetText(parse.name); - nameLabel:SetColor(classColor.r, classColor.g, classColor.b); - nameLabel:SetFontObject(GameFontHighlightSmall); - nameLabel:SetRelativeWidth(relativeWidth); - - local specLabel = AceGUI:Create("Label"); - specLabel:SetText(parse.specName or ""); - specLabel:SetFontObject(GameFontHighlightSmall); - specLabel:SetRelativeWidth(relativeWidth); - - local ilvlLabel = AceGUI:Create("Label"); - ilvlLabel:SetText(parse.itemLevel or ""); - ilvlLabel:SetFontObject(GameFontHighlightSmall); - ilvlLabel:SetRelativeWidth(relativeWidth); - - local dateLabel = AceGUI:Create("Label"); - dateLabel:SetText(date("%m/%d/%y", parse.startTime)); - dateLabel:SetFontObject(GameFontHighlightSmall); - dateLabel:SetRelativeWidth(relativeWidth); - - entryWidget:AddChild(rankLabel); - entryWidget:AddChild(dpsHpsLabel); - entryWidget:AddChild(nameLabel); - entryWidget:AddChild(specLabel); - entryWidget:AddChild(ilvlLabel); - entryWidget:AddChild(dateLabel); - - return entryWidget; +-- Creats a row of labels taking up 1/numLabels relative +-- width per label and adds each label to the container. +-- The labels data should be a list of objects: +-- {name, text, modifyFunction, onClick} +function gui:CreateLabelRow(container, labelDatas) + local relativeWidth = floor((1/#labelDatas)*100)/100; + for _, labelData in pairs(labelDatas) do + local _, text, modifyFunction, onClick = unpack(labelData); + local label; + if onClick then + label = AceGUI:Create("InteractiveLabel"); + label:SetCallback("OnClick", onClick); + else + label = AceGUI:Create("Label"); + end + label:SetText(text); + label:SetRelativeWidth(relativeWidth); + if modifyFunction then + modifyFunction(label) + end + container:AddChild(label); + end end function gui:CreateGuildDropdown() @@ -150,70 +119,137 @@ function gui:CreateEncounterDropdown() return dropdown; end -function gui:CreateHighScoreScrollFrame() - local scrollFrame = AceGUI:Create("ScrollFrame"); - scrollFrame:SetLayout("Flow"); - scrollFrame:SetFullWidth(true); - scrollFrame:SetFullHeight(true); - self.highScoreParsesScrollFrame = scrollFrame; +function gui:CreateFilterEntry(filterId, filterValue) + local container = AceGUI:Create("SimpleGroup"); + container:SetRelativeWidth(0.5); + container:SetLayout("Flow"); + + local label = AceGUI:Create("Label"); + label:SetRelativeWidth(0.65); + + if filterId == "startTime" then + label:SetText("Time: " .. + date(RAID_TIME_FORMAT, filterValue)); + elseif filterId == "name" then + label:SetText("Name: " .. filterValue); + else + return; + end + + + local removeButton = AceGUI:Create("Button") + removeButton:SetText("Remove"); + removeButton:SetHeight(18); + removeButton:SetRelativeWidth(0.30); + removeButton:SetCallback("OnClick", function() + self:UnsetParseFilter(filterId); + end) + + container:AddChild(label); + container:AddChild(removeButton); + + return container; +end - local relativeWidth = floor((1/6)*100)/100; +-- Create the filter container, a row below the dropdowns +-- that displays the current selected filters. +-- Filtering by: "NameOfPlayer", "1/2/3 04:05" +function gui:CreateFilterContainer() + local filterContainer = AceGUI:Create("InlineGroup"); + self.filterContainer = filterContainer; + filterContainer:SetFullWidth(true); + filterContainer:SetLayout("Flow"); + filterContainer:SetTitle("Filtered by"); + + local nothingLabel = AceGUI:Create("Label"); + nothingLabel:SetText("-- Nothing, click on a player name or a time to filter. --"); + nothingLabel:SetFullWidth(true); + filterContainer:AddChild(nothingLabel); + + return filterContainer; +end - -- Header: - -- Rank | DPS/HPS | Name | Class | Spec | Item Level | Date +-- Creates the headers for the parses: +-- Rank | DPS/HPS | Name | Spec | Item Level | Time +function gui:CreateHighScoreParseHeader() local headerContainer = AceGUI:Create("SimpleGroup"); headerContainer:SetFullWidth(true); headerContainer:SetLayout("Flow"); + + local labelDatas = { + {"rank", "Rank"}, + {"dpsHps", "DPS/HPS"}, + {"name", "Name"}, + {"spec", "Spec"}, + {"ilvl", "Item Level"}, + {"time", "Time"} + } + self:CreateLabelRow(headerContainer, labelDatas); + return headerContainer; +end + +-- Creates a row for a parse entry. +-- Rank | DPS/HPS | Name | Spec | Item Level | Time +function gui:CreateHighScoreParseEntry(parse, role, rank) + local entryWidget = AceGUI:Create("SimpleGroup"); + entryWidget:SetFullWidth(true); + entryWidget:SetLayout("Flow"); + entryWidget:SetHeight(30); - local rankLabel = AceGUI:Create("Label"); - rankLabel:SetText("Rank"); - rankLabel:SetFontObject(GameFontHighlightSmall); - rankLabel:SetRelativeWidth(relativeWidth); - - local dpsHpsLabel = AceGUI:Create("Label"); - dpsHpsLabel:SetText("DPS/HPS"); - dpsHpsLabel:SetFontObject(GameFontHighlightSmall); - dpsHpsLabel:SetRelativeWidth(relativeWidth); - - local nameLabel = AceGUI:Create("Label"); - nameLabel:SetText("Name"); - nameLabel:SetFontObject(GameFontHighlightSmall); - nameLabel:SetRelativeWidth(relativeWidth); - - local specLabel = AceGUI:Create("Label"); - specLabel:SetText("Spec"); - specLabel:SetFontObject(GameFontHighlightSmall); - specLabel:SetRelativeWidth(relativeWidth); - - local ilvlLabel = AceGUI:Create("Label"); - ilvlLabel:SetText("Item Level"); - ilvlLabel:SetFontObject(GameFontHighlightSmall); - ilvlLabel:SetRelativeWidth(relativeWidth); - - local dateLabel = AceGUI:Create("Label"); - dateLabel:SetText("Date"); - dateLabel:SetFontObject(GameFontHighlightSmall); - dateLabel:SetRelativeWidth(relativeWidth); - - headerContainer:AddChild(rankLabel); - headerContainer:AddChild(dpsHpsLabel); - headerContainer:AddChild(nameLabel); - headerContainer:AddChild(specLabel); - headerContainer:AddChild(ilvlLabel); - headerContainer:AddChild(dateLabel); + local classColor = {RAID_CLASS_COLORS[parse.class].r, + RAID_CLASS_COLORS[parse.class].g, + RAID_CLASS_COLORS[parse.class].b}; + local dpsHps = self:FormatNumber((role == "HEALER") and parse.hps or parse.dps); + + local labelDatas = { + {"rank", rank}, + {"dpsHps", dpsHps}, + {"name", + parse.name, + function(label) + label:SetColor(unpack(classColor)); + end, + function() + self:ToggleParseFilter("name", parse.name); + end + }, + {"spec", parse.specName}, + {"ilvl", parse.itemLevel}, + {"time", + date(RAID_TIME_FORMAT, parse.startTime), + nil, + function() + self:ToggleParseFilter("startTime", parse.startTime) + end + } + } + + self:CreateLabelRow(entryWidget, labelDatas); + return entryWidget; +end + +function gui:CreateHighScoreScrollFrame() + local scrollFrame = AceGUI:Create("ScrollFrame"); + scrollFrame:SetLayout("Flow"); + scrollFrame:SetFullWidth(true); + scrollFrame:SetFullHeight(true); + + self.highScoreParsesScrollFrame = scrollFrame; local parsesContainer = AceGUI:Create("SimpleGroup"); self.highScoreParsesContainer = parsesContainer; parsesContainer:SetFullWidth(true); parsesContainer:SetLayout("Flow"); - scrollFrame:AddChild(headerContainer); + scrollFrame:AddChild(self:CreateHighScoreParseHeader()); scrollFrame:AddChild(parsesContainer); return scrollFrame; end +-- Creates container in the center of the GUI holding the parses. +-- The group has 3 tabs, one for each of DPSers, Healers and Tanks. function gui:CreateHighScoreTabGroup() local container = AceGUI:Create("TabGroup"); self.highScoreTabGroup = container; @@ -235,28 +271,103 @@ function gui:CreateHighScoreTabGroup() return container; end +-- Creates the main GUI frame. function gui:CreateMainFrame() local frame = AceGUI:Create("Frame") self.mainFrame = frame; - frame:Hide() - frame:SetWidth(800) - frame:SetHeight(600) + frame:Hide(); + frame:SetWidth(800); + frame:SetHeight(600); frame:SetTitle(format("Guild Skada High Score (%s)", addon.versionName)); - frame:SetLayout("Flow") + frame:SetLayout("Flow"); frame:SetCallback("OnClose", function() - gui:HideMainFrame() + gui:HideMainFrame(); end) - frame:AddChild(self:CreateGuildDropdown()); - frame:AddChild(self:CreateZoneDropdown()); - frame:AddChild(self:CreateDifficultyDropdown()); - frame:AddChild(self:CreateEncounterDropdown()); + local dropdownContainer = AceGUI:Create("InlineGroup"); + dropdownContainer:SetLayout("Flow"); + dropdownContainer:SetFullWidth(true); + dropdownContainer:SetTitle("Select an Encounter"); + + dropdownContainer:AddChild(self:CreateGuildDropdown()); + dropdownContainer:AddChild(self:CreateZoneDropdown()); + dropdownContainer:AddChild(self:CreateDifficultyDropdown()); + dropdownContainer:AddChild(self:CreateEncounterDropdown()); + + frame:AddChild(dropdownContainer); + frame:AddChild(self:CreateFilterContainer()); frame:AddChild(self:CreateHighScoreTabGroup()); return frame; end +-- Takes a list of parse objects and applies filters them +-- by the attribute filters defined in parseFilters, +-- returning those passing all filters. +function gui:FilterParses(parses) + if not self.parseFilters then + return parses + end; + + local filteredParses = {} + for _, parse in ipairs(parses) do + local passedAll = true; + for attribute, matchValue in pairs(self.parseFilters) do + if not parse[attribute] or parse[attribute] ~= matchValue then + passedAll = false; + break; + end + end + if passedAll then + tinsert(filteredParses, parse); + end + end + return filteredParses; +end + +function gui:SetParseFilter(attribute, value) + self.parseFilters[attribute] = value; + self:DisplayParses(); + self:DisplayParseFilters(); +end + +function gui:UnsetParseFilter(attribute) + self:SetParseFilter(attribute, nil); +end + +function gui:ToggleParseFilter(attribute, value) + if self.parseFilters[attribute] ~= value then + self:SetParseFilter(attribute, value); + else + self:UnsetParseFilter(attribute, value); + end +end + +function gui:DisplayParseFilters() + self.filterContainer:ReleaseChildren(); + + local filterEntries = {} + for filterId, filterValue in pairs(self.parseFilters) do + local filterEntry = self:CreateFilterEntry(filterId, filterValue); + tinsert(filterEntries, filterEntry); + end + + if #filterEntries > 0 then + for _, filterEntry in ipairs(filterEntries) do + self.filterContainer:AddChild(filterEntry) + end + else + local nothingLabel = AceGUI:Create("Label"); + nothingLabel:SetText("-- Nothing, click on a name or a time to filter. --"); + nothingLabel:SetFullWidth(true); + self.filterContainer:AddChild(nothingLabel); + end +end + +-- Uses the currently selected guild/zone/diff/encounter/role +-- and attempts to fetch all parses for this combination. The +-- parses are also run trough #FilterParses before being displayed. function gui:DisplayParses() local guildName = self.selectedGuild; local zoneId = self.selectedZone; @@ -269,15 +380,18 @@ function gui:DisplayParses() parsesContainer:ReleaseChildren(); if guildName and zoneId and difficultyId and encounter and roleId then - local parses, numParses = addon.highscore:GetParses(guildName, - zoneId, difficultyId, encounter, roleId); - if numParses > 0 then + local parses, _ = addon.highscore:GetParses(guildName, zoneId, difficultyId, encounter, roleId); + parses = self:FilterParses(parses); + + if #parses > 0 then parsesContainer:PauseLayout(); scrollFrame:PauseLayout(); + for rank, parse in ipairs(parses) do local entryWidget = self:CreateHighScoreParseEntry(parse, roleId, rank); parsesContainer:AddChild(entryWidget); end + parsesContainer:ResumeLayout(); parsesContainer:DoLayout(); scrollFrame:ResumeLayout(); @@ -416,6 +530,7 @@ function gui:HideMainFrame() self.zoneDropdown = nil; self.difficultyDropdown = nil; self.encounterDropdown = nil; + self.filterContainer = nil; self.highScoreTabGroup = nil; self.highScoreParsesContainer = nil; self.highScoreParsesScrollFrame = nil; @@ -433,10 +548,14 @@ end function gui:OnEnable() + self.parseFilters = {}; + self:RawHook("CloseSpecialWindows", "OnCloseSpecialWindows", true); end function gui:OnDisable() + wipe(self.parseFilters); + self:HideMainFrame(); self:UnHook("CloseSpecialWindows"); end \ No newline at end of file -- 1.7.9.5