diff classes/ReAction.lua @ 7:f920db5fc6b1

version 0.3
author Flick <flickerstreak@gmail.com>
date Tue, 20 Mar 2007 21:25:29 +0000
parents dfd829db3ad0
children c05fd3e18b4f
line wrap: on
line diff
--- a/classes/ReAction.lua	Tue Mar 20 21:20:20 2007 +0000
+++ b/classes/ReAction.lua	Tue Mar 20 21:25:29 2007 +0000
@@ -1,19 +1,39 @@
+--
+-- ReAction is a base class for action button management. It provides a
+-- framework for button setup, placement, recycling, keybinding, etc.
+-- It does not define any layout or actual action functionality,
+-- which is deferred to derived classes. 
+--
+-- ReAction implements the ReBar.IButton interface. It is designed to be used with ReBar
+-- for grouping and laying out buttons.
+--
+-- ReAction supports the ReBound keybinding interface (only).
+--
+-- Each instance of a ReAction-derived object is associated with a single action
+-- button frame, which may or may not have a one-to-one mapping with actionID.
+--
+-- ReAction makes use of a configuration structure, which can (and should) be
+-- extended by implementations. A single config structure is shared amongst all
+-- ReAction class instances within a single ReBar group, so the structure should
+-- contain sub-tables for any property that needs to be defined on a per-button
+-- basis. Each button is passed a 'barIdx' parameter to be used as an index into
+-- such tables.
+--
+-- The base config structure is as follows:
+--
+-- config = {
+--   type = "ReAction", -- static string (used by ReBar)
+--   subtype = "string", -- ReAction implementation identifier (index into ReAction.buttonTypes)
+--   ids = { {paged list}, {paged list}, ... } -- indexed by self.barIdx
+-- }
+--
+
+
+local AceOO = AceLibrary("AceOO-2.0")
+local kbValidate = AceLibrary("AceConsole-2.0").keybindingValidateFunc
+
+
 -- private constants
-local namePrefix = "ReActionButton"
-local _G = getfenv(0)
-local ACTION_FREE = { }
-local MAX_ACTIONS = 120
-
-local hotKeyDefaultColor    = { r=1.0, g=1.0, b=1.0, a=1.0 }
-local hotKeyDisabledColor   = { r=0.6, g=0.6, b=0.6, a=1.0 }
-local hotKeyOutOfRangeColor = { r=1.0, g=0.2, b=0.2, a=1.0 }
-
-local hotKeyModifierColors = {
-  S = { r=0.6, g=0.6, b=1.0, a=1.0 },  -- shift
-  C = { r=1.0, g=0.82, b=0, a=1.0 },  -- ctrl
-  A = { r=0.1, g=1.0, b=0.1, a=1.0 },  -- alt
-}
-
 -- TODO: localize these key names with GetBindingText(KEY_)
 local keybindAbbreviations = {
   ["Mouse Button "] = "M-",
@@ -24,750 +44,324 @@
   [" Arrow"] = "",
 }
 
-local equippedActionBorderColor = { r=0, g=1.0, b=0, a=0.35 }
 
-local actionUsableColor        = { r=1.0, g=1.0, b=1.0, a=1.0 }
-local actionNotUsableColor     = { r=0.4, g=0.4, b=0.4, a=1.0 }
-local actionNotEnoughManaColor = { r=0.2, g=0.2, b=0.7, a=1.0 }
-local actionOutOfRangeColor    = { r=1.0, g=0.2, b=0.2, a=1.0 }
+------------------------
+-- Interface Declarations
+------------------------
 
--- private variables
-local kbValidate = AceLibrary("AceConsole-2.0").keybindingValidateFunc
-local actionButtonTbl = { }
+-- The ActionType interface defines what the button does when it is clicked. At a
+-- minimum it must do the equivalent of SetAttribute("type", ...) and handle events that
+-- cause the action to change.
+-- ReAction implementations must provide this interface (implicitly or explicitly).
+local IActionType = AceOO.Interface {
+ SetID         = "function", -- SetID(id, [page]) optional argument indicates page #: omitting indicates default. self.config.idx[barIdx] must be updated.
+ GetID         = "function", -- id = GetID([page]) optional argument indicates page #: omitting indicates current page
+ IsActionEmpty = "function", -- bool = IsActionEmpty()
+ SetupAction   = "function", -- one-time setup
+ UpdateAction  = "function", -- general action properties should be refreshed
+ PickupAction  = "function", -- pick up the action on the button and put on the cursor
+ PlaceAction   = "function", -- place the action on the cursor
+ UpdateTooltip = "function", -- update the tooltip with the action's info
+}
 
