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