Thread Tools Display Modes
10-11-19, 08:24 PM   #1
illej
A Murloc Raider
Join Date: Oct 2019
Posts: 4
dynamic key bindings with SecureHandlers

Hey all,

I'm new here, and new to writing WoW addons and a bit at a loss as to how to progress with my mod.

To start with, the functionality I am trying to achieve is as follows:

The idea is that I want to be able to easily cast any Shaman totem with 3 key strokes.

1) Starting state: keys 1 through to 9 are bound as normally to actionbar 1
2) clicking '/click ElementBinder' in a macro rebinds keys 1 to 4 to represent the 4 totem elements (1: Earth, 2: Fire, 3: Water, 4: Air)
3) Now, pressing any of these overridden keys further overrides them to represent individual Totems to cast,(1: cast Earthbind Totem, 2: cast Strength of Earth, etc).
4) After any Totem is cast, clear all existing bindings: keys 1 through to 9 are bound as normally to actionbar 1

So for Example:
Pressing F > 1 > 2 casts Strength of Earth totem
Pressing F > 1 > 1 casts Earthbind Totem
Pressing F > 2 > 1 casts Searing Totem
Pressing F > 3 > 1 casts Healing Stream Totem
etc ..

So far I have been using SecureHandlers to enable this to work in combat - and it does! However, I have no way to clear the overridden bindings once a Totem is cast and I am looking for any help/suggestions to achieve this!

Here is some code snippets.

Macro to initiate the functionality:
Code:
/click ElementBinder
Lua code to handle the click:
Code:
local element_binder = CreateFrame("BUTTON", "ElementBinder", UIParent, "SecureHandlerClickTemplate");
element_binder:SetAttribute("_onclick", [=[
    if button == "LeftButton" then    
        self:SetBindingClick(true, "1", "EarthBtn")
        self:SetBindingClick(true, "2", "FireBtn")
        -- etc for water and air
    end
]=]);

local earth_btn = CreateFrame("BUTTON", "EarthBtn", element_binder, "SecureHandlerClickTemplate")
earth_btn:SetAttribute("_onclick", [=[
    if button == "LeftButton" then
        self:SetBindingSpell(true, "1", "Earthbind Totem")
        self:SetBindingSpell(true, "2", "Strength of Earth Totem")
        -- etc for the rest of the earth totems
    end
]=])
So some solutions I have (unsuccessfully) explored are:
  • trying to use :WrapScript on the 'EarthBtn' frame and call :ClearBindings() in the 'PostClick', but I have no idea really of what frames should be attached to or inherited from, and this never works
  • RegisterEvent('COMBAT_LOG_EVENT'), and parsing the combat log for 'SPELL_SUMMON' and then trying to call :ClearBindings() on one of the SecureHandler frames, but again I can't figure out the inheritance chain to enable this in the protected path.

Also, is there another way of initiating the original 'click' on the first handler other than through a macro?

Any ideas?

Thanks in advance!
  Reply With Quote
10-11-19, 10:21 PM   #2
Kanegasi
A Molten Giant
 
Kanegasi's Avatar
AddOn Author - Click to view addons
Join Date: Apr 2007
Posts: 666
There's a distinction with the terminology you're using. There's no such thing as "clearing" a binding you have set back to what it was. When you set a key to something, the previous setting is lost. Clearing a binding is to remove all actions from it. There is also no inherent default to any key, you either need to remember the default before changing a single binding or use LoadBindings(0) to reset ALL keys.

If your intention is to "reset" 1-4 back to the action buttons, just set them to the action buttons. SetBinding and BindingID are what you're looking for.

If I recall correctly, frame:ClearBindings() only removes the frame from any registered bindings, the actual binding is not changed.

Last edited by Kanegasi : 10-11-19 at 10:26 PM.
  Reply With Quote
10-11-19, 10:31 PM   #3
kurapica.igas
A Chromatic Dragonspawn
Join Date: Aug 2011
Posts: 152
It seems like a feature in my IGAS_UI/ActionBar, you can find an example at the end of the page. Well, maybe not, you require two level key binding.

