Mercurial > wow > reaction
diff modules/Action.lua @ 109:410d036c43b2
- reorganize modularity file structure (part 1)
author | Flick <flickerstreak@gmail.com> |
---|---|
date | Thu, 08 Jan 2009 00:57:27 +0000 |
parents | modules/ReAction_Action/ReAction_Action.lua@b2fb8f7dc780 |
children | 77bb68eb402b |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/modules/Action.lua Thu Jan 08 00:57:27 2009 +0000 @@ -0,0 +1,1157 @@ +--[[ + ReAction Action button module. + + The button module implements standard action button functionality by wrapping Blizzard's + ActionBarButtonTemplate frame and associated functions. + + It also provides action remapping support for multiple pages and possessed targets + (Mind Control, Eyes of the Beast, Karazhan Chess event, various quests, etc). +--]] + +-- local imports +local ReAction = ReAction +local L = ReAction.L +local _G = _G +local CreateFrame = CreateFrame +local format = string.format +local wipe = wipe + +ReAction:UpdateRevision("$Revision$") + +-- libraries +local KB = LibStub("LibKeyBound-1.0") +local LBF -- initialized later + +-- module declaration +local moduleID = "Action" +local module = ReAction:NewModule( moduleID ) + +-- Class declarations +local Button = { } +local Handle = { } +local PropHandler = { } + +-- Event handlers +function module:OnInitialize() + self.db = ReAction.db:RegisterNamespace( moduleID, + { + profile = { + bars = { }, + } + } + ) + self.handles = setmetatable({ }, weak) + + ReAction:RegisterBarOptionGenerator(self, "GetBarOptions") + + ReAction.RegisterCallback(self, "OnCreateBar") + ReAction.RegisterCallback(self, "OnRefreshBar") + ReAction.RegisterCallback(self, "OnDestroyBar") + ReAction.RegisterCallback(self, "OnEraseBar") + ReAction.RegisterCallback(self, "OnRenameBar") + ReAction.RegisterCallback(self, "OnConfigModeChanged") + + LBF = LibStub("LibButtonFacade",true) + + KB.RegisterCallback(self, "LIBKEYBOUND_ENABLED") + KB.RegisterCallback(self, "LIBKEYBOUND_DISABLED") + KB.RegisterCallback(self, "LIBKEYBOUND_MODE_COLOR_CHANGED","LIBKEYBOUND_ENABLED") +end + +function module:OnEnable() + ReAction:RegisterBarType(L["Action Bar"], + { + type = moduleID, + defaultButtonSize = 36, + defaultBarRows = 1, + defaultBarCols = 12, + defaultBarSpacing = 3 + }, true) + ReAction:GetModule("State"):RegisterStateProperty("page", nil, PropHandler.GetOptions(), PropHandler) +end + +function module:OnDisable() + ReAction:UnregisterBarType(L["Action Bar"]) + ReAction:GetModule("State"):UnregisterStateProperty("page") +end + +function module:OnCreateBar(event, bar, name) + if bar.config.type == moduleID then + local profile = self.db.profile + if profile.bars[name] == nil then + profile.bars[name] = { + buttons = { } + } + end + if self.handles[bar] == nil then + self.handles[bar] = Handle:New(bar, profile.bars[name]) + end + end +end + +function module:OnRefreshBar(event, bar, name) + if self.handles[bar] then + self.handles[bar]:Refresh() + end +end + +function module:OnDestroyBar(event, bar, name) + if self.handles[bar] then + self.handles[bar]:Destroy() + self.handles[bar] = nil + end +end + +function module:OnEraseBar(event, bar, name) + self.db.profile.bars[name] = nil +end + +function module:OnRenameBar(event, bar, oldname, newname) + b = self.db.profile.bars + b[newname], b[oldname] = b[oldname], nil +end + +function module:OnConfigModeChanged(event, mode) + for _, h in pairs(self.handles) do + h:SetConfigMode(mode) + end +end + +function module:LIBKEYBOUND_ENABLED(evt) + for _, h in pairs(self.handles) do + h:ShowGrid(true) + h:SetKeybindMode(true) + end +end + +function module:LIBKEYBOUND_DISABLED(evt) + for _, h in pairs(self.handles) do + h:ShowGrid(false) + h:SetKeybindMode(false) + end +end + + +---- Interface ---- +function module:GetBarOptions(bar) + local h = self.handles[bar] + if h then + return h:GetOptions() + end +end + + +---- Bar Handle ---- + +do + local options = { + hideEmpty = { + name = L["Hide Empty Buttons"], + order = 1, + type = "toggle", + width = "double", + get = "GetHideEmpty", + set = "SetHideEmpty", + }, + lockButtons = { + name = L["Lock Buttons"], + desc = L["Prevents picking up/dragging actions.|nNOTE: This setting is overridden by the global setting in Blizzard's Action Buttons tab"], + order = 2, + type = "toggle", + disabled = "LockButtonsDisabled", + get = "GetLockButtons", + set = "SetLockButtons", + }, + lockOnlyCombat = { + name = L["Only in Combat"], + desc = L["Only lock the buttons when in combat"], + order = 3, + type = "toggle", + disabled = "LockButtonsCombatDisabled", + get = "GetLockButtonsCombat", + set = "SetLockButtonsCombat", + }, + pages = { + name = L["# Pages"], + desc = L["Use the Dynamic State tab to specify page transitions"], + order = 4, + type = "range", + min = 1, + max = 10, + step = 1, + get = "GetNumPages", + set = "SetNumPages", + }, + mindcontrol = { + name = L["Mind Control Support"], + desc = L["When possessing a target (e.g. via Mind Control), map the first 12 buttons of this bar to the possessed target's actions."], + order = 5, + type = "toggle", + width = "double", + set = "SetMindControl", + get = "GetMindControl", + }, + actions = { + name = L["Edit Action IDs"], + order = 6, + type = "group", + inline = true, + args = { + method = { + name = L["Assign"], + order = 1, + type = "select", + width = "double", + values = { [0] = L["Choose Method..."], + [1] = L["Individually"], + [2] = L["All at Once"], }, + get = "GetActionEditMethod", + set = "SetActionEditMethod", + }, + rowSelect = { + name = L["Row"], + desc = L["Rows are numbered top to bottom"], + order = 2, + type = "select", + width = "half", + hidden = "IsButtonSelectHidden", + values = "GetRowList", + get = "GetSelectedRow", + set = "SetSelectedRow", + }, + colSelect = { + name = L["Col"], + desc = L["Columns are numbered left to right"], + order = 3, + type = "select", + width = "half", + hidden = "IsButtonSelectHidden", + values = "GetColumnList", + get = "GetSelectedColumn", + set = "SetSelectedColumn", + }, + pageSelect = { + name = L["Page"], + order = 4, + type = "select", + width = "half", + hidden = "IsPageSelectHidden", + values = "GetPageList", + get = "GetSelectedPage", + set = "SetSelectedPage", + }, + single = { + name = L["Action ID"], + usage = L["Specify ID 1-120"], + order = 5, + type = "input", + width = "half", + hidden = "IsButtonSelectHidden", + get = "GetActionID", + set = "SetActionID", + validate = "ValidateActionID", + }, + multi = { + name = L["ID List"], + usage = L["Specify a comma-separated list of IDs for each button in the bar (in order). Separate multiple pages with semicolons (;)"], + order = 6, + type = "input", + multiline = true, + width = "double", + hidden = "IsMultiIDHidden", + get = "GetMultiID", + set = "SetMultiID", + validate = "ValidateMultiID", + }, + }, + }, + } + + local weak = { __mode="k" } + local meta = { __index = Handle } + + function Handle:New( bar, config ) + local self = setmetatable( + { + bar = bar, + config = config, + btns = { } + }, + meta) + + if self.config.buttons == nil then + self.config.buttons = { } + end + self:Refresh() + self:SetKeybindMode(ReAction:GetKeybindMode()) + return self + end + + function Handle:Refresh() + local r, c = self.bar:GetButtonGrid() + local n = r*c + local btnCfg = self.config.buttons + if n ~= #self.btns then + for i = 1, n do + if btnCfg[i] == nil then + btnCfg[i] = {} + end + if self.btns[i] == nil then + local b = Button:New(self, i, btnCfg[i], self.config) + self.btns[i] = b + self.bar:AddButton(i,b) + end + end + for i = n+1, #self.btns do + if self.btns[i] then + self.bar:RemoveButton(self.btns[i]) + self.btns[i]:Destroy() + self.btns[i] = nil + btnCfg[i] = nil + end + end + end + local f = self.bar:GetFrame() + for _, b in ipairs(self.btns) do + b:Refresh() + end + f:SetAttribute("mindcontrol",self.config.mindcontrol) + f:Execute( + [[ + doMindControl = self:GetAttribute("mindcontrol") + control:ChildUpdate() + ]]) + + f:SetAttribute("_onstate-mindcontrol", + -- function _onstate-mindcontrol(self, stateid, newstate) + [[ + control:ChildUpdate() + ]]) + RegisterStateDriver(f, "mindcontrol", "[bonusbar:5] mc; none") + self:UpdateButtonLock() + end + + function Handle:Destroy() + for _,b in pairs(self.btns) do + if b then + b:Destroy() + end + end + end + + function Handle:SetConfigMode(mode) + for _, b in pairs(self.btns) do + b:ShowGrid(mode) + b:ShowActionIDLabel(mode) + end + end + + function Handle:ShowGrid(show) + for _, b in pairs(self.btns) do + b:ShowGrid(show) + end + end + + function Handle:UpdateButtonLock() + local f = self.bar:GetFrame() + f:SetAttribute("lockbuttons",self.config.lockButtons) + f:SetAttribute("lockbuttonscombat",self.config.lockButtonsCombat) + f:Execute( + [[ + lockButtons = self:GetAttribute("lockbuttons") + lockButtonsCombat = self:GetAttribute("lockbuttonscombat") + ]]) + end + + function Handle:SetKeybindMode(mode) + for _, b in pairs(self.btns) do + if mode then + -- set the border for all buttons to the keybind-enable color + b.border:SetVertexColor(KB:GetColorKeyBoundMode()) + b.border:Show() + elseif IsEquippedAction(b:GetActionID()) then + b.border:SetVertexColor(0, 1.0, 0, 0.35) -- from ActionButton.lua + else + b.border:Hide() + end + end + end + + function Handle:GetLastButton() + return self.btns[#self.btns] + end + + -- options handlers + function Handle:GetOptions() + return { + type = "group", + name = L["Action Buttons"], + handler = self, + args = options + } + end + + function Handle:SetHideEmpty(info, value) + if value ~= self.config.hideEmpty then + self.config.hideEmpty = value + self:ShowGrid(not value) + end + end + + function Handle:GetHideEmpty() + return self.config.hideEmpty + end + + function Handle:GetLockButtons() + return LOCK_ACTIONBAR == "1" or self.config.lockButtons + end + + function Handle:SetLockButtons(info, value) + self.config.lockButtons = value + self:UpdateButtonLock() + end + + function Handle:LockButtonsDisabled() + return LOCK_ACTIONBAR == "1" + end + + function Handle:GetLockButtonsCombat() + return self.config.lockButtonsCombat + end + + function Handle:SetLockButtonsCombat(info, value) + self.config.lockButtonsCombat = value + self:UpdateButtonLock() + end + + function Handle:LockButtonsCombatDisabled() + return LOCK_ACTIONBAR == "1" or not self.config.lockButtons + end + + function Handle:GetNumPages() + return self.config.nPages + end + + function Handle:SetNumPages(info, value) + self.config.nPages = value + self:Refresh() + end + + function Handle:GetMindControl() + return self.config.mindcontrol + end + + function Handle:SetMindControl(info, value) + self.config.mindcontrol = value + self:Refresh() + end + + function Handle:GetActionEditMethod() + return self.editMethod or 0 + end + + function Handle:SetActionEditMethod(info, value) + self.editMethod = value + end + + function Handle:IsButtonSelectHidden() + return self.editMethod ~= 1 + end + + function Handle:GetRowList() + local r,c = self.bar:GetButtonGrid() + if self.rowList == nil or #self.rowList ~= r then + local list = { } + for i = 1, r do + table.insert(list,i) + end + self.rowList = list + end + return self.rowList + end + + function Handle:GetSelectedRow() + local r, c = self.bar:GetButtonGrid() + local row = self.selectedRow or 1 + if row > r then + row = 1 + end + self.selectedRow = row + return row + end + + function Handle:SetSelectedRow(info, value) + self.selectedRow = value + end + + function Handle:GetColumnList() + local r,c = self.bar:GetButtonGrid() + if self.columnList == nil or #self.columnList ~= c then + local list = { } + for i = 1, c do + table.insert(list,i) + end + self.columnList = list + end + return self.columnList + end + + function Handle:GetSelectedColumn() + local r, c = self.bar:GetButtonGrid() + local col = self.selectedColumn or 1 + if col > c then + col = 1 + end + self.selectedColumn = col + return col + end + + function Handle:SetSelectedColumn(info, value) + self.selectedColumn = value + end + + function Handle:IsPageSelectHidden() + return self.editMethod ~= 1 or (self.config.nPages or 1) < 2 + end + + function Handle:GetPageList() + local n = self.config.nPages or 1 + if self.pageList == nil or #self.pageList ~= n then + local p = { } + for i = 1, n do + table.insert(p,i) + end + self.pageList = p + end + return self.pageList + end + + function Handle:GetSelectedPage() + local p = self.selectedPage or 1 + if p > (self.config.nPages or 1) then + p = 1 + end + self.selectedPage = p + return p + end + + function Handle:SetSelectedPage(info, value) + self.selectedPage = value + end + + function Handle:GetActionID() + local row = self.selectedRow or 1 + local col = self.selectedColumn or 1 + local r, c = self.bar:GetButtonGrid() + local n = (row-1) * c + col + local btn = self.btns[n] + if btn then + return tostring(btn:GetActionID(self.selectedPage or 1)) + end + end + + function Handle:SetActionID(info, value) + local row = self.selectedRow or 1 + local col = self.selectedColumn or 1 + local r, c = self.bar:GetButtonGrid() + local n = (row-1) * c + col + local btn = self.btns[n] + if btn then + btn:SetActionID(tonumber(value), self.selectedPage or 1) + end + end + + function Handle:ValidateActionID(info, value) + value = tonumber(value) + if value == nil or value < 1 or value > 120 then + return L["Specify ID 1-120"] + end + return true + end + + function Handle:IsMultiIDHidden() + return self.editMethod ~= 2 + end + + function Handle:GetMultiID() + local p = { } + for i = 1, self.config.nPages or 1 do + local b = { } + for _, btn in ipairs(self.btns) do + table.insert(b, btn:GetActionID(i)) + end + table.insert(p, table.concat(b,",")) + end + return table.concat(p,";\n") + end + + + local function ParseMultiID(nBtns, nPages, s) + if s:match("[^%d%s,;]") then + ReAction:Print("items other than digits, spaces, commas, and semicolons in string",s) + return nil + end + local p = { } + for list in s:gmatch("[^;]+") do + local pattern = ("^%s?$"):format(("%s*(%d+)%s*,"):rep(nBtns)) + local ids = { list:match(pattern) } + if #ids ~= nBtns then + ReAction:Print("found",#ids,"buttons instead of",nBtns) + return nil + end + table.insert(p,ids) + end + if #p ~= nPages then + ReAction:Print("found",#p,"pages instead of",nPages) + return nil + end + return p + end + + function Handle:SetMultiID(info, value) + local p = ParseMultiID(#self.btns, self.config.nPages or 1, value) + for page, b in ipairs(p) do + for button, id in ipairs(b) do + self.btns[button]:SetActionID(id, page) + end + end + end + + function Handle:ValidateMultiID(info, value) + local bad = L["Invalid action ID list string"] + if value == nil or ParseMultiID(#self.btns, self.config.nPages or 1, value) == nil then + return bad + end + return true + end +end + + +------ State property options ------ +do + local pageOptions = { + page = { + name = L["Show Page #"], + order = 11, + type = "select", + width = "half", + disabled = "IsPageDisabled", + hidden = "IsPageHidden", + values = "GetPageValues", + set = "SetProp", + get = "GetPage", + }, + } + + local function GetBarConfig(bar) + return module.db.profile.bars[bar:GetName()] + end + + function PropHandler.GetOptions() + return pageOptions + end + + function PropHandler:IsPageDisabled() + local c = GetBarConfig(self.bar) + local n = c and c.nPages or 1 + return not (n > 1) + end + + function PropHandler:IsPageHidden() + return not GetBarConfig(self.bar) + end + + function PropHandler:GetPageValues() + if not self._pagevalues then + self._pagevalues = { } + end + local c = GetBarConfig(self.bar) + if c then + local n = c.nPages + -- cache the results + if self._npages ~= n then + self._npages = n + wipe(self._pagevalues) + for i = 1, n do + self._pagevalues["page"..i] = i + end + end + end + return self._pagevalues + end + + function PropHandler:GetPage(info) + return self:GetProp(info) or 1 + end + +end + +------ ActionID allocation ------ +-- this needs to be high performance when requesting new IDs, +-- or certain controls will become sluggish. However, the new-request +-- infrastructure can be built lazily the first time that a new request +-- comes in (which will only happen at user config time: at static startup +-- config time all actionIDs should already have been assigned and stored +-- in the config file) + +local IDAlloc +do + local n = 120 + + IDAlloc = setmetatable({ wrap = 1, freecount = n }, {__index = function() return 0 end}) + + function IDAlloc:Acquire(id, hint) + id = tonumber(id) + hint = tonumber(hint) + if id and (id < 1 or id > n) then + id = nil + end + if hint and (hint < 1 or hint > n) then + hint = nil + end + if id == nil then + -- get a free ID + if hint and self[hint] == 0 then + -- use the hint if it's free + id = hint + elseif self.freecount > 0 then + -- if neither the id nor the hint are defined or free, but + -- the list is known to have free IDs, then start searching + -- at the hint for a free one + for i = hint or 1, n do + if self[i] == 0 then + id = i + break + end + end + -- self.wrap the search + if id == nil and hint and hint > 1 then + for i = 1, hint - 1 do + if self[i] == 0 then + id = i + break + end + end + end + end + if id == nil then + -- if there are no free IDs, start wrapping at 1 + id = self.wrap + self.wrap = id + 1 + if self.wrap > n then + self.wrap = 1 + end + end + end + if self[id] == 0 then + self.freecount = self.freecount - 1 + end + self[id] = self[id] + 1 + return id + end + + function IDAlloc:Release(id) + id = tonumber(id) + if id and (id >= 1 or id <= n) then + self[id] = self[id] - 1 + if self[id] == 0 then + self.freecount = self.freecount + 1 + self.wrap = 1 + end + end + end +end + +------ Button class ------ + +do + local frameRecycler = { } + local trash = CreateFrame("Frame") + local OnUpdate, KBAttach, GetActionName, GetHotkey, SetKey, FreeKey, ClearBindings, GetBindings + do + local ATTACK_BUTTON_FLASH_TIME = ATTACK_BUTTON_FLASH_TIME + local IsActionInRange = IsActionInRange + + local buttonLookup = setmetatable({},{__mode="kv"}) + + function OnUpdate(frame, elapsed) + -- note: This function taints frame.flashtime and frame.rangeTimer. Both of these + -- are only read by ActionButton_OnUpdate (which this function replaces). In + -- all other places they're just written, so it doesn't taint any secure code. + if frame.flashing == 1 then + frame.flashtime = frame.flashtime - elapsed + if frame.flashtime <= 0 then + local overtime = -frame.flashtime + if overtime >= ATTACK_BUTTON_FLASH_TIME then + overtime = 0 + end + frame.flashtime = ATTACK_BUTTON_FLASH_TIME - overtime + + local flashTexture = frame.flash + if flashTexture:IsShown() then + flashTexture:Hide() + else + flashTexture:Show() + end + end + end + + if frame.rangeTimer then + frame.rangeTimer = frame.rangeTimer - elapsed; + + if frame.rangeTimer <= 0 then + if IsActionInRange(frame.action) == 0 then + frame.icon:SetVertexColor(1.0,0.1,0.1) + else + ActionButton_UpdateUsable(frame) + end + frame.rangeTimer = 0.1 + end + end + end + + -- Use KeyBound-1.0 for binding, but use Override bindings instead of + -- regular bindings to support multiple profile use. This is a little + -- weird with the KeyBound dialog box (which has per-char selector as well + -- as an OK/Cancel box) but it's the least amount of effort to implement. + function GetActionName(f) + local b = buttonLookup[f] + if b then + return format("%s:%s", b.bar:GetName(), b.idx) + end + end + + function GetHotkey(f) + local b = buttonLookup[f] + if b then + return KB:ToShortKey(b:GetConfig().hotkey) + end + end + + function SetKey(f, key) + local b = buttonLookup[f] + if b then + local c = b:GetConfig() + if c.hotkey then + SetOverrideBinding(f, false, c.hotkey, nil) + end + if key then + SetOverrideBindingClick(f, false, key, f:GetName(), nil) + end + c.hotkey = key + b:DisplayHotkey(GetHotkey(f)) + end + end + + function FreeKey(f, key) + local b = buttonLookup[f] + if b then + local c = b:GetConfig() + if c.hotkey == key then + local action = f:GetActionName() + SetOverrideBinding(f, false, c.hotkey, nil) + c.hotkey = nil + b:DisplayHotkey(nil) + return action + end + end + return ReAction:FreeOverrideHotkey(key) + end + + function ClearBindings(f) + SetKey(f, nil) + end + + function GetBindings(f) + local b = buttonLookup[f] + if b then + return b:GetConfig().hotkey + end + end + + function KBAttach( button ) + local f = button:GetFrame() + f.GetActionName = GetActionName + f.GetHotkey = GetHotkey + f.SetKey = SetKey + f.FreeKey = FreeKey + f.ClearBindings = ClearBindings + f.GetBindings = GetBindings + buttonLookup[f] = button + f:SetKey(button:GetConfig().hotkey) + ReAction:RegisterKeybindFrame(f) + if ReAction:GetKeybindMode() then + button.border:SetVertexColor(KB:GetColorKeyBoundMode()) + button.border:Show() + end + end + end + + local meta = {__index = Button} + + function Button:New( handle, idx, config, barConfig ) + local bar = handle.bar + + -- create new self + self = setmetatable( + { + bar = bar, + idx = idx, + config = config, + barConfig = barConfig, + }, meta ) + + local name = config.name or ("ReAction_%s_%s_%d"):format(bar:GetName(),moduleID,idx) + self.name = name + config.name = name + local lastButton = handle:GetLastButton() + config.actionID = IDAlloc:Acquire(config.actionID, lastButton and lastButton.config.actionID) -- gets a free one if none configured + self.nPages = 1 + + -- have to recycle frames with the same name: CreateFrame() doesn't overwrite + -- existing globals. Can't set to nil in the global because it's then tainted. + local parent = bar:GetFrame() + local f = frameRecycler[name] + if f then + f:SetParent(parent) + else + f = CreateFrame("CheckButton", name, parent, "ActionBarButtonTemplate") + -- ditch the old hotkey text because it's tied in ActionButton_Update() to the + -- standard binding. We use override bindings. + local hotkey = _G[name.."HotKey"] + hotkey:SetParent(trash) + hotkey = f:CreateFontString(nil, "ARTWORK", "NumberFontNormalSmallGray") + hotkey:SetWidth(36) + hotkey:SetHeight(18) + hotkey:SetJustifyH("RIGHT") + hotkey:SetJustifyV("TOP") + hotkey:SetPoint("TOPLEFT",f,"TOPLEFT",-2,-2) + f.hotkey = hotkey + f.icon = _G[name.."Icon"] + f.flash = _G[name.."Flash"] + f:SetScript("OnUpdate",OnUpdate) + end + + self.hotkey = f.hotkey + self.border = _G[name.."Border"] + + f:SetAttribute("action", config.actionID) + -- install mind control actions for all buttons just for simplicity + if self.idx <= 12 then + f:SetAttribute("*action-mc", 120 + self.idx) + end + + -- set a _childupdate handler, called within the header's context + f:SetAttribute("_childupdate", + -- function _childupdate(self, snippetid, message) + [[ + local action = "action" + if doMindControl and GetBonusBarOffset() == 5 then + action = "*action-mc" + elseif page and state and page[state] then + action = "*action-"..page[state] + end + local value = self:GetAttribute(action) + self:SetAttribute("action",value) + ]]) + + -- install drag wrappers to lock buttons + bar:GetFrame():WrapScript(f, "OnDragStart", + -- OnDragStart(self, button, kind, value, ...) + [[ + if lockButtons and (PlayerInCombat() or not lockButtonsCombat) and not IsModifiedClick("PICKUPACTION") then + return "clear" + end + ]]) + + self.frame = f + + + -- initialize the hide state + f:SetAttribute("showgrid",0) + self:ShowGrid(not barConfig.hideEmpty) + if ReAction:GetConfigMode() then + self:ShowGrid(true) + end + + -- show the ID label if applicable + self:ShowActionIDLabel(ReAction:GetConfigMode()) + + -- attach the keybinder + KBAttach(self) + + -- attach to skinner + bar:SkinButton(self, + { + HotKey = self.hotkey, + } + ) + + self:Refresh() + return self + end + + function Button:Destroy() + local f = self.frame + f:UnregisterAllEvents() + f:Hide() + f:SetParent(UIParent) + f:ClearAllPoints() + if self.name then + frameRecycler[self.name] = f + end + if self.config.actionID then + IDAlloc:Release(self.config.actionID) + end + if self.config.pageactions then + for _, id in ipairs(self.config.pageactions) do + IDAlloc:Release(id) + end + end + self.frame = nil + self.config = nil + self.bar = nil + end + + function Button:Refresh() + local f = self.frame + self.bar:PlaceButton(self, 36, 36) + self:RefreshPages() + end + + function Button:GetFrame() + return self.frame + end + + function Button:GetName() + return self.name + end + + function Button:GetConfig() + return self.config + end + + function Button:GetActionID(page) + if page == nil then + -- get the effective ID + return self.frame.action -- kept up-to-date by Blizzard's ActionButton_CalculateAction() + else + if page == 1 then + return self.config.actionID + else + return self.config.pageactions and self.config.pageactions[page] or self.config.actionID + end + end + end + + function Button:SetActionID( id, page ) + id = tonumber(id) + page = tonumber(page) + if id == nil or id < 1 or id > 120 then + error("Button:SetActionID - invalid action ID") + end + if page and page ~= 1 then + if not self.config.pageactions then + self.config.pageactions = { } + end + if self.config.pageactions[page] then + IDAlloc:Release(self.config.pageactions[page]) + end + self.config.pageactions[page] = id + IDAlloc:Acquire(self.config.pageactions[page]) + self.frame:SetAttribute(("*action-page%d"):format(page),id) + else + IDAlloc:Release(self.config.actionID) + self.config.actionID = id + IDAlloc:Acquire(self.config.actionID) + self.frame:SetAttribute("action",id) + if self.config.pageactions then + self.config.pageactions[1] = id + self.frame:SetAttribute("*action-page1",id) + end + end + end + + function Button:RefreshPages( force ) + local nPages = self.barConfig.nPages + if nPages and (nPages ~= self.nPages or force) then + local f = self:GetFrame() + local c = self.config.pageactions + if nPages > 1 and not c then + c = { } + self.config.pageactions = c + end + for i = 1, nPages do + if i > 1 then + c[i] = IDAlloc:Acquire(c[i], self.config.actionID + (i-1)*self.bar:GetNumButtons()) + else + c[i] = self.config.actionID -- page 1 is the same as the base actionID + end + f:SetAttribute(("*action-page%d"):format(i),c[i]) + end + for i = nPages+1, #c do + IDAlloc:Release(c[i]) + c[i] = nil + f:SetAttribute(("*action-page%d"):format(i),nil) + end + self.nPages = nPages + end + end + + function Button:ShowGrid( show ) + if not InCombatLockdown() then + local f = self.frame + local count = f:GetAttribute("showgrid") + if show then + count = count + 1 + else + count = count - 1 + end + if count < 0 then + count = 0 + end + f:SetAttribute("showgrid",count) + + if count >= 1 and not f:GetAttribute("statehidden") then + if LBF then + LBF:SetNormalVertexColor(self.frame, 1.0, 1.0, 1.0, 0.5) + else + self.frame:GetNormalTexture():SetVertexColor(1.0, 1.0, 1.0, 0.5); + end + f:Show() + elseif count < 1 and not HasAction(self:GetActionID()) then + f:Hide() + end + end + end + + function Button:ShowActionIDLabel( show ) + local f = self:GetFrame() + if show then + local id = self:GetActionID() + if not f.actionIDLabel then + local label = f:CreateFontString(nil,"OVERLAY","GameFontNormalLarge") + label:SetAllPoints() + label:SetJustifyH("CENTER") + label:SetShadowColor(0,0,0,1) + label:SetShadowOffset(2,-2) + f.actionIDLabel = label -- store the label with the frame for recycling + + f:HookScript("OnAttributeChanged", + function(frame, attr, value) + if label:IsVisible() and attr:match("action") then + label:SetText(tostring(frame.action)) + end + end) + end + f.actionIDLabel:SetText(tostring(id)) + f.actionIDLabel:Show() + elseif f.actionIDLabel then + f.actionIDLabel:Hide() + end + end + + function Button:DisplayHotkey( key ) + self.hotkey:SetText(key or "") + end +end