Quantcast
--[[
	PowerAurasButtons

	Module: Buttons
--]]
-- Create module frames.
local CoreFrame        = PowerAurasButtons;
local ModuleFrame      = CoreFrame:RegisterModule("Buttons", { "Auras" });
local Modules          = CoreFrame.Modules;
--[[
----------------------------------------------------------------------------------------------------
Variables
	Buttons            Stores all registered buttons in a table.
	ButtonsBySlot      All button objects by their action/slot ID.
	ButtonData         Stores the switches for each button - whether it should glow, etc.
	ButtonCache        Stores a cache of the aura actions that apply to specific action buttons.
	ThrottleActive     Stores the current throttle state.
	ThrottlePending    Stores the status of any pending mass updates.
	ThrottleTimer      Stores the current throttle timer for mass updates.
----------------------------------------------------------------------------------------------------
--]]
local Buttons          = {};
local ButtonsBySlot    = {};
local ButtonData       = {};
local ButtonCache      = setmetatable({}, { __mode = "k" });
local ThrottleActive   = nil;
local ThrottlePending  = nil;
local ThrottleTimer    = 0;
-- Upvalues.
local unpack, setmetatable, ActionButton_ShowOverlayGlow, ActionButton_HideOverlayGlow, wipe, type, GetActionInfo,
	GetMacroSpell, GetMacroItem, pairs, IsSpellOVerlayed, hooksecurefunc = unpack, setmetatable,
	ActionButton_ShowOverlayGlow, ActionButton_HideOverlayGlow, wipe, type, GetActionInfo, GetMacroSpell, GetMacroItem,
	pairs, IsSpellOVerlayed, hooksecurefunc;
-- Caches.
local spellcache = setmetatable({}, {__index=function(t,v) local a = {GetSpellInfo(v)} if GetSpellInfo(v) then t[v] = a end return a end});
local function GetSpellInfo(a)
    return unpack(spellcache[a]);
end
local itemcache = setmetatable({}, {__index=function(t,v) local a = {GetItemInfo(v)} if GetItemInfo(v) then t[v] = a end return a end});
local function GetItemInfo(a)
    return unpack(spellcache[a]);
end
--[[
----------------------------------------------------------------------------------------------------
OnButtonUpdate

Event handler for button updates. Updates glows depending on assigned auras, etc.
----------------------------------------------------------------------------------------------------
--]]
function ModuleFrame:OnButtonUpdate(button)
	-- Only bother updating if we can see it.
	if(not button or not button:IsShown()) then return; end
	-- Test the button for glowability.
	ModuleFrame:ProcessButtonActions(button);
	-- Fire button update event.
	CoreFrame:FireModuleEvent("OnButtonUpdate", button:GetName());
	-- So, does the glow need showing or hiding?
	if(ModuleFrame:GetButtonData(button:GetName())["glow"]) then
		-- Show the glow.
		ActionButton_ShowOverlayGlow(button);
	else
		-- Hide the glow.
		ActionButton_HideOverlayGlow(button);
	end
end
--[[
----------------------------------------------------------------------------------------------------
GetButtonData

Retrieves the button data table for the given button ID. Returns nil on failure.
----------------------------------------------------------------------------------------------------
--]]
function ModuleFrame:GetButtonData(buttonID)
	-- Go.
	return ButtonData[buttonID] or nil;
