changeset 116:fb48811a8736

Convert to standard keybindings
author Flick <flickerstreak@gmail.com>
date Fri, 23 Jan 2009 23:44:55 +0000
parents af0172ed7037
children e2257bf1d717
files ReAction.lua locale/enUS.lua modules/Action.lua modules/PetAction.lua
diffstat 4 files changed, 421 insertions(+), 546 deletions(-) [+]
line wrap: on
line diff
--- a/ReAction.lua	Fri Jan 23 23:40:13 2009 +0000
+++ b/ReAction.lua	Fri Jan 23 23:44:55 2009 +0000
@@ -57,10 +57,7 @@
 
 ------ PRIVATE ------
 local weak = {__mode="k"}
-local private = {
-  allKB = setmetatable({}, weak),
-  kbHooked = setmetatable({}, weak),
-}
+local private = { }
 local bars = {}
 local defaultBarConfig = {}
 local barOptionGenerators = { }
@@ -86,9 +83,19 @@
           handler  = ReAction,
           get      = "GetConfigMode",
           set      = function(info, value) ReAction:SetConfigMode(value) end,
+          width    = "double",
           disabled = InCombatLockdown,
           order    = 1
         },
+        skipProfileWarning = {
+          type     = "toggle",
+          name     = L["Skip profile keybind warning"],
+          desc     = L["Don't show a warning about updating keybinds when switching profiles"],
+          get      = function() return ReAction.db.global.skipKeybindWarning end,
+          set      = function(info, value) ReAction.db.global.skipKeybindWarning = value end,
+          width    = "double",
+          order    = 2,
+        },
       },
       plugins = { },
       order = 2,
@@ -107,19 +114,24 @@
 }
 ReAction.options = options
 
-local SelectBar, DestroyBar, InitializeBars, TearDownBars, DeepCopy, CallModuleMethod, SlashHandler, KBAttach
+  -- insert an entry into the WoW static popup dialogs list
+StaticPopupDialogs["REACTION_KB_WARN"] = {
+	text = L["ReAction profile changed: check your keybinds, they may need to be updated."],
+	button1 = L["OK"],
+	hideOnEscape = true,
+  enterClicksFirstButton = true,
+	timeout = 0,
+  showAlert = true,
+  whileDead = true,
+}
+
+local SelectBar, DestroyBar, InitializeBars, TearDownBars, DeepCopy, CallModuleMethod, SlashHandler
 do
   local pcall = pcall
   local geterrorhandler = geterrorhandler
   local self = ReAction
   local inited = false
 
-  local function kb_onEnter( self )
-    if ReAction:GetKeybindMode() then
-      KB:Set(self)
-    end
-  end
-
   function SelectBar(x)
     local bar, name
     if type(x) == "string" then
@@ -215,15 +227,6 @@
     end
   end
 
-  function KBAttach( frame )
-    if not private.kbHooked[frame] then
-      -- avoid taint, particularly with SecureAnchorEnterTemplate
-      -- don't hook scripts multiple times, there isn't any unhook!
-      frame:HookScript("OnEnter",kb_onEnter)
-      private.kbHooked[frame] = true
-    end
-  end
-
 end
 
 
@@ -263,6 +266,7 @@
 function ReAction:OnProfileChanged()
   TearDownBars()
   InitializeBars()
+  self:PopKeybindWarning()
 end
 
 function ReAction:PLAYER_REGEN_DISABLED()
@@ -501,9 +505,6 @@
 function ReAction:SetKeybindMode( mode )
   if mode ~= private.kbMode then
     if mode then
-      for f in pairs(private.allKB) do
-        KBAttach(f)
-      end
       KB:Activate()
     else
       KB:Deactivate()
@@ -516,27 +517,8 @@
   return private.kbMode
 end
 
-function ReAction:RegisterKeybindFrame( f )
-  private.allKB[f] = true
-  if private.kbMode then
-    KBAttach(f)
+function ReAction:PopKeybindWarning()
+  if not self.db.global.skipKeybindWarning then
+    StaticPopup_Show("REACTION_KB_WARN")
   end
 end
-
-function ReAction:FreeOverrideHotkey( key )
-  for f in pairs(private.allKB) do
-    if f.GetBindings then
-      for i = 1, select('#', f:GetBindings()) do
-        if select(i, f:GetBindings()) == key then
-          if f.FreeKey then
-            return f:FreeKey(key)
-          else
-            local action = f.GetActionName and f:GetActionName() or f:GetName()
-            SetOverrideBinding(f, false, key, nil)
-            return action
-          end
-        end
-      end
-    end
-  end
-end
--- a/locale/enUS.lua	Fri Jan 23 23:40:13 2009 +0000
+++ b/locale/enUS.lua	Fri Jan 23 23:44:55 2009 +0000
@@ -16,9 +16,13 @@
 "Global configuration settings",
 "Unlock Bars",
 "Unlock bars for dragging and resizing with the mouse",
+"Skip profile keybind warning",
+"Don't show a warning about updating keybinds when switching profiles",
 "Module Settings",
 "Configuration settings for each module",
 "Default",
+"ReAction profile changed: check your keybinds, they may need to be updated.",
+"OK",
 
 -- Overlay.lua
 "Hold Shift",
@@ -127,11 +131,11 @@
 "for keybind mode",
 
 
