changeset 77:da8ba8783924

- added revision updater to each code file - Changed button/bar class mechanic to metatable-based - Changed buttons to live within a sub-frame, to play nicely between show-empty-buttons and hidestates - bar frame is now available only via accessor - Changed some semantics with AddButton/PlaceButton - Cleaned up action buttons options, fixed hide-when-empty option - moved show-action-ID-label as a button method - converted drag overlay from nested-frame to :Raise() - fixed ReAction:SetConfigMode() to not call event when mode doesn't change - Fixed ordering for dynamic state tab (always last)
author Flick <flickerstreak@gmail.com>
date Mon, 23 Jun 2008 22:27:50 +0000
parents c8c8610fd864
children 502cdb5666e2
files Bar.lua Overlay.lua ReAction.lua State.lua locale/enUS.lua modules/ReAction_Action/ReAction_Action.lua modules/ReAction_ConfigUI/ReAction_ConfigUI.lua modules/ReAction_HideBlizzard/ReAction_HideBlizzard.lua modules/ReAction_ModuleTemplate/ReAction_ModuleName.lua modules/ReAction_PetAction/ReAction_PetAction.lua
diffstat 10 files changed, 256 insertions(+), 237 deletions(-) [+]
line wrap: on
line diff
--- a/Bar.lua	Thu Jun 19 17:48:57 2008 +0000
+++ b/Bar.lua	Mon Jun 23 22:27:50 2008 +0000
@@ -7,18 +7,16 @@
 local format = string.format
 local SecureStateHeader_Refresh = SecureStateHeader_Refresh
 
-
--- update ReAction revision if this file is newer
-local revision = tonumber(("$Revision$"):match("%d+"))
-if revision > ReAction.revision then
-  ReAction.revision = revision
-end
+ReAction:UpdateRevision("$Revision$")
 
 
 ------ BAR CLASS ------
 local Bar = { _classID = {} }
+ReAction.Bar = Bar -- export to ReAction
 
-local function Constructor( self, name, config )
+function Bar:New( name, config )
+  -- create new self
+  self = setmetatable( { }, {__index = Bar} )
   if type(config) ~= "table" then
     error("ReAction.Bar: config table required")
   end
@@ -32,24 +30,43 @@
 
   local parent = config.parent and (ReAction:GetBar(config.parent) or _G[config.parent]) or UIParent
   local f = CreateFrame("Button",name and format("ReAction-%s",name),parent,"SecureStateHeaderTemplate, SecureActionButtonTemplate")
-
-  -- The frame itself is read-only
-  function self:GetFrame()
-    return f
-  end
+  f:SetFrameStrata("MEDIUM")
+  f:SetWidth(config.width)
+  f:SetWidth(config.height)
+  f:Show()
 
   -- The bar itself is also a Button derived from SecureActionButtonTemplate, so it has an OnClick handler
   -- which we can use as a virtual button for keybinds, which will send attribute-value changes to itself.
   -- However, we don't ever want the user to be able to click it directly.
   f:EnableMouse(false)
   f:SetAttribute("type","attribute")
