Mercurial > wow > reaction
diff modules/ReAction_Action/ReAction_Action.lua @ 90:7cabc8ac6c16
Updates for wow 3.0
- TOC update
- updated changed APIs/frame names
- rewrote state code per new SecureHandlers API
- cleaned up Bar, ActionButton code
- removed AceLibrary/Dewdrop, menu from bar right-click
- fixed various small bugs
Updated WowAce external locations
Updated README.html
author | Flick <flickerstreak@gmail.com> |
---|---|
date | Wed, 15 Oct 2008 16:29:41 +0000 |
parents | fc83b3f5b322 |
children | c2504a8b996c |
line wrap: on
line diff
--- a/modules/ReAction_Action/ReAction_Action.lua Mon Oct 13 23:32:33 2008 +0000 +++ b/modules/ReAction_Action/ReAction_Action.lua Wed Oct 15 16:29:41 2008 +0000 @@ -5,9 +5,7 @@ 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). This is done - by interacting with the built-in State module to map these features to states via the - "statebutton" attribute. + (Mind Control, Eyes of the Beast, Karazhan Chess event, various quests, etc). --]] -- local imports @@ -26,41 +24,27 @@ local moduleID = "Action" local module = ReAction:NewModule( moduleID ) --- Button class declaration +-- Class declarations local Button = { } - --- private utility -- -local function RefreshLite(bar) - local btns = module.buttons[bar] - if btns then - for _, b in ipairs(btns) do - b:Refresh() - end - end -end - -local function GetBarConfig(bar) - return module.db.profile.bars[bar:GetName()] -end - +local Handle = { } +local PropHandler = { } -- Event handlers function module:OnInitialize() self.db = ReAction.db:RegisterNamespace( moduleID, { profile = { - buttons = { }, bars = { }, } } ) - self.buttons = { } + self.handles = setmetatable({ }, weak) ReAction:RegisterBarOptionGenerator(self, "GetBarOptions") - ReAction.RegisterCallback(self, "OnCreateBar", "OnRefreshBar") + ReAction.RegisterCallback(self, "OnCreateBar") + ReAction.RegisterCallback(self, "OnRefreshBar") ReAction.RegisterCallback(self, "OnDestroyBar") - ReAction.RegisterCallback(self, "OnRefreshBar") ReAction.RegisterCallback(self, "OnEraseBar") ReAction.RegisterCallback(self, "OnRenameBar") ReAction.RegisterCallback(self, "OnConfigModeChanged") @@ -74,118 +58,86 @@ ReAction:RegisterBarType(L["Action Bar"], { type = moduleID, - defaultButtonSize = 36, + defaultButtonSize = 6, 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 bar.config.type == moduleID then - if self.buttons[bar] == nil then - self.buttons[bar] = { } - end - local btns = self.buttons[bar] - local profile = self.db.profile - if profile.buttons[name] == nil then - profile.buttons[name] = {} - end - if profile.bars[name] == nil then - profile.bars[name] = {} - end - local btnCfg = profile.buttons[name] - local barCfg = profile.bars[name] - - local r, c = bar:GetButtonGrid() - local n = r*c - if n ~= #btns then - for i = 1, n do - if btnCfg[i] == nil then - btnCfg[i] = {} - end - if btns[i] == nil then - local b = Button:New(bar, i, btnCfg[i], barCfg) - btns[i] = b - bar:AddButton(i,b) - end - end - for i = n+1, #btns do - if btns[i] then - bar:RemoveButton(btns[i]) - btns[i] = btns[i]:Destroy() - if btnCfg[i] then - btnCfg[i] = nil - end - end - end - end - RefreshLite(bar) + if self.handles[bar] then + self.handles[bar]:Refresh() end end function module:OnDestroyBar(event, bar, name) - if self.buttons[bar] then - local btns = self.buttons[bar] - for _,b in pairs(btns) do - if b then - b:Destroy() - end - end - self.buttons[bar] = nil + if self.handles[bar] then + self.handles[bar]:Destroy() + self.handles[bar] = nil end end function module:OnEraseBar(event, bar, name) - self.db.profile.buttons[name] = nil self.db.profile.bars[name] = nil end function module:OnRenameBar(event, bar, oldname, newname) - local b = self.db.profile.buttons - b[newname], b[oldname] = b[oldname], nil - b = self.db.profile.bars b[newname], b[oldname] = b[oldname], nil end function module:OnConfigModeChanged(event, mode) - for _, bar in pairs(self.buttons) do - for _, b in pairs(bar) do - b:ShowGrid(mode) - b:ShowActionIDLabel(mode) - end + for _, h in pairs(self.handles) do + h:SetConfigMode(mode) end end function module:LIBKEYBOUND_ENABLED(evt) - -- set the border for all buttons to the keybind-enable color - local r,g,b,a = KB:GetColorKeyBoundMode() - for _, bar in pairs(self.buttons) do - for _, b in pairs(bar) do - b.border:SetVertexColor(r,g,b,a) - b.border:Show() - end + for _, h in pairs(self.handles) do + h:SetKeybindMode(true) end end function module:LIBKEYBOUND_DISABLED(evt) - for _, bar in pairs(self.buttons) do - for _, b in pairs(bar) do - b.border:Hide() - end + for _, h in pairs(self.handles) do + h:SetKeybindMode(false) end end ----- Options ---- +---- Interface ---- +function module:GetBarOptions(bar) + local h = self.handles[bar] + if h then + return h:GetOptions() + end +end + + +---- Bar Handle ---- + do - local Handler = { } - local options = { hideEmpty = { name = L["Hide Empty Buttons"], @@ -206,9 +158,18 @@ 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 = 3, + type = "toggle", + width = "double", + set = "SetMindControl", + get = "GetMindControl", + }, actions = { name = L["Edit Action IDs"], - order = 13, + order = 4, type = "group", inline = true, args = { @@ -277,66 +238,159 @@ get = "GetMultiID", set = "SetMultiID", validate = "ValidateMultiID", - } - } + }, + }, }, } - function module:GetBarOptions(bar) + 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() + 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 + for _, b in ipairs(self.btns) do + b:Refresh() + end + local f = self.bar:GetFrame() + 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") + 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:SetKeybindMode(mode) + for _, b in pairs(self.btns) do + if mode then + -- set the border for all buttons to the keybind-enable color + local r,g,b,a = KB:GetColorKeyBoundMode() + b.border:SetVertexColor(r,g,b,a) + b.border:Show() + 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 = Handler:New(bar), - hidden = "Hidden", + handler = self, args = options } end - -- options handler private - function Handler:New(bar) - return setmetatable( { bar = bar }, { __index = Handler } ) - end - - function Handler:Hidden() - return self.bar.config.type ~= moduleID - end - - function Handler:SetHideEmpty(info, value) - local c = GetBarConfig(self.bar) - if value ~= c.hideEmpty then + function Handle:SetHideEmpty(info, value) + if value ~= self.config.hideEmpty then for b in self.bar:IterateButtons() do b:ShowGrid(not value) end - c.hideEmpty = value + self.config.hideEmpty = value end end - function Handler:GetHideEmpty() - return GetBarConfig(self.bar).hideEmpty + function Handle:GetHideEmpty() + return self.config.hideEmpty end - function Handler:GetNumPages() - return GetBarConfig(self.bar).nPages + function Handle:GetNumPages() + return self.config.nPages end - function Handler:SetNumPages(info, value) - GetBarConfig(self.bar).nPages = value - RefreshLite(self.bar) + function Handle:SetNumPages(info, value) + self.config.nPages = value + self:Refresh() end - function Handler:GetActionEditMethod() + 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 Handler:SetActionEditMethod(info, value) + function Handle:SetActionEditMethod(info, value) self.editMethod = value end - function Handler:IsButtonSelectHidden() + function Handle:IsButtonSelectHidden() return self.editMethod ~= 1 end - function Handler:GetRowList() + function Handle:GetRowList() local r,c = self.bar:GetButtonGrid() if self.rowList == nil or #self.rowList ~= r then local list = { } @@ -348,7 +402,7 @@ return self.rowList end - function Handler:GetSelectedRow() + function Handle:GetSelectedRow() local r, c = self.bar:GetButtonGrid() local row = self.selectedRow or 1 if row > r then @@ -358,11 +412,11 @@ return row end - function Handler:SetSelectedRow(info, value) + function Handle:SetSelectedRow(info, value) self.selectedRow = value end - function Handler:GetColumnList() + function Handle:GetColumnList() local r,c = self.bar:GetButtonGrid() if self.columnList == nil or #self.columnList ~= c then local list = { } @@ -374,7 +428,7 @@ return self.columnList end - function Handler:GetSelectedColumn() + function Handle:GetSelectedColumn() local r, c = self.bar:GetButtonGrid() local col = self.selectedColumn or 1 if col > c then @@ -384,16 +438,16 @@ return col end - function Handler:SetSelectedColumn(info, value) + function Handle:SetSelectedColumn(info, value) self.selectedColumn = value end - function Handler:IsPageSelectHidden() - return self.editMethod ~= 1 or (GetBarConfig(self.bar).nPages or 1) < 2 + function Handle:IsPageSelectHidden() + return self.editMethod ~= 1 or (self.config.nPages or 1) < 2 end - function Handler:GetPageList() - local n = GetBarConfig(self.bar).nPages or 1 + 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 @@ -404,42 +458,42 @@ return self.pageList end - function Handler:GetSelectedPage() + function Handle:GetSelectedPage() local p = self.selectedPage or 1 - if p > (GetBarConfig(self.bar).nPages or 1) then + if p > (self.config.nPages or 1) then p = 1 end self.selectedPage = p return p end - function Handler:SetSelectedPage(info, value) + function Handle:SetSelectedPage(info, value) self.selectedPage = value end - function Handler:GetActionID() + 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 = module.buttons[self.bar][n] + local btn = self.btns[n] if btn then return tostring(btn:GetActionID(self.selectedPage or 1)) end end - function Handler:SetActionID(info, value) + 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 = module.buttons[self.bar][n] + local btn = self.btns[n] if btn then btn:SetActionID(tonumber(value), self.selectedPage or 1) end end - function Handler:ValidateActionID(info, value) + function Handle:ValidateActionID(info, value) value = tonumber(value) if value == nil or value < 1 or value > 120 then return L["Specify ID 1-120"] @@ -447,15 +501,15 @@ return true end - function Handler:IsMultiIDHidden() + function Handle:IsMultiIDHidden() return self.editMethod ~= 2 end - function Handler:GetMultiID() + function Handle:GetMultiID() local p = { } - for i = 1, GetBarConfig(self.bar).nPages or 1 do + for i = 1, self.config.nPages or 1 do local b = { } - for _, btn in ipairs(module.buttons[self.bar]) do + for _, btn in ipairs(self.btns) do table.insert(b, btn:GetActionID(i)) end table.insert(p, table.concat(b,",")) @@ -486,19 +540,18 @@ return p end - function Handler:SetMultiID(info, value) - local btns = module.buttons[self.bar] - local p = ParseMultiID(#btns, GetBarConfig(self.bar).nPages or 1, value) + function Handle:SetMultiID(info, value) + local p = ParseMultiID(#btns, self.config.nPages or 1, value) for page, b in ipairs(p) do for button, id in ipairs(b) do - btns[button]:SetActionID(id, page) + self.btns[button]:SetActionID(id, page) end end end - function Handler:ValidateMultiID(info, value) + function Handle:ValidateMultiID(info, value) local bad = L["Invalid action ID list string"] - if value == nil or ParseMultiID(#module.buttons[self.bar], GetBarConfig(self.bar).nPages or 1, value) == nil then + if value == nil or ParseMultiID(#self.btns, self.config.nPages or 1, value) == nil then return bad end return true @@ -509,20 +562,9 @@ ------ State property options ------ do local pageOptions = { - 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. Select the 'Mind Control' option for the rule type to enable."], - order = 11, - type = "toggle", - disabled = "IsMCDisabled", - hidden = "IsPageHidden", - width = "double", - set = "SetProp", - get = "GetProp", - }, page = { name = L["Show Page #"], - order = 12, + order = 11, type = "select", width = "half", disabled = "IsPageDisabled", @@ -533,53 +575,25 @@ }, } - local function pageImpl( bar, states ) - local map = { } - for state, c in pairs(states) do - if c.mindcontrol then - map[state] = "mc" - elseif c.page then - map[state] = ("page%d"):format(c.page) - end - end - bar:SetStateAttribute("statebutton", map, 1, true) + local function GetBarConfig(bar) + return module.db.profile.bars[bar:GetName()] end - local PageOptsHandler = { } -- will inherit properties and methods via State:RegisterStateProperty - - function PageOptsHandler:IsMCDisabled(info) - if self:IsPageHidden() then - return true - end - -- only allow this if the mind-control selector or custom/keybind is chosen - -- see State.lua for the structure of the 'rule' config element - local rule = self.states[self:GetName()].rule - if rule then - if rule.type == "custom" or rule.type == "keybind" then - return false - else - if rule.values and rule.values.possess then - return false - end - end - end - return true + function PropHandler.GetOptions() + return pageOptions end - function PageOptsHandler:IsPageDisabled() - -- disabled if not an action button - return not GetBarConfig(self.bar) or - -- OR mind-control remapping is enabled - self.states[self:GetName()].mindcontrol or - -- OR only one page is enabled - (GetBarConfig(self.bar).nPages and GetBarConfig(self.bar).nPages < 2) + function PropHandler:IsPageDisabled() + local c = GetBarConfig(self.bar) + local n = c and c.nPages or 1 + return not (n > 1) end - function PageOptsHandler:IsPageHidden() + function PropHandler:IsPageHidden() return not GetBarConfig(self.bar) end - function PageOptsHandler:GetPageValues() + function PropHandler:GetPageValues() local c = GetBarConfig(self.bar) if c then local n = c.nPages @@ -588,18 +602,17 @@ self._npages = n -- cache the results for i = 1, n do - self._pagevalues[i] = i + self._pagevalues["page"..i] = i end end return self._pagevalues end end - function PageOptsHandler:GetPage(info) + function PropHandler:GetPage(info) return self:GetProp(info) or 1 end - - ReAction:GetModule("State"):RegisterStateProperty("page", pageImpl, pageOptions, PageOptsHandler) + end ------ ActionID allocation ------ @@ -680,350 +693,387 @@ ------ Button class ------ -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 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"}) + 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 + 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 - frame.flashtime = ATTACK_BUTTON_FLASH_TIME - overtime + end + + if frame.rangeTimer then + frame.rangeTimer = frame.rangeTimer - elapsed; - local flashTexture = frame.flash - if flashTexture:IsShown() then - flashTexture:Hide() - else - flashTexture:Show() + 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) + 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 - if frame.rangeTimer then - frame.rangeTimer = frame.rangeTimer - elapsed; + -- 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 - if frame.rangeTimer <= 0 then - if IsActionInRange(frame.action) == 0 then - frame.icon:SetVertexColor(1.0,0.1,0.1) + 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 + + -- wrap the OnClick handler to use a pagemap from the header's context + parent:WrapScript(f, "OnClick", + -- function OnClick(self, button, down) + [[ + if doMindControl and GetBonusBarOffset() == 5 then + return "mc" else - ActionButton_UpdateUsable() + return state and page and page[state] or button end - frame.rangeTimer = 0.1 + ]]) + + -- set a _childupdate handler, called within the header's context + -- SetAttribute() is a brute-force way to trigger ActionButton_UpdateAction(). Setting "*action1" + -- will, in the absence of a useful replacement for SecureButton_GetEffectiveButton(), force + -- ActionButton_CalculateAction() to use the new action-id for display purposes. It also + -- sort of obviates the OnClick handler, but hopefully this is only temporary until + -- SecureButton_GetEffectiveButton() gets fixed. + 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) + print("setting action",value,"page[state]=",page and page[state]) + self:SetAttribute("*action1",value) + ]]) + + self.frame = f + self.normalTexture = getglobal(format("%sNormalTexture",f:GetName())) + + -- 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) + + 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 - -- 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) + 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 GetHotkey(f) - local b = buttonLookup[f] - if b then - return KB:ToShortKey(b:GetConfig().hotkey) + 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 SetKey(f, key) - local b = buttonLookup[f] - if b then - local c = b:GetConfig() - if c.hotkey then - SetOverrideBinding(f, false, c.hotkey, nil) + 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 key then - SetOverrideBindingClick(f, false, key, f:GetName(), nil) + if count < 0 then + count = 0 end - c.hotkey = key - b:DisplayHotkey(GetHotkey(f)) + f:SetAttribute("showgrid",count) + + if count >= 1 and not f:GetAttribute("statehidden") then + self.normalTexture:SetVertexColor(1.0, 1.0, 1.0, 0.5); + f:Show() + elseif count < 1 and not HasAction(self:GetActionID()) then + f:Hide() + end 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 + 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 - 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 + f.actionIDLabel:SetText(tostring(id)) + f.actionIDLabel:Show() + elseif f.actionIDLabel then + f.actionIDLabel:Hide() 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) + function Button:DisplayHotkey( key ) + self.hotkey:SetText(key or "") end end - - -function Button:New( bar, idx, config, barConfig ) - -- create new self - self = setmetatable( { }, {__index = Button} ) - self.bar, self.idx, self.config, self.barConfig = bar, idx, config, barConfig - - local name = config.name or ("ReAction_%s_%s_%d"):format(bar:GetName(),moduleID,idx) - self.name = name - config.name = name - local lastButton = module.buttons[bar][#module.buttons[bar]] - 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 (below). Can't set to nil in the global - -- table because you end up getting taint - local parent = bar:GetButtonFrame() - 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 action support for all buttons here just for simplicity - if self.idx <= 12 then - f:SetAttribute("*action-mc", 120 + self.idx) - end - - self.frame = f - self.normalTexture = getglobal(format("%sNormalTexture",f:GetName())) - - -- 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) - - 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.pages 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) - if self.barConfig.mckeybinds then - f:SetAttribute("bindings-mc", self.barConfig.mckeybinds[self.idx]) - end - 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 - -- new in 2.4.1: can't call ActionButton_ShowGrid/HideGrid because they won't update the attribute - 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 - self.normalTexture:SetVertexColor(1.0, 1.0, 1.0, 0.5); - 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 == "state-parent" or 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