--- modules/ReAction_HideBlizzard
+-- HideBlizzard
 "Hide Blizzard Action Bars",
 "Hide the default main bar and extra action bars",
 
--- modules/ReAction_Action
+-- Action
 "Action Bar",
 "Action Bars",
 "Hide Empty Buttons",
@@ -161,16 +165,11 @@
 "Show Page #",
 "Action Buttons",
 
--- modules/ReAction_PetAction
+-- PetAction
 "Pet Action Bar",
 "Pet Buttons",
 
--- modules/ReAction_PossessBar
-"Possess Bar",
-"Hide Empty Possess Bar Buttons",
-"Possess Buttons",
-
--- modules/ReAction_ConfigUI
+-- ConfigUI
 "Center",
 "Left",
 "Right",
--- a/modules/Action.lua	Fri Jan 23 23:40:13 2009 +0000
+++ b/modules/Action.lua	Fri Jan 23 23:44:55 2009 +0000
@@ -18,6 +18,8 @@
 
 ReAction:UpdateRevision("$Revision$")
 
+local weak = { __mode="k" }
+
 -- libraries
 local KB = LibStub("LibKeyBound-1.0")
 local LBF -- initialized later
@@ -267,7 +269,6 @@
     },
   }
 
-  local weak  = { __mode="k" }
   local meta = { __index = Handle }
 
   function Handle:New( bar, config )
@@ -365,15 +366,7 @@
 
   function Handle:SetKeybindMode(mode)
     for _, b in pairs(self.btns) do
-      if mode then
-        -- set the border for all buttons to the keybind-enable color
-      	b.border:SetVertexColor(KB:GetColorKeyBoundMode())
-        b.border:Show()
-      elseif IsEquippedAction(b:GetActionID()) then
-        b.border:SetVertexColor(0, 1.0, 0, 0.35) -- from ActionButton.lua
-      else
-        b.border:Hide()
-      end
+      b:SetKeybindMode(mode)
     end
   end
 
@@ -763,398 +756,368 @@
 end
 
 ------ Button class ------
+local frameRecycler = { }
+local trash = CreateFrame("Frame")
+local OnUpdate, KBAttach, GetHotkey
+do
+  local ATTACK_BUTTON_FLASH_TIME = ATTACK_BUTTON_FLASH_TIME
+  local IsActionInRange = IsActionInRange
 
-do
-  local frameRecycler = { }
-  local trash = CreateFrame("Frame")
-  local OnUpdate, KBAttach, GetActionName, GetHotkey, SetKey, FreeKey, ClearBindings, GetBindings
-  do
-    local ATTACK_BUTTON_FLASH_TIME = ATTACK_BUTTON_FLASH_TIME
-    local IsActionInRange = IsActionInRange
+  function OnUpdate(frame, elapsed)
+    -- note: This function taints frame.flashtime and frame.rangeTimer. Both of these
+    --       are only read by ActionButton_OnUpdate (which this function replaces). In
+    --       all other places they're just written, so it doesn't taint any secure code.
+    if frame.flashing == 1 then
+      frame.flashtime = frame.flashtime - elapsed
+      if frame.flashtime <= 0 then
+        local overtime = -frame.flashtime
+        if overtime >= ATTACK_BUTTON_FLASH_TIME then
+          overtime = 0
+        end
+        frame.flashtime = ATTACK_BUTTON_FLASH_TIME - overtime
 
-    local buttonLookup = setmetatable({},{__mode="kv"})
-
-    function OnUpdate(frame, elapsed)
-      -- note: This function taints frame.flashtime and frame.rangeTimer. Both of these
-      --       are only read by ActionButton_OnUpdate (which this function replaces). In
-      --       all other places they're just written, so it doesn't taint any secure code.
-      if frame.flashing == 1 then
-        frame.flashtime = frame.flashtime - elapsed
-        if frame.flashtime <= 0 then
-          local overtime = -frame.flashtime
-          if overtime >= ATTACK_BUTTON_FLASH_TIME then
-            overtime = 0
-          end
-          frame.flashtime = ATTACK_BUTTON_FLASH_TIME - overtime
-
-          local flashTexture = frame.flash
-          if flashTexture:IsShown() then
-            flashTexture:Hide()
-          else
-            flashTexture:Show()
-          end
-        end
-      end
-      
-      if frame.rangeTimer then
-        frame.rangeTimer = frame.rangeTimer - elapsed;
-
-        if frame.rangeTimer <= 0 then
-          if IsActionInRange(frame.action) == 0 then
-            frame.icon:SetVertexColor(1.0,0.1,0.1)
-          else
-            ActionButton_UpdateUsable(frame)
-          end
-          frame.rangeTimer = 0.1
+        local flashTexture = frame.flash
+        if flashTexture:IsShown() then
+          flashTexture:Hide()
+        else
+          flashTexture:Show()
         end
       end
     end
+    
+    if frame.rangeTimer then
+      frame.rangeTimer = frame.rangeTimer - elapsed;
 
