annotate modules/ReAction_Action/ReAction_Action.lua @ 87:3499ac7c3a9b

Implemented paged actions and mind control actions, and config menus to suit. There's still some sort of bug in the actionID-selection routine, it doesn't always auto-select the IDs that I think it's going to if you resize a bar several times (especially in the presence of multiple pages).
author Flick <flickerstreak@gmail.com>
date Sat, 28 Jun 2008 00:54:21 +0000
parents 502cdb5666e2
children fc83b3f5b322
rev   line source
flickerstreak@24 1 --[[
flickerstreak@53 2 ReAction Action button module.
flickerstreak@24 3
flickerstreak@24 4 The button module implements standard action button functionality by wrapping Blizzard's
flickerstreak@87 5 ActionBarButtonTemplate frame and associated functions.
flickerstreak@77 6
flickerstreak@87 7 It also provides action remapping support for multiple pages and possessed targets
flickerstreak@87 8 (Mind Control, Eyes of the Beast, Karazhan Chess event, various quests, etc). This is done
flickerstreak@87 9 by interacting with the built-in State module to map these features to states via the
flickerstreak@87 10 "statebutton" attribute.
flickerstreak@24 11 --]]
flickerstreak@24 12
flickerstreak@24 13 -- local imports
flickerstreak@24 14 local ReAction = ReAction
flickerstreak@24 15 local L = ReAction.L
flickerstreak@24 16 local _G = _G
flickerstreak@24 17 local CreateFrame = CreateFrame
flickerstreak@24 18
flickerstreak@87 19 ReAction:UpdateRevision("$Revision$")
flickerstreak@77 20
flickerstreak@24 21 -- module declaration
flickerstreak@24 22 local moduleID = "Action"
flickerstreak@28 23 local module = ReAction:NewModule( moduleID )
flickerstreak@24 24
flickerstreak@77 25 -- Button class declaration
flickerstreak@77 26 local Button = { }
flickerstreak@77 27
flickerstreak@87 28 -- private utility --
flickerstreak@75 29 local function RefreshLite(bar)
flickerstreak@75 30 local btns = module.buttons[bar]
flickerstreak@75 31 if btns then
flickerstreak@75 32 for _, b in ipairs(btns) do
flickerstreak@75 33 b:Refresh()
flickerstreak@75 34 end
flickerstreak@75 35 end
flickerstreak@75 36 end
flickerstreak@75 37
flickerstreak@87 38 local function GetBarConfig(bar)
flickerstreak@87 39 return module.db.profile.bars[bar:GetName()]
flickerstreak@87 40 end
flickerstreak@87 41
flickerstreak@87 42
flickerstreak@77 43 -- Event handlers
flickerstreak@24 44 function module:OnInitialize()
flickerstreak@28 45 self.db = ReAction.db:RegisterNamespace( moduleID,
flickerstreak@24 46 {
flickerstreak@28 47 profile = {
flickerstreak@75 48 buttons = { },
flickerstreak@75 49 bars = { },
flickerstreak@28 50 }
flickerstreak@24 51 }
flickerstreak@24 52 )
flickerstreak@24 53 self.buttons = { }
flickerstreak@49 54
flickerstreak@63 55 ReAction:RegisterBarOptionGenerator(self, "GetBarOptions")
flickerstreak@63 56
flickerstreak@63 57 ReAction.RegisterCallback(self, "OnCreateBar", "OnRefreshBar")
flickerstreak@63 58 ReAction.RegisterCallback(self, "OnDestroyBar")
flickerstreak@63 59 ReAction.RegisterCallback(self, "OnRefreshBar")
flickerstreak@63 60 ReAction.RegisterCallback(self, "OnEraseBar")
flickerstreak@63 61 ReAction.RegisterCallback(self, "OnRenameBar")
flickerstreak@63 62 ReAction.RegisterCallback(self, "OnConfigModeChanged")
flickerstreak@63 63
flickerstreak@24 64 end
flickerstreak@24 65
flickerstreak@24 66 function module:OnEnable()
flickerstreak@53 67 ReAction:RegisterBarType(L["Action Bar"],
flickerstreak@53 68 {
flickerstreak@53 69 type = moduleID,
flickerstreak@53 70 defaultButtonSize = 36,
flickerstreak@53 71 defaultBarRows = 1,
flickerstreak@53 72 defaultBarCols = 12,
flickerstreak@53 73 defaultBarSpacing = 3
flickerstreak@53 74 }, true)
flickerstreak@24 75 end
flickerstreak@24 76
flickerstreak@24 77 function module:OnDisable()
flickerstreak@53 78 ReAction:UnregisterBarType(L["Action Bar"])
flickerstreak@24 79 end
flickerstreak@24 80
flickerstreak@63 81 function module:OnRefreshBar(event, bar, name)
flickerstreak@53 82 if bar.config.type == moduleID then
flickerstreak@48 83 if self.buttons[bar] == nil then
flickerstreak@48 84 self.buttons[bar] = { }
flickerstreak@48 85 end
flickerstreak@48 86 local btns = self.buttons[bar]
flickerstreak@48 87 local profile = self.db.profile
flickerstreak@63 88 if profile.buttons[name] == nil then
flickerstreak@63 89 profile.buttons[name] = {}
flickerstreak@48 90 end
flickerstreak@75 91 if profile.bars[name] == nil then
flickerstreak@75 92 profile.bars[name] = {}
flickerstreak@75 93 end
flickerstreak@63 94 local btnCfg = profile.buttons[name]
flickerstreak@75 95 local barCfg = profile.bars[name]
flickerstreak@24 96
flickerstreak@48 97 local r, c = bar:GetButtonGrid()
flickerstreak@48 98 local n = r*c
flickerstreak@87 99 if n ~= #btns then
flickerstreak@87 100 for i = 1, n do
flickerstreak@87 101 if btnCfg[i] == nil then
flickerstreak@87 102 btnCfg[i] = {}
flickerstreak@87 103 end
flickerstreak@87 104 if btns[i] == nil then
flickerstreak@87 105 local b = Button:New(bar, i, btnCfg[i], barCfg)
flickerstreak@87 106 btns[i] = b
flickerstreak@87 107 bar:AddButton(i,b)
flickerstreak@87 108 end
flickerstreak@48 109 end
flickerstreak@87 110 for i = n+1, #btns do
flickerstreak@87 111 if btns[i] then
flickerstreak@87 112 bar:RemoveButton(btns[i])
flickerstreak@87 113 btns[i] = btns[i]:Destroy()
flickerstreak@87 114 if btnCfg[i] then
flickerstreak@87 115 btnCfg[i] = nil
flickerstreak@87 116 end
flickerstreak@52 117 end
flickerstreak@48 118 end
flickerstreak@24 119 end
flickerstreak@75 120 RefreshLite(bar)
flickerstreak@24 121 end
flickerstreak@24 122 end
flickerstreak@24 123
flickerstreak@63 124 function module:OnDestroyBar(event, bar, name)
flickerstreak@24 125 if self.buttons[bar] then
flickerstreak@24 126 local btns = self.buttons[bar]
flickerstreak@24 127 for _,b in pairs(btns) do
flickerstreak@24 128 if b then
flickerstreak@24 129 b:Destroy()
flickerstreak@24 130 end
flickerstreak@24 131 end
flickerstreak@24 132 self.buttons[bar] = nil
flickerstreak@24 133 end
flickerstreak@24 134 end
flickerstreak@24 135
flickerstreak@63 136 function module:OnEraseBar(event, bar, name)
flickerstreak@63 137 self.db.profile.buttons[name] = nil
flickerstreak@75 138 self.db.profile.bars[name] = nil
flickerstreak@24 139 end
flickerstreak@24 140
flickerstreak@63 141 function module:OnRenameBar(event, bar, oldname, newname)
flickerstreak@48 142 local b = self.db.profile.buttons
flickerstreak@48 143 b[newname], b[oldname] = b[oldname], nil
flickerstreak@75 144
flickerstreak@75 145 b = self.db.profile.bars
flickerstreak@75 146 b[newname], b[oldname] = b[oldname], nil
flickerstreak@48 147 end
flickerstreak@48 148
flickerstreak@63 149 function module:OnConfigModeChanged(event, mode)
flickerstreak@77 150 for _, bar in pairs(self.buttons) do
flickerstreak@77 151 for _, b in pairs(bar) do
flickerstreak@77 152 b:ShowGrid(mode)
flickerstreak@77 153 b:ShowActionIDLabel(mode)
flickerstreak@24 154 end
flickerstreak@24 155 end
flickerstreak@24 156 end
flickerstreak@24 157
flickerstreak@50 158
flickerstreak@60 159 ---- Options ----
flickerstreak@87 160 do
flickerstreak@87 161 local Handler = { }
flickerstreak@77 162
flickerstreak@87 163 local options = {
flickerstreak@87 164 hideEmpty = {
flickerstreak@87 165 name = L["Hide Empty Buttons"],
flickerstreak@87 166 order = 1,
flickerstreak@87 167 type = "toggle",
flickerstreak@87 168 width = "double",
flickerstreak@87 169 get = "GetHideEmpty",
flickerstreak@87 170 set = "SetHideEmpty",
flickerstreak@87 171 },
flickerstreak@87 172 pages = {
flickerstreak@87 173 name = L["# Pages"],
flickerstreak@87 174 desc = L["Use the Dynamic State tab to specify page transitions"],
flickerstreak@87 175 order = 2,
flickerstreak@87 176 type = "range",
flickerstreak@87 177 min = 1,
flickerstreak@87 178 max = 10,
flickerstreak@87 179 step = 1,
flickerstreak@87 180 get = "GetNumPages",
flickerstreak@87 181 set = "SetNumPages",
flickerstreak@87 182 },
flickerstreak@87 183 actions = {
flickerstreak@87 184 name = L["Edit Action IDs"],
flickerstreak@87 185 order = 13,
flickerstreak@87 186 type = "group",
flickerstreak@87 187 inline = true,
flickerstreak@87 188 args = {
flickerstreak@87 189 method = {
flickerstreak@87 190 name = L["Assign"],
flickerstreak@87 191 order = 1,
flickerstreak@87 192 type = "select",
flickerstreak@87 193 width = "double",
flickerstreak@87 194 values = { [0] = L["Choose Method..."],
flickerstreak@87 195 [1] = L["Individually"],
flickerstreak@87 196 [2] = L["All at Once"], },
flickerstreak@87 197 get = "GetActionEditMethod",
flickerstreak@87 198 set = "SetActionEditMethod",
flickerstreak@87 199 },
flickerstreak@87 200 rowSelect = {
flickerstreak@87 201 name = L["Row"],
flickerstreak@87 202 desc = L["Rows are numbered top to bottom"],
flickerstreak@87 203 order = 2,
flickerstreak@87 204 type = "select",
flickerstreak@87 205 width = "half",
flickerstreak@87 206 hidden = "IsButtonSelectHidden",
flickerstreak@87 207 values = "GetRowList",
flickerstreak@87 208 get = "GetSelectedRow",
flickerstreak@87 209 set = "SetSelectedRow",
flickerstreak@87 210 },
flickerstreak@87 211 colSelect = {
flickerstreak@87 212 name = L["Col"],
flickerstreak@87 213 desc = L["Columns are numbered left to right"],
flickerstreak@87 214 order = 3,
flickerstreak@87 215 type = "select",
flickerstreak@87 216 width = "half",
flickerstreak@87 217 hidden = "IsButtonSelectHidden",
flickerstreak@87 218 values = "GetColumnList",
flickerstreak@87 219 get = "GetSelectedColumn",
flickerstreak@87 220 set = "SetSelectedColumn",
flickerstreak@87 221 },
flickerstreak@87 222 pageSelect = {
flickerstreak@87 223 name = L["Page"],
flickerstreak@87 224 order = 4,
flickerstreak@87 225 type = "select",
flickerstreak@87 226 width = "half",
flickerstreak@87 227 hidden = "IsPageSelectHidden",
flickerstreak@87 228 values = "GetPageList",
flickerstreak@87 229 get = "GetSelectedPage",
flickerstreak@87 230 set = "SetSelectedPage",
flickerstreak@87 231 },
flickerstreak@87 232 single = {
flickerstreak@87 233 name = L["Action ID"],
flickerstreak@87 234 usage = L["Specify ID 1-120"],
flickerstreak@87 235 order = 5,
flickerstreak@87 236 type = "input",
flickerstreak@87 237 width = "half",
flickerstreak@87 238 hidden = "IsButtonSelectHidden",
flickerstreak@87 239 get = "GetActionID",
flickerstreak@87 240 set = "SetActionID",
flickerstreak@87 241 validate = "ValidateActionID",
flickerstreak@87 242 },
flickerstreak@87 243 multi = {
flickerstreak@87 244 name = L["ID List"],
flickerstreak@87 245 usage = L["Specify a comma-separated list of IDs for each button in the bar (in order). Separate multiple pages with semicolons (;)"],
flickerstreak@87 246 order = 6,
flickerstreak@87 247 type = "input",
flickerstreak@87 248 multiline = true,
flickerstreak@87 249 width = "double",
flickerstreak@87 250 hidden = "IsMultiIDHidden",
flickerstreak@87 251 get = "GetMultiID",
flickerstreak@87 252 set = "SetMultiID",
flickerstreak@87 253 validate = "ValidateMultiID",
flickerstreak@87 254 }
flickerstreak@87 255 }
flickerstreak@87 256 },
flickerstreak@87 257 }
flickerstreak@77 258
flickerstreak@87 259 function module:GetBarOptions(bar)
flickerstreak@87 260 return {
flickerstreak@87 261 type = "group",
flickerstreak@87 262 name = L["Action Buttons"],
flickerstreak@87 263 handler = Handler:New(bar),
flickerstreak@87 264 hidden = "Hidden",
flickerstreak@87 265 args = options
flickerstreak@87 266 }
flickerstreak@77 267 end
flickerstreak@77 268
flickerstreak@87 269 -- options handler private
flickerstreak@77 270 function Handler:New(bar)
flickerstreak@77 271 return setmetatable( { bar = bar }, { __index = Handler } )
flickerstreak@77 272 end
flickerstreak@77 273
flickerstreak@77 274 function Handler:Hidden()
flickerstreak@77 275 return self.bar.config.type ~= moduleID
flickerstreak@77 276 end
flickerstreak@77 277
flickerstreak@77 278 function Handler:SetHideEmpty(info, value)
flickerstreak@77 279 local c = GetBarConfig(self.bar)
flickerstreak@77 280 if value ~= c.hideEmpty then
flickerstreak@77 281 for b in self.bar:IterateButtons() do
flickerstreak@77 282 b:ShowGrid(not value)
flickerstreak@77 283 end
flickerstreak@77 284 c.hideEmpty = value
flickerstreak@77 285 end
flickerstreak@77 286 end
flickerstreak@77 287
flickerstreak@77 288 function Handler:GetHideEmpty()
flickerstreak@77 289 return GetBarConfig(self.bar).hideEmpty
flickerstreak@77 290 end
flickerstreak@87 291
flickerstreak@87 292 function Handler:GetNumPages()
flickerstreak@87 293 return GetBarConfig(self.bar).nPages
flickerstreak@87 294 end
flickerstreak@87 295
flickerstreak@87 296 function Handler:SetNumPages(info, value)
flickerstreak@87 297 GetBarConfig(self.bar).nPages = value
flickerstreak@87 298 RefreshLite(self.bar)
flickerstreak@87 299 end
flickerstreak@87 300
flickerstreak@87 301 function Handler:GetActionEditMethod()
flickerstreak@87 302 return self.editMethod or 0
flickerstreak@87 303 end
flickerstreak@87 304
flickerstreak@87 305 function Handler:SetActionEditMethod(info, value)
flickerstreak@87 306 self.editMethod = value
flickerstreak@87 307 end
flickerstreak@87 308
flickerstreak@87 309 function Handler:IsButtonSelectHidden()
flickerstreak@87 310 return self.editMethod ~= 1
flickerstreak@87 311 end
flickerstreak@87 312
flickerstreak@87 313 function Handler:GetRowList()
flickerstreak@87 314 local r,c = self.bar:GetButtonGrid()
flickerstreak@87 315 if self.rowList == nil or #self.rowList ~= r then
flickerstreak@87 316 local list = { }
flickerstreak@87 317 for i = 1, r do
flickerstreak@87 318 table.insert(list,i)
flickerstreak@87 319 end
flickerstreak@87 320 self.rowList = list
flickerstreak@87 321 end
flickerstreak@87 322 return self.rowList
flickerstreak@87 323 end
flickerstreak@87 324
flickerstreak@87 325 function Handler:GetSelectedRow()
flickerstreak@87 326 local r, c = self.bar:GetButtonGrid()
flickerstreak@87 327 local row = self.selectedRow or 1
flickerstreak@87 328 if row > r then
flickerstreak@87 329 row = 1
flickerstreak@87 330 end
flickerstreak@87 331 self.selectedRow = row
flickerstreak@87 332 return row
flickerstreak@87 333 end
flickerstreak@87 334
flickerstreak@87 335 function Handler:SetSelectedRow(info, value)
flickerstreak@87 336 self.selectedRow = value
flickerstreak@87 337 end
flickerstreak@87 338
flickerstreak@87 339 function Handler:GetColumnList()
flickerstreak@87 340 local r,c = self.bar:GetButtonGrid()
flickerstreak@87 341 if self.columnList == nil or #self.columnList ~= c then
flickerstreak@87 342 local list = { }
flickerstreak@87 343 for i = 1, c do
flickerstreak@87 344 table.insert(list,i)
flickerstreak@87 345 end
flickerstreak@87 346 self.columnList = list
flickerstreak@87 347 end
flickerstreak@87 348 return self.columnList
flickerstreak@87 349 end
flickerstreak@87 350
flickerstreak@87 351 function Handler:GetSelectedColumn()
flickerstreak@87 352 local r, c = self.bar:GetButtonGrid()
flickerstreak@87 353 local col = self.selectedColumn or 1
flickerstreak@87 354 if col > c then
flickerstreak@87 355 col = 1
flickerstreak@87 356 end
flickerstreak@87 357 self.selectedColumn = col
flickerstreak@87 358 return col
flickerstreak@87 359 end
flickerstreak@87 360
flickerstreak@87 361 function Handler:SetSelectedColumn(info, value)
flickerstreak@87 362 self.selectedColumn = value
flickerstreak@87 363 end
flickerstreak@87 364
flickerstreak@87 365 function Handler:IsPageSelectHidden()
flickerstreak@87 366 return self.editMethod ~= 1 or (GetBarConfig(self.bar).nPages or 1) < 2
flickerstreak@87 367 end
flickerstreak@87 368
flickerstreak@87 369 function Handler:GetPageList()
flickerstreak@87 370 local n = GetBarConfig(self.bar).nPages or 1
flickerstreak@87 371 if self.pageList == nil or #self.pageList ~= n then
flickerstreak@87 372 local p = { }
flickerstreak@87 373 for i = 1, n do
flickerstreak@87 374 table.insert(p,i)
flickerstreak@87 375 end
flickerstreak@87 376 self.pageList = p
flickerstreak@87 377 end
flickerstreak@87 378 return self.pageList
flickerstreak@87 379 end
flickerstreak@87 380
flickerstreak@87 381 function Handler:GetSelectedPage()
flickerstreak@87 382 local p = self.selectedPage or 1
flickerstreak@87 383 if p > (GetBarConfig(self.bar).nPages or 1) then
flickerstreak@87 384 p = 1
flickerstreak@87 385 end
flickerstreak@87 386 self.selectedPage = p
flickerstreak@87 387 return p
flickerstreak@87 388 end
flickerstreak@87 389
flickerstreak@87 390 function Handler:SetSelectedPage(info, value)
flickerstreak@87 391 self.selectedPage = value
flickerstreak@87 392 end
flickerstreak@87 393
flickerstreak@87 394 function Handler:GetActionID()
flickerstreak@87 395 local row = self.selectedRow or 1
flickerstreak@87 396 local col = self.selectedColumn or 1
flickerstreak@87 397 local r, c = self.bar:GetButtonGrid()
flickerstreak@87 398 local n = (row-1) * c + col
flickerstreak@87 399 local btn = module.buttons[self.bar][n]
flickerstreak@87 400 if btn then
flickerstreak@87 401 return tostring(btn:GetActionID(self.selectedPage or 1))
flickerstreak@87 402 end
flickerstreak@87 403 end
flickerstreak@87 404
flickerstreak@87 405 function Handler:SetActionID(info, value)
flickerstreak@87 406 local row = self.selectedRow or 1
flickerstreak@87 407 local col = self.selectedColumn or 1
flickerstreak@87 408 local r, c = self.bar:GetButtonGrid()
flickerstreak@87 409 local n = (row-1) * c + col
flickerstreak@87 410 local btn = module.buttons[self.bar][n]
flickerstreak@87 411 if btn then
flickerstreak@87 412 btn:SetActionID(tonumber(value), self.selectedPage or 1)
flickerstreak@87 413 end
flickerstreak@87 414 end
flickerstreak@87 415
flickerstreak@87 416 function Handler:ValidateActionID(info, value)
flickerstreak@87 417 value = tonumber(value)
flickerstreak@87 418 if value == nil or value < 1 or value > 120 then
flickerstreak@87 419 return L["Specify ID 1-120"]
flickerstreak@87 420 end
flickerstreak@87 421 return true
flickerstreak@87 422 end
flickerstreak@87 423
flickerstreak@87 424 function Handler:IsMultiIDHidden()
flickerstreak@87 425 return self.editMethod ~= 2
flickerstreak@87 426 end
flickerstreak@87 427
flickerstreak@87 428 function Handler:GetMultiID()
flickerstreak@87 429 local p = { }
flickerstreak@87 430 for i = 1, GetBarConfig(self.bar).nPages or 1 do
flickerstreak@87 431 local b = { }
flickerstreak@87 432 for _, btn in ipairs(module.buttons[self.bar]) do
flickerstreak@87 433 table.insert(b, btn:GetActionID(i))
flickerstreak@87 434 end
flickerstreak@87 435 table.insert(p, table.concat(b,","))
flickerstreak@87 436 end
flickerstreak@87 437 return table.concat(p,";\n")
flickerstreak@87 438 end
flickerstreak@87 439
flickerstreak@87 440
flickerstreak@87 441 local function ParseMultiID(nBtns, nPages, s)
flickerstreak@87 442 if s:match("[^%d%s,;]") then
flickerstreak@87 443 ReAction:Print("items other than digits, spaces, commas, and semicolons in string",s)
flickerstreak@87 444 return nil
flickerstreak@87 445 end
flickerstreak@87 446 local p = { }
flickerstreak@87 447 for list in s:gmatch("[^;]+") do
flickerstreak@87 448 local pattern = ("^%s?$"):format(("%s*(%d+)%s*,"):rep(nBtns))
flickerstreak@87 449 local ids = { list:match(pattern) }
flickerstreak@87 450 if #ids ~= nBtns then
flickerstreak@87 451 ReAction:Print("found",#ids,"buttons instead of",nBtns)
flickerstreak@87 452 return nil
flickerstreak@87 453 end
flickerstreak@87 454 table.insert(p,ids)
flickerstreak@87 455 end
flickerstreak@87 456 if #p ~= nPages then
flickerstreak@87 457 ReAction:Print("found",#p,"pages instead of",nPages)
flickerstreak@87 458 return nil
flickerstreak@87 459 end
flickerstreak@87 460 return p
flickerstreak@87 461 end
flickerstreak@87 462
flickerstreak@87 463 function Handler:SetMultiID(info, value)
flickerstreak@87 464 local btns = module.buttons[self.bar]
flickerstreak@87 465 local p = ParseMultiID(#btns, GetBarConfig(self.bar).nPages or 1, value)
flickerstreak@87 466 for page, b in ipairs(p) do
flickerstreak@87 467 for button, id in ipairs(b) do
flickerstreak@87 468 btns[button]:SetActionID(id, page)
flickerstreak@87 469 end
flickerstreak@87 470 end
flickerstreak@87 471 end
flickerstreak@87 472
flickerstreak@87 473 function Handler:ValidateMultiID(info, value)
flickerstreak@87 474 local bad = L["Invalid action ID list string"]
flickerstreak@87 475 if value == nil or ParseMultiID(#module.buttons[self.bar], GetBarConfig(self.bar).nPages or 1, value) == nil then
flickerstreak@87 476 return bad
flickerstreak@87 477 end
flickerstreak@87 478 return true
flickerstreak@87 479 end
flickerstreak@77 480 end
flickerstreak@77 481
flickerstreak@77 482
flickerstreak@87 483 ------ State property options ------
flickerstreak@87 484 do
flickerstreak@87 485 local pageOptions = {
flickerstreak@87 486 mindcontrol = {
flickerstreak@87 487 name = L["Mind Control Support"],
flickerstreak@87 488 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 489 order = 11,
flickerstreak@87 490 type = "toggle",
flickerstreak@87 491 disabled = "IsMCDisabled",
flickerstreak@87 492 hidden = "IsPageHidden",
flickerstreak@87 493 width = "double",
flickerstreak@87 494 set = "SetProp",
flickerstreak@87 495 get = "GetProp",
flickerstreak@87 496 },
flickerstreak@87 497 page = {
flickerstreak@87 498 name = L["Show Page #"],
flickerstreak@87 499 order = 12,
flickerstreak@87 500 type = "select",
flickerstreak@87 501 width = "half",
flickerstreak@87 502 disabled = "IsPageDisabled",
flickerstreak@87 503 hidden = "IsPageHidden",
flickerstreak@87 504 values = "GetPageValues",
flickerstreak@87 505 set = "SetProp",
flickerstreak@87 506 get = "GetPage",
flickerstreak@87 507 },
flickerstreak@87 508 }
flickerstreak@50 509
flickerstreak@87 510 local function pageImpl( bar, states )
flickerstreak@87 511 local map = { }
flickerstreak@87 512 for state, c in pairs(states) do
flickerstreak@87 513 if c.mindcontrol then
flickerstreak@87 514 map[state] = "mc"
flickerstreak@87 515 elseif c.page then
flickerstreak@87 516 map[state] = ("page%d"):format(c.page)
flickerstreak@87 517 end
flickerstreak@87 518 end
flickerstreak@87 519 bar:SetStateAttribute("statebutton", map, 1, true)
flickerstreak@87 520 end
flickerstreak@87 521
flickerstreak@87 522 local PageOptsHandler = { } -- will inherit properties and methods via State:RegisterStateProperty
flickerstreak@87 523
flickerstreak@87 524 function PageOptsHandler:IsMCDisabled(info)
flickerstreak@87 525 if self:IsPageHidden() then
flickerstreak@87 526 return true
flickerstreak@87 527 end
flickerstreak@87 528 -- only allow this if the mind-control selector or custom/keybind is chosen
flickerstreak@87 529 -- see State.lua for the structure of the 'rule' config element
flickerstreak@87 530 local rule = self.states[self:GetName()].rule
flickerstreak@87 531 if rule then
flickerstreak@87 532 if rule.type == "custom" or rule.type == "keybind" then
flickerstreak@87 533 return false
flickerstreak@87 534 else
flickerstreak@87 535 if rule.values and rule.values.possess then
flickerstreak@87 536 return false
flickerstreak@24 537 end
flickerstreak@24 538 end
flickerstreak@24 539 end
flickerstreak@87 540 return true
flickerstreak@87 541 end
flickerstreak@87 542
flickerstreak@87 543 function PageOptsHandler:IsPageDisabled()
flickerstreak@87 544 -- disabled if not an action button
flickerstreak@87 545 return not GetBarConfig(self.bar) or
flickerstreak@87 546 -- OR mind-control remapping is enabled
flickerstreak@87 547 self.states[self:GetName()].mindcontrol or
flickerstreak@87 548 -- OR only one page is enabled
flickerstreak@87 549 (GetBarConfig(self.bar).nPages and GetBarConfig(self.bar).nPages < 2)
flickerstreak@87 550 end
flickerstreak@87 551
flickerstreak@87 552 function PageOptsHandler:IsPageHidden()
flickerstreak@87 553 return not GetBarConfig(self.bar)
flickerstreak@87 554 end
flickerstreak@87 555
flickerstreak@87 556 function PageOptsHandler:GetPageValues()
flickerstreak@87 557 local c = GetBarConfig(self.bar)
flickerstreak@87 558 if c then
flickerstreak@87 559 local n = c.nPages
flickerstreak@87 560 if self._npages ~= n then
flickerstreak@87 561 self._pagevalues = { }
flickerstreak@87 562 self._npages = n
flickerstreak@87 563 -- cache the results
flickerstreak@87 564 for i = 1, n do
flickerstreak@87 565 self._pagevalues[i] = i
flickerstreak@87 566 end
flickerstreak@87 567 end
flickerstreak@87 568 return self._pagevalues
flickerstreak@87 569 end
flickerstreak@87 570 end
flickerstreak@87 571
flickerstreak@87 572 function PageOptsHandler:GetPage(info)
flickerstreak@87 573 return self:GetProp(info) or 1
flickerstreak@87 574 end
flickerstreak@87 575
flickerstreak@87 576 ReAction:GetModule("State"):RegisterStateProperty("page", pageImpl, pageOptions, PageOptsHandler)
flickerstreak@87 577 end
flickerstreak@87 578
flickerstreak@87 579 ------ ActionID allocation ------
flickerstreak@87 580 -- this needs to be high performance when requesting new IDs,
flickerstreak@87 581 -- or certain controls will become sluggish. However, the new-request
flickerstreak@87 582 -- infrastructure can be built lazily the first time that a new request
flickerstreak@87 583 -- comes in (which will only happen at user config time: at static startup
flickerstreak@87 584 -- config time all actionIDs should already have been assigned and stored
flickerstreak@87 585 -- in the config file)
flickerstreak@87 586
flickerstreak@87 587 local IDAlloc
flickerstreak@87 588 do
flickerstreak@87 589 local n = 120
flickerstreak@87 590
flickerstreak@87 591 IDAlloc = setmetatable({ wrap = 1, freecount = n }, {__index = function() return 0 end})
flickerstreak@87 592
flickerstreak@87 593 function IDAlloc:Acquire(id, hint)
flickerstreak@87 594 id = tonumber(id)
flickerstreak@87 595 hint = tonumber(hint)
flickerstreak@87 596 if id and (id < 1 or id > n) then
flickerstreak@87 597 id = nil
flickerstreak@87 598 end
flickerstreak@87 599 if hint and (hint < 1 or hint > n) then
flickerstreak@87 600 hint = nil
flickerstreak@87 601 end
flickerstreak@87 602 if id == nil then
flickerstreak@87 603 -- get a free ID
flickerstreak@87 604 if hint and self[hint] == 0 then
flickerstreak@87 605 -- use the hint if it's free
flickerstreak@87 606 id = hint
flickerstreak@87 607 elseif self.freecount > 0 then
flickerstreak@87 608 -- if neither the id nor the hint are defined or free, but
flickerstreak@87 609 -- the list is known to have free IDs, then start searching
flickerstreak@87 610 -- at the hint for a free one
flickerstreak@87 611 for i = hint or 1, n do
flickerstreak@87 612 if self[i] == 0 then
flickerstreak@87 613 id = i
flickerstreak@87 614 break
flickerstreak@87 615 end
flickerstreak@87 616 end
flickerstreak@87 617 -- self.wrap the search
flickerstreak@87 618 if id == nil and hint and hint > 1 then
flickerstreak@87 619 for i = 1, hint - 1 do
flickerstreak@87 620 if self[i] == 0 then
flickerstreak@87 621 id = i
flickerstreak@87 622 break
flickerstreak@87 623 end
flickerstreak@87 624 end
flickerstreak@87 625 end
flickerstreak@87 626 end
flickerstreak@87 627 if id == nil then
flickerstreak@87 628 -- if there are no free IDs, start wrapping at 1
flickerstreak@87 629 id = self.wrap
flickerstreak@87 630 self.wrap = id + 1
flickerstreak@87 631 if self.wrap > n then
flickerstreak@87 632 self.wrap = 1
flickerstreak@87 633 end
flickerstreak@24 634 end
flickerstreak@24 635 end
flickerstreak@87 636 if self[id] == 0 then
flickerstreak@87 637 self.freecount = self.freecount - 1
flickerstreak@87 638 end
flickerstreak@87 639 self[id] = self[id] + 1
flickerstreak@87 640 return id
flickerstreak@24 641 end
flickerstreak@24 642
flickerstreak@87 643 function IDAlloc:Release(id)
flickerstreak@87 644 id = tonumber(id)
flickerstreak@87 645 if id and (id >= 1 or id <= n) then
flickerstreak@87 646 self[id] = self[id] - 1
flickerstreak@87 647 if self[id] == 0 then
flickerstreak@87 648 self.freecount = self.freecount + 1
flickerstreak@87 649 self.wrap = 1
flickerstreak@87 650 end
flickerstreak@87 651 end
flickerstreak@87 652 end
flickerstreak@87 653 end
flickerstreak@87 654
flickerstreak@87 655 local frameRecycler = { }
flickerstreak@87 656
flickerstreak@87 657 ------ Button class ------
flickerstreak@77 658 function Button:New( bar, idx, config, barConfig )
flickerstreak@77 659 -- create new self
flickerstreak@77 660 self = setmetatable( { }, {__index = Button} )
flickerstreak@75 661 self.bar, self.idx, self.config, self.barConfig = bar, idx, config, barConfig
flickerstreak@24 662
flickerstreak@87 663 local name = config.name or ("ReAction_%s_%s_%d"):format(bar:GetName(),moduleID,idx)
flickerstreak@87 664 self.name = name
flickerstreak@87 665 config.name = name
flickerstreak@87 666 local lastButton = module.buttons[bar][#module.buttons[bar]]
flickerstreak@87 667 config.actionID = IDAlloc:Acquire(config.actionID, lastButton and lastButton.config.actionID) -- gets a free one if none configured
flickerstreak@75 668 self.nPages = 1
flickerstreak@24 669
flickerstreak@87 670 -- have to recycle frames with the same name: CreateFrame()
flickerstreak@87 671 -- doesn't overwrite existing globals (below). Can't set to nil in the global
flickerstreak@87 672 -- table because you end up getting taint
flickerstreak@87 673 local parent = bar:GetButtonFrame()
flickerstreak@87 674 local f = frameRecycler[name]
flickerstreak@87 675 if f then
flickerstreak@87 676 f:SetParent(parent)
flickerstreak@87 677 else
flickerstreak@87 678 f = CreateFrame("CheckButton", name, parent, "ActionBarButtonTemplate")
flickerstreak@87 679 end
flickerstreak@24 680
flickerstreak@24 681 f:SetAttribute("action", config.actionID)
flickerstreak@75 682 -- install mind control action support for all buttons here just for simplicity
flickerstreak@75 683 if self.idx <= 12 then
flickerstreak@87 684 f:SetAttribute("*action-mc", 120 + self.idx)
flickerstreak@75 685 end
flickerstreak@49 686
flickerstreak@77 687 self.frame = f
flickerstreak@77 688 self.normalTexture = getglobal(format("%sNormalTexture",f:GetName()))
flickerstreak@49 689
flickerstreak@77 690 -- initialize the hide state
flickerstreak@87 691 f:SetAttribute("showgrid",0)
flickerstreak@77 692 self:ShowGrid(not barConfig.hideEmpty)
flickerstreak@77 693 if ReAction:GetConfigMode() then
flickerstreak@77 694 self:ShowGrid(true)
flickerstreak@49 695 end
flickerstreak@50 696
flickerstreak@77 697 -- show the ID label if applicable
flickerstreak@77 698 self:ShowActionIDLabel(ReAction:GetConfigMode())
flickerstreak@77 699
flickerstreak@77 700 self:Refresh()
flickerstreak@77 701 return self
flickerstreak@24 702 end
flickerstreak@24 703
flickerstreak@24 704 function Button:Destroy()
flickerstreak@24 705 local f = self.frame
flickerstreak@24 706 f:UnregisterAllEvents()
flickerstreak@24 707 f:Hide()
flickerstreak@24 708 f:SetParent(UIParent)
flickerstreak@24 709 f:ClearAllPoints()
flickerstreak@28 710 if self.name then
flickerstreak@87 711 frameRecycler[self.name] = f
flickerstreak@24 712 end
flickerstreak@52 713 if self.config.actionID then
flickerstreak@87 714 IDAlloc:Release(self.config.actionID)
flickerstreak@52 715 end
flickerstreak@75 716 if self.config.pages then
flickerstreak@87 717 for _, id in ipairs(self.config.pageactions) do
flickerstreak@87 718 IDAlloc:Release(id)
flickerstreak@75 719 end
flickerstreak@75 720 end
flickerstreak@24 721 self.frame = nil
flickerstreak@24 722 self.config = nil
flickerstreak@24 723 self.bar = nil
flickerstreak@24 724 end
flickerstreak@24 725
flickerstreak@75 726 function Button:Refresh()
flickerstreak@75 727 local f = self.frame
flickerstreak@75 728 self.bar:PlaceButton(self, 36, 36)
flickerstreak@75 729 if self.barConfig.mckeybinds then
flickerstreak@75 730 f:SetAttribute("bindings-mc", self.barConfig.mckeybinds[self.idx])
flickerstreak@75 731 end
flickerstreak@75 732 self:RefreshPages()
flickerstreak@24 733 end
flickerstreak@24 734
flickerstreak@24 735 function Button:GetFrame()
flickerstreak@24 736 return self.frame
flickerstreak@24 737 end
flickerstreak@24 738
flickerstreak@24 739 function Button:GetName()
flickerstreak@24 740 return self.name
flickerstreak@24 741 end
flickerstreak@24 742
flickerstreak@87 743 function Button:GetActionID(page)
flickerstreak@87 744 if page == nil then
flickerstreak@87 745 -- get the effective ID
flickerstreak@87 746 return self.frame.action -- kept up-to-date by Blizzard's ActionButton_CalculateAction()
flickerstreak@87 747 else
flickerstreak@87 748 if page == 1 then
flickerstreak@87 749 return self.config.actionID
flickerstreak@87 750 else
flickerstreak@87 751 return self.config.pageactions and self.config.pageactions[page] or self.config.actionID
flickerstreak@87 752 end
flickerstreak@87 753 end
flickerstreak@24 754 end
flickerstreak@28 755
flickerstreak@87 756 function Button:SetActionID( id, page )
flickerstreak@87 757 id = tonumber(id)
flickerstreak@87 758 page = tonumber(page)
flickerstreak@87 759 if id == nil or id < 1 or id > 120 then
flickerstreak@87 760 error("Button:SetActionID - invalid action ID")
flickerstreak@87 761 end
flickerstreak@87 762 if page and page ~= 1 then
flickerstreak@87 763 if not self.config.pageactions then
flickerstreak@87 764 self.config.pageactions = { }
flickerstreak@87 765 end
flickerstreak@87 766 if self.config.pageactions[page] then
flickerstreak@87 767 IDAlloc:Release(self.config.pageactions[page])
flickerstreak@87 768 end
flickerstreak@87 769 self.config.pageactions[page] = id
flickerstreak@87 770 IDAlloc:Acquire(self.config.pageactions[page])
flickerstreak@87 771 self.frame:SetAttribute(("*action-page%d"):format(page),id)
flickerstreak@87 772 else
flickerstreak@87 773 IDAlloc:Release(self.config.actionID)
flickerstreak@87 774 self.config.actionID = id
flickerstreak@87 775 IDAlloc:Acquire(self.config.actionID)
flickerstreak@87 776 self.frame:SetAttribute("action",id)
flickerstreak@87 777 if self.config.pageactions then
flickerstreak@87 778 self.config.pageactions[1] = id
flickerstreak@87 779 self.frame:SetAttribute("*action-page1",id)
flickerstreak@87 780 end
flickerstreak@87 781 end
flickerstreak@87 782 end
flickerstreak@87 783
flickerstreak@87 784 function Button:RefreshPages( force )
flickerstreak@87 785 local nPages = self.barConfig.nPages
flickerstreak@87 786 if nPages and (nPages ~= self.nPages or force) then
flickerstreak@75 787 local f = self:GetFrame()
flickerstreak@87 788 local c = self.config.pageactions
flickerstreak@75 789 if nPages > 1 and not c then
flickerstreak@75 790 c = { }
flickerstreak@87 791 self.config.pageactions = c
flickerstreak@75 792 end
flickerstreak@75 793 for i = 1, nPages do
flickerstreak@87 794 if i > 1 then
flickerstreak@87 795 c[i] = IDAlloc:Acquire(c[i], self.config.actionID + (i-1)*self.bar:GetNumButtons())
flickerstreak@87 796 else
flickerstreak@87 797 c[i] = self.config.actionID -- page 1 is the same as the base actionID
flickerstreak@87 798 end
flickerstreak@87 799 f:SetAttribute(("*action-page%d"):format(i),c[i])
flickerstreak@75 800 end
flickerstreak@75 801 for i = nPages+1, #c do
flickerstreak@87 802 IDAlloc:Release(c[i])
flickerstreak@75 803 c[i] = nil
flickerstreak@87 804 f:SetAttribute(("*action-page%d"):format(i),nil)
flickerstreak@75 805 end
flickerstreak@87 806 self.nPages = nPages
flickerstreak@75 807 end
flickerstreak@75 808 end
flickerstreak@28 809
flickerstreak@77 810 function Button:ShowGrid( show )
flickerstreak@77 811 if not InCombatLockdown() then
flickerstreak@77 812 -- new in 2.4.1: can't call ActionButton_ShowGrid/HideGrid because they won't update the attribute
flickerstreak@77 813 local f = self.frame
flickerstreak@77 814 local count = f:GetAttribute("showgrid")
flickerstreak@77 815 if show then
flickerstreak@77 816 count = count + 1
flickerstreak@77 817 else
flickerstreak@77 818 count = count - 1
flickerstreak@28 819 end
flickerstreak@77 820 if count < 0 then
flickerstreak@77 821 count = 0
flickerstreak@77 822 end
flickerstreak@77 823 f:SetAttribute("showgrid",count)
flickerstreak@77 824
flickerstreak@77 825 if count >= 1 and not f:GetAttribute("statehidden") then
flickerstreak@77 826 self.normalTexture:SetVertexColor(1.0, 1.0, 1.0, 0.5);
flickerstreak@77 827 f:Show()
flickerstreak@77 828 elseif count < 1 and not HasAction(self:GetActionID()) then
flickerstreak@77 829 f:Hide()
flickerstreak@77 830 end
flickerstreak@28 831 end
flickerstreak@77 832 end
flickerstreak@77 833
flickerstreak@77 834 function Button:ShowActionIDLabel( show )
flickerstreak@87 835 local f = self:GetFrame()
flickerstreak@77 836 if show then
flickerstreak@77 837 local id = self:GetActionID()
flickerstreak@87 838 if not f.actionIDLabel then
flickerstreak@77 839 local label = f:CreateFontString(nil,"OVERLAY","GameFontNormalLarge")
flickerstreak@77 840 label:SetAllPoints()
flickerstreak@77 841 label:SetJustifyH("CENTER")
flickerstreak@77 842 label:SetShadowColor(0,0,0,1)
flickerstreak@77 843 label:SetShadowOffset(2,-2)
flickerstreak@87 844 f.actionIDLabel = label -- store the label with the frame for recycling
flickerstreak@77 845 f:HookScript("OnAttributeChanged",
flickerstreak@77 846 function(frame, attr, value)
flickerstreak@87 847 if label:IsVisible() and (attr == "state-parent" or attr:match("action")) then
flickerstreak@87 848 label:SetText(tostring(frame.action))
flickerstreak@77 849 end
flickerstreak@77 850 end)
flickerstreak@77 851 end
flickerstreak@87 852 f.actionIDLabel:SetText(tostring(id))
flickerstreak@87 853 f.actionIDLabel:Show()
flickerstreak@87 854 elseif f.actionIDLabel then
flickerstreak@87 855 f.actionIDLabel:Hide()
flickerstreak@77 856 end
flickerstreak@77 857 end
flickerstreak@77 858