Mercurial > wow > reaction
comparison modules/Action.lua @ 109:410d036c43b2
- reorganize modularity file structure (part 1)
| author | Flick <flickerstreak@gmail.com> |
|---|---|
| date | Thu, 08 Jan 2009 00:57:27 +0000 |
| parents | modules/ReAction_Action/ReAction_Action.lua@b2fb8f7dc780 |
| children | 77bb68eb402b |
comparison
equal
deleted
inserted
replaced
| 108:b2fb8f7dc780 | 109:410d036c43b2 |
|---|---|
| 1 --[[ | |
| 2 ReAction Action button module. | |
| 3 | |
| 4 The button module implements standard action button functionality by wrapping Blizzard's | |
| 5 ActionBarButtonTemplate frame and associated functions. | |
| 6 | |
| 7 It also provides action remapping support for multiple pages and possessed targets | |
| 8 (Mind Control, Eyes of the Beast, Karazhan Chess event, various quests, etc). | |
| 9 --]] | |
| 10 | |
| 11 -- local imports | |
| 12 local ReAction = ReAction | |
| 13 local L = ReAction.L | |
| 14 local _G = _G | |
| 15 local CreateFrame = CreateFrame | |
| 16 local format = string.format | |
| 17 local wipe = wipe | |
| 18 | |
| 19 ReAction:UpdateRevision("$Revision$") | |
| 20 | |
| 21 -- libraries | |
| 22 local KB = LibStub("LibKeyBound-1.0") | |
| 23 local LBF -- initialized later | |
| 24 | |
| 25 -- module declaration | |
| 26 local moduleID = "Action" | |
| 27 local module = ReAction:NewModule( moduleID ) | |
| 28 | |
| 29 -- Class declarations | |
| 30 local Button = { } | |
| 31 local Handle = { } | |
| 32 local PropHandler = { } | |
| 33 | |
| 34 -- Event handlers | |
| 35 function module:OnInitialize() | |
| 36 self.db = ReAction.db:RegisterNamespace( moduleID, | |
| 37 { | |
| 38 profile = { | |
| 39 bars = { }, | |
| 40 } | |
| 41 } | |
| 42 ) | |
| 43 self.handles = setmetatable({ }, weak) | |
| 44 | |
| 45 ReAction:RegisterBarOptionGenerator(self, "GetBarOptions") | |
| 46 | |
| 47 ReAction.RegisterCallback(self, "OnCreateBar") | |
| 48 ReAction.RegisterCallback(self, "OnRefreshBar") | |
| 49 ReAction.RegisterCallback(self, "OnDestroyBar") | |
| 50 ReAction.RegisterCallback(self, "OnEraseBar") | |
| 51 ReAction.RegisterCallback(self, "OnRenameBar") | |
| 52 ReAction.RegisterCallback(self, "OnConfigModeChanged") | |
| 53 | |
| 54 LBF = LibStub("LibButtonFacade",true) | |
| 55 | |
| 56 KB.RegisterCallback(self, "LIBKEYBOUND_ENABLED") | |
| 57 KB.RegisterCallback(self, "LIBKEYBOUND_DISABLED") | |
| 58 KB.RegisterCallback(self, "LIBKEYBOUND_MODE_COLOR_CHANGED","LIBKEYBOUND_ENABLED") | |
| 59 end | |
| 60 | |
| 61 function module:OnEnable() | |
| 62 ReAction:RegisterBarType(L["Action Bar"], | |
| 63 { | |
| 64 type = moduleID, | |
| 65 defaultButtonSize = 36, | |
| 66 defaultBarRows = 1, | |
| 67 defaultBarCols = 12, | |
| 68 defaultBarSpacing = 3 | |
| 69 }, true) | |
| 70 ReAction:GetModule("State"):RegisterStateProperty("page", nil, PropHandler.GetOptions(), PropHandler) | |
| 71 end | |
| 72 | |
| 73 function module:OnDisable() | |
| 74 ReAction:UnregisterBarType(L["Action Bar"]) | |
| 75 ReAction:GetModule("State"):UnregisterStateProperty("page") | |
| 76 end | |
| 77 | |
| 78 function module:OnCreateBar(event, bar, name) | |
| 79 if bar.config.type == moduleID then | |
| 80 local profile = self.db.profile | |
| 81 if profile.bars[name] == nil then | |
| 82 profile.bars[name] = { | |
| 83 buttons = { } | |
| 84 } | |
| 85 end | |
| 86 if self.handles[bar] == nil then | |
| 87 self.handles[bar] = Handle:New(bar, profile.bars[name]) | |
| 88 end | |
| 89 end | |
| 90 end | |
| 91 | |
| 92 function module:OnRefreshBar(event, bar, name) | |
| 93 if self.handles[bar] then | |
| 94 self.handles[bar]:Refresh() | |
| 95 end | |
| 96 end | |
| 97 | |
| 98 function module:OnDestroyBar(event, bar, name) | |
| 99 if self.handles[bar] then | |
| 100 self.handles[bar]:Destroy() | |
| 101 self.handles[bar] = nil | |
| 102 end | |
| 103 end | |
| 104 | |
| 105 function module:OnEraseBar(event, bar, name) | |
| 106 self.db.profile.bars[name] = nil | |
| 107 end | |
| 108 | |
| 109 function module:OnRenameBar(event, bar, oldname, newname) | |
| 110 b = self.db.profile.bars | |
| 111 b[newname], b[oldname] = b[oldname], nil | |
| 112 end | |
| 113 | |
| 114 function module:OnConfigModeChanged(event, mode) | |
| 115 for _, h in pairs(self.handles) do | |
| 116 h:SetConfigMode(mode) | |
| 117 end | |
| 118 end | |
| 119 | |
| 120 function module:LIBKEYBOUND_ENABLED(evt) | |
| 121 for _, h in pairs(self.handles) do | |
| 122 h:ShowGrid(true) | |
| 123 h:SetKeybindMode(true) | |
| 124 end | |
| 125 end | |
| 126 | |
| 127 function module:LIBKEYBOUND_DISABLED(evt) | |
| 128 for _, h in pairs(self.handles) do | |
| 129 h:ShowGrid(false) | |
| 130 h:SetKeybindMode(false) | |
| 131 end | |
| 132 end | |
| 133 | |
| 134 | |
| 135 ---- Interface ---- | |
| 136 function module:GetBarOptions(bar) | |
| 137 local h = self.handles[bar] | |
| 138 if h then | |
| 139 return h:GetOptions() | |
| 140 end | |
| 141 end | |
| 142 | |
| 143 | |
| 144 ---- Bar Handle ---- | |
| 145 | |
| 146 do | |
| 147 local options = { | |
| 148 hideEmpty = { | |
| 149 name = L["Hide Empty Buttons"], | |
| 150 order = 1, | |
| 151 type = "toggle", | |
| 152 width = "double", | |
| 153 get = "GetHideEmpty", | |
| 154 set = "SetHideEmpty", | |
| 155 }, | |
| 156 lockButtons = { | |
| 157 name = L["Lock Buttons"], | |
| 158 desc = L["Prevents picking up/dragging actions.|nNOTE: This setting is overridden by the global setting in Blizzard's Action Buttons tab"], | |
| 159 order = 2, | |
| 160 type = "toggle", | |
| 161 disabled = "LockButtonsDisabled", | |
| 162 get = "GetLockButtons", | |
| 163 set = "SetLockButtons", | |
| 164 }, | |
| 165 lockOnlyCombat = { | |
| 166 name = L["Only in Combat"], | |
| 167 desc = L["Only lock the buttons when in combat"], | |
| 168 order = 3, | |
| 169 type = "toggle", | |
| 170 disabled = "LockButtonsCombatDisabled", | |
| 171 get = "GetLockButtonsCombat", | |
| 172 set = "SetLockButtonsCombat", | |
| 173 }, | |
| 174 pages = { | |
| 175 name = L["# Pages"], | |
| 176 desc = L["Use the Dynamic State tab to specify page transitions"], | |
| 177 order = 4, | |
| 178 type = "range", | |
| 179 min = 1, | |
| 180 max = 10, | |
| 181 step = 1, | |
| 182 get = "GetNumPages", | |
| 183 set = "SetNumPages", | |
| 184 }, | |
| 185 mindcontrol = { | |
| 186 name = L["Mind Control Support"], | |
| 187 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."], | |
| 188 order = 5, | |
| 189 type = "toggle", | |
| 190 width = "double", | |
| 191 set = "SetMindControl", | |
| 192 get = "GetMindControl", | |
| 193 }, | |
| 194 actions = { | |
| 195 name = L["Edit Action IDs"], | |
| 196 order = 6, | |
| 197 type = "group", | |
| 198 inline = true, | |
| 199 args = { | |
| 200 method = { | |
| 201 name = L["Assign"], | |
| 202 order = 1, | |
| 203 type = "select", | |
| 204 width = "double", | |
| 205 values = { [0] = L["Choose Method..."], | |
| 206 [1] = L["Individually"], | |
| 207 [2] = L["All at Once"], }, | |
| 208 get = "GetActionEditMethod", | |
| 209 set = "SetActionEditMethod", | |
| 210 }, | |
| 211 rowSelect = { | |
| 212 name = L["Row"], | |
| 213 desc = L["Rows are numbered top to bottom"], | |
| 214 order = 2, | |
| 215 type = "select", | |
| 216 width = "half", | |
| 217 hidden = "IsButtonSelectHidden", | |
| 218 values = "GetRowList", | |
| 219 get = "GetSelectedRow", | |
| 220 set = "SetSelectedRow", | |
| 221 }, | |
| 222 colSelect = { | |
| 223 name = L["Col"], | |
| 224 desc = L["Columns are numbered left to right"], | |
| 225 order = 3, | |
| 226 type = "select", | |
| 227 width = "half", | |
| 228 hidden = "IsButtonSelectHidden", | |
| 229 values = "GetColumnList", | |
| 230 get = "GetSelectedColumn", | |
| 231 set = "SetSelectedColumn", | |
| 232 }, | |
| 233 pageSelect = { | |
| 234 name = L["Page"], | |
| 235 order = 4, | |
| 236 type = "select", | |
| 237 width = "half", | |
| 238 hidden = "IsPageSelectHidden", | |
| 239 values = "GetPageList", | |
| 240 get = "GetSelectedPage", | |
| 241 set = "SetSelectedPage", | |
| 242 }, | |
| 243 single = { | |
| 244 name = L["Action ID"], | |
| 245 usage = L["Specify ID 1-120"], | |
| 246 order = 5, | |
| 247 type = "input", | |
| 248 width = "half", | |
| 249 hidden = "IsButtonSelectHidden", | |
| 250 get = "GetActionID", | |
| 251 set = "SetActionID", | |
| 252 validate = "ValidateActionID", | |
| 253 }, | |
| 254 multi = { | |
| 255 name = L["ID List"], | |
| 256 usage = L["Specify a comma-separated list of IDs for each button in the bar (in order). Separate multiple pages with semicolons (;)"], | |
| 257 order = 6, | |
| 258 type = "input", | |
| 259 multiline = true, | |
| 260 width = "double", | |
| 261 hidden = "IsMultiIDHidden", | |
| 262 get = "GetMultiID", | |
| 263 set = "SetMultiID", | |
| 264 validate = "ValidateMultiID", | |
| 265 }, | |
| 266 }, | |
| 267 }, | |
| 268 } | |
| 269 | |
| 270 local weak = { __mode="k" } | |
| 271 local meta = { __index = Handle } | |
| 272 | |
| 273 function Handle:New( bar, config ) | |
| 274 local self = setmetatable( | |
| 275 { | |
| 276 bar = bar, | |
| 277 config = config, | |
| 278 btns = { } | |
| 279 }, | |
| 280 meta) | |
| 281 | |
| 282 if self.config.buttons == nil then | |
| 283 self.config.buttons = { } | |
| 284 end | |
| 285 self:Refresh() | |
| 286 self:SetKeybindMode(ReAction:GetKeybindMode()) | |
| 287 return self | |
| 288 end | |
| 289 | |
| 290 function Handle:Refresh() | |
| 291 local r, c = self.bar:GetButtonGrid() | |
| 292 local n = r*c | |
| 293 local btnCfg = self.config.buttons | |
| 294 if n ~= #self.btns then | |
| 295 for i = 1, n do | |
| 296 if btnCfg[i] == nil then | |
| 297 btnCfg[i] = {} | |
| 298 end | |
| 299 if self.btns[i] == nil then | |
| 300 local b = Button:New(self, i, btnCfg[i], self.config) | |
| 301 self.btns[i] = b | |
| 302 self.bar:AddButton(i,b) | |
| 303 end | |
| 304 end | |
| 305 for i = n+1, #self.btns do | |
| 306 if self.btns[i] then | |
| 307 self.bar:RemoveButton(self.btns[i]) | |
| 308 self.btns[i]:Destroy() | |
| 309 self.btns[i] = nil | |
| 310 btnCfg[i] = nil | |
| 311 end | |
| 312 end | |
| 313 end | |
| 314 local f = self.bar:GetFrame() | |
| 315 for _, b in ipairs(self.btns) do | |
| 316 b:Refresh() | |
| 317 end | |
| 318 f:SetAttribute("mindcontrol",self.config.mindcontrol) | |
| 319 f:Execute( | |
| 320 [[ | |
| 321 doMindControl = self:GetAttribute("mindcontrol") | |
| 322 control:ChildUpdate() | |
| 323 ]]) | |
| 324 | |
| 325 f:SetAttribute("_onstate-mindcontrol", | |
| 326 -- function _onstate-mindcontrol(self, stateid, newstate) | |
| 327 [[ | |
| 328 control:ChildUpdate() | |
| 329 ]]) | |
| 330 RegisterStateDriver(f, "mindcontrol", "[bonusbar:5] mc; none") | |
| 331 self:UpdateButtonLock() | |
| 332 end | |
| 333 | |
| 334 function Handle:Destroy() | |
| 335 for _,b in pairs(self.btns) do | |
| 336 if b then | |
| 337 b:Destroy() | |
| 338 end | |
| 339 end | |
| 340 end | |
| 341 | |
| 342 function Handle:SetConfigMode(mode) | |
| 343 for _, b in pairs(self.btns) do | |
| 344 b:ShowGrid(mode) | |
| 345 b:ShowActionIDLabel(mode) | |
| 346 end | |
| 347 end | |
| 348 | |
| 349 function Handle:ShowGrid(show) | |
| 350 for _, b in pairs(self.btns) do | |
| 351 b:ShowGrid(show) | |
| 352 end | |
| 353 end | |
| 354 | |
| 355 function Handle:UpdateButtonLock() | |
| 356 local f = self.bar:GetFrame() | |
| 357 f:SetAttribute("lockbuttons",self.config.lockButtons) | |
| 358 f:SetAttribute("lockbuttonscombat",self.config.lockButtonsCombat) | |
| 359 f:Execute( | |
| 360 [[ | |
| 361 lockButtons = self:GetAttribute("lockbuttons") | |
| 362 lockButtonsCombat = self:GetAttribute("lockbuttonscombat") | |
| 363 ]]) | |
| 364 end | |
| 365 | |
| 366 function Handle:SetKeybindMode(mode) | |
| 367 for _, b in pairs(self.btns) do | |
| 368 if mode then | |
| 369 -- set the border for all buttons to the keybind-enable color | |
| 370 b.border:SetVertexColor(KB:GetColorKeyBoundMode()) | |
| 371 b.border:Show() | |
| 372 elseif IsEquippedAction(b:GetActionID()) then | |
| 373 b.border:SetVertexColor(0, 1.0, 0, 0.35) -- from ActionButton.lua | |
| 374 else | |
| 375 b.border:Hide() | |
| 376 end | |
| 377 end | |
| 378 end | |
| 379 | |
| 380 function Handle:GetLastButton() | |
| 381 return self.btns[#self.btns] | |
| 382 end | |
| 383 | |
| 384 -- options handlers | |
| 385 function Handle:GetOptions() | |
| 386 return { | |
| 387 type = "group", | |
| 388 name = L["Action Buttons"], | |
| 389 handler = self, | |
| 390 args = options | |
| 391 } | |
| 392 end | |
| 393 | |
| 394 function Handle:SetHideEmpty(info, value) | |
| 395 if value ~= self.config.hideEmpty then | |
| 396 self.config.hideEmpty = value | |
| 397 self:ShowGrid(not value) | |
| 398 end | |
| 399 end | |
| 400 | |
| 401 function Handle:GetHideEmpty() | |
| 402 return self.config.hideEmpty | |
| 403 end | |
| 404 | |
| 405 function Handle:GetLockButtons() | |
| 406 return LOCK_ACTIONBAR == "1" or self.config.lockButtons | |
| 407 end | |
| 408 | |
| 409 function Handle:SetLockButtons(info, value) | |
| 410 self.config.lockButtons = value | |
| 411 self:UpdateButtonLock() | |
| 412 end | |
| 413 | |
| 414 function Handle:LockButtonsDisabled() | |
| 415 return LOCK_ACTIONBAR == "1" | |
| 416 end | |
| 417 | |
| 418 function Handle:GetLockButtonsCombat() | |
| 419 return self.config.lockButtonsCombat | |
| 420 end | |
| 421 | |
| 422 function Handle:SetLockButtonsCombat(info, value) | |
| 423 self.config.lockButtonsCombat = value | |
| 424 self:UpdateButtonLock() | |
| 425 end | |
| 426 | |
| 427 function Handle:LockButtonsCombatDisabled() | |
| 428 return LOCK_ACTIONBAR == "1" or not self.config.lockButtons | |
| 429 end | |
| 430 | |
| 431 function Handle:GetNumPages() | |
| 432 return self.config.nPages | |
| 433 end | |
| 434 | |
| 435 function Handle:SetNumPages(info, value) | |
| 436 self.config.nPages = value | |
| 437 self:Refresh() | |
| 438 end | |
| 439 | |
| 440 function Handle:GetMindControl() | |
| 441 return self.config.mindcontrol | |
| 442 end | |
| 443 | |
| 444 function Handle:SetMindControl(info, value) | |
| 445 self.config.mindcontrol = value | |
| 446 self:Refresh() | |
| 447 end | |
| 448 | |
| 449 function Handle:GetActionEditMethod() | |
| 450 return self.editMethod or 0 | |
| 451 end | |
| 452 | |
| 453 function Handle:SetActionEditMethod(info, value) | |
| 454 self.editMethod = value | |
| 455 end | |
| 456 | |
| 457 function Handle:IsButtonSelectHidden() | |
| 458 return self.editMethod ~= 1 | |
| 459 end | |
| 460 | |
| 461 function Handle:GetRowList() | |
| 462 local r,c = self.bar:GetButtonGrid() | |
| 463 if self.rowList == nil or #self.rowList ~= r then | |
| 464 local list = { } | |
| 465 for i = 1, r do | |
| 466 table.insert(list,i) | |
| 467 end | |
| 468 self.rowList = list | |
| 469 end | |
| 470 return self.rowList | |
| 471 end | |
| 472 | |
| 473 function Handle:GetSelectedRow() | |
| 474 local r, c = self.bar:GetButtonGrid() | |
| 475 local row = self.selectedRow or 1 | |
| 476 if row > r then | |
| 477 row = 1 | |
| 478 end | |
| 479 self.selectedRow = row | |
| 480 return row | |
| 481 end | |
| 482 | |
| 483 function Handle:SetSelectedRow(info, value) | |
| 484 self.selectedRow = value | |
| 485 end | |
| 486 | |
| 487 function Handle:GetColumnList() | |
| 488 local r,c = self.bar:GetButtonGrid() | |
| 489 if self.columnList == nil or #self.columnList ~= c then | |
| 490 local list = { } | |
| 491 for i = 1, c do | |
| 492 table.insert(list,i) | |
| 493 end | |
| 494 self.columnList = list | |
| 495 end | |
| 496 return self.columnList | |
| 497 end | |
| 498 | |
| 499 function Handle:GetSelectedColumn() | |
| 500 local r, c = self.bar:GetButtonGrid() | |
| 501 local col = self.selectedColumn or 1 | |
| 502 if col > c then | |
| 503 col = 1 | |
| 504 end | |
| 505 self.selectedColumn = col | |
| 506 return col | |
| 507 end | |
| 508 | |
| 509 function Handle:SetSelectedColumn(info, value) | |
| 510 self.selectedColumn = value | |
| 511 end | |
| 512 | |
| 513 function Handle:IsPageSelectHidden() | |
| 514 return self.editMethod ~= 1 or (self.config.nPages or 1) < 2 | |
| 515 end | |
| 516 | |
| 517 function Handle:GetPageList() | |
| 518 local n = self.config.nPages or 1 | |
| 519 if self.pageList == nil or #self.pageList ~= n then | |
| 520 local p = { } | |
| 521 for i = 1, n do | |
| 522 table.insert(p,i) | |
| 523 end | |
| 524 self.pageList = p | |
| 525 end | |
| 526 return self.pageList | |
| 527 end | |
| 528 | |
| 529 function Handle:GetSelectedPage() | |
| 530 local p = self.selectedPage or 1 | |
| 531 if p > (self.config.nPages or 1) then | |
| 532 p = 1 | |
| 533 end | |
| 534 self.selectedPage = p | |
| 535 return p | |
| 536 end | |
| 537 | |
| 538 function Handle:SetSelectedPage(info, value) | |
| 539 self.selectedPage = value | |
| 540 end | |
| 541 | |
| 542 function Handle:GetActionID() | |
| 543 local row = self.selectedRow or 1 | |
| 544 local col = self.selectedColumn or 1 | |
| 545 local r, c = self.bar:GetButtonGrid() | |
| 546 local n = (row-1) * c + col | |
| 547 local btn = self.btns[n] | |
| 548 if btn then | |
| 549 return tostring(btn:GetActionID(self.selectedPage or 1)) | |
| 550 end | |
| 551 end | |
| 552 | |
| 553 function Handle:SetActionID(info, value) | |
| 554 local row = self.selectedRow or 1 | |
| 555 local col = self.selectedColumn or 1 | |
| 556 local r, c = self.bar:GetButtonGrid() | |
| 557 local n = (row-1) * c + col | |
| 558 local btn = self.btns[n] | |
| 559 if btn then | |
| 560 btn:SetActionID(tonumber(value), self.selectedPage or 1) | |
| 561 end | |
| 562 end | |
| 563 | |
| 564 function Handle:ValidateActionID(info, value) | |
| 565 value = tonumber(value) | |
| 566 if value == nil or value < 1 or value > 120 then | |
| 567 return L["Specify ID 1-120"] | |
| 568 end | |
| 569 return true | |
| 570 end | |
| 571 | |
| 572 function Handle:IsMultiIDHidden() | |
| 573 return self.editMethod ~= 2 | |
| 574 end | |
| 575 | |
| 576 function Handle:GetMultiID() | |
| 577 local p = { } | |
| 578 for i = 1, self.config.nPages or 1 do | |
| 579 local b = { } | |
| 580 for _, btn in ipairs(self.btns) do | |
| 581 table.insert(b, btn:GetActionID(i)) | |
| 582 end | |
| 583 table.insert(p, table.concat(b,",")) | |
| 584 end | |
| 585 return table.concat(p,";\n") | |
| 586 end | |
| 587 | |
| 588 | |
| 589 local function ParseMultiID(nBtns, nPages, s) | |
| 590 if s:match("[^%d%s,;]") then | |
| 591 ReAction:Print("items other than digits, spaces, commas, and semicolons in string",s) | |
| 592 return nil | |
| 593 end | |
| 594 local p = { } | |
| 595 for list in s:gmatch("[^;]+") do | |
| 596 local pattern = ("^%s?$"):format(("%s*(%d+)%s*,"):rep(nBtns)) | |
| 597 local ids = { list:match(pattern) } | |
| 598 if #ids ~= nBtns then | |
| 599 ReAction:Print("found",#ids,"buttons instead of",nBtns) | |
| 600 return nil | |
| 601 end | |
| 602 table.insert(p,ids) | |
| 603 end | |
| 604 if #p ~= nPages then | |
| 605 ReAction:Print("found",#p,"pages instead of",nPages) | |
| 606 return nil | |
| 607 end | |
| 608 return p | |
| 609 end | |
| 610 | |
| 611 function Handle:SetMultiID(info, value) | |
| 612 local p = ParseMultiID(#self.btns, self.config.nPages or 1, value) | |
| 613 for page, b in ipairs(p) do | |
| 614 for button, id in ipairs(b) do | |
| 615 self.btns[button]:SetActionID(id, page) | |
| 616 end | |
| 617 end | |
| 618 end | |
| 619 | |
| 620 function Handle:ValidateMultiID(info, value) | |
| 621 local bad = L["Invalid action ID list string"] | |
| 622 if value == nil or ParseMultiID(#self.btns, self.config.nPages or 1, value) == nil then | |
| 623 return bad | |
| 624 end | |
| 625 return true | |
| 626 end | |
| 627 end | |
| 628 | |
| 629 | |
| 630 ------ State property options ------ | |
| 631 do | |
| 632 local pageOptions = { | |
| 633 page = { | |
| 634 name = L["Show Page #"], | |
| 635 order = 11, | |
| 636 type = "select", | |
| 637 width = "half", | |
| 638 disabled = "IsPageDisabled", | |
| 639 hidden = "IsPageHidden", | |
| 640 values = "GetPageValues", | |
| 641 set = "SetProp", | |
| 642 get = "GetPage", | |
| 643 }, | |
| 644 } | |
| 645 | |
| 646 local function GetBarConfig(bar) | |
| 647 return module.db.profile.bars[bar:GetName()] | |
| 648 end | |
| 649 | |
| 650 function PropHandler.GetOptions() | |
| 651 return pageOptions | |
| 652 end | |
| 653 | |
| 654 function PropHandler:IsPageDisabled() | |
| 655 local c = GetBarConfig(self.bar) | |
| 656 local n = c and c.nPages or 1 | |
| 657 return not (n > 1) | |
| 658 end | |
| 659 | |
| 660 function PropHandler:IsPageHidden() | |
| 661 return not GetBarConfig(self.bar) | |
| 662 end | |
| 663 | |
| 664 function PropHandler:GetPageValues() | |
| 665 if not self._pagevalues then | |
| 666 self._pagevalues = { } | |
| 667 end | |
| 668 local c = GetBarConfig(self.bar) | |
| 669 if c then | |
| 670 local n = c.nPages | |
| 671 -- cache the results | |
| 672 if self._npages ~= n then | |
| 673 self._npages = n | |
| 674 wipe(self._pagevalues) | |
| 675 for i = 1, n do | |
| 676 self._pagevalues["page"..i] = i | |
| 677 end | |
| 678 end | |
| 679 end | |
| 680 return self._pagevalues | |
| 681 end | |
| 682 | |
| 683 function PropHandler:GetPage(info) | |
| 684 return self:GetProp(info) or 1 | |
| 685 end | |
| 686 | |
| 687 end | |
| 688 | |
| 689 ------ ActionID allocation ------ | |
| 690 -- this needs to be high performance when requesting new IDs, | |
| 691 -- or certain controls will become sluggish. However, the new-request | |
| 692 -- infrastructure can be built lazily the first time that a new request | |
| 693 -- comes in (which will only happen at user config time: at static startup | |
| 694 -- config time all actionIDs should already have been assigned and stored | |
| 695 -- in the config file) | |
| 696 | |
| 697 local IDAlloc | |
| 698 do | |
| 699 local n = 120 | |
| 700 | |
| 701 IDAlloc = setmetatable({ wrap = 1, freecount = n }, {__index = function() return 0 end}) | |
| 702 | |
| 703 function IDAlloc:Acquire(id, hint) | |
| 704 id = tonumber(id) | |
| 705 hint = tonumber(hint) | |
| 706 if id and (id < 1 or id > n) then | |
| 707 id = nil | |
| 708 end | |
| 709 if hint and (hint < 1 or hint > n) then | |
| 710 hint = nil | |
| 711 end | |
| 712 if id == nil then | |
| 713 -- get a free ID | |
| 714 if hint and self[hint] == 0 then | |
| 715 -- use the hint if it's free | |
| 716 id = hint | |
| 717 elseif self.freecount > 0 then | |
| 718 -- if neither the id nor the hint are defined or free, but | |
| 719 -- the list is known to have free IDs, then start searching | |
| 720 -- at the hint for a free one | |
| 721 for i = hint or 1, n do | |
| 722 if self[i] == 0 then | |
| 723 id = i | |
| 724 break | |
| 725 end | |
| 726 end | |
| 727 -- self.wrap the search | |
| 728 if id == nil and hint and hint > 1 then | |
| 729 for i = 1, hint - 1 do | |
| 730 if self[i] == 0 then | |
| 731 id = i | |
| 732 break | |
| 733 end | |
| 734 end | |
| 735 end | |
| 736 end | |
| 737 if id == nil then | |
| 738 -- if there are no free IDs, start wrapping at 1 | |
| 739 id = self.wrap | |
| 740 self.wrap = id + 1 | |
| 741 if self.wrap > n then | |
| 742 self.wrap = 1 | |
| 743 end | |
| 744 end | |
| 745 end | |
| 746 if self[id] == 0 then | |
| 747 self.freecount = self.freecount - 1 | |
| 748 end | |
| 749 self[id] = self[id] + 1 | |
| 750 return id | |
| 751 end | |
| 752 | |
| 753 function IDAlloc:Release(id) | |
| 754 id = tonumber(id) | |
| 755 if id and (id >= 1 or id <= n) then | |
| 756 self[id] = self[id] - 1 | |
| 757 if self[id] == 0 then | |
| 758 self.freecount = self.freecount + 1 | |
| 759 self.wrap = 1 | |
| 760 end | |
| 761 end | |
| 762 end | |
| 763 end | |
| 764 | |
| 765 ------ Button class ------ | |
| 766 | |
| 767 do | |
| 768 local frameRecycler = { } | |
| 769 local trash = CreateFrame("Frame") | |
| 770 local OnUpdate, KBAttach, GetActionName, GetHotkey, SetKey, FreeKey, ClearBindings, GetBindings | |
| 771 do | |
| 772 local ATTACK_BUTTON_FLASH_TIME = ATTACK_BUTTON_FLASH_TIME | |
| 773 local IsActionInRange = IsActionInRange | |
| 774 | |
| 775 local buttonLookup = setmetatable({},{__mode="kv"}) | |
| 776 | |
| 777 function OnUpdate(frame, elapsed) | |
| 778 -- note: This function taints frame.flashtime and frame.rangeTimer. Both of these | |
| 779 -- are only read by ActionButton_OnUpdate (which this function replaces). In | |
| 780 -- all other places they're just written, so it doesn't taint any secure code. | |
| 781 if frame.flashing == 1 then | |
| 782 frame.flashtime = frame.flashtime - elapsed | |
| 783 if frame.flashtime <= 0 then | |
| 784 local overtime = -frame.flashtime | |
| 785 if overtime >= ATTACK_BUTTON_FLASH_TIME then | |
| 786 overtime = 0 | |
| 787 end | |
| 788 frame.flashtime = ATTACK_BUTTON_FLASH_TIME - overtime | |
| 789 | |
| 790 local flashTexture = frame.flash | |
| 791 if flashTexture:IsShown() then | |
| 792 flashTexture:Hide() | |
| 793 else | |
| 794 flashTexture:Show() | |
| 795 end | |
| 796 end | |
| 797 end | |
| 798 | |
| 799 if frame.rangeTimer then | |
| 800 frame.rangeTimer = frame.rangeTimer - elapsed; | |
| 801 | |
| 802 if frame.rangeTimer <= 0 then | |
| 803 if IsActionInRange(frame.action) == 0 then | |
| 804 frame.icon:SetVertexColor(1.0,0.1,0.1) | |
| 805 else | |
| 806 ActionButton_UpdateUsable(frame) | |
| 807 end | |
| 808 frame.rangeTimer = 0.1 | |
| 809 end | |
| 810 end | |
| 811 end | |
| 812 | |
| 813 -- Use KeyBound-1.0 for binding, but use Override bindings instead of | |
| 814 -- regular bindings to support multiple profile use. This is a little | |
| 815 -- weird with the KeyBound dialog box (which has per-char selector as well | |
| 816 -- as an OK/Cancel box) but it's the least amount of effort to implement. | |
| 817 function GetActionName(f) | |
| 818 local b = buttonLookup[f] | |
| 819 if b then | |
| 820 return format("%s:%s", b.bar:GetName(), b.idx) | |
| 821 end | |
| 822 end | |
| 823 | |
| 824 function GetHotkey(f) | |
| 825 local b = buttonLookup[f] | |
| 826 if b then | |
| 827 return KB:ToShortKey(b:GetConfig().hotkey) | |
| 828 end | |
| 829 end | |
| 830 | |
| 831 function SetKey(f, key) | |
| 832 local b = buttonLookup[f] | |
| 833 if b then | |
| 834 local c = b:GetConfig() | |
| 835 if c.hotkey then | |
| 836 SetOverrideBinding(f, false, c.hotkey, nil) | |
| 837 end | |
| 838 if key then | |
| 839 SetOverrideBindingClick(f, false, key, f:GetName(), nil) | |
| 840 end | |
| 841 c.hotkey = key | |
| 842 b:DisplayHotkey(GetHotkey(f)) | |
| 843 end | |
| 844 end | |
| 845 | |
| 846 function FreeKey(f, key) | |
| 847 local b = buttonLookup[f] | |
| 848 if b then | |
| 849 local c = b:GetConfig() | |
| 850 if c.hotkey == key then | |
| 851 local action = f:GetActionName() | |
| 852 SetOverrideBinding(f, false, c.hotkey, nil) | |
| 853 c.hotkey = nil | |
| 854 b:DisplayHotkey(nil) | |
| 855 return action | |
| 856 end | |
| 857 end | |
| 858 return ReAction:FreeOverrideHotkey(key) | |
| 859 end | |
| 860 | |
| 861 function ClearBindings(f) | |
| 862 SetKey(f, nil) | |
| 863 end | |
| 864 | |
| 865 function GetBindings(f) | |
| 866 local b = buttonLookup[f] | |
| 867 if b then | |
| 868 return b:GetConfig().hotkey | |
| 869 end | |
| 870 end | |
| 871 | |
| 872 function KBAttach( button ) | |
| 873 local f = button:GetFrame() | |
| 874 f.GetActionName = GetActionName | |
| 875 f.GetHotkey = GetHotkey | |
| 876 f.SetKey = SetKey | |
| 877 f.FreeKey = FreeKey | |
| 878 f.ClearBindings = ClearBindings | |
| 879 f.GetBindings = GetBindings | |
| 880 buttonLookup[f] = button | |
| 881 f:SetKey(button:GetConfig().hotkey) | |
| 882 ReAction:RegisterKeybindFrame(f) | |
| 883 if ReAction:GetKeybindMode() then | |
| 884 button.border:SetVertexColor(KB:GetColorKeyBoundMode()) | |
| 885 button.border:Show() | |
| 886 end | |
| 887 end | |
| 888 end | |
| 889 | |
| 890 local meta = {__index = Button} | |
| 891 | |
| 892 function Button:New( handle, idx, config, barConfig ) | |
| 893 local bar = handle.bar | |
| 894 | |
| 895 -- create new self | |
| 896 self = setmetatable( | |
| 897 { | |
| 898 bar = bar, | |
| 899 idx = idx, | |
| 900 config = config, | |
| 901 barConfig = barConfig, | |
| 902 }, meta ) | |
| 903 | |
| 904 local name = config.name or ("ReAction_%s_%s_%d"):format(bar:GetName(),moduleID,idx) | |
| 905 self.name = name | |
| 906 config.name = name | |
| 907 local lastButton = handle:GetLastButton() | |
| 908 config.actionID = IDAlloc:Acquire(config.actionID, lastButton and lastButton.config.actionID) -- gets a free one if none configured | |
| 909 self.nPages = 1 | |
| 910 | |
| 911 -- have to recycle frames with the same name: CreateFrame() doesn't overwrite | |
| 912 -- existing globals. Can't set to nil in the global because it's then tainted. | |
| 913 local parent = bar:GetFrame() | |
| 914 local f = frameRecycler[name] | |
| 915 if f then | |
| 916 f:SetParent(parent) | |
| 917 else | |
| 918 f = CreateFrame("CheckButton", name, parent, "ActionBarButtonTemplate") | |
| 919 -- ditch the old hotkey text because it's tied in ActionButton_Update() to the | |
| 920 -- standard binding. We use override bindings. | |
| 921 local hotkey = _G[name.."HotKey"] | |
| 922 hotkey:SetParent(trash) | |
| 923 hotkey = f:CreateFontString(nil, "ARTWORK", "NumberFontNormalSmallGray") | |
| 924 hotkey:SetWidth(36) | |
| 925 hotkey:SetHeight(18) | |
| 926 hotkey:SetJustifyH("RIGHT") | |
| 927 hotkey:SetJustifyV("TOP") | |
| 928 hotkey:SetPoint("TOPLEFT",f,"TOPLEFT",-2,-2) | |
| 929 f.hotkey = hotkey | |
| 930 f.icon = _G[name.."Icon"] | |
| 931 f.flash = _G[name.."Flash"] | |
| 932 f:SetScript("OnUpdate",OnUpdate) | |
| 933 end | |
| 934 | |
| 935 self.hotkey = f.hotkey | |
| 936 self.border = _G[name.."Border"] | |
| 937 | |
| 938 f:SetAttribute("action", config.actionID) | |
| 939 -- install mind control actions for all buttons just for simplicity | |
| 940 if self.idx <= 12 then | |
| 941 f:SetAttribute("*action-mc", 120 + self.idx) | |
| 942 end | |
| 943 | |
| 944 -- set a _childupdate handler, called within the header's context | |
| 945 f:SetAttribute("_childupdate", | |
| 946 -- function _childupdate(self, snippetid, message) | |
| 947 [[ | |
| 948 local action = "action" | |
| 949 if doMindControl and GetBonusBarOffset() == 5 then | |
| 950 action = "*action-mc" | |
| 951 elseif page and state and page[state] then | |
| 952 action = "*action-"..page[state] | |
| 953 end | |
| 954 local value = self:GetAttribute(action) | |
| 955 self:SetAttribute("action",value) | |
| 956 ]]) | |
| 957 | |
| 958 -- install drag wrappers to lock buttons | |
| 959 bar:GetFrame():WrapScript(f, "OnDragStart", | |
| 960 -- OnDragStart(self, button, kind, value, ...) | |
| 961 [[ | |
| 962 if lockButtons and (PlayerInCombat() or not lockButtonsCombat) and not IsModifiedClick("PICKUPACTION") then | |
| 963 return "clear" | |
| 964 end | |
| 965 ]]) | |
| 966 | |
| 967 self.frame = f | |
| 968 | |
| 969 | |
| 970 -- initialize the hide state | |
| 971 f:SetAttribute("showgrid",0) | |
| 972 self:ShowGrid(not barConfig.hideEmpty) | |
| 973 if ReAction:GetConfigMode() then | |
| 974 self:ShowGrid(true) | |
| 975 end | |
| 976 | |
| 977 -- show the ID label if applicable | |
| 978 self:ShowActionIDLabel(ReAction:GetConfigMode()) | |
| 979 | |
| 980 -- attach the keybinder | |
| 981 KBAttach(self) | |
| 982 | |
| 983 -- attach to skinner | |
| 984 bar:SkinButton(self, | |
| 985 { | |
| 986 HotKey = self.hotkey, | |
| 987 } | |
| 988 ) | |
| 989 | |
| 990 self:Refresh() | |
| 991 return self | |
| 992 end | |
| 993 | |
| 994 function Button:Destroy() | |
| 995 local f = self.frame | |
| 996 f:UnregisterAllEvents() | |
| 997 f:Hide() | |
| 998 f:SetParent(UIParent) | |
| 999 f:ClearAllPoints() | |
| 1000 if self.name then | |
| 1001 frameRecycler[self.name] = f | |
| 1002 end | |
| 1003 if self.config.actionID then | |
| 1004 IDAlloc:Release(self.config.actionID) | |
| 1005 end | |
| 1006 if self.config.pageactions then | |
| 1007 for _, id in ipairs(self.config.pageactions) do | |
| 1008 IDAlloc:Release(id) | |
| 1009 end | |
| 1010 end | |
| 1011 self.frame = nil | |
| 1012 self.config = nil | |
| 1013 self.bar = nil | |
| 1014 end | |
| 1015 | |
| 1016 function Button:Refresh() | |
| 1017 local f = self.frame | |
| 1018 self.bar:PlaceButton(self, 36, 36) | |
| 1019 self:RefreshPages() | |
| 1020 end | |
| 1021 | |
| 1022 function Button:GetFrame() | |
| 1023 return self.frame | |
| 1024 end | |
| 1025 | |
| 1026 function Button:GetName() | |
| 1027 return self.name | |
| 1028 end | |
| 1029 | |
| 1030 function Button:GetConfig() | |
| 1031 return self.config | |
| 1032 end | |
| 1033 | |
| 1034 function Button:GetActionID(page) | |
| 1035 if page == nil then | |
| 1036 -- get the effective ID | |
| 1037 return self.frame.action -- kept up-to-date by Blizzard's ActionButton_CalculateAction() | |
| 1038 else | |
| 1039 if page == 1 then | |
| 1040 return self.config.actionID | |
| 1041 else | |
| 1042 return self.config.pageactions and self.config.pageactions[page] or self.config.actionID | |
| 1043 end | |
| 1044 end | |
| 1045 end | |
| 1046 | |
| 1047 function Button:SetActionID( id, page ) | |
| 1048 id = tonumber(id) | |
| 1049 page = tonumber(page) | |
| 1050 if id == nil or id < 1 or id > 120 then | |
| 1051 error("Button:SetActionID - invalid action ID") | |
| 1052 end | |
| 1053 if page and page ~= 1 then | |
| 1054 if not self.config.pageactions then | |
| 1055 self.config.pageactions = { } | |
| 1056 end | |
| 1057 if self.config.pageactions[page] then | |
| 1058 IDAlloc:Release(self.config.pageactions[page]) | |
| 1059 end | |
| 1060 self.config.pageactions[page] = id | |
| 1061 IDAlloc:Acquire(self.config.pageactions[page]) | |
| 1062 self.frame:SetAttribute(("*action-page%d"):format(page),id) | |
| 1063 else | |
| 1064 IDAlloc:Release(self.config.actionID) | |
| 1065 self.config.actionID = id | |
| 1066 IDAlloc:Acquire(self.config.actionID) | |
| 1067 self.frame:SetAttribute("action",id) | |
| 1068 if self.config.pageactions then | |
| 1069 self.config.pageactions[1] = id | |
| 1070 self.frame:SetAttribute("*action-page1",id) | |
| 1071 end | |
| 1072 end | |
| 1073 end | |
| 1074 | |
| 1075 function Button:RefreshPages( force ) | |
| 1076 local nPages = self.barConfig.nPages | |
| 1077 if nPages and (nPages ~= self.nPages or force) then | |
| 1078 local f = self:GetFrame() | |
| 1079 local c = self.config.pageactions | |
| 1080 if nPages > 1 and not c then | |
| 1081 c = { } | |
| 1082 self.config.pageactions = c | |
| 1083 end | |
| 1084 for i = 1, nPages do | |
| 1085 if i > 1 then | |
| 1086 c[i] = IDAlloc:Acquire(c[i], self.config.actionID + (i-1)*self.bar:GetNumButtons()) | |
| 1087 else | |
| 1088 c[i] = self.config.actionID -- page 1 is the same as the base actionID | |
| 1089 end | |
| 1090 f:SetAttribute(("*action-page%d"):format(i),c[i]) | |
| 1091 end | |
| 1092 for i = nPages+1, #c do | |
| 1093 IDAlloc:Release(c[i]) | |
| 1094 c[i] = nil | |
| 1095 f:SetAttribute(("*action-page%d"):format(i),nil) | |
| 1096 end | |
| 1097 self.nPages = nPages | |
| 1098 end | |
| 1099 end | |
| 1100 | |
| 1101 function Button:ShowGrid( show ) | |
| 1102 if not InCombatLockdown() then | |
| 1103 local f = self.frame | |
| 1104 local count = f:GetAttribute("showgrid") | |
| 1105 if show then | |
| 1106 count = count + 1 | |
| 1107 else | |
| 1108 count = count - 1 | |
| 1109 end | |
| 1110 if count < 0 then | |
| 1111 count = 0 | |
| 1112 end | |
| 1113 f:SetAttribute("showgrid",count) | |
| 1114 | |
| 1115 if count >= 1 and not f:GetAttribute("statehidden") then | |
| 1116 if LBF then | |
| 1117 LBF:SetNormalVertexColor(self.frame, 1.0, 1.0, 1.0, 0.5) | |
| 1118 else | |
| 1119 self.frame:GetNormalTexture():SetVertexColor(1.0, 1.0, 1.0, 0.5); | |
| 1120 end | |
| 1121 f:Show() | |
| 1122 elseif count < 1 and not HasAction(self:GetActionID()) then | |
| 1123 f:Hide() | |
| 1124 end | |
| 1125 end | |
| 1126 end | |
| 1127 | |
| 1128 function Button:ShowActionIDLabel( show ) | |
| 1129 local f = self:GetFrame() | |
| 1130 if show then | |
| 1131 local id = self:GetActionID() | |
| 1132 if not f.actionIDLabel then | |
| 1133 local label = f:CreateFontString(nil,"OVERLAY","GameFontNormalLarge") | |
| 1134 label:SetAllPoints() | |
| 1135 label:SetJustifyH("CENTER") | |
| 1136 label:SetShadowColor(0,0,0,1) | |
| 1137 label:SetShadowOffset(2,-2) | |
| 1138 f.actionIDLabel = label -- store the label with the frame for recycling | |
| 1139 | |
| 1140 f:HookScript("OnAttributeChanged", | |
| 1141 function(frame, attr, value) | |
| 1142 if label:IsVisible() and attr:match("action") then | |
| 1143 label:SetText(tostring(frame.action)) | |
| 1144 end | |
| 1145 end) | |
| 1146 end | |
| 1147 f.actionIDLabel:SetText(tostring(id)) | |
| 1148 f.actionIDLabel:Show() | |
| 1149 elseif f.actionIDLabel then | |
| 1150 f.actionIDLabel:Hide() | |
| 1151 end | |
| 1152 end | |
| 1153 | |
| 1154 function Button:DisplayHotkey( key ) | |
| 1155 self.hotkey:SetText(key or "") | |
| 1156 end | |
| 1157 end |