-  f:SetFrameStrata("MEDIUM")
-  f:SetWidth(config.width)
-  f:SetWidth(config.height)
-  f:Show()
+
+  -- Buttons are contained in an anonymous intermediate sub-frame. This arrangement is to specifically
+  -- address the issue of the interaction with hidestates and auto-hiding empty action buttons (the two
+  -- don't play nicely together). It also has the fringe benefit of making show/hide faster because a
+  -- single frame is shown/hidden instead of potentially dozens. Unfortunately it does add an extra layer
+  -- of indirection to all state changes, as a secondary (trivial) statemap must be invoked. This
+  -- complicates frame setup slightly.
+  local bf = CreateFrame("Frame", nil, f, "SecureStateHeaderTemplate")
+  bf:SetAllPoints()
+  bf:Show()
+  bf:SetAttribute("useparent*",true)       -- this facilitates SecureButton_GetModifiedAttribute()
+  bf:SetAttribute("statemap-parent","*:=") -- however some methods don't use it, so propagate the state too
+  f:SetAttribute("addchild",bf)
+
+  -- Both frames are read-only. Override the default accessors for this object.
+  function self:GetFrame()
+    return f
+  end
+
+  function self:GetButtonFrame()
+    return bf
+  end
 
   self:ApplyAnchor()
   ReAction.RegisterCallback(self, "OnConfigModeChanged")
+
+  return self
 end
 
 function Bar:Destroy()
@@ -158,6 +175,18 @@
   return self.name
 end
 
+function Bar:GetFrame()
+  -- this method is included for documentation purposes. It is overridden
+  -- in the New method for each object.
+  error("Invalid Bar object: used without initialization")
+end
+
+function Bar:GetButtonFrame()
+  -- this method is included for documentation purposes. It is overridden
+  -- in the New method for each object.
+  error("Invalid Bar object: used without initialization")
+end
+
 -- only ReAction:RenameBar() should call this function
 function Bar:SetName(name)
   self.name = name
@@ -168,7 +197,9 @@
 end
 
 function Bar:AddButton(idx, button)
+  -- store in a reverse-index array
   self.buttons[button] = idx
+  self:GetButtonFrame():SetAttribute("addchild",button:GetFrame())
   SecureStateHeader_Refresh(self:GetFrame())
 end
 
@@ -234,7 +265,7 @@
   end
 end
 
--- Set an attribute on the frame (or its buttons if 'doButtons' = true)
+-- Set an attribute on the frame (or buttonFrame if 'buttonFrame' = true)
 -- Either or both 'map' and 'default' can be passed:
 --   - If 'map' is omitted, then 'default' is set to the attribute.
 --   - If 'map' is provided, then it is interpreted as an unordered
@@ -243,7 +274,8 @@
 --     string, e.g. "<state1>:<value1>;<state2>:<value2>". If 'default'
 --     is also provided, then its value will be converted to a string
 --     and appended.
-function Bar:SetStateAttribute( attribute, map, default, doButtons )
+function Bar:SetStateAttribute( attribute, map, default, buttonFrame )
+  local f = buttonFrame and self:GetButtonFrame() or self:GetFrame()
   local value = default
   if map then
     local tmp = { }
@@ -255,29 +287,6 @@
     end
     value = table.concat(tmp,";")
   end
-  if doButtons then
-    for b in pairs(self.buttons) do
-      local f = b.GetFrame and b:GetFrame()
-      if f then
-        f:SetAttribute(attribute, value)
-      end
-    end
-  else
-    self:GetFrame():SetAttribute(attribute, value)
-  end
-  SecureStateHeader_Refresh(self:GetFrame())
+  f:SetAttribute(attribute, value)
+  SecureStateHeader_Refresh(f)
 end
-
-
------- Export as a class-factory ------
-ReAction.Bar = {
-  prototype = Bar,
-  New = function(self, ...)
-    local x = { }
-    for k,v in pairs(Bar) do
-      x[k] = v
-    end
-    Constructor(x, ...)
-    return x
-  end
-}
--- a/Overlay.lua	Thu Jun 19 17:48:57 2008 +0000
+++ b/Overlay.lua	Mon Jun 23 22:27:50 2008 +0000
@@ -7,6 +7,8 @@
 local format = string.format
 local GameTooltip = GameTooltip
 
+ReAction:UpdateRevision("$Revision: 103 $")
+
 -- Looking for a lightweight AceConfig3-struct-compatible 
 -- replacement for Dewdrop (e.g. forthcoming AceConfigDropdown-3.0?).
 -- Considering Blizzard's EasyMenu/UIDropDownMenu, but that's
@@ -55,7 +57,7 @@
 -- Bar config overlay
 --
 -- localize some of these for small OnUpdate performance boost
-local Bar           = ReAction.Bar.prototype
+local Bar           = ReAction.Bar
 local GetSize       = Bar.GetSize
 local GetButtonSize = Bar.GetButtonSize
 local GetButtonGrid = Bar.GetButtonGrid
@@ -413,15 +415,7 @@
   f:SetResizable(true)
   f:SetClampedToScreen(true)
 
-  -- buttons on the bar should be direct children of the bar frame.
-  -- The control elements need to float on top of this, which we could
-  -- do with SetFrameLevel() or Raise(), but it's more reliable to do it
-  -- via frame nesting, hence good old foo's appearance here.
-  local foo = CreateFrame("Frame",nil,f)
-  foo:SetAllPoints()
-  foo:SetClampedToScreen(true)
-
-  local control = CreateFrame("Button", nil, foo)
+  local control = CreateFrame("Button", nil, f)
   control:EnableMouse(true)
   control:SetToplevel(true)
   control:SetPoint("TOPLEFT", -4, 4)
@@ -433,6 +427,7 @@
     edgeSize = 16,
     insets = { left = 0, right = 0, top = 0, bottom = 0 },
   })