+-- The Display interface defines the "look and feel" of the action button. It should define the
+-- actual widgets and widget layout and provide methods to update the display.
+-- ReAction implementations must provide this interface (implicitly or explicitly).
+-- Note that ReAction implementations may also require additional display interfaces to be supported.
+--
+-- Also note: the class 'new' method must take *only* the primary button ID as an argument.
+local IDisplay = AceOO.Interface {
+  SetupDisplay       = "function", -- SetupDisplay(buttonName), one-time setup
+  UpdateDisplay      = "function", -- UpdateDisplay(), general display state should be refreshed
+  TempShow           = "function", -- TempShow(visible), calls to this can be nested so keep track.
+  GetActionFrame     = "function", -- f = GetActionFrame(), return a frame derived from SecureActionButtonTemplate (note: this is inherited unimplemented from ReBar.IButton)
+  GetBaseButtonSize  = "function", -- sz = GetBaseButtonSize(), return size in pixels of the nominal button (square)
+  DisplayID          = "function", -- DisplayID(id), show the action ID (or equivalent). Pass nil to hide.
+  DisplayHotkey      = "function", -- DisplayHotkey(keyText), set the hotkey display text
+}
 
 
--- ReActionButton is a class prototype object.
-ReActionButton = AceLibrary("AceOO-2.0").Class("AceEvent-2.0")
+----------------------------
+-- ReAction class definition
+----------------------------
 
--------------------
--- Class methods
--------------------
+ReAction = AceOO.Class("AceEvent-2.0", ReBar.IButton, IActionType, IDisplay)
+ReAction.virtual = true
+ReAction.IActionType = IActionType
+ReAction.IDisplay = IDisplay
 
--- In addition to supporting the 'new' creation method (in which case an action ID must be specified directly),
--- ReActionButton supports the 'acquire'/'release' creation method, which recycles objects and manages actionID assignment.
 
-function ReActionButton:acquire(parent, config, barIdx)
+-----------------------
+-- Static class members
+-----------------------
+
+ReAction.recycler = CreateFrame("Frame",nil,UIParent)
+ReAction.recycler:SetAllPoints(UIParent)
+ReAction.recycler:Hide()
+
+ReAction.buttonTypes = { }
+
+
+
+-----------------------
+-- Static class methods
+-----------------------
+
+function ReAction:AddButtonType( name, class, maxIDs )
+  self.buttonTypes[name] = { subtype = class, maxIDs = maxIDs }
+end
+
+function ReAction:GetButtonType( name )
+  if name then
+    local t = self.buttonTypes[name]
+    if t then
+      return t.subtype, t.maxIDs
+    end
+  end
+end
+
+function ReAction:GetButtonTypeList()
+  local list = { }
+  for name, _ in pairs(self.buttonTypes) do
+    table.insert(list,name)
+  end
+  return list
+end
+
+function ReAction:GetAvailableID( subtype, hint )
+  local class, maxIDs = self:GetButtonType(subtype)
+
+  -- store the list of action buttons in use in the button type class factory
+  if class._idTbl == nil then class._idTbl = { } end
+  local t = class._idTbl
   local id = nil
-  for i = 1, MAX_ACTIONS do
-    if actionButtonTbl[i] == nil or actionButtonTbl[i].inUse == false then
+  
+  -- find lowest ID not in use
+  for i = 1, maxIDs do
+    if t[i] == nil or t[i].inUse == false then
       id = i
       break
     end
   end
 
-  if id == nil then return nil end  -- all buttons and action ids are in use
+  if id == nil then return nil end  -- all action ids are in use
 
-  local hint = config.actionIDs[barIdx]
-  if hint and (actionButtonTbl[hint] == nil or actionButtonTbl[hint].inUse == false) then
+  -- if a hint is given, see if that one is free instead
+  if hint and (t[hint] == nil or t[hint].inUse == false) then
     id = hint
   end
 
-  if actionButtonTbl[id] == nil then 
-    actionButtonTbl[id] = { }
+  if t[id] == nil then 
+    t[id] = { }
   end
-  local t = actionButtonTbl[id]
+  t[id].inUse = true
 
-  t.inUse = true
-  if t.button then
-    t.button:Configure(parent,config,barIdx)
-  else
-    t.button = self:new(parent,config,barIdx,id)
+  return id, t[id]
+end
+
+function ReAction:Acquire(config, barIdx, pages, buttonsPerPage)
+  local btnType = self:GetButtonType(config.subtype)
+  if not btnType then
+    error("ReAction: Unknown button type specified.")
+  end
+  pages = pages or 1
+  
+  local ids = { }
+  local primary = nil
+
+  for i = 1, pages do
+    local hint = config.ids[barIdx] and config.ids[barIdx][i]
+    if hint == nil and i > 1 and ids[i-1] then
+      hint = ids[i-1] + (buttonsPerPage or 0)
+    end
+    local id, p = self:GetAvailableID(config.subtype, hint)
+    if id == nil then
+      break
+    end
+    primary = primary or p
+    if id then
+      ids[i] = id
+    end
   end
 
