Mercurial > wow > reaction
view modules/Action.lua @ 121:fb6c3a642ae3
Added proper vehicle bar support. The exit-vehicle button is a little kludgy, needs to be cleaned up later.
author | Flick <flickerstreak@gmail.com> |
---|---|
date | Mon, 09 Feb 2009 19:02:58 +0000 |
parents | fb48811a8736 |
children | 729232aeeb5e |
line wrap: on
line source
--[[ 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$") local weak = { __mode="k" } -- 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", }, vehicle = { name = L["Vehicle Support"], desc = L["When on a vehicle, map the first 6 buttons of this bar to the vehicle actions. The vehicle-exit button is mapped to the 7th button. Pitch controls are not supported."], order = 6, type = "toggle", width = "double", get = "GetVehicle", set = "SetVehicle", }, actions = { name = L["Edit Action IDs"], order = 7, 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 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:SetAttribute("vehicle",self.config.vehicle) f:Execute( [[ doMindControl = self:GetAttribute("mindcontrol") doVehicle = self:GetAttribute("vehicle") control:ChildUpdate() ]]) f:SetAttribute("_onstate-mc", -- function _onstate-mc(self, stateid, newstate) [[ local oldMcVehicleState = mcVehicleState mcVehicleState = newstate control:ChildUpdate() if oldMcVehicleState == "vehicle" or mcVehicleState == "vehicle" then control:ChildUpdate("vehicle") end ]]) RegisterStateDriver(f, "mc", "[target=vehicle,exists] vehicle; [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 b:SetKeybindMode(mode) 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:GetVehicle() return self.config.vehicle end function Handle:SetVehicle(info, value) self.config.vehicle = 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 ------ local frameRecycler = { } local trash = CreateFrame("Frame") local OnUpdate, GetActionName, GetHotkey do local ATTACK_BUTTON_FLASH_TIME = ATTACK_BUTTON_FLASH_TIME local IsActionInRange = IsActionInRange 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 function GetActionName(f) local b = f and f._reactionButton if b then return format("%s:%s", b.bar:GetName(), b.idx) end end function GetHotkey(f) return KB:ToShortKey(GetBindingKey(format("CLICK %s:LeftButton",f:GetName()))) end -- This is a bit hokey : install a bare hook on ActionButton_UpdateHotkey because -- even though it's secure it's never called in a way that can cause taint. This is -- for performance reasons to avoid having to hook frame:OnEvent securely. local UpdateHotkey_old = ActionButton_UpdateHotkeys ActionButton_UpdateHotkeys = function( frame, ... ) local b = frame._reactionButton if b then b.hotkey:SetText( GetHotkey(frame) ) else return UpdateHotkey_old(frame, ...) 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. 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 f._reactionButton = self self.hotkey = f.hotkey self.border = _G[name.."Border"] f:SetAttribute("action", config.actionID) f:SetAttribute("default-action", config.actionID) -- install mind control actions for all buttons just for simplicity if self.idx <= 12 then f:SetAttribute("mc-action", 120 + self.idx) end -- set a tooltip onEnter f:SetScript("OnEnter", function(frame) if ReAction:GetKeybindMode() then KB:Set(frame) elseif frame.vehicleExitMode then GameTooltip_AddNewbieTip(frame, LEAVE_VEHICLE, 1.0, 1.0, 1.0, nil); else ActionButton_SetTooltip(frame) end end) -- set a _childupdate handler, called within the header's context f:SetAttribute("_childupdate", -- function _childupdate(self, snippetid, message) [[ local action = "default-action" if (doVehicle and mcVehicleState == "vehicle") or (doMindControl and mcVehicleState == "mc") then action = "mc-action" elseif page and state and page[state] then action = "action-"..page[state] end local value = self:GetAttribute(action) if value then self:SetAttribute("action",value) end ]]) -- Install a handler for the 7th button (only) to show/hide a -- vehicle exit button. This is more than a little bit hack-ish and -- will be replaced in the next iteration with the reimplementation -- of action button functionality. if idx == 7 then local barFrame = bar:GetFrame() function barFrame:ShowVehicleExit(show) local tx = f.vehicleExitTexture if show then if not tx then tx = f:CreateTexture(nil,"ARTWORK") tx:SetAllPoints() -- copied from Blizzard/VehicleMenuBar.lua SkinsData tx:SetTexture("Interface\\Vehicles\\UI-Vehicles-Button-Exit-Up") tx:SetTexCoord(0.140625, 0.859375, 0.140625, 0.859375) f.vehicleExitTexture = tx end tx:Show() f.vehicleExitMode = true elseif tx then tx:SetTexCoord(0,1,0,1) tx:Hide() f.vehicleExitMode = false end end f:SetAttribute("macrotext","/run VehicleExit()") f:SetAttribute("_childupdate-vehicle", -- function _childupdate-vehicle(self, snippetid, message) [[ local show = (mcVehicleState == "vehicle") if show then self:SetAttribute("type","macro") self:SetAttribute("showgrid",self:GetAttribute("showgrid")+1) self:Show() else self:SetAttribute("type","action") local showgrid = self:GetAttribute("showgrid") showgrid = showgrid - 1 if showgrid < 0 then showgrid = 0 end self:SetAttribute("showgrid",self:GetAttribute("showgrid")-1) if showgrid <= 0 then self:Hide() end end control:CallMethod("ShowVehicleExit",show) ]]) end -- 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 -- set the hotkey text self.hotkey:SetText( GetHotkey(self.frame) ) -- show the ID label if applicable self:ShowActionIDLabel(ReAction:GetConfigMode()) -- 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() f:SetAttribute("_childupdate",nil) f:SetAttribute("_childupdate-vehicle",nil) 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 f._reactionButton = nil 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:SetKeybindMode( mode ) if mode then self.frame.GetActionName = GetActionName self.frame.GetHotkey = GetHotkey -- set the border for all buttons to the keybind-enable color self.border:SetVertexColor(KB:GetColorKeyBoundMode()) self.border:Show() elseif IsEquippedAction(self:GetActionID()) then self.border:SetVertexColor(0, 1.0, 0, 0.35) -- from ActionButton.lua else self.border:Hide() end end