-    -- Use KeyBound-1.0 for binding, but use Override bindings instead of
-    -- regular bindings to support multiple profile use. This is a little
-    -- weird with the KeyBound dialog box (which has per-char selector as well
-    -- as an OK/Cancel box) but it's the least amount of effort to implement.
-    function GetActionName(f)
-      local b = buttonLookup[f]
-      if b then
-        return format("%s:%s", b.bar:GetName(), b.idx)
-      end
-    end
-
-    function GetHotkey(f)
-      local b = buttonLookup[f]
-      if b then
-        return KB:ToShortKey(b:GetConfig().hotkey)
-      end
-    end
-
-    function SetKey(f, key)
-      local b = buttonLookup[f]
-      if b then
-        local c = b:GetConfig()
-        if c.hotkey then
-          SetOverrideBinding(f, false, c.hotkey, nil)
+      if frame.rangeTimer <= 0 then
+        if IsActionInRange(frame.action) == 0 then
+          frame.icon:SetVertexColor(1.0,0.1,0.1)
+        else
+          ActionButton_UpdateUsable(frame)
         end
-        if key then
-          SetOverrideBindingClick(f, false, key, f:GetName(), nil)
-        end
-        c.hotkey = key
-        b:DisplayHotkey(GetHotkey(f))
-      end
-    end
-
-    function FreeKey(f, key)
-      local b = buttonLookup[f]
-      if b then
-        local c = b:GetConfig()
-        if c.hotkey == key then
-          local action = f:GetActionName()
-          SetOverrideBinding(f, false, c.hotkey, nil)
-          c.hotkey = nil
-          b:DisplayHotkey(nil)
-          return action
-        end
-      end
-      return ReAction:FreeOverrideHotkey(key)
-    end
-
-    function ClearBindings(f)
-      SetKey(f, nil)
-    end
-
-    function GetBindings(f)
-      local b = buttonLookup[f]
-      if b then
-        return b:GetConfig().hotkey
-      end
-    end
-
-    function KBAttach( button )
-      local f = button:GetFrame()
-      f.GetActionName = GetActionName
-      f.GetHotkey     = GetHotkey
-      f.SetKey        = SetKey
-      f.FreeKey       = FreeKey
-      f.ClearBindings = ClearBindings
-      f.GetBindings   = GetBindings
-      buttonLookup[f] = button
-      f:SetKey(button:GetConfig().hotkey)
-      ReAction:RegisterKeybindFrame(f)
-      if ReAction:GetKeybindMode() then
-      	button.border:SetVertexColor(KB:GetColorKeyBoundMode())
-        button.border:Show()
+        frame.rangeTimer = 0.1
       end
     end
   end
 
-  local meta = {__index = Button}
-
-  function Button:New( handle, idx, config, barConfig )
-    local bar = handle.bar
-
-    -- create new self
-    self = setmetatable( 
-      { 
-        bar = bar,
-        idx = idx,
-        config = config,
-        barConfig = barConfig,
-      }, meta )
-
-    local name = config.name or ("ReAction_%s_%s_%d"):format(bar:GetName(),moduleID,idx)
-    self.name = name
-    config.name = name
-    local lastButton = handle:GetLastButton()
-    config.actionID = IDAlloc:Acquire(config.actionID, lastButton and lastButton.config.actionID) -- gets a free one if none configured
-    self.nPages = 1
-    
-    -- have to recycle frames with the same name: CreateFrame() doesn't overwrite
-    -- existing globals. Can't set to nil in the global because it's then tainted.
-    local parent = bar:GetFrame()
-    local f = frameRecycler[name]
-    if f then
-      f:SetParent(parent)
-    else
-      f = CreateFrame("CheckButton", name, parent, "ActionBarButtonTemplate")
-      -- ditch the old hotkey text because it's tied in ActionButton_Update() to the
-      -- standard binding. We use override bindings.
-      local hotkey = _G[name.."HotKey"]
-      hotkey:SetParent(trash)
-      hotkey = f:CreateFontString(nil, "ARTWORK", "NumberFontNormalSmallGray")
-      hotkey:SetWidth(36)
-      hotkey:SetHeight(18)
-      hotkey:SetJustifyH("RIGHT")
-      hotkey:SetJustifyV("TOP")
-      hotkey:SetPoint("TOPLEFT",f,"TOPLEFT",-2,-2)
-      f.hotkey = hotkey
-      f.icon = _G[name.."Icon"]
-      f.flash = _G[name.."Flash"]
-      f:SetScript("OnUpdate",OnUpdate)
-    end
-
-    self.hotkey = f.hotkey
-    self.border = _G[name.."Border"]
-
-    f:SetAttribute("action", config.actionID)
-    f:SetAttribute("default-action", config.actionID)
-    -- install mind control actions for all buttons just for simplicity
-    if self.idx <= 12 then
-      f:SetAttribute("mindcontrol-action", 120 + self.idx)
-    end
-    
-    -- set a _childupdate handler, called within the header's context
-    f:SetAttribute("_childupdate", 
-      -- function _childupdate(self, snippetid, message)
-      [[
-        local action = "default-action"
-        if doMindControl and GetBonusBarOffset() == 5 then
-          action = "mindcontrol-action"
-        elseif page and state and page[state] then
-          action = "action-"..page[state]
-        end
-        local value = self:GetAttribute(action)
-        if value then
-          self:SetAttribute("action",value)
-        end
-      ]])
-
-    -- install drag wrappers to lock buttons 
-    bar:GetFrame():WrapScript(f, "OnDragStart",
-      -- OnDragStart(self, button, kind, value, ...)
-      [[
-        if lockButtons and (PlayerInCombat() or not lockButtonsCombat) and not IsModifiedClick("PICKUPACTION") then
-          return "clear"
-        end
-      ]])
-
-    self.frame = f
-
-
-    -- initialize the hide state
-    f:SetAttribute("showgrid",0)
-    self:ShowGrid(not barConfig.hideEmpty)
-    if ReAction:GetConfigMode() then
-      self:ShowGrid(true)
-    end
-
-    -- show the ID label if applicable
-    self:ShowActionIDLabel(ReAction:GetConfigMode())
-
-    -- attach the keybinder
-    KBAttach(self)
-
-    -- attach to skinner
-    bar:SkinButton(self,
-      {
-        HotKey = self.hotkey,
-      }
-    )
-
-    self:Refresh()
-    return self
-  end
-
-  function Button:Destroy()
-    local f = self.frame
-    f:UnregisterAllEvents()
-    f:Hide()
-    f:SetParent(UIParent)
-    f:ClearAllPoints()
-    if self.name then
-      frameRecycler[self.name] = f
-    end
-    if self.config.actionID then
-      IDAlloc:Release(self.config.actionID)
-    end
-    if self.config.pageactions then
-      for _, id in ipairs(self.config.pageactions) do
-        IDAlloc:Release(id)
-      end
-    end
-    self.frame = nil
-    self.config = nil
-    self.bar = nil
-  end
-
-  function Button:Refresh()
-    local f = self.frame
-    self.bar:PlaceButton(self, 36, 36)
-    self:RefreshPages()
-  end
-
-  function Button:GetFrame()
-    return self.frame
-  end
-
-  function Button:GetName()
-    return self.name
-  end
-
-  function Button:GetConfig()
-    return self.config
-  end
-
-  function Button:GetActionID(page)
-    if page == nil then
-      -- get the effective ID
-      return self.frame.action -- kept up-to-date by Blizzard's ActionButton_CalculateAction()
-    else
-      if page == 1 then
-        return self.config.actionID
-      else
-        return self.config.pageactions and self.config.pageactions[page] or self.config.actionID
-      end
+  local function GetActionName(f)
+    local b = f and f._reactionButton
+    if b then
+      return format("%s:%s", b.bar:GetName(), b.idx)
     end
   end
 