-  -- fix screwy config with overlapping IDs
-  config.actionIDs[barIdx] = id
-  return t.button
+  if primary then
+    if not primary.button then
+      primary.button = btnType:new(ids[1])
+    end
+    if primary.button then
+      config.ids[barIdx] = ids
+      primary.button:Configure(config,barIdx)
+    end
+  end
+
+  return primary and primary.button
 end
 
-function ReActionButton:release( b )
+function ReAction:Release( b )
   if b then
-    actionButtonTbl[b:GetActionID()].inUse = false
+    for i = 1, #b.config.ids[b.barIdx] do
+      local id = b:GetID(i)
+      if id then
+        b.class._idTbl[id].inUse = false
+      end
+    end
+    b.config = nil
     b:Recycle()
   end
 end
 
-
-
-function ReActionButton:ShowAllActionIDs()
-  for _, b in ipairs(actionButtonTbl) do
-    b:ShowActionID()
+function ReAction:ShowAllIds()
+  for _, t in pairs(self.buttonTypes) do
+    if t.subtype._idTbl then
+      for _, tbl in pairs(t.subtype._idTbl) do
+        if tbl.button then tbl.button:DisplayID(tbl.button:GetID()) end
+      end
+    end
   end
+  self.showIDs_ = true
 end
 
-function ReActionButton:HideAllActionIDs()
-  for _, b in ipairs(actionButtonTbl) do
-    b:HideActionID()
+function ReAction:HideAllIds()
+  for _, t in pairs(self.buttonTypes) do
+    if t.subtype._idTbl then
+      for _, tbl in pairs(t.subtype._idTbl) do
+        if tbl.button then tbl.button:DisplayID(nil) end
+      end
+    end
   end
+  self.showIDs_ = false
 end
 
 
 ----------------------
 -- Instance methods
 ----------------------
-function ReActionButton.prototype:init( parentFrame, config, barIdx, id )
-  ReActionButton.super.prototype.init(self)
 
-  -- create the button widget
-  self.name = namePrefix..id
-  self.button = CreateFrame("CheckButton", self.name, parentFrame, "ReActionButtonTemplate")
-  
-  -- store references to the various sub-frames so we don't have to look it up all the time
-  self.frames      = {
-    hotkey         = _G[self.name.."HotKey"],
-    count          = _G[self.name.."Count"],
-    cooldown       = _G[self.name.."Cooldown"],
---    nCooldown      = _G[self.name.."CooldownNumeric"],
-    macro          = _G[self.name.."Name"],
-    icon           = _G[self.name.."Icon"],
-    border         = _G[self.name.."Border"],
-    normalTexture  = _G[self.name.."NormalTexture"],
-    flash          = _G[self.name.."Flash"],
-    actionID       = _G[self.name.."ActionID"],
-  }
+-- constructor
 
-  -- provide a reference back to this object for the frame to use in event handlers
-  self.button.rxnBtn = self
-
-  -- set the action ID
-  self:SetActionID(id)
-
-  -- register button with ReBinder for keybinding
-  ReBinder:AddKeybindTarget(self.button)
-
-  -- initialize
-  self:Configure(parentFrame, config, barIdx)
+function ReAction.prototype:init()
+  ReAction.super.prototype.init(self)
 end
 
-local function tcopy(t)
-  local r = { }
-  for k, v in pairs(t) do
-    r[k] = (type(v) == "table" and tcopy(v) or v)
-  end
-  return r
+
+-- ReBar.IButton interface
+
+function ReAction.prototype:BarUnlocked()
+  self:TempShow(true)
 end
 
-function ReActionButton.prototype:Recycle()
-  local b = self.button
-
-  self:SetKeyBinding(nil)
-  self:UpdateDisplay()
-  b:UnregisterAllEvents()
-  b:SetParent(ReActionButtonRecycleFrame)
-  b:ClearAllPoints()
-  b:SetPoint("TOPLEFT",0,0)
-  b:Hide()
-  self.config = tcopy(self.config) -- ew, but necessary
+function ReAction.prototype:BarLocked()
+  self:TempShow(false)
 end
 