Back to the question, I have an example for you

Lua Code:
  1. -- The button configs
  2. local Config = {
  3.     {
  4.         {
  5.             type = "macro",
  6.             macrotext = "/run print('1-1')"
  7.         },
  8.         {
  9.             type = "macro",
  10.             macrotext = "/run print('1-2')"
  11.         },
  12.     },
  13.     {
  14.         {
  15.             type = "macro",
  16.             macrotext = "/run print('2-1')"
  17.         },
  18.     },
  19.     {
  20.         {
  21.             type = "macro",
  22.             macrotext = "/run print('3-1')"
  23.         },
  24.     },
  25. }
  26.  
  27. -- A mananger frame to control all secure behaviors
  28. local _ManagerFrame = CreateFrame("Frame", "TwoLevelKeyManager", UIParent, "SecureHandlerStateTemplate")
  29.  
  30. _ManagerFrame.Execute = SecureHandlerExecute
  31. _ManagerFrame.WrapScript = function(self, frame, script, preBody, postBody) return SecureHandlerWrapScript(frame, script, self, preBody, postBody) end
  32. _ManagerFrame.SetFrameRef = SecureHandlerSetFrameRef
  33.  
  34. _ManagerFrame:Execute[[
  35.     Manager = self
  36.  
  37.     ToggleButtons = newtable()
  38.     ToggleState = newtable()
  39.  
  40.     -- secure code snippets to be run by manager
  41.     CloseFinalLevel = [==[
  42.         local index = ...
  43.         print("CloseFinalLevel", index)
  44.         if ToggleState[index] then
  45.             ToggleState[index] = false
  46.  
  47.             for i, btn in ipairs(ToggleButtons[index]) do
  48.                 print("Clear Bind", btn:GetName())
  49.                 btn:ClearBinding("" .. i)
  50.             end
  51.         end
  52.         print("Clear Bind", ToggleButtons[index][0]:GetName())
  53.         ToggleButtons[index][0]:ClearBinding("" .. index)
  54.     ]==]
  55.  
  56.     OpenFinalLevel = [==[
  57.         local index = ...
  58.         print("OpenFinalLevel", index)
  59.         if not ToggleState[index] then
  60.             ToggleState[index] = true
  61.  
  62.             for i, btn in ipairs(ToggleButtons[index]) do
  63.                 print("Bind", btn:GetName())
  64.                 btn:SetBindingClick(true, "" .. i, btn:GetName(), "LeftButton")
  65.             end
  66.         end
  67.     ]==]
  68.  
  69.     ToggleRoot = [==[
  70.         print("ToggleRoot", not ToggleState[0])
  71.         if ToggleState[0] then
  72.             -- Clear binding for all
  73.             ToggleState[0] = false
  74.  
  75.             for i, buttons in ipairs(ToggleButtons) do
  76.                 Manager:Run(CloseFinalLevel, i)
  77.             end
  78.         else
  79.             -- Bind key to first level
  80.             ToggleState[0] = true
  81.  
  82.             for i, buttons in ipairs(ToggleButtons) do
  83.                 print("Bind", buttons[0]:GetName())
  84.                 buttons[0]:SetBindingClick(true, "" .. i, buttons[0]:GetName(), "LeftButton")
  85.             end
  86.         end
  87.     ]==]
  88. ]]
  89.  
  90. -- Create buttons
  91. local rootbtn = CreateFrame("CheckButton", "RootToggle", UIParent, "SecureActionButtonTemplate")
  92.  
  93. -- Bind the root key
  94. SetOverrideBindingClick(rootbtn, true, "F", "RootToggle", "LeftButton")
  95.  
  96. -- Toggle the first level button's key binding
  97. _ManagerFrame:WrapScript(rootbtn, "OnClick", [[Manager:Run(ToggleRoot)]])
  98.  
  99. -- Generate the first and second level buttons and register them to the mananger
  100. for i, firstlvl in ipairs(Config) do
  101.     local btn = CreateFrame("CheckButton", "FirstLvlToggle" .. i, UIParent, "SecureActionButtonTemplate")
  102.     _ManagerFrame:SetFrameRef("FirstLvlToggle", btn)
  103.  
  104.     for j, nxtlvl in ipairs(firstlvl) do
  105.         local fbtn = CreateFrame("CheckButton", "NxtLvlToggle" .. i .. "_" .. j, UIParent, "SecureActionButtonTemplate")
  106.         _ManagerFrame:SetFrameRef("NxtLvlToggle" .. j, fbtn)
  107.  
  108.         for k, v in pairs(nxtlvl) do
  109.             fbtn:SetAttribute(k, v) -- Bind marco
  110.         end
  111.  
  112.         -- Clear all key bindings after click the second level button
  113.         _ManagerFrame:WrapScript(fbtn, "OnClick", [[return button, true]], [[Manager:Run(ToggleRoot)]])
  114.     end
  115.  
  116.     _ManagerFrame:Execute(string.format([[
  117.         local index, count = %d, %d
  118.         local buttons = newtable()
  119.         buttons[0] = Manager:GetFrameRef("FirstLvlToggle")
  120.         print("Register", index, buttons[0]:GetName())
  121.         for i = 1, count do
  122.             buttons[i] = Manager:GetFrameRef("NxtLvlToggle" .. i)
  123.             print("Register", buttons[i]:GetName())
  124.         end
  125.  
  126.         ToggleButtons[index] = buttons
  127.     ]], i, #firstlvl))
  128.  
  129.     -- Bind the key to the second level
  130.     _ManagerFrame:WrapScript(btn, "OnClick", string.format([[Manager:Run(OpenFinalLevel, %d)]], i))
  131. end

Run it with WowLua or my Cube or whatever in the game, then press F-1-1, F-1-2, F-2-1 to see the result, the log should show how the code works.

I have no high level shaman, so I use other macro for examples.
  Reply With Quote
10-14-19, 04:55 PM   #4
MooreaTv
An Aku'mai Servant
AddOn Author - Click to view addons
Join Date: May 2019
Posts: 38
if you use SetOverrideBindingClick and ClearOverrideBindings you'll get your temporary bindings

example of use:

https://github.com/mooreatv/AuctionD....lua#L145-L151
  Reply With Quote
10-20-19, 03:15 AM   #5
illej
A Murloc Raider
Join Date: Oct 2019
Posts: 4
Originally Posted by kurapica.igas View Post
It seems like a feature in my IGAS_UI/ActionBar, you can find an example at the end of the page. Well, maybe not, you require two level key binding.

Back to the question, I have an example for you

Lua Code:
  1. -- The button configs
  2. local Config = {
  3.     {
  4.         {
  5.             type = "macro",
  6.             macrotext = "/run print('1-1')"
  7.         },
  8.         {
  9.             type = "macro",
  10.             macrotext = "/run print('1-2')"
  11.         },
  12.     },
  13.     {
  14.         {
  15.             type = "macro",
  16.             macrotext = "/run print('2-1')"
  17.         },
  18.     },
  19.     {
  20.         {
  21.             type = "macro",
  22.             macrotext = "/run print('3-1')"
  23.         },
  24.     },
  25. }
  26.  
  27. -- A mananger frame to control all secure behaviors
  28. local _ManagerFrame = CreateFrame("Frame", "TwoLevelKeyManager", UIParent, "SecureHandlerStateTemplate")
  29.  
  30. _ManagerFrame.Execute = SecureHandlerExecute
  31. _ManagerFrame.WrapScript = function(self, frame, script, preBody, postBody) return SecureHandlerWrapScript(frame, script, self, preBody, postBody) end
  32. _ManagerFrame.SetFrameRef = SecureHandlerSetFrameRef
  33.  
  34. _ManagerFrame:Execute[[
  35.     Manager = self
  36.  
  37.     ToggleButtons = newtable()
  38.     ToggleState = newtable()
  39.  
  40.     -- secure code snippets to be run by manager
  41.     CloseFinalLevel = [==[
  42.         local index = ...
  43.         print("CloseFinalLevel", index)
  44.         if ToggleState[index] then
  45.             ToggleState[index] = false
  46.  
  47.             for i, btn in ipairs(ToggleButtons[index]) do
  48.                 print("Clear Bind", btn:GetName())
  49.                 btn:ClearBinding("" .. i)
  50.             end
  51.         end
  52.         print("Clear Bind", ToggleButtons[index][0]:GetName())
  53.         ToggleButtons[index][0]:ClearBinding("" .. index)
  54.     ]==]
  55.  
  56.     OpenFinalLevel = [==[
  57.         local index = ...
  58.         print("OpenFinalLevel", index)
  59.         if not ToggleState[index] then
  60.             ToggleState[index] = true
  61.  
  62.             for i, btn in ipairs(ToggleButtons[index]) do
  63.                 print("Bind", btn:GetName())
  64.                 btn:SetBindingClick(true, "" .. i, btn:GetName(), "LeftButton")
  65.             end
  66.         end
  67.     ]==]
  68.  
  69.     ToggleRoot = [==[
  70.         print("ToggleRoot", not ToggleState[0])
  71.         if ToggleState[0] then
  72.             -- Clear binding for all
  73.             ToggleState[0] = false
  74.  
  75.             for i, buttons in ipairs(ToggleButtons) do
  76.                 Manager:Run(CloseFinalLevel, i)
  77.             end
  78.         else
  79.             -- Bind key to first level
  80.             ToggleState[0] = true
  81.  
  82.             for i, buttons in ipairs(ToggleButtons) do
  83.                 print("Bind", buttons[0]:GetName())
  84.                 buttons[0]:SetBindingClick(true, "" .. i, buttons[0]:GetName(), "LeftButton")
  85.             end
  86.         end
  87.     ]==]
  88. ]]
  89.  
  90. -- Create buttons
  91. local rootbtn = CreateFrame("CheckButton", "RootToggle", UIParent, "SecureActionButtonTemplate")
  92.  
  93. -- Bind the root key
  94. SetOverrideBindingClick(rootbtn, true, "F", "RootToggle", "LeftButton")
  95.  
  96. -- Toggle the first level button's key binding
  97. _ManagerFrame:WrapScript(rootbtn, "OnClick", [[Manager:Run(ToggleRoot)]])
  98.  
  99. -- Generate the first and second level buttons and register them to the mananger
  100. for i, firstlvl in ipairs(Config) do
  101.     local btn = CreateFrame("CheckButton", "FirstLvlToggle" .. i, UIParent, "SecureActionButtonTemplate")
  102.     _ManagerFrame:SetFrameRef("FirstLvlToggle", btn)
  103.  
  104.     for j, nxtlvl in ipairs(firstlvl) do
  105.         local fbtn = CreateFrame("CheckButton", "NxtLvlToggle" .. i .. "_" .. j, UIParent, "SecureActionButtonTemplate")
  106.         _ManagerFrame:SetFrameRef("NxtLvlToggle" .. j, fbtn)
  107.  
  108.         for k, v in pairs(nxtlvl) do
  109.             fbtn:SetAttribute(k, v) -- Bind marco
  110.         end
  111.  
  112.         -- Clear all key bindings after click the second level button
  113.         _ManagerFrame:WrapScript(fbtn, "OnClick", [[return button, true]], [[Manager:Run(ToggleRoot)]])
  114.     end
  115.  
  116.     _ManagerFrame:Execute(string.format([[
  117.         local index, count = %d, %d
  118.         local buttons = newtable()
  119.         buttons[0] = Manager:GetFrameRef("FirstLvlToggle")
  120.         print("Register", index, buttons[0]:GetName())
  121.         for i = 1, count do
  122.             buttons[i] = Manager:GetFrameRef("NxtLvlToggle" .. i)
  123.             print("Register", buttons[i]:GetName())
  124.         end
  125.  
  126.         ToggleButtons[index] = buttons
  127.     ]], i, #firstlvl))
  128.  
  129.     -- Bind the key to the second level
  130.     _ManagerFrame:WrapScript(btn, "OnClick", string.format([[Manager:Run(OpenFinalLevel, %d)]], i))
  131. end

Run it with WowLua or my Cube or whatever in the game, then press F-1-1, F-1-2, F-2-1 to see the result, the log should show how the code works.

I have no high level shaman, so I use other macro for examples.
Oh wow that is exactly what I was looking for! Thank you so much! There is no way I would have figured that out myself! How did you learn about this stuff? Any particular resources? Or did you just work through the Blizz UI source?
  Reply With Quote
10-20-19, 03:16 AM   #6
illej
A Murloc Raider
Join Date: Oct 2019
Posts: 4
Originally Posted by MooreaTv View Post
if you use SetOverrideBindingClick and ClearOverrideBindings you'll get your temporary bindings

example of use:

https://github.com/mooreatv/AuctionD....lua#L145-L151
Thanks! That was what I was using initially but it wouldn't work in combat Hence delving into SecureHandler-Hell
  Reply With Quote
10-20-19, 03:20 AM   #7
illej
A Murloc Raider
Join Date: Oct 2019
Posts: 4
Originally Posted by Kanegasi View Post
There's a distinction with the terminology you're using. There's no such thing as "clearing" a binding you have set back to what it was. When you set a key to something, the previous setting is lost. Clearing a binding is to remove all actions from it. There is also no inherent default to any key, you either need to remember the default before changing a single binding or use LoadBindings(0) to reset ALL keys.

If your intention is to "reset" 1-4 back to the action buttons, just set them to the action buttons. SetBinding and BindingID are what you're looking for.

If I recall correctly, frame:ClearBindings() only removes the frame from any registered bindings, the actual binding is not changed.
Sorry I mustn't have been clear in my initial post, I was referring to SetOverrideBinding and ClearOverrideBinding. From what I have read the protected path uses this functionality but it is called SetBinding / ClearBinding even though under the hood it is calling SetOverrideBinding / ClearOverrideBinding

Thanks for your reply anyway!
  Reply With Quote
10-20-19, 06:03 AM   #8
kurapica.igas
A Chromatic Dragonspawn
Join Date: Aug 2011
Posts: 152
Originally Posted by illej View Post
Oh wow that is exactly what I was looking for! Thank you so much! There is no way I would have figured that out myself! How did you learn about this stuff? Any particular resources? Or did you just work through the Blizz UI source?
Several years ago, I study the whole restrict code for three times to figure out all usages, here is the list and read order:

1. RestrictedInfrastructure.lua: Wrap the normal frame to secure handler, and some api for secure using.

2. RestrictedEnvironment.lua: the restricted environment to execute the secure snippets and some api.

3. RestrictedFrames.lua: the secure handler methods that can be used in secure snippets.

4. RestrictedExecution.lua: the execution of the secure snippets, the core part is every secure handler(the wrapper of the frame) has a standalone environment, that's why I always create a manager to process the secure snippets, so all processed in one environment.

5. SecureHandlers.lua: All for secure handler's events, the best in it is the SecureHandlerWrapScript, so I can use manager to wrap all secure frame's actions.

6. SecureTemplates.lua: the template for secure frames, the common is the SecureActionButtonTemplate, used for action button.

7. SecureGroupHeaders.lua: for the raid panel, it's a hard topic, I use it for my secure panel lib, so they can refresh well in the combat no matter player join or leave.

8. SecureStateDriver.lua: It's the best system event trigger(although only support marco conditions) to force the secure frames refreshing during the combat.

I have practiced those features in my own raid panel, container, action bar addon(no dependencies), some features are never used in other addons, the secure system is a gold mine but hard for digging.

If your are interesting, I have some post in this forum talked about the usages of the secure frames, you may learn some tricks from them.
  Reply With Quote

WoWInterface » Developer Discussions » Lua/XML Help » dynamic key bindings with SecureHandlers

Thread Tools
Display Modes

Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

vB code is On
Smilies are On
[IMG] code is On
HTML code is Off