changeset 72:aa88aed52124

Fixed bugs with state keybinds. Simplified state driver API
author Flick <flickerstreak@gmail.com>
date Thu, 05 Jun 2008 18:34:36 +0000
parents 3d2cef5dc459
children dd01feae0d89
files Bar.lua State.lua locale/enUS.lua
diffstat 3 files changed, 103 insertions(+), 105 deletions(-) [+]
line wrap: on
line diff
--- a/Bar.lua	Wed Jun 04 21:46:51 2008 +0000
+++ b/Bar.lua	Thu Jun 05 18:34:36 2008 +0000
@@ -51,6 +51,9 @@
   f:SetParent(UIParent)
   f:ClearAllPoints()
   ReAction.UnregisterAllCallbacks(self)
+  if self.statedriver then
+    UnregisterStateDriver(f, "reaction")
+  end
   self.labelString = nil
   self.controlFrame = nil
   self.frame = nil
@@ -176,6 +179,49 @@
   return self.config.nPages or 1
 end
 
+  --
+  -- 'rule' is a rule-string to pass to RegisterStateDriver
+  -- 'states' is a { ["statename"] = <don't care> } table of all state names
+  -- 'keybinds' is a { ["statename"] = keybind } table of all keybound states
+  --
+function Bar:SetStateDriver( rule, states, keybinds )
+  local f = self.frame
+  local kbprefix = ""
+  do
+    local tmp = { }
+    for s, k in pairs(keybinds) do
+      if k and #k > 0 then -- filter out false table entries
+        -- if in a keybound state, set the stack to the new state but stay in the keybound state.
+        -- use $s as a placeholder for the current state, it will be gsub()'d in later
+        table.insert(tmp,("%s:$s set() %s"):format(s,s))
+      end
+    end
+    table.insert(tmp,kbprefix)  -- to get a trailing ';' if the table is not empty
+    kbprefix = table.concat(tmp,";")
+  end
+  for state in pairs(states) do
+    -- For all states: if in a keybound state, stay there (with stack manipulation, see above).
+    -- Otherwise, go to the state
+    f:SetAttribute(("statemap-reaction-%s"):format(state),("%s%s"):format(kbprefix:gsub("%$s",state),state))
+
+    local binding = keybinds[state]
+    self:SetStateKeybind(binding, state) -- set the binding even if nil, to clear it unconditionally
+    if binding then
+      -- for key bindings, use the state-stack to toggle between the last state and the keybound state
+      -- use a different 'virtual state' passed to attribute 'reaction-state' for key bindings, "<state>_binding"
+      f:SetAttribute(("statemap-reaction-%s_binding"):format(state), ("%s:pop();*:set(%s)"):format(state,state))
+    end
+  end
+
+  if rule and #rule > 0 then
+    self.stateDriver = true
+    RegisterStateDriver(f, "reaction", rule)
+  elseif self.statedriver then
+    self.statedriver = false
+    UnregisterStateDriver(f, "reaction")
+  end
+end
+
 function Bar:SetHideStates(s)
   for f in pairs(self.buttons) do
     if f:GetParent() == self.frame then
@@ -186,36 +232,36 @@
 end
 
 function Bar:SetStateKeybind(key, state, defaultstate)
-  -- set a keybind to toggle transitioning unconditionally to a state
-  -- use a tiny offscreen button to get around making the bar itself a clickable button
+  -- Lazily create a tiny offscreen button which sends "<state>_binding" values to the
+  -- bar frame's state-reaction attribute, by using an override binding to generate a 
+  -- click on the button with a virtual mouse button "state". 
+  -- This gets around making the bar itself a clickable button, which is not desirable
   local f = self.statebuttonframe
-  local off = ("%s_off"):format(state)
   if key then
     if not f then
-      f = CreateFrame("Button",self:GetName().."_statebutton",UIParent,"SecureActionButtonTemplate")
+      f = CreateFrame("Button",self:GetName().."_statebutton",self.frame,"SecureActionButtonTemplate")
       f:SetPoint("BOTTOMRIGHT",UIParent,"TOPLEFT")
       f:SetWidth(1)
       f:SetHeight(1)
-      f:SetAttribute("attribute-name", "state")
-      f:SetAttribute("attribute-frame",self.frame)
-      f:SetAttribute("stateheader",self.frame)
+      f:SetAttribute("type*","attribute")
+      f:SetAttribute("attribute-name*","state-reaction")
+      f:SetAttribute("attribute-frame*",self.frame)
       f:Show()
+      f.bindings = { }
       self.statebuttonframe = f
     end
-    -- map two virtual buttons to toggle between the state and the default
-    f:SetAttribute(("statebutton-%s"):format(state),("%s:%s;%s"):format(state,off,state))
-    f:SetAttribute(("type-%s"):format(state),"attribute")
-    f:SetAttribute(("type-%s"):format(off),"attribute")
-    f:SetAttribute(("attribute-value-%s"):format(state), state)
-    f:SetAttribute(("attribute-value-%s"):format(off), defaultstate)
-    SetBindingClick(key, f:GetName(), state)
+    f:SetAttribute(("attribute-value-%s"):format(state),("%s_binding"):format(state))
+      -- clear the old binding, if any, for this state
+    if f.bindings[state] then
+      SetOverrideBinding(self.frame, false, f.bindings[state], nil)
+    end
+    SetOverrideBindingClick(self.frame, false, key, f:GetName(), state) -- the state name is used as the virtual button
+    f.bindings[state] = key
   elseif f then
-    f:SetAttribute(("type-%s"):format(state),ATTRIBUTE_NOOP)
-    f:SetAttribute(("type-%s"):format(off),ATTRIBUTE_NOOP)
-    local action = ("CLICK %s:%s"):format(f:GetName(),state)
-    key = GetBindingKey(action)
+    key = f.bindings[state]
     if key then
-      SetBinding(key,nil)
+      SetOverrideBinding(self.frame, false, key, nil)
+      f.bindings[state] = nil
     end
   end
 end
@@ -227,7 +273,10 @@
     table.insert(tmp, ("%s:page%d"):format(s,p))
   end
   local spec = table.concat(tmp,";")
-  f:SetAttribute("statebutton",spec)
+  local current = f:GetAttribute("statebutton")
+  if spec ~= f:GetAttribute("statebutton") then
+    f:SetAttribute("statebutton", spec)
+  end
   SecureStateHeader_Refresh(f)
 end
 
--- a/State.lua	Wed Jun 04 21:46:51 2008 +0000
+++ b/State.lua	Thu Jun 05 18:34:36 2008 +0000
@@ -106,87 +106,58 @@
     return r
   end
 
-  local function BuildRuleString(states)
-    local s = ""
+  local function BuildRules(states)
+    local rules = { }
+    local keybinds = { }
     local default
-    local sorted = fieldsort(states, "rule", "order")
-    for idx, name in ipairs(sorted) do
-      local state = states[name]
-      local semi = #s > 0 and "; " or ""
-      local mode = tfetch(state,"rule","type")
-      if mode == "default" then
-        default = name
-      elseif mode == "custom" then
-        if state.rule.custom then
+    local fmt = "%s %s"
+    for idx, state in ipairs(fieldsort(states, "rule", "order")) do
+      local c = states[state].rule
+      local type = tfetch(c,"type")
+      if type == "default" then
+        default = default or state
+      elseif type == "keybind" then
+        keybinds[state] = c.keybind or false
+      elseif type == "custom" then
+        if c.custom then
           -- strip out all spaces from the custom rule
-          s = ("%s%s%s %s"):format(s, semi, state.rule.custom:gsub("%s",""), name)
+          table.insert(rules, fmt:format(c.custom:gsub("%s",""), state))
         end
-      elseif mode == "any" then
-        if state.rule.values then
-          local clause = ""
-          for key, value in pairs(state.rule.values) do
-            clause = ("%s[%s]"):format(clause,ruleformats[key])
+      elseif type == "any" then
+        if c.values then
+          local clauses = { }
+          for key, value in pairs(c.values) do
+            table.insert(clauses, ("[%s]"):format(ruleformats[key]))
           end
-          if #clause > 0 then
-            s = ("%s%s%s %s"):format(s, semi, clause, name)
+          if #clauses > 0 then
+            table.insert(rules, fmt:format(table.concat(clauses), state))
           end
         end
-      elseif mode == "all" then
-        if state.rule.values then
-          local clause = ""
-          for key, value in pairs(state.rule.values) do
-            clause = ("%s%s%s"):format(clause,#clause > 0 and "," or "", ruleformats[key])
+      elseif type == "all" then
+        if c.values then
+          local clauses = { }
+          for key, value in pairs(c.values) do
+            table.insert(clauses, ruleformats[key])
           end
-          if #clause > 0 then
-            s = ("%s%s[%s] %s"):format(s, semi, clause, name)
+          if #clauses > 0 then
+            table.insert(rules, fmt:format(("[%s]"):format(table.concat(clauses, ",")), state))
           end
         end
       end
     end
     if default then
-      s = ("%s%s%s"):format(s, #s > 0 and "; " or "", default)
+      table.insert(rules, default)
     end
-    return s, default
+    return rules, keybinds
   end
 
-  local drivers = setmetatable({},{__mode="k"})
   local propertyFuncs = { }
 
   function ApplyStates( bar )
     local states = tfetch(module.db.profile.bars, bar:GetName(), "states")
     if states then
-      local frame = bar:GetFrame()
-      local string, default = BuildRuleString(states)
-      if string and #string > 0 then
-        drivers[bar] = true
-        -- register a map for each "statemap-reaction-XXX" to set 'state' to 'XXX'
-        -- UNLESS we're in a keybound state AND there's a default state, in which case
-        -- all keybound states go back to themselves.
-        local keybindprefix
-        if default then
-          local tmp = { }
-          for state, config in pairs(states) do
-            if tfetch(config, "rule", "type") == "keybind" then
-              bar:SetStateKeybind(tfetch(config,"rule","keybind"), state, tfetch(config,"rule","keybindreturn") or default or 0)
-              table.insert(tmp, ("%s:%s"):format(state,state))
-            end
-          end
-          if #tmp > 0 then
-            table.insert(tmp,"") -- to get a final ';'
-          end
-          keybindprefix = table.concat(tmp,";")
-        end
-        for state in pairs(states) do
-          frame:SetAttribute(("statemap-reaction-%s"):format(state), ("%s%s"):format(keybindprefix or "",state))
-        end
-        -- register a handler to set the value of attribute "state-reaction" 
-        -- in response to events as per the rule string
-        RegisterStateDriver(frame, "reaction", string)
-        SecureStateHeader_Refresh(frame)
-      elseif drivers[bar] then
-        UnregisterStateDriver(frame, "reaction")
-        drivers[bar] = nil
-      end
+      local rules, keybinds = BuildRules(states)
+      bar:SetStateDriver(table.concat(rules,";"), states, keybinds)
       for k, f in pairs(propertyFuncs) do
         f(bar, states)
       end
@@ -816,7 +787,7 @@
             type = "group",
             args = {
               desc = {
-                name = L["Invoking a state keybind overrides all other transition rules. Toggle the keybind again to remove the override and return to the specified toggle-off state."],
+                name = L["Invoking a state keybind toggles an override of all other transition rules."],
                 order = 1,
                 type = "description",
               },
@@ -834,26 +805,6 @@
                        end,
                 get  = function() return getrule("keybind") end,
               },
-              default = {
-                name = L["Toggle Off State"],
-                desc = L["Select a state to return to when the keybind override is toggled off"],
-                order = 3,
-                type = "select",
-                values = function()
-                           local t = { }
-                           for k in pairs(states) do
-                             if k ~= opts.name then
-                               t[k] = k
-                             end
-                           end
-                           return t
-                         end,
-                set = function(info, value)
-                        setrule("keybindreturn",value)
-                        update()
-                      end,
-                get = function() return getrule("keybindreturn") end,
-              },
             },
           },
         },
--- a/locale/enUS.lua	Wed Jun 04 21:46:51 2008 +0000
+++ b/locale/enUS.lua	Thu Jun 05 18:34:36 2008 +0000
@@ -99,11 +99,9 @@
 "Syntax like macro rules: see preset rules for examples",
 "Invalid custom rule '%s': each clause must appear within [brackets]",
 "Keybinding",
-"Invoking a state keybind overrides all other transition rules. Toggle the keybind again to remove the override and return to the specified toggle-off state.",
+"Invoking a state keybind toggles an override of all other transition rules.",
 "State Hotkey",
 "Define an override toggle keybind",
-"Toggle Off State",
-"Select a state to return to when the keybind override is toggled off",
 "Dynamic State",
 "States are evaluated in the order they are listed",
 "New State...",