-  function Button:SetActionID( id, page )
-    id = tonumber(id)
-    page = tonumber(page)
-    if id == nil or id < 1 or id > 120 then
-      error("Button:SetActionID - invalid action ID")
-    end
-    if page and page ~= 1 then
-      if not self.config.pageactions then
-        self.config.pageactions = { }
-      end
-      if self.config.pageactions[page] then
-        IDAlloc:Release(self.config.pageactions[page])
-      end
-      self.config.pageactions[page] = id
-      IDAlloc:Acquire(self.config.pageactions[page])
-      self.frame:SetAttribute(("action-page%d"):format(page),id)
-    else
-      IDAlloc:Release(self.config.actionID)
-      self.config.actionID = id
-      IDAlloc:Acquire(self.config.actionID)
-      self.frame:SetAttribute("action",id)
-      if self.config.pageactions then
-        self.config.pageactions[1] = id
-        self.frame:SetAttribute("action-page1",id)
-      end
+  function GetHotkey(f)
+    return KB:ToShortKey(GetBindingKey(format("CLICK %s:LeftButton",f:GetName())))
+  end
+
+  local function kb_onEnter( self )
+    if ReAction:GetKeybindMode() then
+      KB:Set(self)
     end
   end
 
-  function Button:RefreshPages( force )
-    local nPages = self.barConfig.nPages
-    if nPages and (nPages ~= self.nPages or force) then
-      local f = self:GetFrame()
-      local c = self.config.pageactions
-      if nPages > 1 and not c then
-        c = { }
-        self.config.pageactions = c
-      end
-      for i = 1, nPages do
-        if i > 1 then
-          c[i] = IDAlloc:Acquire(c[i], self.config.actionID + (i-1)*self.bar:GetNumButtons())
-        else
-          c[i] = self.config.actionID  -- page 1 is the same as the base actionID
-        end
-        f:SetAttribute(("action-page%d"):format(i),c[i])
-      end
-      for i = nPages+1, #c do
-        IDAlloc:Release(c[i])
-        c[i] = nil
-        f:SetAttribute(("action-page%d"):format(i),nil)
-      end
-      self.nPages = nPages
+  function KBAttach( button )
+    if not button.kbHooked then
+      button.kbHooked = true
+      local f = button:GetFrame()
+      f:HookScript("OnEnter", kb_onEnter)
+      f.GetActionName = GetActionName
+      f.GetHotkey     = GetHotkey
     end
   end
 
-  function Button:ShowGrid( show )
-    if not InCombatLockdown() then
-      local f = self.frame
-      local count = f:GetAttribute("showgrid")
-      if show then
-        count = count + 1
-      else
-        count = count - 1
-      end
-      if count < 0 then
-        count = 0
-      end
-      f:SetAttribute("showgrid",count)
-
-      if count >= 1 and not f:GetAttribute("statehidden") then
-        if LBF then
-          LBF:SetNormalVertexColor(self.frame, 1.0, 1.0, 1.0, 0.5)
-        else
-          self.frame:GetNormalTexture():SetVertexColor(1.0, 1.0, 1.0, 0.5);
-        end
-        f:Show()
-      elseif count < 1 and not HasAction(self:GetActionID()) then
-        f:Hide()
-      end
+  -- This is a bit hokey : install a bare hook on ActionButton_UpdateHotkey because
+  -- even though it's secure it's never called in a way that can cause taint. This is 
+  -- for performance reasons to avoid having to hook frame:OnEvent securely.
+  local UpdateHotkey_old = ActionButton_UpdateHotkeys
+  ActionButton_UpdateHotkeys = function( frame, ... )
+    local b = frame._reactionButton
+    if b then
+      b.hotkey:SetText( GetHotkey(frame) )
+    else
+      return UpdateHotkey_old(frame, ...)
     end
   end
+end
 
-  function Button:ShowActionIDLabel( show )
-    local f = self:GetFrame()
-    if show then
-      local id = self:GetActionID()
-      if not f.actionIDLabel then
-        local label = f:CreateFontString(nil,"OVERLAY","GameFontNormalLarge")
-        label:SetAllPoints()
-        label:SetJustifyH("CENTER")
-        label:SetShadowColor(0,0,0,1)
-        label:SetShadowOffset(2,-2)
-        f.actionIDLabel = label -- store the label with the frame for recycling
+local meta = {__index = Button}
 
-        f:HookScript("OnAttributeChanged", 
-          function(frame, attr, value)
-            if label:IsVisible() and attr:match("action") then
-              label:SetText(tostring(frame.action))
-            end
-          end)
+function Button:New( handle, idx, config, barConfig )
+  local bar = handle.bar
+
+  -- create new self
+  self = setmetatable( 
+    { 
+      bar = bar,
+      idx = idx,
+      config = config,
+      barConfig = barConfig,
+    }, meta )
+
+  local name = config.name or ("ReAction_%s_%s_%d"):format(bar:GetName(),moduleID,idx)
+  self.name = name
+  config.name = name
+  local lastButton = handle:GetLastButton()
+  config.actionID = IDAlloc:Acquire(config.actionID, lastButton and lastButton.config.actionID) -- gets a free one if none configured
+  self.nPages = 1
+  
+  -- have to recycle frames with the same name: CreateFrame() doesn't overwrite
+  -- existing globals. Can't set to nil in the global because it's then tainted.
+  local parent = bar:GetFrame()
+  local f = frameRecycler[name]
+  if f then
+    f:SetParent(parent)
+  else
+    f = CreateFrame("CheckButton", name, parent, "ActionBarButtonTemplate")
+    -- ditch the old hotkey text because it's tied in ActionButton_Update() to the
+    -- standard binding.
+    local hotkey = _G[name.."HotKey"]
+    hotkey:SetParent(trash)
+    hotkey = f:CreateFontString(nil, "ARTWORK", "NumberFontNormalSmallGray")
+    hotkey:SetWidth(36)
+    hotkey:SetHeight(18)
+    hotkey:SetJustifyH("RIGHT")
+    hotkey:SetJustifyV("TOP")
+    hotkey:SetPoint("TOPLEFT",f,"TOPLEFT",-2,-2)
+    f.hotkey = hotkey
+    f.icon = _G[name.."Icon"]
+    f.flash = _G[name.."Flash"]
+    f:SetScript("OnUpdate",OnUpdate)
+  end
+
+  f._reactionButton = self
+
+  self.hotkey = f.hotkey
+  self.border = _G[name.."Border"]
+
+  f:SetAttribute("action", config.actionID)
+  f:SetAttribute("default-action", config.actionID)
+  -- install mind control actions for all buttons just for simplicity
+  if self.idx <= 12 then
+    f:SetAttribute("mindcontrol-action", 120 + self.idx)
+  end
+  
+  -- set a _childupdate handler, called within the header's context
+  f:SetAttribute("_childupdate", 
+    -- function _childupdate(self, snippetid, message)
+    [[
+      local action = "default-action"
+      if doMindControl and GetBonusBarOffset() == 5 then
+        action = "mindcontrol-action"
+      elseif page and state and page[state] then
+        action = "action-"..page[state]
       end
-      f.actionIDLabel:SetText(tostring(id))
-      f.actionIDLabel:Show()
-    elseif f.actionIDLabel then
-      f.actionIDLabel:Hide()
+      local value = self:GetAttribute(action)
+      if value then
+        self:SetAttribute("action",value)
+      end
+    ]])
+
+  -- install drag wrappers to lock buttons 
+  bar:GetFrame():WrapScript(f, "OnDragStart",
+    -- OnDragStart(self, button, kind, value, ...)
+    [[
+      if lockButtons and (PlayerInCombat() or not lockButtonsCombat) and not IsModifiedClick("PICKUPACTION") then
+        return "clear"
+      end
+    ]])
+
+  self.frame = f
+
+  -- initialize the hide state
+  f:SetAttribute("showgrid",0)
+  self:ShowGrid(not barConfig.hideEmpty)
+  if ReAction:GetConfigMode() then
+    self:ShowGrid(true)
+  end
+
+  -- set the hotkey text
+  self.hotkey:SetText( GetHotkey(self.frame) )
+
+  -- show the ID label if applicable
+  self:ShowActionIDLabel(ReAction:GetConfigMode())
+
+  -- attach to skinner
+  bar:SkinButton(self,
+    {
+      HotKey = self.hotkey,
+    }
+  )
+
+  self:Refresh()
+  return self
+end
+
+function Button:Destroy()
+  local f = self.frame
+  f:UnregisterAllEvents()
+  f:Hide()
+  f:SetParent(UIParent)
+  f:ClearAllPoints()
+  if self.name then
+    frameRecycler[self.name] = f
+  end
+  if self.config.actionID then
+    IDAlloc:Release(self.config.actionID)
+  end
+  if self.config.pageactions then
+    for _, id in ipairs(self.config.pageactions) do
+      IDAlloc:Release(id)
     end
   end