+  control:SetClampedToScreen(true)
 
   -- textures
   local bgTex = control:CreateTexture(nil,"BACKGROUND")
@@ -509,11 +504,11 @@
     edge:Show()
   end
 
-  -- corner drag handles, again nested in an anonymous frame so that they are on top
-  local foo2 = CreateFrame("Frame",nil,control)
-  foo2:SetAllPoints(true)
+  -- corner drag handles, nested in an anonymous frame so that they are on top
+  local foo = CreateFrame("Frame",nil,control)
+  foo:SetAllPoints(true)
   for _, point in pairs({"BOTTOMLEFT","TOPLEFT","BOTTOMRIGHT","TOPRIGHT"}) do
-    local corner = CreateFrame("Frame",nil,foo2)
+    local corner = CreateFrame("Frame",nil,foo)
     corner:EnableMouse(true)
     corner:SetWidth(12)
     corner:SetHeight(12)
@@ -669,6 +664,7 @@
       self.controlFrame = CreateControls(self)
     end
     self.controlFrame:Show()
+    self.controlFrame:Raise()
   elseif self.controlFrame then
     CloseMenu(self.controlFrame)
     self.controlFrame:Hide()
--- a/ReAction.lua	Thu Jun 19 17:48:57 2008 +0000
+++ b/ReAction.lua	Mon Jun 23 22:27:50 2008 +0000
@@ -253,6 +253,13 @@
 
 
 ------ API ------
+function ReAction:UpdateRevision(str)
+  local revision = tonumber(str:match("%d+"))
+  if revision and revision > ReAction.revision then
+    ReAction.revision = revision
+  end
+end
+
 function ReAction:UserError(msg)
   -- any user errors should be flashed to the UIErrorsFrame
   UIErrorsFrame:AddMessage(msg)
@@ -441,8 +448,10 @@
 end
 
 function ReAction:SetConfigMode( mode )
-  private.configMode = mode
-  callbacks:Fire("OnConfigModeChanged", mode)
+  if mode ~= private.configMode then
+    private.configMode = mode
+    callbacks:Fire("OnConfigModeChanged", mode)
+  end
 end
 
 function ReAction:GetConfigMode()
--- a/State.lua	Thu Jun 19 17:48:57 2008 +0000
+++ b/State.lua	Mon Jun 23 22:27:50 2008 +0000
@@ -10,6 +10,8 @@
 local InCombatLockdown = InCombatLockdown
 local format = string.format
 
+ReAction:UpdateRevision("$Revision: 103 $")
+
 -- module declaration
 local moduleID = "State"
 local module = ReAction:NewModule( moduleID, "AceEvent-3.0" )
@@ -172,7 +174,7 @@
           -- ?? button:UpdateBindingSet(kbset)
         end
       end
-      bar:SetStateAttribute("statebindings", map)
+      bar:SetStateAttribute("statebindings", map, true) -- apply to button frame, bindings only work for direct children
     end,
 
     enableAnchor = function( bar, states )
@@ -902,8 +904,9 @@
     local private = { }
     local states = tbuild(module.db.profile.bars, bar:GetName(), "states")
     local options = {
+      name = L["Dynamic State"],
       type = "group",
-      name = L["Dynamic State"],
+      order = -1,
       childGroups = "tree",
       disabled = InCombatLockdown,
       args = {
--- a/locale/enUS.lua	Thu Jun 19 17:48:57 2008 +0000
+++ b/locale/enUS.lua	Mon Jun 23 22:27:50 2008 +0000
@@ -123,6 +123,7 @@
 "Action Bar",
 "Action Bars",
 "Hide Empty Buttons",
+"Hide buttons when empty. This option is not supported for multi-state bars",
 "Action Buttons",
 
 -- modules/ReAction_PetAction
--- a/modules/ReAction_Action/ReAction_Action.lua	Thu Jun 19 17:48:57 2008 +0000
+++ b/modules/ReAction_Action/ReAction_Action.lua	Mon Jun 23 22:27:50 2008 +0000
@@ -2,8 +2,10 @@
   ReAction Action button module.
 
   The button module implements standard action button functionality by wrapping Blizzard's 
-  ActionButton frame and associated functions. It also provides some button layout
-  modification tools.
+  ActionButton frame and associated functions.
+
+  It also provides support for multiple pages (interacting with the State module) as well
+  as optional action remapping for possessed targets (mind control).
 
 --]]
 
@@ -13,15 +15,16 @@
 local _G = _G
 local CreateFrame = CreateFrame
 
+ReAction:UpdateRevision("$Revision: 103 $")
+
 -- module declaration
 local moduleID = "Action"
 local module = ReAction:NewModule( moduleID )
 
+-- Button class declaration
+local Button = { }
+
 -- private --
-local function GetBarConfig(bar)
-  return module.db.profile.bars[bar:GetName()]
-end
-
 local function RefreshLite(bar)
   local btns = module.buttons[bar]
   if btns then
@@ -31,8 +34,7 @@
   end
 end
 
-
--- module methods
+-- Event handlers
 function module:OnInitialize()
   self.db = ReAction.db:RegisterNamespace( moduleID,
     { 
@@ -44,21 +46,6 @@
   )
   self.buttons = { }
 
-  ReAction:RegisterOptions(self, {
-      [moduleID] = {
-        type = "group",
-        name = L["Action Bars"],
-        args = {
-          hideEmpty = {
-            type = "toggle",
-            name = L["Hide Empty Buttons"],
-            get  = function() return self.db.profile.hideEmptyButtons end,
-            set  = function(info, val) module:SetHideEmptyButtons(val) end,
-          }
-        }
-      }
-    })
-
   ReAction:RegisterBarOptionGenerator(self, "GetBarOptions")
 
   ReAction.RegisterCallback(self, "OnCreateBar", "OnRefreshBar")
@@ -108,11 +95,9 @@
         btnCfg[i] = {}
       end
       if btns[i] == nil then
-        local ok, b = pcall(self.BtnClass.New, self.BtnClass, bar, i, btnCfg[i], barCfg)
-        if ok and b then
-          btns[i] = b
-          bar:AddButton(i,b)
-        end
+        local b = Button:New(bar, i, btnCfg[i], barCfg)
+        btns[i] = b
+        bar:AddButton(i,b)
       end
     end
     for i = n+1, #btns do
@@ -153,77 +138,71 @@
   b[newname], b[oldname] = b[oldname], nil
 end
 
-function module:SetHideEmptyButtons(hide)
-  if hide ~= self.db.profile.hideEmptyButtons then
-    for _, bar in pairs(self.buttons) do
-      for _, b in pairs(bar) do
-        if hide then
-          ActionButton_HideGrid(b.frame)
-        else
-          ActionButton_ShowGrid(b.frame)
-        end
-      end
-    end
-    self.db.profile.hideEmptyButtons = hide
-  end
-end
-
 function module:OnConfigModeChanged(event, mode)
-  for _, bar in ReAction:IterateBars() do
-    if bar and self.buttons[bar] then
-      for _, b in pairs(self.buttons[bar]) do
-        if b then
-          if mode then
-            ActionButton_ShowGrid(b.frame)
-            self:showActionIDLabel(b)
-          else
-            ActionButton_HideGrid(b.frame)
-            self:hideActionIDLabel(b)
-          end
-        end
-      end
+  for _, bar in pairs(self.buttons) do
+    for _, b in pairs(bar) do
+      b:ShowGrid(mode)
+      b:ShowActionIDLabel(mode)
     end
   end
 end
 
-function module:showActionIDLabel(button)
-  if not button.actionIDLabel and button:GetActionID() then
-    local f = button:GetFrame()
-    local label = f:CreateFontString(nil,"OVERLAY","GameFontNormalLarge")
-    label:SetAllPoints()
-    label:SetJustifyH("CENTER")
-    label:SetShadowColor(0,0,0,1)
-    label:SetShadowOffset(2,-2)
-    label:SetText(tostring(button:GetActionID()))
-    button.actionIDLabel = label
-    f:HookScript("OnAttributeChanged", 
-      function(frame, attr, value)
-        if attr == "state-parent" then
-          label:SetText(tostring(button:GetActionID()))
-        end
-      end)
-  end
-  button.actionIDLabel:Show()
-end
-
-function module:hideActionIDLabel(button)
-  if button.actionIDLabel then
-    button.actionIDLabel:Hide()
-  end
-end
-
 
 ---- Options ----
+local Handler = { }
+
+local options = {
+  hideEmpty = {
+    name = L["Hide Empty Buttons"],
+    desc = L["Hide buttons when empty. This option is not supported for multi-state bars"],
+    order = 1,
+    type = "toggle",
+    get  = "GetHideEmpty",
+    set  = "SetHideEmpty",
+  },
+}
+
 function module:GetBarOptions(bar)
   return {
     type = "group",
     name = L["Action Buttons"],
-    hidden = function() return bar.config.type ~= moduleID end,
-    args = {
-    }
+    handler = Handler:New(bar),
+    hidden = "Hidden",
+    args = options
   }
 end
 
+-- options handler private
+do
+  local function GetBarConfig( bar )
+    return module.db.profile.bars[bar:GetName()]
+  end
+
+  function Handler:New(bar)
+    return setmetatable( { bar = bar }, { __index = Handler } )
+  end
+
+  function Handler:Hidden()
+    return self.bar.config.type ~= moduleID
+  end
+
+  function Handler:SetHideEmpty(info, value)
+    local c = GetBarConfig(self.bar)
+    if value ~= c.hideEmpty then
+      for b in self.bar:IterateButtons() do
+        b:ShowGrid(not value)
+      end
+      c.hideEmpty = value
+    end
+  end
+
+  function Handler:GetHideEmpty()
+    return GetBarConfig(self.bar).hideEmpty
+  end
+end
+
+
+------ Button class ------
 
 -- use-count of action IDs
 local nActionIDs = 120
@@ -256,25 +235,17 @@
   end
 })
 
-
-
-
------- Button class ------
-local Button = { }
-
-local function Constructor( self, bar, idx, config, barConfig )
+function Button:New( bar, idx, config, barConfig )
+  -- create new self
+  self = setmetatable( { }, {__index = Button} )
   self.bar, self.idx, self.config, self.barConfig = bar, idx, config, barConfig
 
-  local barFrame = bar:GetFrame()
-
   config.name = config.name or ("ReAction_%s_%d"):format(bar:GetName(),idx)
   self.name = config.name
   config.actionID = ActionIDList[config.actionID] -- gets a free one if none configured
   self.nPages = 1
   
-  local f = CreateFrame("CheckButton", self.name, barFrame, "ActionBarButtonTemplate")
-
-  -- TODO: re-implement ActionButton event handlers that don't do secure stuff
+  local f = CreateFrame("CheckButton", self.name, bar:GetButtonFrame(), "ActionBarButtonTemplate")
 
   -- this will probably cause taint and/or performance problems, using right now for display/debugging purposes
   f:SetScript("OnAttributeChanged", ActionButton_UpdateAction)
@@ -285,19 +256,20 @@
     f:SetAttribute("action-mc", 120 + self.idx)
   end
 
-  barFrame:SetAttribute("addchild",f)
+  self.frame = f
+  self.normalTexture = getglobal(format("%sNormalTexture",f:GetName()))
 
-  self.frame = f
-  self:Refresh()
-
-  if not module.db.profile.hideEmptyButtons then
-    ActionButton_ShowGrid(self.frame)
+  -- initialize the hide state
+  self:ShowGrid(not barConfig.hideEmpty)
+  if ReAction:GetConfigMode() then
+    self:ShowGrid(true)
   end
 
-  if ReAction.configMode then
-    ActionButton_ShowGrid(self.frame)
-    module:showActionIDLabel(self)
-  end
+  -- show the ID label if applicable
+  self:ShowActionIDLabel(ReAction:GetConfigMode())
+
+  self:Refresh()
+  return self
 end
 
 function Button:Destroy()
@@ -367,14 +339,52 @@
   end
 end
 
--- export as a class-factory to module
-module.BtnClass = {
-  New = function(self, ...)
-    local x = { }
-    for k,v in pairs(Button) do
-      x[k] = v
+function Button:ShowGrid( show )
+  if not InCombatLockdown() then
+    -- new in 2.4.1: can't call ActionButton_ShowGrid/HideGrid because they won't update the attribute
+    local f = self.frame
+    local count = f:GetAttribute("showgrid")
+    if show then
+      count = count + 1
+    else
+      count = count - 1
     end
-    Constructor(x, ...)
-    return x
+    if count < 0 then
+      count = 0
+    end
+    f:SetAttribute("showgrid",count)
+
+    if count >= 1 and not f:GetAttribute("statehidden") then
+      self.normalTexture:SetVertexColor(1.0, 1.0, 1.0, 0.5);
+      f:Show()
+    elseif count < 1 and not HasAction(self:GetActionID()) then
+      f:Hide()
+    end
   end
-}
+end
+
+function Button:ShowActionIDLabel( show )
+  if show then
+    local id = self:GetActionID()
+    if not self.actionIDLabel and id and id ~= 0 then
+      local f = self:GetFrame()
+      local label = f:CreateFontString(nil,"OVERLAY","GameFontNormalLarge")
+      label:SetAllPoints()
+      label:SetJustifyH("CENTER")
+      label:SetShadowColor(0,0,0,1)
+      label:SetShadowOffset(2,-2)
+      label:SetText(tostring(id))
+      self.actionIDLabel = label
+      f:HookScript("OnAttributeChanged", 
+        function(frame, attr, value)
+          if attr == "state-parent" then
+            label:SetText(tostring(self:GetActionID()))
+          end
+        end)
+    end
+    self.actionIDLabel:Show()
+  elseif self.actionIDLabel then
+    self.actionIDLabel:Hide()
+  end
+end
+
--- a/modules/ReAction_ConfigUI/ReAction_ConfigUI.lua	Thu Jun 19 17:48:57 2008 +0000
+++ b/modules/ReAction_ConfigUI/ReAction_ConfigUI.lua	Mon Jun 23 22:27:50 2008 +0000
@@ -11,6 +11,8 @@
 local AceConfigReg = LibStub("AceConfigRegistry-3.0")
 local AceConfigDialog = LibStub("AceConfigDialog-3.0")
 
+ReAction:UpdateRevision("$Revision: 103 $")
+
 -- some constants
 local configName = "ReAction"
 
--- a/modules/ReAction_HideBlizzard/ReAction_HideBlizzard.lua	Thu Jun 19 17:48:57 2008 +0000
+++ b/modules/ReAction_HideBlizzard/ReAction_HideBlizzard.lua	Mon Jun 23 22:27:50 2008 +0000
@@ -10,6 +10,8 @@
 local ReAction = ReAction
 local L = ReAction.L
 
+ReAction:UpdateRevision("$Revision: 103 $")
+
 -- module declaration
 local moduleID = "HideBlizzard"
 local module = ReAction:NewModule( moduleID )
--- a/modules/ReAction_ModuleTemplate/ReAction_ModuleName.lua	Thu Jun 19 17:48:57 2008 +0000
+++ b/modules/ReAction_ModuleTemplate/ReAction_ModuleName.lua	Mon Jun 23 22:27:50 2008 +0000
@@ -8,6 +8,8 @@
 local L = ReAction.L
 local _G = _G
 
+ReAction:UpdateRevision("$Revision: 103 $")
+
 -- module declaration
 local moduleID = "MyModuleName"
 local module = ReAction:NewModule( moduleID,
--- a/modules/ReAction_PetAction/ReAction_PetAction.lua	Thu Jun 19 17:48:57 2008 +0000
+++ b/modules/ReAction_PetAction/ReAction_PetAction.lua	Mon Jun 23 22:27:50 2008 +0000
@@ -2,8 +2,7 @@
   ReAction Pet Action button module
 
   The button module implements standard action button functionality by wrapping Blizzard's 
-  PetActionButton frame and associated functions. It also provides some button layout
-  modification tools.
+  PetActionButton frame and associated functions.
 
 --]]
 
@@ -13,10 +12,15 @@
 local _G = _G
 local CreateFrame = CreateFrame
 
+ReAction:UpdateRevision("$Revision: 103 $")
+
 -- module declaration
 local moduleID = "PetAction"
 local module = ReAction:NewModule( moduleID )
 
