flickerstreak@24: --[[ flickerstreak@53: ReAction Action button module. flickerstreak@24: flickerstreak@24: The button module implements standard action button functionality by wrapping Blizzard's flickerstreak@87: ActionBarButtonTemplate frame and associated functions. flickerstreak@77: flickerstreak@87: It also provides action remapping support for multiple pages and possessed targets flickerstreak@87: (Mind Control, Eyes of the Beast, Karazhan Chess event, various quests, etc). This is done flickerstreak@87: by interacting with the built-in State module to map these features to states via the flickerstreak@87: "statebutton" attribute. flickerstreak@24: --]] flickerstreak@24: flickerstreak@24: -- local imports flickerstreak@24: local ReAction = ReAction flickerstreak@24: local L = ReAction.L flickerstreak@24: local _G = _G flickerstreak@24: local CreateFrame = CreateFrame flickerstreak@24: flickerstreak@87: ReAction:UpdateRevision("$Revision$") flickerstreak@77: flickerstreak@24: -- module declaration flickerstreak@24: local moduleID = "Action" flickerstreak@28: local module = ReAction:NewModule( moduleID ) flickerstreak@24: flickerstreak@77: -- Button class declaration flickerstreak@77: local Button = { } flickerstreak@77: flickerstreak@87: -- private utility -- flickerstreak@75: local function RefreshLite(bar) flickerstreak@75: local btns = module.buttons[bar] flickerstreak@75: if btns then flickerstreak@75: for _, b in ipairs(btns) do flickerstreak@75: b:Refresh() flickerstreak@75: end flickerstreak@75: end flickerstreak@75: end flickerstreak@75: flickerstreak@87: local function GetBarConfig(bar) flickerstreak@87: return module.db.profile.bars[bar:GetName()] flickerstreak@87: end flickerstreak@87: flickerstreak@87: flickerstreak@77: -- Event handlers flickerstreak@24: function module:OnInitialize() flickerstreak@28: self.db = ReAction.db:RegisterNamespace( moduleID, flickerstreak@24: { flickerstreak@28: profile = { flickerstreak@75: buttons = { }, flickerstreak@75: bars = { }, flickerstreak@28: } flickerstreak@24: } flickerstreak@24: ) flickerstreak@24: self.buttons = { } flickerstreak@49: flickerstreak@63: ReAction:RegisterBarOptionGenerator(self, "GetBarOptions") flickerstreak@63: flickerstreak@63: ReAction.RegisterCallback(self, "OnCreateBar", "OnRefreshBar") flickerstreak@63: ReAction.RegisterCallback(self, "OnDestroyBar") flickerstreak@63: ReAction.RegisterCallback(self, "OnRefreshBar") flickerstreak@63: ReAction.RegisterCallback(self, "OnEraseBar") flickerstreak@63: ReAction.RegisterCallback(self, "OnRenameBar") flickerstreak@63: ReAction.RegisterCallback(self, "OnConfigModeChanged") flickerstreak@63: flickerstreak@24: end flickerstreak@24: flickerstreak@24: function module:OnEnable() flickerstreak@53: ReAction:RegisterBarType(L["Action Bar"], flickerstreak@53: { flickerstreak@53: type = moduleID, flickerstreak@53: defaultButtonSize = 36, flickerstreak@53: defaultBarRows = 1, flickerstreak@53: defaultBarCols = 12, flickerstreak@53: defaultBarSpacing = 3 flickerstreak@53: }, true) flickerstreak@24: end flickerstreak@24: flickerstreak@24: function module:OnDisable() flickerstreak@53: ReAction:UnregisterBarType(L["Action Bar"]) flickerstreak@24: end flickerstreak@24: flickerstreak@63: function module:OnRefreshBar(event, bar, name) flickerstreak@53: if bar.config.type == moduleID then flickerstreak@48: if self.buttons[bar] == nil then flickerstreak@48: self.buttons[bar] = { } flickerstreak@48: end flickerstreak@48: local btns = self.buttons[bar] flickerstreak@48: local profile = self.db.profile flickerstreak@63: if profile.buttons[name] == nil then flickerstreak@63: profile.buttons[name] = {} flickerstreak@48: end flickerstreak@75: if profile.bars[name] == nil then flickerstreak@75: profile.bars[name] = {} flickerstreak@75: end flickerstreak@63: local btnCfg = profile.buttons[name] flickerstreak@75: local barCfg = profile.bars[name] flickerstreak@24: flickerstreak@48: local r, c = bar:GetButtonGrid() flickerstreak@48: local n = r*c flickerstreak@87: if n ~= #btns then flickerstreak@87: for i = 1, n do flickerstreak@87: if btnCfg[i] == nil then flickerstreak@87: btnCfg[i] = {} flickerstreak@87: end flickerstreak@87: if btns[i] == nil then flickerstreak@87: local b = Button:New(bar, i, btnCfg[i], barCfg) flickerstreak@87: btns[i] = b flickerstreak@87: bar:AddButton(i,b) flickerstreak@87: end flickerstreak@48: end flickerstreak@87: for i = n+1, #btns do flickerstreak@87: if btns[i] then flickerstreak@87: bar:RemoveButton(btns[i]) flickerstreak@87: btns[i] = btns[i]:Destroy() flickerstreak@87: if btnCfg[i] then flickerstreak@87: btnCfg[i] = nil flickerstreak@87: end flickerstreak@52: end flickerstreak@48: end flickerstreak@24: end flickerstreak@75: RefreshLite(bar) flickerstreak@24: end flickerstreak@24: end flickerstreak@24: flickerstreak@63: function module:OnDestroyBar(event, bar, name) flickerstreak@24: if self.buttons[bar] then flickerstreak@24: local btns = self.buttons[bar] flickerstreak@24: for _,b in pairs(btns) do flickerstreak@24: if b then flickerstreak@24: b:Destroy() flickerstreak@24: end flickerstreak@24: end flickerstreak@24: self.buttons[bar] = nil flickerstreak@24: end flickerstreak@24: end flickerstreak@24: flickerstreak@63: function module:OnEraseBar(event, bar, name) flickerstreak@63: self.db.profile.buttons[name] = nil flickerstreak@75: self.db.profile.bars[name] = nil flickerstreak@24: end flickerstreak@24: flickerstreak@63: function module:OnRenameBar(event, bar, oldname, newname) flickerstreak@48: local b = self.db.profile.buttons flickerstreak@48: b[newname], b[oldname] = b[oldname], nil flickerstreak@75: flickerstreak@75: b = self.db.profile.bars flickerstreak@75: b[newname], b[oldname] = b[oldname], nil flickerstreak@48: end flickerstreak@48: flickerstreak@63: function module:OnConfigModeChanged(event, mode) flickerstreak@77: for _, bar in pairs(self.buttons) do flickerstreak@77: for _, b in pairs(bar) do flickerstreak@77: b:ShowGrid(mode) flickerstreak@77: b:ShowActionIDLabel(mode) flickerstreak@24: end flickerstreak@24: end flickerstreak@24: end flickerstreak@24: flickerstreak@50: flickerstreak@60: ---- Options ---- flickerstreak@87: do flickerstreak@87: local Handler = { } flickerstreak@77: flickerstreak@87: local options = { flickerstreak@87: hideEmpty = { flickerstreak@87: name = L["Hide Empty Buttons"], flickerstreak@87: order = 1, flickerstreak@87: type = "toggle", flickerstreak@87: width = "double", flickerstreak@87: get = "GetHideEmpty", flickerstreak@87: set = "SetHideEmpty", flickerstreak@87: }, flickerstreak@87: pages = { flickerstreak@87: name = L["# Pages"], flickerstreak@87: desc = L["Use the Dynamic State tab to specify page transitions"], flickerstreak@87: order = 2, flickerstreak@87: type = "range", flickerstreak@87: min = 1, flickerstreak@87: max = 10, flickerstreak@87: step = 1, flickerstreak@87: get = "GetNumPages", flickerstreak@87: set = "SetNumPages", flickerstreak@87: }, flickerstreak@87: actions = { flickerstreak@87: name = L["Edit Action IDs"], flickerstreak@87: order = 13, flickerstreak@87: type = "group", flickerstreak@87: inline = true, flickerstreak@87: args = { flickerstreak@87: method = { flickerstreak@87: name = L["Assign"], flickerstreak@87: order = 1, flickerstreak@87: type = "select", flickerstreak@87: width = "double", flickerstreak@87: values = { [0] = L["Choose Method..."], flickerstreak@87: [1] = L["Individually"], flickerstreak@87: [2] = L["All at Once"], }, flickerstreak@87: get = "GetActionEditMethod", flickerstreak@87: set = "SetActionEditMethod", flickerstreak@87: }, flickerstreak@87: rowSelect = { flickerstreak@87: name = L["Row"], flickerstreak@87: desc = L["Rows are numbered top to bottom"], flickerstreak@87: order = 2, flickerstreak@87: type = "select", flickerstreak@87: width = "half", flickerstreak@87: hidden = "IsButtonSelectHidden", flickerstreak@87: values = "GetRowList", flickerstreak@87: get = "GetSelectedRow", flickerstreak@87: set = "SetSelectedRow", flickerstreak@87: }, flickerstreak@87: colSelect = { flickerstreak@87: name = L["Col"], flickerstreak@87: desc = L["Columns are numbered left to right"], flickerstreak@87: order = 3, flickerstreak@87: type = "select", flickerstreak@87: width = "half", flickerstreak@87: hidden = "IsButtonSelectHidden", flickerstreak@87: values = "GetColumnList", flickerstreak@87: get = "GetSelectedColumn", flickerstreak@87: set = "SetSelectedColumn", flickerstreak@87: }, flickerstreak@87: pageSelect = { flickerstreak@87: name = L["Page"], flickerstreak@87: order = 4, flickerstreak@87: type = "select", flickerstreak@87: width = "half", flickerstreak@87: hidden = "IsPageSelectHidden", flickerstreak@87: values = "GetPageList", flickerstreak@87: get = "GetSelectedPage", flickerstreak@87: set = "SetSelectedPage", flickerstreak@87: }, flickerstreak@87: single = { flickerstreak@87: name = L["Action ID"], flickerstreak@87: usage = L["Specify ID 1-120"], flickerstreak@87: order = 5, flickerstreak@87: type = "input", flickerstreak@87: width = "half", flickerstreak@87: hidden = "IsButtonSelectHidden", flickerstreak@87: get = "GetActionID", flickerstreak@87: set = "SetActionID", flickerstreak@87: validate = "ValidateActionID", flickerstreak@87: }, flickerstreak@87: multi = { flickerstreak@87: name = L["ID List"], flickerstreak@87: usage = L["Specify a comma-separated list of IDs for each button in the bar (in order). Separate multiple pages with semicolons (;)"], flickerstreak@87: order = 6, flickerstreak@87: type = "input", flickerstreak@87: multiline = true, flickerstreak@87: width = "double", flickerstreak@87: hidden = "IsMultiIDHidden", flickerstreak@87: get = "GetMultiID", flickerstreak@87: set = "SetMultiID", flickerstreak@87: validate = "ValidateMultiID", flickerstreak@87: } flickerstreak@87: } flickerstreak@87: }, flickerstreak@87: } flickerstreak@77: flickerstreak@87: function module:GetBarOptions(bar) flickerstreak@87: return { flickerstreak@87: type = "group", flickerstreak@87: name = L["Action Buttons"], flickerstreak@87: handler = Handler:New(bar), flickerstreak@87: hidden = "Hidden", flickerstreak@87: args = options flickerstreak@87: } flickerstreak@77: end flickerstreak@77: flickerstreak@87: -- options handler private flickerstreak@77: function Handler:New(bar) flickerstreak@77: return setmetatable( { bar = bar }, { __index = Handler } ) flickerstreak@77: end flickerstreak@77: flickerstreak@77: function Handler:Hidden() flickerstreak@77: return self.bar.config.type ~= moduleID flickerstreak@77: end flickerstreak@77: flickerstreak@77: function Handler:SetHideEmpty(info, value) flickerstreak@77: local c = GetBarConfig(self.bar) flickerstreak@77: if value ~= c.hideEmpty then flickerstreak@77: for b in self.bar:IterateButtons() do flickerstreak@77: b:ShowGrid(not value) flickerstreak@77: end flickerstreak@77: c.hideEmpty = value flickerstreak@77: end flickerstreak@77: end flickerstreak@77: flickerstreak@77: function Handler:GetHideEmpty() flickerstreak@77: return GetBarConfig(self.bar).hideEmpty flickerstreak@77: end flickerstreak@87: flickerstreak@87: function Handler:GetNumPages() flickerstreak@87: return GetBarConfig(self.bar).nPages flickerstreak@87: end flickerstreak@87: flickerstreak@87: function Handler:SetNumPages(info, value) flickerstreak@87: GetBarConfig(self.bar).nPages = value flickerstreak@87: RefreshLite(self.bar) flickerstreak@87: end flickerstreak@87: flickerstreak@87: function Handler:GetActionEditMethod() flickerstreak@87: return self.editMethod or 0 flickerstreak@87: end flickerstreak@87: flickerstreak@87: function Handler:SetActionEditMethod(info, value) flickerstreak@87: self.editMethod = value flickerstreak@87: end flickerstreak@87: flickerstreak@87: function Handler:IsButtonSelectHidden() flickerstreak@87: return self.editMethod ~= 1 flickerstreak@87: end flickerstreak@87: flickerstreak@87: function Handler:GetRowList() flickerstreak@87: local r,c = self.bar:GetButtonGrid() flickerstreak@87: if self.rowList == nil or #self.rowList ~= r then flickerstreak@87: local list = { } flickerstreak@87: for i = 1, r do flickerstreak@87: table.insert(list,i) flickerstreak@87: end flickerstreak@87: self.rowList = list flickerstreak@87: end flickerstreak@87: return self.rowList flickerstreak@87: end flickerstreak@87: flickerstreak@87: function Handler:GetSelectedRow() flickerstreak@87: local r, c = self.bar:GetButtonGrid() flickerstreak@87: local row = self.selectedRow or 1 flickerstreak@87: if row > r then flickerstreak@87: row = 1 flickerstreak@87: end flickerstreak@87: self.selectedRow = row flickerstreak@87: return row flickerstreak@87: end flickerstreak@87: flickerstreak@87: function Handler:SetSelectedRow(info, value) flickerstreak@87: self.selectedRow = value flickerstreak@87: end flickerstreak@87: flickerstreak@87: function Handler:GetColumnList() flickerstreak@87: local r,c = self.bar:GetButtonGrid() flickerstreak@87: if self.columnList == nil or #self.columnList ~= c then flickerstreak@87: local list = { } flickerstreak@87: for i = 1, c do flickerstreak@87: table.insert(list,i) flickerstreak@87: end flickerstreak@87: self.columnList = list flickerstreak@87: end flickerstreak@87: return self.columnList flickerstreak@87: end flickerstreak@87: flickerstreak@87: function Handler:GetSelectedColumn() flickerstreak@87: local r, c = self.bar:GetButtonGrid() flickerstreak@87: local col = self.selectedColumn or 1 flickerstreak@87: if col > c then flickerstreak@87: col = 1 flickerstreak@87: end flickerstreak@87: self.selectedColumn = col flickerstreak@87: return col flickerstreak@87: end flickerstreak@87: flickerstreak@87: function Handler:SetSelectedColumn(info, value) flickerstreak@87: self.selectedColumn = value flickerstreak@87: end flickerstreak@87: flickerstreak@87: function Handler:IsPageSelectHidden() flickerstreak@87: return self.editMethod ~= 1 or (GetBarConfig(self.bar).nPages or 1) < 2 flickerstreak@87: end flickerstreak@87: flickerstreak@87: function Handler:GetPageList() flickerstreak@87: local n = GetBarConfig(self.bar).nPages or 1 flickerstreak@87: if self.pageList == nil or #self.pageList ~= n then flickerstreak@87: local p = { } flickerstreak@87: for i = 1, n do flickerstreak@87: table.insert(p,i) flickerstreak@87: end flickerstreak@87: self.pageList = p flickerstreak@87: end flickerstreak@87: return self.pageList flickerstreak@87: end flickerstreak@87: flickerstreak@87: function Handler:GetSelectedPage() flickerstreak@87: local p = self.selectedPage or 1 flickerstreak@87: if p > (GetBarConfig(self.bar).nPages or 1) then flickerstreak@87: p = 1 flickerstreak@87: end flickerstreak@87: self.selectedPage = p flickerstreak@87: return p flickerstreak@87: end flickerstreak@87: flickerstreak@87: function Handler:SetSelectedPage(info, value) flickerstreak@87: self.selectedPage = value flickerstreak@87: end flickerstreak@87: flickerstreak@87: function Handler:GetActionID() flickerstreak@87: local row = self.selectedRow or 1 flickerstreak@87: local col = self.selectedColumn or 1 flickerstreak@87: local r, c = self.bar:GetButtonGrid() flickerstreak@87: local n = (row-1) * c + col flickerstreak@87: local btn = module.buttons[self.bar][n] flickerstreak@87: if btn then flickerstreak@87: return tostring(btn:GetActionID(self.selectedPage or 1)) flickerstreak@87: end flickerstreak@87: end flickerstreak@87: flickerstreak@87: function Handler:SetActionID(info, value) flickerstreak@87: local row = self.selectedRow or 1 flickerstreak@87: local col = self.selectedColumn or 1 flickerstreak@87: local r, c = self.bar:GetButtonGrid() flickerstreak@87: local n = (row-1) * c + col flickerstreak@87: local btn = module.buttons[self.bar][n] flickerstreak@87: if btn then flickerstreak@87: btn:SetActionID(tonumber(value), self.selectedPage or 1) flickerstreak@87: end flickerstreak@87: end flickerstreak@87: flickerstreak@87: function Handler:ValidateActionID(info, value) flickerstreak@87: value = tonumber(value) flickerstreak@87: if value == nil or value < 1 or value > 120 then flickerstreak@87: return L["Specify ID 1-120"] flickerstreak@87: end flickerstreak@87: return true flickerstreak@87: end flickerstreak@87: flickerstreak@87: function Handler:IsMultiIDHidden() flickerstreak@87: return self.editMethod ~= 2 flickerstreak@87: end flickerstreak@87: flickerstreak@87: function Handler:GetMultiID() flickerstreak@87: local p = { } flickerstreak@87: for i = 1, GetBarConfig(self.bar).nPages or 1 do flickerstreak@87: local b = { } flickerstreak@87: for _, btn in ipairs(module.buttons[self.bar]) do flickerstreak@87: table.insert(b, btn:GetActionID(i)) flickerstreak@87: end flickerstreak@87: table.insert(p, table.concat(b,",")) flickerstreak@87: end flickerstreak@87: return table.concat(p,";\n") flickerstreak@87: end flickerstreak@87: flickerstreak@87: flickerstreak@87: local function ParseMultiID(nBtns, nPages, s) flickerstreak@87: if s:match("[^%d%s,;]") then flickerstreak@87: ReAction:Print("items other than digits, spaces, commas, and semicolons in string",s) flickerstreak@87: return nil flickerstreak@87: end flickerstreak@87: local p = { } flickerstreak@87: for list in s:gmatch("[^;]+") do flickerstreak@87: local pattern = ("^%s?$"):format(("%s*(%d+)%s*,"):rep(nBtns)) flickerstreak@87: local ids = { list:match(pattern) } flickerstreak@87: if #ids ~= nBtns then flickerstreak@87: ReAction:Print("found",#ids,"buttons instead of",nBtns) flickerstreak@87: return nil flickerstreak@87: end flickerstreak@87: table.insert(p,ids) flickerstreak@87: end flickerstreak@87: if #p ~= nPages then flickerstreak@87: ReAction:Print("found",#p,"pages instead of",nPages) flickerstreak@87: return nil flickerstreak@87: end flickerstreak@87: return p flickerstreak@87: end flickerstreak@87: flickerstreak@87: function Handler:SetMultiID(info, value) flickerstreak@87: local btns = module.buttons[self.bar] flickerstreak@87: local p = ParseMultiID(#btns, GetBarConfig(self.bar).nPages or 1, value) flickerstreak@87: for page, b in ipairs(p) do flickerstreak@87: for button, id in ipairs(b) do flickerstreak@87: btns[button]:SetActionID(id, page) flickerstreak@87: end flickerstreak@87: end flickerstreak@87: end flickerstreak@87: flickerstreak@87: function Handler:ValidateMultiID(info, value) flickerstreak@87: local bad = L["Invalid action ID list string"] flickerstreak@87: if value == nil or ParseMultiID(#module.buttons[self.bar], GetBarConfig(self.bar).nPages or 1, value) == nil then flickerstreak@87: return bad flickerstreak@87: end flickerstreak@87: return true flickerstreak@87: end flickerstreak@77: end flickerstreak@77: flickerstreak@77: flickerstreak@87: ------ State property options ------ flickerstreak@87: do flickerstreak@87: local pageOptions = { flickerstreak@87: mindcontrol = { flickerstreak@87: name = L["Mind Control Support"], flickerstreak@87: 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."], flickerstreak@87: order = 11, flickerstreak@87: type = "toggle", flickerstreak@87: disabled = "IsMCDisabled", flickerstreak@87: hidden = "IsPageHidden", flickerstreak@87: width = "double", flickerstreak@87: set = "SetProp", flickerstreak@87: get = "GetProp", flickerstreak@87: }, flickerstreak@87: page = { flickerstreak@87: name = L["Show Page #"], flickerstreak@87: order = 12, flickerstreak@87: type = "select", flickerstreak@87: width = "half", flickerstreak@87: disabled = "IsPageDisabled", flickerstreak@87: hidden = "IsPageHidden", flickerstreak@87: values = "GetPageValues", flickerstreak@87: set = "SetProp", flickerstreak@87: get = "GetPage", flickerstreak@87: }, flickerstreak@87: } flickerstreak@50: flickerstreak@87: local function pageImpl( bar, states ) flickerstreak@87: local map = { } flickerstreak@87: for state, c in pairs(states) do flickerstreak@87: if c.mindcontrol then flickerstreak@87: map[state] = "mc" flickerstreak@87: elseif c.page then flickerstreak@87: map[state] = ("page%d"):format(c.page) flickerstreak@87: end flickerstreak@87: end flickerstreak@87: bar:SetStateAttribute("statebutton", map, 1, true) flickerstreak@87: end flickerstreak@87: flickerstreak@87: local PageOptsHandler = { } -- will inherit properties and methods via State:RegisterStateProperty flickerstreak@87: flickerstreak@87: function PageOptsHandler:IsMCDisabled(info) flickerstreak@87: if self:IsPageHidden() then flickerstreak@87: return true flickerstreak@87: end flickerstreak@87: -- only allow this if the mind-control selector or custom/keybind is chosen flickerstreak@87: -- see State.lua for the structure of the 'rule' config element flickerstreak@87: local rule = self.states[self:GetName()].rule flickerstreak@87: if rule then flickerstreak@87: if rule.type == "custom" or rule.type == "keybind" then flickerstreak@87: return false flickerstreak@87: else flickerstreak@87: if rule.values and rule.values.possess then flickerstreak@87: return false flickerstreak@24: end flickerstreak@24: end flickerstreak@24: end flickerstreak@87: return true flickerstreak@87: end flickerstreak@87: flickerstreak@87: function PageOptsHandler:IsPageDisabled() flickerstreak@87: -- disabled if not an action button flickerstreak@87: return not GetBarConfig(self.bar) or flickerstreak@87: -- OR mind-control remapping is enabled flickerstreak@87: self.states[self:GetName()].mindcontrol or flickerstreak@87: -- OR only one page is enabled flickerstreak@87: (GetBarConfig(self.bar).nPages and GetBarConfig(self.bar).nPages < 2) flickerstreak@87: end flickerstreak@87: flickerstreak@87: function PageOptsHandler:IsPageHidden() flickerstreak@87: return not GetBarConfig(self.bar) flickerstreak@87: end flickerstreak@87: flickerstreak@87: function PageOptsHandler:GetPageValues() flickerstreak@87: local c = GetBarConfig(self.bar) flickerstreak@87: if c then flickerstreak@87: local n = c.nPages flickerstreak@87: if self._npages ~= n then flickerstreak@87: self._pagevalues = { } flickerstreak@87: self._npages = n flickerstreak@87: -- cache the results flickerstreak@87: for i = 1, n do flickerstreak@87: self._pagevalues[i] = i flickerstreak@87: end flickerstreak@87: end flickerstreak@87: return self._pagevalues flickerstreak@87: end flickerstreak@87: end flickerstreak@87: flickerstreak@87: function PageOptsHandler:GetPage(info) flickerstreak@87: return self:GetProp(info) or 1 flickerstreak@87: end flickerstreak@87: flickerstreak@87: ReAction:GetModule("State"):RegisterStateProperty("page", pageImpl, pageOptions, PageOptsHandler) flickerstreak@87: end flickerstreak@87: flickerstreak@87: ------ ActionID allocation ------ flickerstreak@87: -- this needs to be high performance when requesting new IDs, flickerstreak@87: -- or certain controls will become sluggish. However, the new-request flickerstreak@87: -- infrastructure can be built lazily the first time that a new request flickerstreak@87: -- comes in (which will only happen at user config time: at static startup flickerstreak@87: -- config time all actionIDs should already have been assigned and stored flickerstreak@87: -- in the config file) flickerstreak@87: flickerstreak@87: local IDAlloc flickerstreak@87: do flickerstreak@87: local n = 120 flickerstreak@87: flickerstreak@87: IDAlloc = setmetatable({ wrap = 1, freecount = n }, {__index = function() return 0 end}) flickerstreak@87: flickerstreak@87: function IDAlloc:Acquire(id, hint) flickerstreak@87: id = tonumber(id) flickerstreak@87: hint = tonumber(hint) flickerstreak@87: if id and (id < 1 or id > n) then flickerstreak@87: id = nil flickerstreak@87: end flickerstreak@87: if hint and (hint < 1 or hint > n) then flickerstreak@87: hint = nil flickerstreak@87: end flickerstreak@87: if id == nil then flickerstreak@87: -- get a free ID flickerstreak@87: if hint and self[hint] == 0 then flickerstreak@87: -- use the hint if it's free flickerstreak@87: id = hint flickerstreak@87: elseif self.freecount > 0 then flickerstreak@87: -- if neither the id nor the hint are defined or free, but flickerstreak@87: -- the list is known to have free IDs, then start searching flickerstreak@87: -- at the hint for a free one flickerstreak@87: for i = hint or 1, n do flickerstreak@87: if self[i] == 0 then flickerstreak@87: id = i flickerstreak@87: break flickerstreak@87: end flickerstreak@87: end flickerstreak@87: -- self.wrap the search flickerstreak@87: if id == nil and hint and hint > 1 then flickerstreak@87: for i = 1, hint - 1 do flickerstreak@87: if self[i] == 0 then flickerstreak@87: id = i flickerstreak@87: break flickerstreak@87: end flickerstreak@87: end flickerstreak@87: end flickerstreak@87: end flickerstreak@87: if id == nil then flickerstreak@87: -- if there are no free IDs, start wrapping at 1 flickerstreak@87: id = self.wrap flickerstreak@87: self.wrap = id + 1 flickerstreak@87: if self.wrap > n then flickerstreak@87: self.wrap = 1 flickerstreak@87: end flickerstreak@24: end flickerstreak@24: end flickerstreak@87: if self[id] == 0 then flickerstreak@87: self.freecount = self.freecount - 1 flickerstreak@87: end flickerstreak@87: self[id] = self[id] + 1 flickerstreak@87: return id flickerstreak@24: end flickerstreak@24: flickerstreak@87: function IDAlloc:Release(id) flickerstreak@87: id = tonumber(id) flickerstreak@87: if id and (id >= 1 or id <= n) then flickerstreak@87: self[id] = self[id] - 1 flickerstreak@87: if self[id] == 0 then flickerstreak@87: self.freecount = self.freecount + 1 flickerstreak@87: self.wrap = 1 flickerstreak@87: end flickerstreak@87: end flickerstreak@87: end flickerstreak@87: end flickerstreak@87: flickerstreak@87: local frameRecycler = { } flickerstreak@87: flickerstreak@87: ------ Button class ------ flickerstreak@77: function Button:New( bar, idx, config, barConfig ) flickerstreak@77: -- create new self flickerstreak@77: self = setmetatable( { }, {__index = Button} ) flickerstreak@75: self.bar, self.idx, self.config, self.barConfig = bar, idx, config, barConfig flickerstreak@24: flickerstreak@87: local name = config.name or ("ReAction_%s_%s_%d"):format(bar:GetName(),moduleID,idx) flickerstreak@87: self.name = name flickerstreak@87: config.name = name flickerstreak@87: local lastButton = module.buttons[bar][#module.buttons[bar]] flickerstreak@87: config.actionID = IDAlloc:Acquire(config.actionID, lastButton and lastButton.config.actionID) -- gets a free one if none configured flickerstreak@75: self.nPages = 1 flickerstreak@24: flickerstreak@87: -- have to recycle frames with the same name: CreateFrame() flickerstreak@87: -- doesn't overwrite existing globals (below). Can't set to nil in the global flickerstreak@87: -- table because you end up getting taint flickerstreak@87: local parent = bar:GetButtonFrame() flickerstreak@87: local f = frameRecycler[name] flickerstreak@87: if f then flickerstreak@87: f:SetParent(parent) flickerstreak@87: else flickerstreak@87: f = CreateFrame("CheckButton", name, parent, "ActionBarButtonTemplate") flickerstreak@87: end flickerstreak@24: flickerstreak@24: f:SetAttribute("action", config.actionID) flickerstreak@75: -- install mind control action support for all buttons here just for simplicity flickerstreak@75: if self.idx <= 12 then flickerstreak@87: f:SetAttribute("*action-mc", 120 + self.idx) flickerstreak@75: end flickerstreak@49: flickerstreak@77: self.frame = f flickerstreak@77: self.normalTexture = getglobal(format("%sNormalTexture",f:GetName())) flickerstreak@49: flickerstreak@77: -- initialize the hide state flickerstreak@87: f:SetAttribute("showgrid",0) flickerstreak@77: self:ShowGrid(not barConfig.hideEmpty) flickerstreak@77: if ReAction:GetConfigMode() then flickerstreak@77: self:ShowGrid(true) flickerstreak@49: end flickerstreak@50: flickerstreak@77: -- show the ID label if applicable flickerstreak@77: self:ShowActionIDLabel(ReAction:GetConfigMode()) flickerstreak@77: flickerstreak@77: self:Refresh() flickerstreak@77: return self flickerstreak@24: end flickerstreak@24: flickerstreak@24: function Button:Destroy() flickerstreak@24: local f = self.frame flickerstreak@24: f:UnregisterAllEvents() flickerstreak@24: f:Hide() flickerstreak@24: f:SetParent(UIParent) flickerstreak@24: f:ClearAllPoints() flickerstreak@28: if self.name then flickerstreak@87: frameRecycler[self.name] = f flickerstreak@24: end flickerstreak@52: if self.config.actionID then flickerstreak@87: IDAlloc:Release(self.config.actionID) flickerstreak@52: end flickerstreak@75: if self.config.pages then flickerstreak@87: for _, id in ipairs(self.config.pageactions) do flickerstreak@87: IDAlloc:Release(id) flickerstreak@75: end flickerstreak@75: end flickerstreak@24: self.frame = nil flickerstreak@24: self.config = nil flickerstreak@24: self.bar = nil flickerstreak@24: end flickerstreak@24: flickerstreak@75: function Button:Refresh() flickerstreak@75: local f = self.frame flickerstreak@75: self.bar:PlaceButton(self, 36, 36) flickerstreak@75: if self.barConfig.mckeybinds then flickerstreak@75: f:SetAttribute("bindings-mc", self.barConfig.mckeybinds[self.idx]) flickerstreak@75: end flickerstreak@75: self:RefreshPages() flickerstreak@24: end flickerstreak@24: flickerstreak@24: function Button:GetFrame() flickerstreak@24: return self.frame flickerstreak@24: end flickerstreak@24: flickerstreak@24: function Button:GetName() flickerstreak@24: return self.name flickerstreak@24: end flickerstreak@24: flickerstreak@87: function Button:GetActionID(page) flickerstreak@87: if page == nil then flickerstreak@87: -- get the effective ID flickerstreak@87: return self.frame.action -- kept up-to-date by Blizzard's ActionButton_CalculateAction() flickerstreak@87: else flickerstreak@87: if page == 1 then flickerstreak@87: return self.config.actionID flickerstreak@87: else flickerstreak@87: return self.config.pageactions and self.config.pageactions[page] or self.config.actionID flickerstreak@87: end flickerstreak@87: end flickerstreak@24: end flickerstreak@28: flickerstreak@87: function Button:SetActionID( id, page ) flickerstreak@87: id = tonumber(id) flickerstreak@87: page = tonumber(page) flickerstreak@87: if id == nil or id < 1 or id > 120 then flickerstreak@87: error("Button:SetActionID - invalid action ID") flickerstreak@87: end flickerstreak@87: if page and page ~= 1 then flickerstreak@87: if not self.config.pageactions then flickerstreak@87: self.config.pageactions = { } flickerstreak@87: end flickerstreak@87: if self.config.pageactions[page] then flickerstreak@87: IDAlloc:Release(self.config.pageactions[page]) flickerstreak@87: end flickerstreak@87: self.config.pageactions[page] = id flickerstreak@87: IDAlloc:Acquire(self.config.pageactions[page]) flickerstreak@87: self.frame:SetAttribute(("*action-page%d"):format(page),id) flickerstreak@87: else flickerstreak@87: IDAlloc:Release(self.config.actionID) flickerstreak@87: self.config.actionID = id flickerstreak@87: IDAlloc:Acquire(self.config.actionID) flickerstreak@87: self.frame:SetAttribute("action",id) flickerstreak@87: if self.config.pageactions then flickerstreak@87: self.config.pageactions[1] = id flickerstreak@87: self.frame:SetAttribute("*action-page1",id) flickerstreak@87: end flickerstreak@87: end flickerstreak@87: end flickerstreak@87: flickerstreak@87: function Button:RefreshPages( force ) flickerstreak@87: local nPages = self.barConfig.nPages flickerstreak@87: if nPages and (nPages ~= self.nPages or force) then flickerstreak@75: local f = self:GetFrame() flickerstreak@87: local c = self.config.pageactions flickerstreak@75: if nPages > 1 and not c then flickerstreak@75: c = { } flickerstreak@87: self.config.pageactions = c flickerstreak@75: end flickerstreak@75: for i = 1, nPages do flickerstreak@87: if i > 1 then flickerstreak@87: c[i] = IDAlloc:Acquire(c[i], self.config.actionID + (i-1)*self.bar:GetNumButtons()) flickerstreak@87: else flickerstreak@87: c[i] = self.config.actionID -- page 1 is the same as the base actionID flickerstreak@87: end flickerstreak@87: f:SetAttribute(("*action-page%d"):format(i),c[i]) flickerstreak@75: end flickerstreak@75: for i = nPages+1, #c do flickerstreak@87: IDAlloc:Release(c[i]) flickerstreak@75: c[i] = nil flickerstreak@87: f:SetAttribute(("*action-page%d"):format(i),nil) flickerstreak@75: end flickerstreak@87: self.nPages = nPages flickerstreak@75: end flickerstreak@75: end flickerstreak@28: flickerstreak@77: function Button:ShowGrid( show ) flickerstreak@77: if not InCombatLockdown() then flickerstreak@77: -- new in 2.4.1: can't call ActionButton_ShowGrid/HideGrid because they won't update the attribute flickerstreak@77: local f = self.frame flickerstreak@77: local count = f:GetAttribute("showgrid") flickerstreak@77: if show then flickerstreak@77: count = count + 1 flickerstreak@77: else flickerstreak@77: count = count - 1 flickerstreak@28: end flickerstreak@77: if count < 0 then flickerstreak@77: count = 0 flickerstreak@77: end flickerstreak@77: f:SetAttribute("showgrid",count) flickerstreak@77: flickerstreak@77: if count >= 1 and not f:GetAttribute("statehidden") then flickerstreak@77: self.normalTexture:SetVertexColor(1.0, 1.0, 1.0, 0.5); flickerstreak@77: f:Show() flickerstreak@77: elseif count < 1 and not HasAction(self:GetActionID()) then flickerstreak@77: f:Hide() flickerstreak@77: end flickerstreak@28: end flickerstreak@77: end flickerstreak@77: flickerstreak@77: function Button:ShowActionIDLabel( show ) flickerstreak@87: local f = self:GetFrame() flickerstreak@77: if show then flickerstreak@77: local id = self:GetActionID() flickerstreak@87: if not f.actionIDLabel then flickerstreak@77: local label = f:CreateFontString(nil,"OVERLAY","GameFontNormalLarge") flickerstreak@77: label:SetAllPoints() flickerstreak@77: label:SetJustifyH("CENTER") flickerstreak@77: label:SetShadowColor(0,0,0,1) flickerstreak@77: label:SetShadowOffset(2,-2) flickerstreak@87: f.actionIDLabel = label -- store the label with the frame for recycling flickerstreak@77: f:HookScript("OnAttributeChanged", flickerstreak@77: function(frame, attr, value) flickerstreak@87: if label:IsVisible() and (attr == "state-parent" or attr:match("action")) then flickerstreak@87: label:SetText(tostring(frame.action)) flickerstreak@77: end flickerstreak@77: end) flickerstreak@77: end flickerstreak@87: f.actionIDLabel:SetText(tostring(id)) flickerstreak@87: f.actionIDLabel:Show() flickerstreak@87: elseif f.actionIDLabel then flickerstreak@87: f.actionIDLabel:Hide() flickerstreak@77: end flickerstreak@77: end flickerstreak@77: