Quantcast

Added basic filtering to the GUI (possible to filter by player name and kill date).

Peter Eliasson [01-19-15 - 17:19]
Added basic filtering to the GUI (possible to filter by player name and kill date).
Filename
src/gui.lua
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