+  f._reactionButton = nil
+  self.frame = nil
+  self.config = nil
+  self.bar = nil
+end
 
-  function Button:DisplayHotkey( key )
-    self.hotkey:SetText(key or "")
+function Button:Refresh()
+  local f = self.frame
+  self.bar:PlaceButton(self, 36, 36)
+  self:RefreshPages()
+end
+
+function Button:GetFrame()
+  return self.frame
+end
+
+function Button:GetName()
+  return self.name
+end
+
+function Button:GetConfig()
+  return self.config
+end
+
+function Button:GetActionID(page)
+  if page == nil then
+    -- get the effective ID
+    return self.frame.action -- kept up-to-date by Blizzard's ActionButton_CalculateAction()
+  else
+    if page == 1 then
+      return self.config.actionID
+    else
+      return self.config.pageactions and self.config.pageactions[page] or self.config.actionID
+    end
   end
 end
+
+function Button:SetActionID( id, page )
+  id = tonumber(id)
+  page = tonumber(page)
+  if id == nil or id < 1 or id > 120 then
+    error("Button:SetActionID - invalid action ID")
+  end
+  if page and page ~= 1 then
+    if not self.config.pageactions then
+      self.config.pageactions = { }
+    end
+    if self.config.pageactions[page] then
+      IDAlloc:Release(self.config.pageactions[page])
+    end
+    self.config.pageactions[page] = id
+    IDAlloc:Acquire(self.config.pageactions[page])
+    self.frame:SetAttribute(("action-page%d"):format(page),id)
+  else
+    IDAlloc:Release(self.config.actionID)
+    self.config.actionID = id
+    IDAlloc:Acquire(self.config.actionID)
+    self.frame:SetAttribute("action",id)
+    if self.config.pageactions then
+      self.config.pageactions[1] = id
+      self.frame:SetAttribute("action-page1",id)
+    end
+  end
+end
+
+function Button:RefreshPages( force )
+  local nPages = self.barConfig.nPages
+  if nPages and (nPages ~= self.nPages or force) then
+    local f = self:GetFrame()
+    local c = self.config.pageactions
+    if nPages > 1 and not c then
+      c = { }
+      self.config.pageactions = c
+    end
+    for i = 1, nPages do
+      if i > 1 then
+        c[i] = IDAlloc:Acquire(c[i], self.config.actionID + (i-1)*self.bar:GetNumButtons())
+      else
+        c[i] = self.config.actionID  -- page 1 is the same as the base actionID
+      end
+      f:SetAttribute(("action-page%d"):format(i),c[i])
+    end
+    for i = nPages+1, #c do
+      IDAlloc:Release(c[i])
+      c[i] = nil
+      f:SetAttribute(("action-page%d"):format(i),nil)
+    end
+    self.nPages = nPages
+  end
+end
+
+function Button:ShowGrid( show )
+  if not InCombatLockdown() then
+    local f = self.frame
+    local count = f:GetAttribute("showgrid")
+    if show then
+      count = count + 1
+    else
+      count = count - 1
+    end
+    if count < 0 then
+      count = 0
+    end
+    f:SetAttribute("showgrid",count)
+
+    if count >= 1 and not f:GetAttribute("statehidden") then
+      if LBF then
+        LBF:SetNormalVertexColor(self.frame, 1.0, 1.0, 1.0, 0.5)
+      else
+        self.frame:GetNormalTexture():SetVertexColor(1.0, 1.0, 1.0, 0.5);
+      end
+      f:Show()
+    elseif count < 1 and not HasAction(self:GetActionID()) then
+      f:Hide()
+    end
+  end
+end
+
+function Button:ShowActionIDLabel( show )
+  local f = self:GetFrame()
+  if show then
+    local id = self:GetActionID()
+    if not f.actionIDLabel then
+      local label = f:CreateFontString(nil,"OVERLAY","GameFontNormalLarge")
+      label:SetAllPoints()
+      label:SetJustifyH("CENTER")
+      label:SetShadowColor(0,0,0,1)
+      label:SetShadowOffset(2,-2)
+      f.actionIDLabel = label -- store the label with the frame for recycling
+
+      f:HookScript("OnAttributeChanged", 
+        function(frame, attr, value)
+          if label:IsVisible() and attr:match("action") then
+            label:SetText(tostring(frame.action))
+          end
+        end)
+    end
+    f.actionIDLabel:SetText(tostring(id))
+    f.actionIDLabel:Show()
+  elseif f.actionIDLabel then
+    f.actionIDLabel:Hide()
+  end
+end
+
+function Button:SetKeybindMode( mode )
+  if mode then
+    KBAttach( self )
+    -- set the border for all buttons to the keybind-enable color
+    self.border:SetVertexColor(KB:GetColorKeyBoundMode())
+    self.border:Show()
+  elseif IsEquippedAction(self:GetActionID()) then
+    self.border:SetVertexColor(0, 1.0, 0, 0.35) -- from ActionButton.lua
+  else
+    self.border:Hide()
+  end
+end
\ No newline at end of file
--- a/modules/PetAction.lua	Fri Jan 23 23:40:13 2009 +0000
+++ b/modules/PetAction.lua	Fri Jan 23 23:44:55 2009 +0000
@@ -296,90 +296,22 @@
 
 local frameRecycler = {}
 local trash = CreateFrame("Frame")
