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 |