end
--[[
----------------------------------------------------------------------------------------------------
ProcessButtonActions

Processes all of the assigned actions on a button. This will determine whether a button should
be glowing, showing displays, etc.
----------------------------------------------------------------------------------------------------
--]]
function ModuleFrame:ProcessButtonActions(button)
	-- Few locals.
	local buttonID = button:GetName();
	-- Get the button data table if it exists. Otherwise, make a new one. We recycle the old one
	-- so the memory size won't fluctuate.
	local buttonData = ButtonData[buttonID] or {};
	-- Wipe the data.
	wipe(buttonData);
	-- Fire button processing event.
	CoreFrame:FireModuleEvent("OnButtonProcess", buttonID);
	-- Get the non blizzard auras.
	local CustomAuras, BlizzAuras = Modules.Auras:GetAuras();
	-- More locals.
	local buttonAction, buttonActionType, buttonActionID, buttonMacro, displayCount;
	-- Get the button action ID.
	buttonAction = button._state_action or button.action;
	-- Action needs to be integer.
	if(not buttonAction or type(buttonAction) ~= "number") then
		-- Action isn't valid.
		ButtonData[buttonID] = buttonData;
		return;
	end
	-- Make sure button is cached by slot.
	ButtonsBySlot[buttonAction] = button;
	-- Get the button action data.
	buttonActionType, buttonActionID = GetActionInfo(buttonAction);
	-- Get macro names if needed.
	if(buttonActionType == "macro") then
		buttonMacro = GetMacroSpell(buttonActionID) or GetMacroItem(buttonActionID);
	end
	-- Create cache if needed.
	if(not ButtonCache[buttonAction]) then
		ButtonCache[buttonAction] = {};
	end
	local buttonCache = ButtonCache[buttonAction];
	-- Right, first off we need to go over all the auras see if they're linked to this one.
	for auraID, _ in pairs(CustomAuras) do
		-- Update cache if needed...
		if(not buttonCache[auraID]) then
			buttonCache[auraID] = {};
		end
		local auraCache = buttonCache[auraID];
		-- Aura needs to be active.
		if(Modules.Auras:IsAuraShown(auraID)) then
			-- And go over the actions.
			for auraActionID, auraActionData in pairs(Modules.Auras:GetAuraActions(auraID)) do
				-- Action needs to be a valid ID (> 0)
				if(auraActionData["id"] and auraActionData["id"] > 0) then
					-- Check cache for a shortcut.
					if(auraCache[auraActionID] == true and CoreFrame:GetModuleSetting("Buttons", "EnableCache")) then
						-- Enable glows if the action says so.
						Modules.Auras:MergeAuraAction(buttonData, auraActionData);
						-- Fire the OnAuraDisplay event.
						CoreFrame:FireModuleEvent("OnButtonDisplayAura", buttonID, auraID, auraActionData,
							auraActionID);
					else
						-- If the type/data keys match, or this is a macro/spell combo then continue.
						if(buttonActionType == auraActionData["type"]
						or (buttonActionType == "macro" and auraActionData["type"] == "spell")
						or (buttonActionType == "macro" and auraActionData["type"] == "item")) then
							-- Compare ID's. If they match, we're golden. If they don't, do macro
							-- comparisons.
							if((buttonActionID == auraActionData["id"]
							and buttonActionType == auraActionData["type"])
							or buttonMacro and (auraActionData["type"] == "spell"
							and GetSpellInfo(auraActionData["id"]) == buttonMacro
							or auraActionData["type"] == "item"
							and GetItemInfo(auraActionData["id"]) == buttonMacro)) then
								-- Enable glows if the action says so.
								Modules.Auras:MergeAuraAction(buttonData, auraActionData);
								-- Fire the OnAuraDisplay event.
								CoreFrame:FireModuleEvent("OnButtonDisplayAura", buttonID, auraID,
									auraActionData, auraActionID);
								-- Cache it.
								auraCache[auraActionID] = true;
							end
						end
					end
				end
			end
		end
	end
	-- Blizzard auras need checking if glow isn't on, and if enabled.
	if(CoreFrame:GetModuleSetting("Buttons", "ShowBlizzardGlows")) then
		if(not buttonData["glow"] and buttonActionType == "spell"
		and IsSpellOverlayed(buttonActionID)) then
			-- It needs to glow.
			buttonData["glow"] = true;
		elseif(not buttonData["glow"] and buttonActionType == "macro") then
			-- Macros should glow too.
			buttonMacro = GetMacroSpell(buttonActionID) or GetMacroItem(buttonActionID);
			-- Loop over active Blizzard auras.
			for blizzAuraID, _ in pairs(BlizzAuras) do
				-- Check ID.
				if(not buttonData["glow"] and buttonMacro
				and (buttonMacro == GetSpellInfo(blizzAuraID)
				or GetItemInfo(blizzAuraID) == buttonMacro)) then
					-- Yeah, it's a match. Timers/Stacks aren't on for blizz ones.
					buttonData["glow"] = true;
					break; -- Break early, it doesn't matter if any others are glowing or not.
				end
			end
		end
	end
	-- Update.
	ButtonData[buttonID] = buttonData;