-local KBAttach, GetActionName, GetHotkey, SetKey, FreeKey, ClearBindings, GetBindings, OnEnter, OnLeave
-do
-  local buttonLookup = setmetatable({},{__mode="kv"})
 
-  -- Use KeyBound-1.0 for binding, but use Override bindings instead of
-  -- regular bindings to support multiple profile use. This is a little
-  -- weird with the KeyBound dialog box (which has per-char selector as well
-  -- as an OK/Cancel box) but it's the least amount of effort to implement.
-  function GetActionName(f)
-    local b = buttonLookup[f]
-    if b then
-      return format("%s:%s", b.bar:GetName(), b.idx)
-    end
+local function GetActionName(f)
+  local b = f and f._reactionButton
+  if b then
+    return format("%s:%s", b.bar:GetName(), b.idx)
   end
+end
 
-  function GetHotkey(f)
-    local b = buttonLookup[f]
-    if b then
-      return KB:ToShortKey(b:GetConfig().hotkey)
-    end
-  end
+local function GetHotkey(f)
+  return KB:ToShortKey(GetBindingKey(format("CLICK %s:LeftButton",f:GetName())))
+end
 
-  function SetKey(f, key)
-    local b = buttonLookup[f]
-    if b then
-      local c = b:GetConfig()
-      if c.hotkey then
-        SetOverrideBinding(f, false, c.hotkey, nil)
-      end
-      if key then
-        SetOverrideBindingClick(f, false, key, f:GetName(), nil)
-      end
-      c.hotkey = key
-      b:DisplayHotkey(GetHotkey(f))
-    end
-  end
-
-  function FreeKey(f, key)
-    local b = buttonLookup[f]
-    if b then
-      local c = b:GetConfig()
-      if c.hotkey == key then
-        local action = f:GetActionName()
-        SetOverrideBinding(f, false, c.hotkey, nil)
-        c.hotkey = nil
-        b:DisplayHotkey(nil)
-        return action
-      end
-    end
-    return ReAction:FreeOverrideHotkey(key)
-  end
-
-  function ClearBindings(f)
-    SetKey(f, nil)
-  end
-
-  function GetBindings(f)
-    local b = buttonLookup[f]
-    if b then
-      return b:GetConfig().hotkey
-    end
-  end
-
-  function KBAttach( button )
-    local f = button:GetFrame()
-    f.GetActionName = GetActionName
-    f.GetHotkey     = GetHotkey
-    f.SetKey        = SetKey
-    f.FreeKey       = FreeKey
-    f.ClearBindings = ClearBindings
-    f.GetBindings   = GetBindings
-    buttonLookup[f] = button
-    f:SetKey(button:GetConfig().hotkey)
-    ReAction:RegisterKeybindFrame(f)
-    if ReAction:GetKeybindMode() then
-      button.border:SetVertexColor(KB:GetColorKeyBoundMode())
-      button.border:Show()
-    end
-  end
-
-  function OnEnter( self )
-    if not self.tooltipName then
-      return;
-    end
+local function OnEnter( self )
+  if ReAction:GetKeybindMode() then
+    KB:Set(self)
+  elseif self.tooltipName then
     local uber = GetCVar("UberTooltips")
     if self.isToken or (uber == "0") then
       if uber == "0" then
@@ -387,12 +319,7 @@
       else
         GameTooltip_SetDefaultAnchor(GameTooltip, self)
       end
-      local tooltip = self.tooltipName
-      local k = GetBindings(self)
-      if k then
-        tooltip = tooltip .. format(" %s(%s)%s", NORMAL_FONT_COLOR_CODE, k, FONT_COLOR_CODE_CLOSE)
-      end
-      GameTooltip:SetText(tooltip)
+      GameTooltip:SetText(self.tooltipName)
       if self.tooltipSubtext then
         GameTooltip:AddLine(self.tooltipSubtext, "", 0.5, 0.5, 0.5)
       end
@@ -402,11 +329,10 @@
       GameTooltip:SetPetAction(self:GetID())
     end
   end
+end
 
-  function OnLeave()
-    GameTooltip:Hide()
-  end
-
+local function OnLeave()
+  GameTooltip:Hide()
 end
 
 local meta = { __index = Button }
@@ -424,7 +350,7 @@
   config.name = name
   self.name = name
   config.actionID = ActionIDList[config.actionID] -- gets a free one if none configured
-  
+
   -- have to recycle frames with the same name:
   -- otherwise you either get references to old textures because named CreateFrame()
   -- doesn't overwrite existing globals. Can't set them to nil in the global table, 
