| flickerstreak@28 | 1 --[[ | 
| flickerstreak@53 | 2   ReAction Pet Action button module | 
| flickerstreak@53 | 3 | 
| flickerstreak@53 | 4   The button module implements standard action button functionality by wrapping Blizzard's | 
| flickerstreak@53 | 5   PetActionButton frame and associated functions. It also provides some button layout | 
| flickerstreak@53 | 6   modification tools. | 
| flickerstreak@28 | 7 | 
| flickerstreak@28 | 8 --]] | 
| flickerstreak@28 | 9 | 
| flickerstreak@28 | 10 -- local imports | 
| flickerstreak@28 | 11 local ReAction = ReAction | 
| flickerstreak@28 | 12 local L = ReAction.L | 
| flickerstreak@28 | 13 local _G = _G | 
| flickerstreak@53 | 14 local CreateFrame = CreateFrame | 
| flickerstreak@28 | 15 | 
| flickerstreak@28 | 16 -- module declaration | 
| flickerstreak@28 | 17 local moduleID = "PetAction" | 
| flickerstreak@28 | 18 local module = ReAction:NewModule( moduleID ) | 
| flickerstreak@28 | 19 | 
| flickerstreak@28 | 20 -- module methods | 
| flickerstreak@28 | 21 function module:OnInitialize() | 
| flickerstreak@53 | 22   self.db = ReAction.db:RegisterNamespace( moduleID, | 
| flickerstreak@28 | 23     { | 
| flickerstreak@28 | 24       profile = { | 
| flickerstreak@28 | 25         buttons = { } | 
| flickerstreak@28 | 26       } | 
| flickerstreak@28 | 27     } | 
| flickerstreak@28 | 28   ) | 
| flickerstreak@53 | 29   self.buttons = { } | 
| flickerstreak@60 | 30   self.options = setmetatable({},{__mode="k"}) | 
| flickerstreak@28 | 31 end | 
| flickerstreak@28 | 32 | 
| flickerstreak@28 | 33 function module:OnEnable() | 
| flickerstreak@53 | 34   ReAction:RegisterBarType(L["Pet Action Bar"], | 
| flickerstreak@53 | 35     { | 
| flickerstreak@53 | 36       type = moduleID , | 
| flickerstreak@53 | 37       defaultButtonSize = 30, | 
| flickerstreak@53 | 38       defaultBarRows = 1, | 
| flickerstreak@53 | 39       defaultBarCols = 10, | 
| flickerstreak@53 | 40       defaultBarSpacing = 8 | 
| flickerstreak@53 | 41     }) | 
| flickerstreak@28 | 42 end | 
| flickerstreak@28 | 43 | 
| flickerstreak@28 | 44 function module:OnDisable() | 
| flickerstreak@53 | 45   ReAction:UnregisterBarType(L["Pet Action Bar"]) | 
| flickerstreak@28 | 46 end | 
| flickerstreak@28 | 47 | 
| flickerstreak@53 | 48 function module:ApplyToBar(bar) | 
| flickerstreak@53 | 49   if bar.config.type == moduleID then | 
| flickerstreak@53 | 50     -- auto show/hide when pet exists | 
| flickerstreak@53 | 51     bar:GetFrame():SetAttribute("unit","pet") | 
| flickerstreak@53 | 52     RegisterUnitWatch(bar:GetFrame()) | 
| flickerstreak@53 | 53     self:RefreshBar(bar) | 
| flickerstreak@28 | 54   end | 
| flickerstreak@28 | 55 end | 
| flickerstreak@28 | 56 | 
| flickerstreak@53 | 57 function module:RefreshBar(bar) | 
| flickerstreak@53 | 58   if bar.config.type == moduleID then | 
| flickerstreak@53 | 59     if self.buttons[bar] == nil then | 
| flickerstreak@53 | 60       self.buttons[bar] = { } | 
| flickerstreak@53 | 61     end | 
| flickerstreak@53 | 62     local btns = self.buttons[bar] | 
| flickerstreak@53 | 63     local profile = self.db.profile | 
| flickerstreak@53 | 64     local barName = bar:GetName() | 
| flickerstreak@53 | 65     if profile.buttons[barName] == nil then | 
| flickerstreak@53 | 66       profile.buttons[barName] = {} | 
| flickerstreak@53 | 67     end | 
| flickerstreak@53 | 68     local btnCfg = profile.buttons[barName] | 
| flickerstreak@53 | 69 | 
| flickerstreak@53 | 70     local r, c = bar:GetButtonGrid() | 
| flickerstreak@53 | 71     local n = r*c | 
| flickerstreak@53 | 72     for i = 1, n do | 
| flickerstreak@53 | 73       if btnCfg[i] == nil then | 
| flickerstreak@53 | 74         btnCfg[i] = {} | 
| flickerstreak@53 | 75       end | 
| flickerstreak@53 | 76       if btns[i] == nil then | 
| flickerstreak@53 | 77         local ok, b = pcall(self.BtnClass.new, self.BtnClass, bar, i, btnCfg[i]) | 
| flickerstreak@53 | 78         if ok and b then | 
| flickerstreak@53 | 79           btns[i] = b | 
| flickerstreak@53 | 80         end | 
| flickerstreak@53 | 81       else | 
| flickerstreak@53 | 82         btns[i]:Refresh(bar,i) | 
| flickerstreak@53 | 83       end | 
| flickerstreak@53 | 84     end | 
| flickerstreak@53 | 85     for i = n+1, #btns do | 
| flickerstreak@53 | 86       if btns[i] then | 
| flickerstreak@53 | 87         btns[i] = btns[i]:Destroy() | 
| flickerstreak@53 | 88         if btnCfg[i] then | 
| flickerstreak@53 | 89           btnCfg[i] = nil | 
| flickerstreak@53 | 90         end | 
| flickerstreak@53 | 91       end | 
| flickerstreak@53 | 92     end | 
| flickerstreak@53 | 93   end | 
| flickerstreak@53 | 94 end | 
| flickerstreak@53 | 95 | 
| flickerstreak@53 | 96 function module:RemoveFromBar(bar) | 
| flickerstreak@53 | 97   if self.buttons[bar] then | 
| flickerstreak@53 | 98     local btns = self.buttons[bar] | 
| flickerstreak@53 | 99     for _,b in pairs(btns) do | 
| flickerstreak@53 | 100       if b then | 
| flickerstreak@53 | 101         b:Destroy() | 
| flickerstreak@53 | 102       end | 
| flickerstreak@53 | 103     end | 
| flickerstreak@53 | 104     self.buttons[bar] = nil | 
| flickerstreak@53 | 105   end | 
| flickerstreak@53 | 106 end | 
| flickerstreak@53 | 107 | 
| flickerstreak@53 | 108 function module:EraseBarConfig(barName) | 
| flickerstreak@53 | 109   self.db.profile.buttons[barName] = nil | 
| flickerstreak@53 | 110 end | 
| flickerstreak@53 | 111 | 
| flickerstreak@53 | 112 function module:RenameBarConfig(oldname, newname) | 
| flickerstreak@53 | 113   local b = self.db.profile.buttons | 
| flickerstreak@53 | 114   b[newname], b[oldname] = b[oldname], nil | 
| flickerstreak@53 | 115 end | 
| flickerstreak@53 | 116 | 
| flickerstreak@53 | 117 | 
| flickerstreak@53 | 118 function module:ApplyConfigMode(mode,bars) | 
| flickerstreak@53 | 119   for _, bar in pairs(bars) do | 
| flickerstreak@53 | 120     if bar and self.buttons[bar] then | 
| flickerstreak@53 | 121       for _, b in pairs(self.buttons[bar]) do | 
| flickerstreak@53 | 122         if b then | 
| flickerstreak@53 | 123           if mode then | 
| flickerstreak@53 | 124             self:showActionIDLabel(b) | 
| flickerstreak@53 | 125           else | 
| flickerstreak@53 | 126             self:hideActionIDLabel(b) | 
| flickerstreak@53 | 127           end | 
| flickerstreak@53 | 128         end | 
| flickerstreak@53 | 129       end | 
| flickerstreak@53 | 130       local f = bar:GetFrame() | 
| flickerstreak@53 | 131       if mode then | 
| flickerstreak@53 | 132         UnregisterUnitWatch(f) | 
| flickerstreak@53 | 133         f:Show() | 
| flickerstreak@53 | 134       else | 
| flickerstreak@53 | 135         RegisterUnitWatch(f) | 
| flickerstreak@53 | 136       end | 
| flickerstreak@53 | 137     end | 
| flickerstreak@53 | 138   end | 
| flickerstreak@53 | 139 end | 
| flickerstreak@53 | 140 | 
| flickerstreak@53 | 141 function module:showActionIDLabel(button) | 
| flickerstreak@53 | 142   -- store the action ID label in the frame due to frame recycling | 
| flickerstreak@53 | 143   if not button:GetFrame().actionIDLabel and button:GetActionID() then | 
| flickerstreak@53 | 144     local label = button:GetFrame():CreateFontString(nil,"OVERLAY","GameFontNormalLarge") | 
| flickerstreak@53 | 145     label:SetAllPoints() | 
| flickerstreak@53 | 146     label:SetJustifyH("CENTER") | 
| flickerstreak@53 | 147     label:SetShadowColor(0,0,0,1) | 
| flickerstreak@53 | 148     label:SetShadowOffset(2,-2) | 
| flickerstreak@53 | 149     label:SetText(tostring(button:GetActionID())) | 
| flickerstreak@53 | 150     button:GetFrame().actionIDLabel = label | 
| flickerstreak@53 | 151   end | 
| flickerstreak@53 | 152   button:GetFrame().actionIDLabel:Show() | 
| flickerstreak@53 | 153 end | 
| flickerstreak@53 | 154 | 
| flickerstreak@53 | 155 function module:hideActionIDLabel(button) | 
| flickerstreak@53 | 156   if button:GetFrame().actionIDLabel then | 
| flickerstreak@53 | 157     button:GetFrame().actionIDLabel:Hide() | 
| flickerstreak@53 | 158   end | 
| flickerstreak@53 | 159 end | 
| flickerstreak@53 | 160 | 
| flickerstreak@60 | 161 ---- Options ---- | 
| flickerstreak@60 | 162 function module:GetBarOptions(bar) | 
| flickerstreak@60 | 163   if not self.options[bar] then | 
| flickerstreak@60 | 164     self.options[bar] = { | 
| flickerstreak@60 | 165       type = "group", | 
| flickerstreak@60 | 166       name = L["Pet Buttons"], | 
| flickerstreak@60 | 167       hidden = function() return bar.config.type ~= moduleID end, | 
| flickerstreak@60 | 168       args = { | 
| flickerstreak@60 | 169       } | 
| flickerstreak@60 | 170     } | 
| flickerstreak@60 | 171   end | 
| flickerstreak@60 | 172   return self.options[bar] | 
| flickerstreak@59 | 173 end | 
| flickerstreak@59 | 174 | 
| flickerstreak@53 | 175 | 
| flickerstreak@28 | 176 | 
| flickerstreak@28 | 177 -- use-count of action IDs | 
| flickerstreak@53 | 178 local nActionIDs = NUM_PET_ACTION_SLOTS | 
| flickerstreak@28 | 179 local ActionIDList = setmetatable( {}, { | 
| flickerstreak@28 | 180   __index = function(self, idx) | 
| flickerstreak@28 | 181     if idx == nil then | 
| flickerstreak@53 | 182       for i = 1, nActionIDs do | 
| flickerstreak@28 | 183         if rawget(self,i) == nil then | 
| flickerstreak@28 | 184           rawset(self,i,1) | 
| flickerstreak@28 | 185           return i | 
| flickerstreak@28 | 186         end | 
| flickerstreak@28 | 187       end | 
| flickerstreak@53 | 188       error("ran out of pet action IDs") | 
| flickerstreak@28 | 189     else | 
| flickerstreak@28 | 190       local c = rawget(self,idx) or 0 | 
| flickerstreak@28 | 191       rawset(self,idx,c+1) | 
| flickerstreak@28 | 192       return idx | 
| flickerstreak@28 | 193     end | 
| flickerstreak@28 | 194   end, | 
| flickerstreak@28 | 195   __newindex = function(self,idx,value) | 
| flickerstreak@28 | 196     if value == nil then | 
| flickerstreak@28 | 197       value = rawget(self,idx) | 
| flickerstreak@28 | 198       if value == 1 then | 
| flickerstreak@28 | 199         value = nil | 
| flickerstreak@28 | 200       elseif value then | 
| flickerstreak@28 | 201         value = value - 1 | 
| flickerstreak@28 | 202       end | 
| flickerstreak@28 | 203     end | 
| flickerstreak@28 | 204     rawset(self,idx,value) | 
| flickerstreak@28 | 205   end | 
| flickerstreak@28 | 206 }) | 
| flickerstreak@28 | 207 | 
| flickerstreak@53 | 208 local frameRecycler = {} | 
| flickerstreak@28 | 209 | 
| flickerstreak@53 | 210 | 
| flickerstreak@53 | 211 ------ Button class ------ | 
| flickerstreak@28 | 212 local Button = { } | 
| flickerstreak@28 | 213 | 
| flickerstreak@28 | 214 local function Constructor( self, bar, idx, config ) | 
| flickerstreak@28 | 215   self.bar, self.idx, self.config = bar, idx, config | 
| flickerstreak@28 | 216 | 
| flickerstreak@28 | 217   local barFrame = bar:GetFrame() | 
| flickerstreak@28 | 218 | 
| flickerstreak@56 | 219   local name = config.name or ("ReAction_%s_Pet_%d"):format(bar:GetName(),idx) | 
| flickerstreak@53 | 220   config.name = name | 
| flickerstreak@53 | 221   self.name = config.name | 
| flickerstreak@28 | 222   config.actionID = ActionIDList[config.actionID] -- gets a free one if none configured | 
| flickerstreak@28 | 223 | 
| flickerstreak@53 | 224   -- have to recycle frames with the same name: | 
| flickerstreak@53 | 225   -- otherwise you either get references to old textures because named CreateFrame() | 
| flickerstreak@53 | 226   -- doesn't overwrite existing globals (below) | 
| flickerstreak@53 | 227   -- or, if you set them to nil in the global table, you get taint because of the | 
| flickerstreak@53 | 228   -- crappy PetActionBar code. | 
| flickerstreak@53 | 229   local f = frameRecycler[name] | 
| flickerstreak@53 | 230   if f then | 
| flickerstreak@53 | 231     f:SetParent(barFrame) | 
| flickerstreak@53 | 232     f:Show() | 
| flickerstreak@53 | 233   else | 
| flickerstreak@53 | 234     f = CreateFrame("CheckButton", name, barFrame, "PetActionButtonTemplate") | 
| flickerstreak@53 | 235   end | 
| flickerstreak@53 | 236   if config.actionID then | 
| flickerstreak@53 | 237     f:SetID(config.actionID) -- PetActionButtonTemplate isn't a proper SecureActionButton | 
| flickerstreak@53 | 238   end | 
| flickerstreak@53 | 239   f:SetFrameStrata("MEDIUM") | 
| flickerstreak@53 | 240 | 
| flickerstreak@28 | 241   barFrame:SetAttribute("addchild",f) | 
| flickerstreak@28 | 242 | 
| flickerstreak@53 | 243   self.frame = f | 
| flickerstreak@53 | 244   self.icon = _G[("%sIcon"):format(name)] | 
| flickerstreak@53 | 245   self.acTex = _G[("%sAutoCastable"):format(name)] | 
| flickerstreak@53 | 246   self.acModel = _G[("%sAutoCast"):format(name)] | 
| flickerstreak@53 | 247   self.cooldown = _G[("%sCooldown"):format(name)] | 
| flickerstreak@53 | 248   self.hotkey = _G[("%sHotKey"):format(name)] | 
| flickerstreak@53 | 249 | 
| flickerstreak@53 | 250   f:HookScript("OnDragStart", function() self:Update() end) | 
| flickerstreak@53 | 251   f:HookScript("OnReceiveDrag", function() self:Update() end) | 
| flickerstreak@53 | 252 | 
| flickerstreak@53 | 253 	f:RegisterEvent("PLAYER_CONTROL_LOST"); | 
| flickerstreak@53 | 254 	f:RegisterEvent("PLAYER_CONTROL_GAINED"); | 
| flickerstreak@53 | 255 	f:RegisterEvent("PLAYER_FARSIGHT_FOCUS_CHANGED"); | 
| flickerstreak@53 | 256 	f:RegisterEvent("UNIT_PET"); | 
| flickerstreak@53 | 257 	f:RegisterEvent("UNIT_FLAGS"); | 
| flickerstreak@53 | 258 	f:RegisterEvent("UNIT_AURA"); | 
| flickerstreak@53 | 259 	f:RegisterEvent("PET_BAR_UPDATE"); | 
| flickerstreak@53 | 260 	f:RegisterEvent("PET_BAR_UPDATE_COOLDOWN"); | 
| flickerstreak@53 | 261 | 
| flickerstreak@53 | 262   f:SetScript("OnEvent", | 
| flickerstreak@53 | 263     function(event,arg1) | 
| flickerstreak@53 | 264       if event =="PET_BAR_UPDATE_COOLDOWN" then | 
| flickerstreak@53 | 265         self:UpdateCooldown() | 
| flickerstreak@53 | 266       elseif event == "UPDATE_BINDINGS" then | 
| flickerstreak@53 | 267         self:UpdateHotkey() | 
| flickerstreak@53 | 268       else | 
| flickerstreak@53 | 269         self:Update() | 
| flickerstreak@53 | 270       end | 
| flickerstreak@53 | 271     end) | 
| flickerstreak@28 | 272 | 
| flickerstreak@28 | 273   self:Refresh(bar,idx) | 
| flickerstreak@28 | 274 end | 
| flickerstreak@28 | 275 | 
| flickerstreak@28 | 276 function Button:Destroy() | 
| flickerstreak@28 | 277   local f = self.frame | 
| flickerstreak@28 | 278   f:UnregisterAllEvents() | 
| flickerstreak@28 | 279   f:Hide() | 
| flickerstreak@28 | 280   f:SetParent(UIParent) | 
| flickerstreak@28 | 281   f:ClearAllPoints() | 
| flickerstreak@28 | 282   if self.name then | 
| flickerstreak@53 | 283     frameRecycler[self.name] = f | 
| flickerstreak@28 | 284     _G[self.name] = nil | 
| flickerstreak@28 | 285   end | 
| flickerstreak@53 | 286   if self.config.actionID then | 
| flickerstreak@53 | 287     ActionIDList[self.config.actionID] = nil | 
| flickerstreak@53 | 288   end | 
| flickerstreak@28 | 289   self.frame = nil | 
| flickerstreak@28 | 290   self.config = nil | 
| flickerstreak@28 | 291   self.bar = nil | 
| flickerstreak@28 | 292 end | 
| flickerstreak@28 | 293 | 
| flickerstreak@53 | 294 function Button:Refresh(bar,idx) | 
| flickerstreak@53 | 295   bar:PlaceButton(self.frame, idx, 30, 30) | 
| flickerstreak@53 | 296   self:Update() | 
| flickerstreak@53 | 297   self:UpdateHotkey() | 
| flickerstreak@53 | 298 end | 
| flickerstreak@53 | 299 | 
| flickerstreak@53 | 300 function Button:GetFrame() | 
| flickerstreak@53 | 301   return self.frame | 
| flickerstreak@53 | 302 end | 
| flickerstreak@53 | 303 | 
| flickerstreak@53 | 304 function Button:GetName() | 
| flickerstreak@53 | 305   return self.name | 
| flickerstreak@53 | 306 end | 
| flickerstreak@53 | 307 | 
| flickerstreak@53 | 308 function Button:GetActionID() | 
| flickerstreak@53 | 309   return self.config.actionID | 
| flickerstreak@53 | 310 end | 
| flickerstreak@53 | 311 | 
| flickerstreak@53 | 312 function Button:Update() | 
| flickerstreak@53 | 313   local id = self.frame:GetID() | 
| flickerstreak@53 | 314   local name, subtext, texture, isToken, isActive, autoCastAllowed, autoCastEnabled = GetPetActionInfo(id); | 
| flickerstreak@53 | 315   local f = self.frame | 
| flickerstreak@53 | 316   --ReAction:Print(("id %d: '%s', '%s', '%s', '%s', '%s', '%s', '%s'"):format(tostring(id), tostring(name),tostring(subtext),tostring(texture),tostring(isToken),tostring(isActive),tostring(autoCastAllowed),tostring(autoCastEnabled))) | 
| flickerstreak@53 | 317 | 
| flickerstreak@53 | 318   if isToken then | 
| flickerstreak@53 | 319     self.icon:SetTexture(_G[texture]); | 
| flickerstreak@53 | 320     f.tooltipName = _G[name]; | 
| flickerstreak@53 | 321   else | 
| flickerstreak@53 | 322     self.icon:SetTexture(texture); | 
| flickerstreak@53 | 323     f.tooltipName = name; | 
| flickerstreak@53 | 324   end | 
| flickerstreak@53 | 325 | 
| flickerstreak@53 | 326   f.isToken = isToken; | 
| flickerstreak@53 | 327 	f.tooltipSubtext = subtext; | 
| flickerstreak@53 | 328   f:SetChecked( isActive and 1 or 0); | 
| flickerstreak@53 | 329 | 
| flickerstreak@53 | 330   if autoCastAllowed then | 
| flickerstreak@53 | 331     self.acTex:Show(); | 
| flickerstreak@53 | 332   else | 
| flickerstreak@53 | 333     self.acTex:Hide(); | 
| flickerstreak@53 | 334   end | 
| flickerstreak@53 | 335 | 
| flickerstreak@53 | 336   if autoCastEnabled then | 
| flickerstreak@53 | 337     self.acModel:Show(); | 
| flickerstreak@53 | 338   else | 
| flickerstreak@53 | 339     self.acModel:Hide(); | 
| flickerstreak@53 | 340   end | 
| flickerstreak@53 | 341 | 
| flickerstreak@53 | 342   if texture then | 
| flickerstreak@53 | 343     if GetPetActionsUsable() then | 
| flickerstreak@53 | 344       SetDesaturation(self.icon,nil) | 
| flickerstreak@53 | 345     else | 
| flickerstreak@53 | 346       SetDesaturation(self.icon,1) | 
| flickerstreak@53 | 347     end | 
| flickerstreak@53 | 348     self.icon:Show(); | 
| flickerstreak@53 | 349     f:SetNormalTexture("Interface\\Buttons\\UI-Quickslot2"); | 
| flickerstreak@53 | 350   else | 
| flickerstreak@53 | 351     self.icon:Hide(); | 
| flickerstreak@53 | 352     f:SetNormalTexture("Interface\\Buttons\\UI-Quickslot"); | 
| flickerstreak@53 | 353   end | 
| flickerstreak@53 | 354 | 
| flickerstreak@53 | 355   self:UpdateCooldown() | 
| flickerstreak@53 | 356 end | 
| flickerstreak@53 | 357 | 
| flickerstreak@53 | 358 function Button:UpdateCooldown() | 
| flickerstreak@53 | 359 	local start, duration, enable = GetPetActionCooldown(self.frame:GetID()); | 
| flickerstreak@53 | 360   CooldownFrame_SetTimer(self.cooldown, start, duration, enable); | 
| flickerstreak@53 | 361 end | 
| flickerstreak@53 | 362 | 
| flickerstreak@53 | 363 function Button:UpdateHotkey() | 
| flickerstreak@53 | 364 | 
| flickerstreak@53 | 365 end | 
| flickerstreak@28 | 366 | 
| flickerstreak@28 | 367 -- export as a class-factory to module | 
| flickerstreak@28 | 368 module.BtnClass = { | 
| flickerstreak@28 | 369   new = function(self, ...) | 
| flickerstreak@28 | 370     local x = { } | 
| flickerstreak@28 | 371     for k,v in pairs(Button) do | 
| flickerstreak@28 | 372       x[k] = v | 
| flickerstreak@28 | 373     end | 
| flickerstreak@28 | 374     Constructor(x, ...) | 
| flickerstreak@28 | 375     return x | 
| flickerstreak@28 | 376   end | 
| flickerstreak@28 | 377 } |