end
--[[
----------------------------------------------------------------------------------------------------
OnUpdateTrigger

Fired when OnAuraShow/OnAuraHide are called. Performs a mass button update.
----------------------------------------------------------------------------------------------------
--]]
function ModuleFrame:OnUpdateTrigger()
	-- Check to see if update throttling is enabled. If it is, we'll queue this update.
	if(CoreFrame:GetModuleSetting("Buttons", "Throttle") > 0 and ThrottleActive) then
		-- We're throttled.
		ThrottlePending = true;
	else
		-- Throttle further updates (won't do anything if disabled!)
		ModuleFrame:ThrottleUpdates();
		-- Iterate over the active buttons and go to town on it.
		for buttonID, button in pairs(Buttons) do
			-- Buttons that should be made but aren't registered yet are recorded as TRUE values.
			-- On each mass update we try to replace these with actual buttons, this fixes issues
			-- with Dominos.
			if(button and button == true) then
				Buttons[buttonID] = _G[buttonID] or true;
			end
			-- Pass to UpdateButton.
			if(button and button ~= true) then
				ModuleFrame:OnButtonUpdate(Buttons[buttonID]);
			end
		end
	end
end
--[[
----------------------------------------------------------------------------------------------------
ThrottleUpdates

Throttles further updates if the feature is enabled.
----------------------------------------------------------------------------------------------------
--]]
function ModuleFrame:ThrottleUpdates()
	-- Make sure it's enabled.
	if(CoreFrame:GetModuleSetting("Buttons", "Throttle") == 0) then return; end
	-- Stop further updates.
	ThrottleActive = true;
	-- Reset our throttle timer.
	ThrottleTimer = 0;
	-- Register update script.
	ModuleFrame:SetScript("OnUpdate", ModuleFrame.OnUpdate);
end
--[[
----------------------------------------------------------------------------------------------------
OnUpdate

Acts as our function for throttling update requests. It's called OnUpdate but is only present
while we're throttling - we unregister it after.
----------------------------------------------------------------------------------------------------
--]]
function ModuleFrame:OnUpdate(elapsed)
	-- Update time elapsed.
	ThrottleTimer = ThrottleTimer + elapsed;
	-- Time up?
	if(ThrottleTimer < CoreFrame:GetModuleSetting("Buttons", "Throttle")) then return; end
	-- Remove update script.
	ModuleFrame:SetScript("OnUpdate", nil);
	-- Time up! Rip this off.
	ThrottleActive = nil;
	-- Any updates queued?
	if(ThrottlePending) then
		-- Trigger update, remove the var.
		ThrottlePending = nil;
		-- This will trigger re-throttling if another request comes along while processing this
		-- one. It's intended.
		ModuleFrame:OnUpdateTrigger();
	end
end
--[[
----------------------------------------------------------------------------------------------------
RegisterButtons

Registers buttons into our button array for glow activation purposes.
----------------------------------------------------------------------------------------------------
--]]
function ModuleFrame:RegisterButtons(key, count)
	-- Register, nils included (it's a Dominos thing)
	local button = nil;
	for i=1,(count or 12) do
		-- Register it.
		Buttons[key .. i] = _G[key .. i] or true;
	end
end
--[[
----------------------------------------------------------------------------------------------------
OnActionCreate

Fired when an action is created. Used to set defaults in the newly made action ID.
----------------------------------------------------------------------------------------------------
--]]
function ModuleFrame:OnActionCreate(auraID, actionID)
	-- Get action.
	local actionData = Modules.Auras:GetAuraAction(auraID, actionID);
	-- Write.
	actionData["glow"] = true;
	-- Save.
	Modules.Auras:SetAuraAction(auraID, actionID, actionData);
end
--[[
----------------------------------------------------------------------------------------------------
IsEnabled

Checks to see if the module is enabled.
----------------------------------------------------------------------------------------------------
--]]
function ModuleFrame:IsEnabled()
	return true;
end
--[[
----------------------------------------------------------------------------------------------------
FixSettings

Fixes all saved variables and migrates older ones across.
----------------------------------------------------------------------------------------------------
--]]
function ModuleFrame:FixSettings(force)
	-- Do the module settings exist?
	if(not CoreFrame:GetSetting("Buttons") or force) then
		-- We'd best fix that then.
		PowerAurasButtons_SettingsDB["Buttons"] = {
			["Throttle"] = 0.05,
			["RegisterBlizzardButtons"] = true,
			["ShowBlizzardGlows"] = true,
			["EnableCache"] = true,
		};
	end
end
--[[
----------------------------------------------------------------------------------------------------
OnInitialize

Fired by the module handler. Put all the loading code into here.
----------------------------------------------------------------------------------------------------
--]]
function ModuleFrame:OnInitialize()
	-- Fix settings first.
	ModuleFrame:FixSettings();
	-- Register the needed buttons.
	if(Dominos) then
		-- Dominos reuses the Blizzard AB's and creates 60 of its own.
		CoreFrame:Debug("Dominos detected");
		ModuleFrame:RegisterButtons("DominosActionButton", 60);
	elseif(LibStub) then
		-- Bartender4 is a tad more tricky. It uses LAB which makes buttons as needed.
		-- So we need to check for LAB (and LibStub), then scan all loaded buttons and make
		-- sure future ones are added.
		local LAB = LibStub("LibActionButton-1.0", true);
		if(LAB) then
			CoreFrame:Debug("Bartender4/LibActionButton detected");
			-- LibActionButton found. Go over all of the buttons.
			for button in pairs(LAB:GetAllButtons()) do
				Buttons[button:GetName()] = button;
			end
			-- In addition, make sure this applies to future buttons.
			LAB:RegisterCallback("OnButtonCreated", function(_, button)
				Buttons[button:GetName()] = button;
			end);
			-- Add a button update hook.
			LAB:RegisterCallback("OnButtonUpdate", function(_, button)
				ModuleFrame:OnButtonUpdate(button);
			end);
		end
	end
	-- Odds are you're using the default buttons if you're not using Dominos/BT.
	-- Register them if not told otherwise.
	if(CoreFrame:GetModuleSetting("Buttons", "RegisterBlizzardButtons") or Dominos) then
		CoreFrame:Debug("Registering Blizzard buttons");
		ModuleFrame:RegisterButtons("ActionButton");
		ModuleFrame:RegisterButtons("BonusActionButton");
		ModuleFrame:RegisterButtons("MultiBarRightButton");
		ModuleFrame:RegisterButtons("MultiBarLeftButton");
		ModuleFrame:RegisterButtons("MultiBarBottomRightButton");
		ModuleFrame:RegisterButtons("MultiBarBottomLeftButton");
	end
	-- If you use Dominos or have the Blizzard buttons on, you need this.
	if(Dominos or CoreFrame:GetModuleSetting("Buttons", "RegisterBlizzardButtons")) then
		-- Hook for button updates.
		hooksecurefunc("ActionButton_Update", function(button)
			ModuleFrame:OnButtonUpdate(button);
		end);
	end
	-- Create some events for modules to hook on to.
	CoreFrame:RegisterModuleEvent("OnButtonUpdate");
	CoreFrame:RegisterModuleEvent("OnButtonProcess");
	CoreFrame:RegisterModuleEvent("OnButtonDisplayAura");
	-- Register OnAuraShow/OnAuraHide.
	CoreFrame:RegisterModuleEventListener("OnAuraShow", ModuleFrame, ModuleFrame.OnUpdateTrigger);
	CoreFrame:RegisterModuleEventListener("OnAuraHide", ModuleFrame, ModuleFrame.OnUpdateTrigger);
	CoreFrame:RegisterModuleEventListener("OnActionCreate", ModuleFrame);
	-- Cache clearing functions, ABS fires even if macros are updated!
	if(CoreFrame:GetModuleSetting("Buttons", "EnableCache") == true) then
		CoreFrame:RegisterBlizzEventListener("ACTIONBAR_SLOT_CHANGED", ModuleFrame, function(self, id)
			-- Cache macros only.
			local buttonType, macroID = GetActionInfo(id);
			if(buttonType ~= "macro" or not ButtonsBySlot[id]
				or not CoreFrame:GetModuleSetting("Buttons", "EnableCache")) then return; end
			-- Make sure table exists.
			if(not ButtonCache[id]) then
				ButtonCache[id] = {};
			else
				wipe(ButtonCache[id]);
			end
			-- Trigger re-update.
			ModuleFrame:OnButtonUpdate(ButtonsBySlot[id]);
		end);
	end
	-- Done.
	return true;
end