@@ -463,15 +389,16 @@
   self.hotkey   = f.hotkey
   self.border   = _G[("%sBorder"):format(name)]
 
+  f._reactionButton = self
 
-  f:RegisterEvent("PLAYER_CONTROL_LOST");
-	f:RegisterEvent("PLAYER_CONTROL_GAINED");
-	f:RegisterEvent("PLAYER_FARSIGHT_FOCUS_CHANGED");
-	f:RegisterEvent("UNIT_PET");
-	f:RegisterEvent("UNIT_FLAGS");
-	f:RegisterEvent("UNIT_AURA");
-	f:RegisterEvent("PET_BAR_UPDATE");
-	f:RegisterEvent("PET_BAR_UPDATE_COOLDOWN");
+  f:RegisterEvent("PLAYER_CONTROL_LOST")
+	f:RegisterEvent("PLAYER_CONTROL_GAINED")
+	f:RegisterEvent("PLAYER_FARSIGHT_FOCUS_CHANGED")
+	f:RegisterEvent("UNIT_PET")
+	f:RegisterEvent("UNIT_FLAGS")
+	f:RegisterEvent("UNIT_AURA")
+	f:RegisterEvent("PET_BAR_UPDATE")
+	f:RegisterEvent("PET_BAR_UPDATE_COOLDOWN")
 
   f:SetScript("OnEvent",
     function(event,arg1)
@@ -493,8 +420,6 @@
       end
     ]])
 
-  KBAttach(self)
-
   -- attach to skinner
   bar:SkinButton(self,
     {
@@ -503,6 +428,7 @@
   )
 
   self:Refresh()
+  self:UpdateHotkey()
   self:SetKeybindMode(ReAction:GetKeybindMode())
 
   return self
@@ -521,6 +447,7 @@
   if self.config.actionID then
     ActionIDList[self.config.actionID] = nil
   end
+  f._reactionButton = nil
   self.frame = nil
   self.config = nil
   self.bar = nil
@@ -551,26 +478,25 @@
 
 function Button:Update()
   local id = self.frame:GetID()
-  local name, subtext, texture, isToken, isActive, autoCastAllowed, autoCastEnabled = GetPetActionInfo(id);
+  local name, subtext, texture, isToken, isActive, autoCastAllowed, autoCastEnabled = GetPetActionInfo(id)
   local f = self.frame
-  --ReAction:Print(("id %d: '%s', '%s', '%s', '%s', '%s', '%s', '%s'"):format(tostring(id), tostring(name),tostring(subtext),tostring(texture),tostring(isToken),tostring(isActive),tostring(autoCastAllowed),tostring(autoCastEnabled)))
 
   if isToken then
-    self.icon:SetTexture(_G[texture]);
-    f.tooltipName = _G[name];
+    self.icon:SetTexture(_G[texture])
+    f.tooltipName = _G[name]
   else
-    self.icon:SetTexture(texture);
-    f.tooltipName = name;
+    self.icon:SetTexture(texture)
+    f.tooltipName = name
   end
 
-  f.isToken = isToken;
-	f.tooltipSubtext = subtext;
-  f:SetChecked( isActive and 1 or 0);
+  f.isToken = isToken
+	f.tooltipSubtext = subtext
+  f:SetChecked( isActive and 1 or 0)
 
   if autoCastAllowed then
-    self.acTex:Show();
+    self.acTex:Show()
   else
-    self.acTex:Hide();
+    self.acTex:Hide()
   end
 
   if autoCastEnabled then
@@ -585,23 +511,23 @@
     else
       SetDesaturation(self.icon,1)
     end
-    self.icon:Show();
-    f:SetNormalTexture("Interface\\Buttons\\UI-Quickslot2");
+    self.icon:Show()
+    f:SetNormalTexture("Interface\\Buttons\\UI-Quickslot2")
   else
-    self.icon:Hide();
-    f:SetNormalTexture("Interface\\Buttons\\UI-Quickslot");
+    self.icon:Hide()
+    f:SetNormalTexture("Interface\\Buttons\\UI-Quickslot")
   end
 
   self:UpdateCooldown()
 end
 
 function Button:UpdateCooldown()
-	local start, duration, enable = GetPetActionCooldown(self.frame:GetID());
-  CooldownFrame_SetTimer(self.cooldown, start, duration, enable);
+	local start, duration, enable = GetPetActionCooldown(self.frame:GetID())
+  CooldownFrame_SetTimer(self.cooldown, start, duration, enable)
 end
 
 function Button:UpdateHotkey()
-  self:DisplayHotkey(GetHotkey(self.frame))
+  self.hotkey:SetText(GetHotkey(self.frame) or "")
 end
 
 function Button:ShowActionIDLabel(show)
@@ -625,6 +551,9 @@
 
 function Button:SetKeybindMode(mode)
   if mode then
+    local f = self.frame
+    f.GetActionName = GetActionName
+    f.GetHotkey     = GetHotkey
     self.border:SetVertexColor(KB:GetColorKeyBoundMode())
     self.border:Show()
   else
@@ -632,6 +561,3 @@
   end
 end
 
-function Button:DisplayHotkey( key )
-  self.hotkey:SetText(key or "")
-end