+-- Button class declaration
+local Button = { }
+
 -- module methods
 function module:OnInitialize()
   self.db = ReAction.db:RegisterNamespace( moduleID,
@@ -81,18 +85,15 @@
         btnCfg[i] = {}
       end
       if btns[i] == nil then
-        local ok, b = pcall(self.BtnClass.new, self.BtnClass, bar, i, btnCfg[i])
-        if ok and b then
-          btns[i] = b
-          bar:AddButton(i,b)
-        end
-      else
-        btns[i]:Refresh(bar,i)
+        local b = Button:New(bar,i,btnCfg[i])
+        btns[i] = b
+        bar:AddButton(i,b)
       end
+      btns[i]:Refresh()
     end
     for i = n+1, #btns do
       if btns[i] then
-        bar:RemoveButton(b)
+        bar:RemoveButton(btns[i])
         btns[i] = btns[i]:Destroy()
         if btnCfg[i] then
           btnCfg[i] = nil
@@ -125,17 +126,13 @@
 
 
 function module:OnConfigModeChanged(event, mode)
+  for _, buttons in pairs(self.buttons) do
+    for _, b in pairs(buttons) do
+      b:ShowActionIDLabel(mode)
+    end
+  end
   for _, bar in ReAction:IterateBars() do
     if bar and self.buttons[bar] then
-      for _, b in pairs(self.buttons[bar]) do
-        if b then
-          if mode then
-            self:showActionIDLabel(b)
-          else
-            self:hideActionIDLabel(b)
-          end
-        end
-      end
       local f = bar:GetFrame()
       if mode then
         UnregisterUnitWatch(f)
@@ -147,25 +144,6 @@
   end
 end
 
-function module:showActionIDLabel(button)
-  -- store the action ID label in the frame due to frame recycling
-  if not button:GetFrame().actionIDLabel and button:GetActionID() then
-    local label = button:GetFrame():CreateFontString(nil,"OVERLAY","GameFontNormalLarge")
-    label:SetAllPoints()
-    label:SetJustifyH("CENTER")
-    label:SetShadowColor(0,0,0,1)
-    label:SetShadowOffset(2,-2)
-    label:SetText(tostring(button:GetActionID()))
-    button:GetFrame().actionIDLabel = label
-  end
-  button:GetFrame().actionIDLabel:Show()
-end
-
-function module:hideActionIDLabel(button)
-  if button:GetFrame().actionIDLabel then
-    button:GetFrame().actionIDLabel:Hide()
-  end
-end
 
 ---- Options ----
 function module:GetBarOptions(bar)
@@ -180,6 +158,8 @@
 
 
 
+------ Button class ------
+
 -- use-count of action IDs
 local nActionIDs = NUM_PET_ACTION_SLOTS
 local ActionIDList = setmetatable( {}, {
@@ -213,18 +193,14 @@
 
 local frameRecycler = {}
 
-
------- Button class ------
-local Button = { }
-
-local function Constructor( self, bar, idx, config )
+function Button:New( bar, idx, config )
+  -- create new self
+  self = setmetatable( { }, { __index = Button } )
   self.bar, self.idx, self.config = bar, idx, config
 
-  local barFrame = bar:GetFrame()
-
   local name = config.name or ("ReAction_%s_Pet_%d"):format(bar:GetName(),idx)
   config.name = name
-  self.name = config.name
+  self.name = name
   config.actionID = ActionIDList[config.actionID] -- gets a free one if none configured
   
   -- have to recycle frames with the same name:
@@ -232,20 +208,17 @@
   -- doesn't overwrite existing globals (below)
   -- or, if you set them to nil in the global table, you get taint because of the
   -- crappy PetActionBar code.
+  local parent = bar:GetButtonFrame()
   local f = frameRecycler[name]
   if f then
-    f:SetParent(barFrame)
-    f:Show()
+    f:SetParent(parent)
   else
-    f = CreateFrame("CheckButton", name, barFrame, "PetActionButtonTemplate")
+    f = CreateFrame("CheckButton", name, parent, "PetActionButtonTemplate")
   end
   if config.actionID then
     f:SetID(config.actionID) -- PetActionButtonTemplate isn't a proper SecureActionButton
   end
   f:SetFrameStrata("MEDIUM")
-
-  barFrame:SetAttribute("addchild",f)
-
   self.frame = f
   self.icon = _G[("%sIcon"):format(name)]
   self.acTex = _G[("%sAutoCastable"):format(name)]
@@ -256,7 +229,7 @@
   f:HookScript("OnDragStart", function() self:Update() end)
   f:HookScript("OnReceiveDrag", function() self:Update() end)
 
-	f:RegisterEvent("PLAYER_CONTROL_LOST");
+  f:RegisterEvent("PLAYER_CONTROL_LOST");
 	f:RegisterEvent("PLAYER_CONTROL_GAINED");
 	f:RegisterEvent("PLAYER_FARSIGHT_FOCUS_CHANGED");
 	f:RegisterEvent("UNIT_PET");
@@ -276,7 +249,8 @@
       end
     end)
 
-  self:Refresh(bar,idx)
+  self:Refresh()
+  return self
 end
 
 function Button:Destroy()
@@ -297,8 +271,8 @@
   self.bar = nil
 end
 
-function Button:Refresh(bar,idx)
-  bar:PlaceButton(self, 30, 30)
+function Button:Refresh()
+  self.bar:PlaceButton(self, 30, 30)
   self:Update()
   self:UpdateHotkey()
 end
@@ -370,14 +344,20 @@
 
 end
 
--- export as a class-factory to module
-module.BtnClass = {
-  new = function(self, ...)
-    local x = { }
-    for k,v in pairs(Button) do
-      x[k] = v
+function Button:ShowActionIDLabel(show)
+  if show then
+    -- store the action ID label in the frame due to frame recycling
+    if not self.actionIDLabel and self:GetActionID() then
+      local label = self.frame:CreateFontString(nil,"OVERLAY","GameFontNormalLarge")
+      label:SetAllPoints()
+      label:SetJustifyH("CENTER")
+      label:SetShadowColor(0,0,0,1)
+      label:SetShadowOffset(2,-2)
+      label:SetText(tostring(self:GetActionID()))
+      self.actionIDLabel = label
     end
-    Constructor(x, ...)
-    return x
+    self.actionIDLabel:Show()
+  elseif self.actionIDLabel then
+    self.actionIDLabel:Hide()
   end
-}
+end