-function ReActionButton.prototype:BarUnlocked()
-  self:ShowGridTmp()
-end
-
-function ReActionButton.prototype:BarLocked()
-  self:HideGridTmp()
-end
-
-
--- set the button location
-function ReActionButton.prototype:PlaceButton(point, x, y, sz)
-  local b = self.button
-  local scale = sz / 36
+function ReAction.prototype:PlaceButton(parent, point, x, y, sz)
+  local b = self:GetActionFrame()
+  local baseSize = self:GetBaseButtonSize()
+  local scale = baseSize and baseSize ~= 0 and ((sz or 1) / baseSize) or 1
+  if scale == 0 then scale = 1 end
   b:ClearAllPoints()
-  b:SetScale( scale )
+  b:SetParent(parent)
+  b:SetScale(scale)
   b:SetPoint(point,x/scale,y/scale)
 end
 
-
-function ReActionButton.prototype:ShowActionID()
-  self.frames.actionID:Show()
-end
-
-function ReActionButton.prototype:HideActionID()
-  self.frames.actionID:Hide()
-end
-
-
-
--- configuration and setup
-function ReActionButton.prototype:Configure( parentFrame, config, barIdx )
-  self.config   = config
-  self.barIdx   = barIdx
-  self.showGridTmp_ = 0
-
-  self.button:ClearAllPoints()
-  self.button:SetParent(parentFrame)
-
-  self:SetupAttributes()
-  self:RegisterStaticEvents()
-
-  self:ApplyLayout()
-  self:ApplyStyle()
-
-  self:UpdateDisplay()
-end
-
-function ReActionButton.prototype:SetupAttributes()
-  local b = self.button
-  b:SetAttribute("type", "action")
-	b:SetAttribute("shift-type*", ATTRIBUTE_NOOP)
-	b:SetAttribute("checkselfcast", true)
-	b:SetAttribute("useparent-unit", true)
-end
-
-function ReActionButton.prototype:RegisterStaticEvents()
-	self:RegisterEvent("PLAYER_ENTERING_WORLD")
-	self:RegisterEvent("ACTIONBAR_SLOT_CHANGED")
-	self:RegisterEvent("UPDATE_BINDINGS")
-  self:RegisterEvent("ACTIONBAR_SHOWGRID")
-  self:RegisterEvent("ACTIONBAR_HIDEGRID")
-end
-
-function ReActionButton.prototype:RegisterActionEvents()
-  self:RegisterEvent("ACTIONBAR_UPDATE_STATE")
-  self:RegisterEvent("ACTIONBAR_UPDATE_USABLE")
-  self:RegisterEvent("ACTIONBAR_UPDATE_COOLDOWN", "ACTIONBAR_UPDATE_USABLE")
-  self:RegisterEvent("UPDATE_INVENTORY_ALERTS", "ACTIONBAR_UPDATE_USABLE")
-  self:RegisterEvent("PLAYER_AURAS_CHANGED", "ACTIONBAR_UPDATE_USABLE")
-  self:RegisterEvent("PLAYER_TARGET_CHANGED", "ACTIONBAR_UPDATE_USABLE")
-  self:RegisterEvent("UNIT_INVENTORY_CHANGED")
-  self:RegisterEvent("CRAFT_SHOW")
-  self:RegisterEvent("CRAFT_CLOSE", "CRAFT_SHOW")
-  self:RegisterEvent("TRADE_SKILL_SHOW", "CRAFT_SHOW")
-  self:RegisterEvent("TRADE_SKILL_CLOSE", "CRAFT_SHOW")
-  self:RegisterEvent("PLAYER_ENTER_COMBAT", "CRAFT_SHOW")
-  self:RegisterEvent("PLAYER_LEAVE_COMBAT")
-  self:RegisterEvent("START_AUTOREPEAT_SPELL")
-  self:RegisterEvent("STOP_AUTOREPEAT_SPELL")
-
-  self.button:SetScript("OnUpdate", function() self:OnUpdate(arg1) end)
-  self.actionEventsRegistered = true
-end
-
-function ReActionButton.prototype:UnregisterActionEvents()
-  self:UnregisterEvent("ACTIONBAR_UPDATE_STATE")
-  self:UnregisterEvent("ACTIONBAR_UPDATE_USABLE")
-  self:UnregisterEvent("ACTIONBAR_UPDATE_COOLDOWN")
-  self:UnregisterEvent("UPDATE_INVENTORY_ALERTS")
-  self:UnregisterEvent("PLAYER_AURAS_CHANGED")
-  self:UnregisterEvent("PLAYER_TARGET_CHANGED")
-  self:UnregisterEvent("UNIT_INVENTORY_CHANGED")
-  self:UnregisterEvent("CRAFT_SHOW")
-  self:UnregisterEvent("CRAFT_CLOSE")
-  self:UnregisterEvent("TRADE_SKILL_SHOW")
-  self:UnregisterEvent("TRADE_SKILL_CLOSE")
-  self:UnregisterEvent("PLAYER_ENTER_COMBAT")
-  self:UnregisterEvent("PLAYER_LEAVE_COMBAT")
-  self:UnregisterEvent("START_AUTOREPEAT_SPELL")
-  self:UnregisterEvent("STOP_AUTOREPEAT_SPELL")
-
-  self.button:SetScript("OnUpdate", nil)
-  self.actionEventsRegistered = false
-end
-
-
--- event handlers
-function ReActionButton.prototype:ACTIONBAR_SLOT_CHANGED()
-  if arg1 == 0 or arg1 == self:GetActionID() then
-    self:UpdateDisplay()
-  end
-end
-
-function ReActionButton.prototype:PLAYER_ENTERING_WORLD()
-  self:UpdateDisplay()
-end
-
-function ReActionButton.prototype:UPDATE_BINDINGS()
-  self:UpdateDisplay()
-end
-
-function ReActionButton.prototype:ACTIONBAR_SHOWGRID()
-  self:ShowGridTmp()
-end
-
-function ReActionButton.prototype:ACTIONBAR_HIDEGRID()
-  self:HideGridTmp()
-end
-
-function ReActionButton.prototype:ACTIONBAR_UPDATE_STATE()
-  self:UpdateCheckedState()
-end
-
-function ReActionButton.prototype:ACTIONBAR_UPDATE_USABLE()
-  self:UpdateUsable()
-  self:UpdateCooldown()
-  self:ColorHotKey()
-end
-
-function ReActionButton.prototype:UNIT_INVENTORY_CHANGED()
-  if arg1 == "player" then
-    self:UpdateDisplay()
-  end
-end
-
-function ReActionButton.prototype:CRAFT_SHOW()
-  self:UpdateCheckedState()
-end
-
-function ReActionButton.prototype:PLAYER_ENTER_COMBAT()
-  if IsAttackAction(self:GetActionID()) then
-    self:StartFlash()
-  end
-end
-
-function ReActionButton.prototype:PLAYER_LEAVE_COMBAT()
-  if IsAttackAction(self:GetActionID()) then
-    self:StopFlash()
-  end
-end
-
-function ReActionButton.prototype:START_AUTOREPEAT_SPELL()
-  if IsAutoRepeatAction(self:GetActionID()) then
-    self:StartFlash()
-  end
-end
-
-function ReActionButton.prototype:STOP_AUTOREPEAT_SPELL()
-  if self:IsFlashing() and not IsAttackAction(self:GetActionID()) then
-    self:StopFlash()
-  end
-end
-
-
--- OnUpdate handler
-function ReActionButton.prototype:OnUpdate(elapsed)
-  local action = self:GetActionID()
-  local f = self.frames
-
-  -- handle flashing
-	if self:IsFlashing() then
-		self.flashtime = self.flashtime - elapsed
-		if self.flashtime <= 0 then
-			local overtime = -self.flashtime
-			if overtime >= ATTACK_BUTTON_FLASH_TIME then
-				overtime = 0
-			end
-			self.flashtime = ATTACK_BUTTON_FLASH_TIME - overtime
-
-			if f.flash:IsVisible() then
-        f.flash:Hide()
-      else
-        f.flash:Show()
-			end
-		end
-	end
-	
-	-- Handle range indicator
-	if self.rangeTimer then
-		self.rangeTimer = self.rangeTimer - elapsed
-		if self.rangeTimer <= 0 then
-      self:ColorHotKey()
-      self:UpdateUsable()
-			self.rangeTimer = TOOLTIP_UPDATE_TIME
-		end
-	end
-
-  -- handle toltip update
-  if self.tooltipTime then
-    self.tooltipTime = self.tooltipTime - elapsed
-    if self.tooltipTime <= 0 then
-      if GameTooltip:IsOwned(self.button) then
-        self:UpdateTooltip()
-      else
-        self.tooltipTime = nil
+function ReAction.prototype:SetPages( n )
+  n = tonumber(n)
+  local ids = self.config.ids[self.barIdx]
+  if n and n >= 1 then
+    -- note that as long as n >= 1 then id[1] will never be modified, which is what we want
+    -- because then the button frame ID would be out of sync with the static button name
+    while #ids < n do
+      local id = ReAction:GetAvailableID(self.config.subtype, ids[#ids] + #self.config.ids)
+      if id == nil then
+        break
       end
+      self:SetID( id, #ids + 1 )
+      table.insert(ids, id)
+    end
+    while #ids > n do
+      local id = table.remove(ids)
+      self:SetID( nil, #ids + 1 )
+      self.class._idTbl[id].inUse = false
     end
   end
 end
 
+-- Event handlers
 
+function ReAction.prototype:UPDATE_BINDINGS()
+  self:DisplayHotkey(self:GetKeyBindingText(nil, true))
+end
 
 
--- keybinding functions
-function ReActionButton.prototype:SetKeyBinding( k )
+-- Internal functions
+
+function ReAction.prototype:Recycle()
+  --self:SetKeyBinding(nil) -- TODO: only if we're using override bindings
+  self:UnregisterAllEvents()
+  
+  -- tuck the frame away
+  local b = self:GetActionFrame()
+  b:SetParent(ReAction.recycler)
+  b:ClearAllPoints()
+  b:SetPoint("TOPLEFT",0,0)
+  b:Hide()
+end
+
+function ReAction.prototype:Configure( config, barIdx )
+  self.config   = config
+  self.barIdx   = barIdx
+
+  local ids = config.ids[barIdx]
+  self:SetID(ids[1]) -- default id
+  for i = 1, #ids do
+    self:SetID(ids[i], i) -- paged ids
+  end
+  self:UpdateAction()
+  self:UpdateDisplay()
+  self:DisplayHotkey(self:GetKeyBindingText(nil, true))
+
+	self:RegisterEvent("UPDATE_BINDINGS")
+end
+
+function ReAction.prototype:SetKeyBinding( k, mouseBtn )
   if k == nil or kbValidate(k) then
-    local current = self:GetKeyBinding()
-  	ClearOverrideBindings(self.button)
+    local current = self:GetKeyBinding(mouseBtn)
+  	-- !!!TODO: do we need this?
+  	-- ClearOverrideBindings(self:GetActionFrame())
     if current then
       SetBinding(current,nil)
     end
     if k then
-      SetBindingClick(k, self.name, "LeftButton")
+      -- TODO: use OverrideBinding and store the keybinding in the profile.
+      SetBindingClick(k, self:GetActionFrame():GetName(), mouseBtn or "LeftButton")
     end
   end
 end
 
-function ReActionButton.prototype:GetKeyBinding()
-  return GetBindingKey("CLICK "..self.name..":LeftButton")
+function ReAction.prototype:GetKeyBinding( mouseBtn )
+  return GetBindingKey("CLICK "..self:GetActionFrame():GetName()..":"..(mouseBtn or "LeftButton"))
 end
 
--- action ID functions
-function ReActionButton.prototype:SetActionID( id )
-  self.actionID = tonumber(id) -- force data integrity
-  self:ApplyActionID()
+function ReAction.prototype:GetKeyBindingText( mouseBtn, abbrev )
+  local key = self:GetKeyBinding(mouseBtn)
+  local txt = key and GetBindingText(key, "KEY_", abbrev and 1) or ""
+
+  if txt and abbrev then
+    -- further abbreviate some key names
+    for pat, rep in pairs(keybindAbbreviations) do
+      txt = string.gsub(txt,pat,rep)
+    end
+  end
+  return txt
 end
 
-function ReActionButton.prototype:GetActionID()
-  return self.actionID
-end
-
-function ReActionButton.prototype:ApplyActionID()
-  local action = tonumber(self:GetActionID())
-  self.button:SetAttribute("action",action)
-  self.frames.actionID:SetText(action or "")
-end
-
-function ReActionButton.prototype:ShowActionID()
-  self.frames.actionID:Show()
-end
-
-function ReActionButton.prototype:HideActionID()
-  self.frames.actionID:Hide()
-end
-
-function ReActionButton:ShowAllActionIDs() -- class-wide function
-  for _, tbl in pairs(actionButtonTbl) do
-    if tbl.button then tbl.button:ShowActionID() end
-  end
-end
-
-function ReActionButton:HideAllActionIDs() -- class-wide function
-  for _, tbl in pairs(actionButtonTbl) do
-    if tbl.button then tbl.button:HideActionID() end
-  end
-end
-
-
--- action transfer functions
-function ReActionButton.prototype:ShouldPickupAction(mouseButton)
-	return IsShiftKeyDown() and not SecureButton_GetModifiedAttribute(self.button, "type", mouseButton)
-end
-
-function ReActionButton.prototype:ShowGridTmp()
-  self.showGridTmp_ = self.showGridTmp_ + 1
-  self:UpdateVisibility()
-end
-
-function ReActionButton.prototype:HideGridTmp()
-  self.showGridTmp_ = self.showGridTmp_ - 1
-  self:UpdateVisibility()
-end
-
-function ReActionButton.prototype:ShowGrid()
-  self.config.showGrid = true
-  self:UpdateVisibility()
-end
-
-function ReActionButton.prototype:HideGrid()
-  self.config.showGrid = false
-  self:UpdateVisibility()
-end
-
-
-
--- layout & style functions
-function ReActionButton.prototype:ApplyLayout()
-  local f = self.frames
-
-  if self.config.keyBindLoc then
-    local h = f.hotkey
-    local loc = self.config.keyBindLoc
-    local top = string.match(loc,"TOP")
-    local bottom = string.match(loc, "BOTTOM")
-    h:ClearAllPoints()
-    h:SetWidth(40)
-    h:SetPoint(top or bottom,0,top and 2 or -2)
-    local j
-    if string.match(loc,"LEFT") then
-      j = "LEFT"
-    elseif string.match(loc,"RIGHT") then
-      j = "RIGHT"
-    else
-      j = "CENTER"
-    end
-    h:SetJustifyH(j)
-  end
-
-  if self.config.stackCountLoc then
-    local c = f.count
-    local loc = self.config.stackCountLoc
-    local top = string.match(loc,"TOP")
-    local bottom = string.match(loc, "BOTTOM")
-    c:ClearAllPoints()
-    c:SetWidth(40)
-    c:SetPoint(top or bottom,0,top and 2 or -2)
-    local j
-    if string.match(loc,"LEFT") then
-      j = "LEFT"
-    elseif string.match(loc,"RIGHT") then
-      j = "RIGHT"
-    else
-      j = "CENTER"
-    end
-    c:SetJustifyH(j)
-  end
-
-  if self.config.showKeyBind then
-    f.hotkey:Show()
-  else
-    f.hotkey:Hide()
-  end
-    
-  if self.config.showStackCount then
-    f.count:Show()
-  else
-    f.count:Hide()
-  end
-
---[[
-  if self.config.showNumericCooldown then
-    f.nCooldown:Show()
-  else
-    f.nCooldown:Hide()
-  end
-]]
-
-  if self.config.showMacroName then
-    f.macro:Show()
-  else
-    f.macro:Hide()
-  end
-end
-
-function ReActionButton.prototype:ApplyStyle()
-  local f = self.frames
-  -- for now, just a static style
-  f.hotkey:SetFontObject(NumberFontNormal)
-  f.count:SetFontObject(NumberFontNormalYellow)
-end
-
-
-
--- start/stop flashing
-function ReActionButton.prototype:StartFlash()
-  self.flashing = true
-  self.flashtime = 0
-  self:UpdateCheckedState()
-end
-
-function ReActionButton.prototype:StopFlash()
-  self.flashing = false
-  self.frames.flash:Hide()
-  self:UpdateCheckedState()
-end
-
-function ReActionButton.prototype:IsFlashing()
-  return self.flashing
-end
-
-
-
-
-
--- set the tooltip
-function ReActionButton.prototype:SetTooltip()
-  GameTooltip_SetDefaultAnchor(GameTooltip, self.button)
+function ReAction.prototype:SetTooltip()
+  GameTooltip_SetDefaultAnchor(GameTooltip, self:GetActionFrame())
   self:UpdateTooltip()
 end
 
-function ReActionButton.prototype:ClearTooltip()
+function ReAction.prototype:ClearTooltip()
   tooltipTime = nil
   GameTooltip:Hide()
 end
-
-
-
--- colorize the hotkey
-function ReActionButton.prototype:ColorHotKey()
-  local action = self:GetActionID()
-  local c = hotKeyDefaultColor
-
-  if action and HasAction(action) then
-    if IsActionInRange(action) == 0 then
-      c = hotKeyOutOfRangeColor
-    elseif self.config.keyBindColorCode then
-      local modKey = string.match( self.frames.hotkey:GetText() or "", "([ACS])%-")
-      c = modKey and hotKeyModifierColors[modKey] or c
-    end
-  else
-    c = hotKeyDisabledColor
-  end
-
-  self.frames.hotkey:SetTextColor(c.r, c.g, c.b)
-end
-
-
-
-
--- display update functions
-function ReActionButton.prototype:UpdateDisplay()
-  self:UpdateIcon()
-  self:UpdateHotkey()
-  self:UpdateCount()
-  self:UpdateMacroText()
-  self:UpdateUsable()
-  self:UpdateCooldown()
-  self:UpdateFlash()
-  self:UpdateEvents()
-  self:UpdateVisibility()
-  self:UpdateTooltip()
-end
-
-function ReActionButton.prototype:UpdateIcon()
-  local f = self.frames
-  local b = self.button
-
-  local action = self:GetActionID()
-  local texture = action and GetActionTexture(action)
-
-  if action and texture then
-    f.icon:SetTexture(texture)
-    f.icon:Show()
-    self.rangeTimer = -1
-    b:SetNormalTexture("Interface\\Buttons\\UI-Quickslot2")
-  else
-    f.icon:Hide()
-    f.cooldown:Hide()
-    self.rangeTimer = nil
-    b:SetNormalTexture("Interface\\Buttons\\UI-Quickslot")
-  end
-
-  self:UpdateCheckedState()
-
-  -- Add a green border if action is an equipped item
-  if action and IsEquippedAction(action) then
-    local c = equippedActionBorderColor
-    f.border:SetVertexColor(c.r, c.g, c.b, c.a or 1)
-    f.border:Show()
-  else
-    f.border:Hide()
-  end
-end
-
-function ReActionButton.prototype:UpdateCheckedState()
-  local action = self:GetActionID()
-	if action and (IsCurrentAction(action) or IsAutoRepeatAction(action)) then
-		self.button:SetChecked(1)
-	else
-		self.button:SetChecked(0)
-	end
-end
-
-
-function ReActionButton.prototype:UpdateHotkey()
-  local action = self:GetActionID()
-  local b = self.button
-  local f = self.frames
-  local key = self:GetKeyBinding()
-  local txt = GetBindingText(key, "KEY_",1)
-
-  -- abbreviate long key names
-  for pat, rep in pairs(keybindAbbreviations) do
-    txt = string.gsub(txt,pat,rep)
-  end
-   
-  if txt then
-    f.hotkey:SetText(string.upper(txt))
-    self:ColorHotKey()
-  else
-    f.hotkey:SetText("")
-  end
-end
-
-function ReActionButton.prototype:UpdateCount()
-  local action = self:GetActionID()
-	if action and (IsConsumableAction(action) or IsStackableAction(action)) then
-		self.frames.count:SetText(GetActionCount(action))
-	else
-		self.frames.count:SetText("")
-	end
-end
-
-function ReActionButton.prototype:UpdateMacroText()
-  local action = self:GetActionID()
-	self.frames.macro:SetText(action and GetActionText(action) or "")
-end
-
-function ReActionButton.prototype:UpdateUsable()
-  local f = self.frames
-  local action = self:GetActionID()
-	local isUsable, notEnoughMana
-  if action then
-    isUsable, notEnoughMana = IsUsableAction(action)
-  end
-	if isUsable then
-    local c = actionUsableColor
-    if IsActionInRange(action) == 0 then
-      c = actionOutOfRangeColor
-    else
-  		f.normalTexture:SetVertexColor(c.r, c.g, c.b, c.a)
-    end
-		f.icon:SetVertexColor(c.r, c.g, c.b, c.a)
-	elseif notEnoughMana then
-    local c = actionNotEnoughManaColor
-		f.icon:SetVertexColor(c.r, c.g, c.b, c.a)
-		f.normalTexture:SetVertexColor(c.r, c.g, c.b, c.a)
-  else
-    local c = actionNotUsableColor
-		f.icon:SetVertexColor(c.r, c.g, c.b, c.a)
-		f.normalTexture:SetVertexColor(1.0, 1.0, 1.0)
-	end
-end
-
-function ReActionButton.prototype:UpdateCooldown()
-  local action = self:GetActionID()
-  if action then
-  	local start, duration, enable = GetActionCooldown(self:GetActionID())
-  	CooldownFrame_SetTimer(self.frames.cooldown, start, duration, enable)
-    -- do numeric cooldown stuff here
-  end
-end
-
-function ReActionButton.prototype:UpdateFlash()
-  local b = self.button
-  local action = self:GetActionID()
-	if action and ((IsAttackAction(action) and IsCurrentAction(action)) or IsAutoRepeatAction(action)) then
-    self:StartFlash()
-	else
-    self:StopFlash()
-	end
-end
-
-function ReActionButton.prototype:UpdateVisibility()
-  local action = self:GetActionID()
-  local b = self.button
-
-  if b:GetAttribute("statehidden") then
-    b:Hide()
-  elseif action and HasAction(action) then
-    b:GetNormalTexture():SetAlpha(1.0)
-    b:Show()
-  elseif self.showGridTmp_ > 0 or self.config.showGrid then
-    b:GetNormalTexture():SetAlpha(0.5)
-    self.frames.cooldown:Hide()
-    b:Show()
-  else
-    b:Hide()
-  end
-end
-
-function ReActionButton.prototype:UpdateEvents()
-  local action = self:GetActionID()
-	if action and HasAction(action) then
-    self:RegisterActionEvents()
-  elseif self.actionEventsRegistered then
-    self:UnregisterActionEvents()
-	end
-end
-
-function ReActionButton.prototype:UpdateTooltip()
-  local action = self:GetActionID()
-	if GameTooltip:IsOwned(self.button) and action and GameTooltip:SetAction(action) then
-		self.tooltipTime = TOOLTIP_UPDATE_TIME
-	else
-		self.tooltipTime = nil
-	end
-end
-
-
-