changeset 7:f920db5fc6b1

version 0.3
author Flick <flickerstreak@gmail.com>
date Tue, 20 Mar 2007 21:25:29 +0000
parents 2da5089ab7ff
children c05fd3e18b4f
files Bindings.xml Button.xml Buttons.lua Defaults.lua Options.lua README.html ReAction.toc classes/ReAction.lua classes/ReAction_ActionDisplay.lua classes/ReAction_ActionType.lua classes/ReAction_ColorScheme.lua classes/ReAction_PetActionDisplay.lua classes/ReAction_PetActionType.lua classes/ReAnchor.lua classes/ReAnchor.xml classes/ReBar.lua classes/ReBar.xml classes/ReBound.lua classes/ReBound.xml libs/AceLibrary/AceLibrary.lua libs/AceLocale-2.2/AceLocale-2.2.lua locale-enUS.lua main.lua
diffstat 23 files changed, 4104 insertions(+), 1606 deletions(-) [+]
line wrap: on
line diff
--- a/Bindings.xml	Tue Mar 20 21:20:20 2007 +0000
+++ b/Bindings.xml	Tue Mar 20 21:25:29 2007 +0000
@@ -1,8 +1,8 @@
 <Bindings>
   <Binding name="REACTION_TOGGLELOCK" header="REACTION">
-    ReAction:ToggleLocked()
+    ReActionAddOn:ToggleLocked()
   </Binding>
-  <Binding name="REBINDER_TOGGLEBINDINGMODE">
-    ReBinder:ToggleEnabled()
+  <Binding name="REBOUND_TOGGLEBINDINGMODE">
+    ReBound:ToggleEnabled()
   </Binding>
 </Bindings>
--- a/Button.xml	Tue Mar 20 21:20:20 2007 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,143 +0,0 @@
-<Ui xmlns="http://www.blizzard.com/wow/ui/" 
-  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-  xsi:schemaLocation="http://www.blizzard.com/wow/ui/..\FrameXML\UI.xsd">
-
-
-  <CheckButton name="ReActionButtonTemplate" inherits="ActionButtonTemplate, SecureActionButtonTemplate" virtual="true" enableMouse="true">
-		<Layers>
-		  <Layer level="ARTWORK">
-        <FontString name="$parentActionID" inherits="NumberFontNormalSmall" hidden="true" text="">
-          <Anchors>
-            <Anchor point="BOTTOMLEFT"/>
-          </Anchors>
-          <Color r="1.0" g="0.82" b="0.0"/>
-        </FontString>
-      </Layer>
-		</Layers>
-		<Scripts>
-			<OnLoad>
-        this:RegisterForDrag("LeftButton", "RightButton")
-        this:RegisterForClicks("AnyUp")
-			</OnLoad>
-			<PostClick>
-        if this.rxnBtn:ShouldPickupAction(button) then
-					PickupAction(this.rxnBtn:GetActionID())
-				end
-        this.rxnBtn:UpdateCheckedState()
-			</PostClick>
-			<OnDragStart>
-				if LOCK_ACTIONBAR ~= "1" then
-					PickupAction(this.rxnBtn:GetActionID())
-          this.rxnBtn:UpdateDisplay()
-				end
-			</OnDragStart>
-			<OnReceiveDrag>
-				if LOCK_ACTIONBAR ~= "1" then
-					PlaceAction(this.rxnBtn:GetActionID())
-          this.rxnBtn:UpdateDisplay()
-				end
-			</OnReceiveDrag>
-			<OnEnter>
-        this.rxnBtn:SetTooltip()
-			</OnEnter>
-			<OnLeave>
-				this.rxnBtn:ClearTooltip()
-			</OnLeave>
-		</Scripts>
-  </CheckButton>
-
-  <Frame name="ReActionButtonRecyler" parent="UIParent" hidden="true" setAllPoints="true" enableMouse="false"/>
-  
-  <Frame name="ReActionKeybindFrame" frameStrata="DIALOG" enableMouse="true" enableKeyboard="true" parent="UIParent" hidden="true">
-    <Size>
-      <AbsDimension x="160" y="52"/>
-    </Size>
-    <Backdrop file="Interface\Tooltips\UI-Tooltip-Background" edgeFile="Interface\Tooltips\UI-Tooltip-Border" tile="true">
-      <EdgeSize>
-        <AbsValue val="16"/>
-      </EdgeSize>
-      <TileSize>
-        <AbsValue val="16"/>
-      </TileSize>
-      <BackgroundInsets>
-        <AbsInset left="0" right="0" top="0" bottom="0"/>
-      </BackgroundInsets>
-    </Backdrop>
-    <Layers>
-      <Layer level="BACKGROUND">
-        <Texture>
-          <Color r="0" g="0" b="0" a="0.5"/>
-        </Texture>
-      </Layer>
-      <Layer level="ARTWORK">
-        <FontString inherits="GameFontNormal" text="Set Keybinding">
-          <Anchors>
-            <Anchor point="TOP">
-              <Offset>
-                <AbsDimension x="0" y="-5"/>
-              </Offset>
-            </Anchor>
-          </Anchors>
-        </FontString>
-      </Layer>
-    </Layers>
-    <Frames>
-      <Button name="$parentButton" inherits="UIPanelButtonTemplate2" enableKeyboard="true" enableMouse="true">
-        <Size>
-          <AbsDimension x="120" y="22"/>
-        </Size>
-        <Anchors>
-          <Anchor point="BOTTOM" relativePoint="BOTTOM">
-            <Offset>
-              <AbsDimension x="-2" y="5"/>
-            </Offset>
-          </Anchor>
-        </Anchors>
-        <Scripts>
-          <OnLoad>
-            this:RegisterForClicks("AnyUp")
-          </OnLoad>
-          <OnClick>
-            if this.selected and this.keybindTarget then
-              this.keybindTarget:HandleKeybindAssign(this, nil, arg1)
-            else
-              this.selected = not(this.selected)
-            end
-            if this.selected and this.keybindTarget then
-              this:LockHighlight()
-              this:SetScript("OnKeyDown", 
-                function() 
-                  this.keybindTarget:HandleKeybindAssign(this,arg1)
-                  if not this.selected then 
-                    this:UnlockHighlight()
-                    this:SetScript("OnKeyDown", nil)
-                  end
-                end )
-            else
-              this:UnlockHighlight()
-              this:SetScript("OnKeyDown", nil)
-            end
-          </OnClick>
-          <OnShow>
-            this.keybindTarget.button:SetChecked(1)
-            this.keybindTarget:UpdateDisplay()
-          </OnShow>
-          <OnHide>
-            if this.keybindTarget then
-              this.keybindTarget.button:SetChecked(0)
-              this.keybindTarget:UpdateDisplay()
-            end
-          </OnHide>
-        </Scripts>
-        <NormalFont    inherits="GameFontHighlightSmall"/>
-        <HighlightFont inherits="GameFontHighlightSmall"/>
-      </Button>
-    </Frames>
-    <Scripts>
-      <OnLoad>
-        table.insert(UISpecialFrames, this:GetName())  -- auto-hide on escape
-      </OnLoad>
-    </Scripts>
-  </Frame>
-
-</Ui>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Buttons.lua	Tue Mar 20 21:25:29 2007 +0000
@@ -0,0 +1,271 @@
+-- Buttons.lua
+--
+-- Defines button types for use with the ReAction AddOn
+--
+
+local AceOO = AceLibrary("AceOO-2.0")
+
+local Action = AceOO.Class(
+  ReAction, 
+  ReAction.ActionType, 
+  ReAction.ActionDisplay, 
+  ReAction.DefaultColorScheme
+)
+
+local PetAction = AceOO.Class(
+  ReAction, 
+  ReAction.PetActionType, 
+  ReAction.PetActionDisplay, 
+  ReAction.DefaultColorScheme
+)
+
+
+ReAction:AddButtonType( "Action",    Action,    120 )
+ReAction:AddButtonType( "Pet Action", PetAction, 10  )
+
+
+
+-----------------------
+-- static class members
+-----------------------
+Action.defaultProfile = {
+  type = "ReAction",
+  subtype = "Action",
+  ids = { },
+  selfcast = nil,
+  keyBindLoc = "TOPRIGHT",
+  stackCountLoc = "BOTTOMRIGHT",
+  showKeyBind = true,
+  showStackCount = true,
+  showMacroText = true,
+  showGrid = false,
+  showBorder = true,
+  keyBindColorCode = false,
+}
+
+PetAction.defaultProfile = {
+  type = "ReAction",
+  subtype = "Pet Action",
+  ids = { },
+  keyBindLoc = "TOPRIGHT",
+  showKeyBind = false,
+  showGrid = false,
+  showBorder = true,
+  keyBindColorCode = false,
+}
+
+
+
+-----------------------
+-- static class methods
+-----------------------
+function Action:GetDefaultProfile()
+  return self.defaultProfile
+end
+
+function PetAction:GetDefaultProfile()
+  return self.defaultProfile
+end
+
+
+
+local labelPlacementOptions = { 
+  "TOP", 
+  "BOTTOM", 
+  "LEFT", 
+  "RIGHT", 
+  "TOPLEFT", 
+  "TOPRIGHT", 
+  "BOTTOMLEFT", 
+  "BOTTOMRIGHT" 
+}
+
+
+function Action:GenerateOptionsTable( config, buttonListFunc )
+
+  local function refresh()
+    for _, b in pairs(buttonListFunc()) do
+      b:UpdateAction()
+      b:UpdateTooltip()
+      b:UpdateDisplay()
+    end
+  end
+
+  return {
+    type = "group",
+    args = {
+      selfcast = {
+        type = "text",
+        name = "Self Cast",
+        desc = "Choose a modifier key to hold down or an alternate button to click to self-cast spells. "..
+               "'default' uses the settings in the Interface Options Advanced panel (use this to achieve 'smart self cast'). "..
+               "'right-click' self-casting is not supported for multi-page bars.",
+        get  = function() return config.selfcast or "default" end,
+        set  = function(opt) 
+          if opt == "default" then opt = nil end
+          config.selfcast = opt
+          for _, b in pairs(buttonListFunc()) do
+            b:UpdateSelfcast()
+          end
+        end,
+        validate = { "default", "none", "alt", "ctrl", "shift", "right-click" },
+      },
+
+      keyloc = {
+        type = "text",
+        name = "Hotkey Location",
+        desc = "Sets hotkey location",
+        get  = function() return config.keyBindLoc end,
+        set  = function(loc) config.keyBindLoc = loc ; refresh() end,
+        validate = labelPlacementOptions,
+      },
+      
+      stackloc = {
+        type = "text",
+        name = "Stack Count Location",
+        desc = "Sets stack count location",
+        get  = function() return config.stackCountLoc end,
+        set  = function(loc) config.stackCountLoc = loc ; refresh() end,
+        validate = labelPlacementOptions,
+      },
+
+      showkeybind = {
+        type = "toggle",
+        name = "Show Hotkey",
+        desc = "Toggle show/hide hot key labels",
+        get  = function() return config.showKeyBind end,
+        set  = function() config.showKeyBind = not config.showKeyBind ; refresh() end,
+      },
+
+      showstackcount = {
+        type = "toggle",
+        name = "Show Stack Count",
+        desc = "Toggle show/hide stack count labels",
+        get  = function() return config.showStackCount end,
+        set  = function() config.showStackCount = not config.showStackCount ; refresh() end,
+      },
+
+      showmacrotext = {
+        type = "toggle",
+        name = "Show Macro Names",
+        desc = "Toggle show/hide macro name labels",
+        get  = function() return config.showMacroText end,
+        set  = function() config.showMacroText = not config.showMacroText ; refresh() end,
+      },
+
+      showgrid = {
+        type = "toggle",
+        name = "Always Show Buttons",
+        desc = "Show button placeholders when no action is assigned or on the cursor. Note that buttons are always shown when bars are unlocked.",
+        get  = function() return config.showGrid end,
+        set  = function() config.showGrid = not config.showGrid ; refresh() end,
+      },
+
+      colorhotkeys = {
+        type = "toggle",
+        name = "Colorize Hotkeys",
+        desc = "Toggles coloring hotkeys based on the modifier key. Out-of-range coloring is always enabled.",
+        get  = function() return config.keyBindColorCode end,
+        set  = function() config.keyBindColorCode = not config.keyBindColorCode ; refresh() end,
+      },
+
+      --[[
+      hideborder = {
+        type = "toggle",
+        name = "Hide Border",
+        desc = "Toggles hiding of the button border frame.",
+        get  = function() return not config.showBorder end,
+        set  = function() config.showBorder = not config.showBorder ; refresh() end,
+      },]]
+    }
+  }
+end
+
+
+function PetAction:GenerateOptionsTable( config, buttonListFunc )
+
+  local function refresh()
+    for _, b in pairs(buttonListFunc()) do
+      b:UpdateAction()
+      b:UpdateTooltip()
+      b:UpdateDisplay()
+    end
+  end
+
+  return {
+    type = "group",
+    args = {
+      keyloc = {
+        type = "text",
+        name = "Hotkey Location",
+        desc = "Sets hotkey location",
+        get  = function() return config.keyBindLoc end,
+        set  = function(loc) config.keyBindLoc = loc ; refresh() end,
+        validate = { "TOP", "BOTTOM", "LEFT", "RIGHT", "TOPLEFT", "TOPRIGHT", "BOTTOMLEFT", "BOTTOMRIGHT" },
+      },
+      
+      showkeybind = {
+        type = "toggle",
+        name = "Show Hotkey",
+        desc = "Toggle show/hide hot key labels",
+        get  = function() return config.showKeyBind end,
+        set  = function() config.showKeyBind = not config.showGrid ; refresh() end,
+      },
+
+      showgrid = {
+        type = "toggle",
+        name = "Always Show Buttons",
+        desc = "Show button placeholders when no action is assigned or on the cursor. Note that buttons are always shown when bars are unlocked.",
+        get  = function() return config.showGrid end,
+        set  = function() config.showGrid = not config.showGrid ; refresh() end,
+      },
+
+      colorhotkeys = {
+        type = "toggle",
+        name = "Colorize Hotkeys",
+        desc = "Toggles coloring hotkeys based on the modifier key. Out-of-range coloring is always enabled.",
+        get  = function() return config.keyBindColorCode end,
+        set  = function() config.keyBindColorCode = not config.keyBindColorCode ; refresh() end,
+      },
+
+      --[[
+      hideborder = {
+        type = "toggle",
+        name = "Hide Border",
+        desc = "Toggles hiding of the button border frame.",
+        get  = function() return not config.showBorder end,
+        set  = function() config.showBorder = not config.showBorder ; refresh() end,
+      },]]
+    }
+  }
+end
+
+
+
+----------------------
+-- Instance methods
+----------------------
+function Action.prototype:init( id )
+  Action.super.prototype.init(self)
+  
+  self:SetupDisplay("ReActionButton"..id)
+  self:SetupAction()
+
+  -- register button with ReBound for keybinding
+  if ReBound then
+    ReBound:AddKeybindTarget(self:GetActionFrame())
+  end
+end
+
+function PetAction.prototype:init( id )
+  Action.super.prototype.init(self)
+  
+  self:SetupDisplay("ReActionPetButton"..id)
+  self:SetupAction()
+
+  -- register button with ReBound for keybinding
+  if ReBound then
+    ReBound:AddKeybindTarget(self:GetActionFrame())
+  end
+end
+
--- a/Defaults.lua	Tue Mar 20 21:20:20 2007 +0000
+++ b/Defaults.lua	Tue Mar 20 21:25:29 2007 +0000
@@ -1,79 +1,231 @@
+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
+end
+
 -- ReAction default variable tables
+local defaultActionConfig    = ReAction:GetButtonType("Action"):GetDefaultProfile()
+local defaultPetActionConfig = ReAction:GetButtonType("Pet Action"):GetDefaultProfile()
 
 
--- configuration options for ReActionButton groups
-ReActionButtonConfigDefaults = {
-  type = "ReActionButton",
-  actionIDs = { },
-  keyBindLoc = "TOPLEFT",
-  keyBindColorCode = true,
-  stackCountLoc = "BOTTOMRIGHT",
-  showKeyBind = true,
-  showNumericCooldown = false,
-  showStackCount = true,
-  showMacroName = true,
-  showGrid = true,
+-- default saved variables
+ReAction_DefaultProfile = {
+  hideArt = false,
+
+  bars = { }
 }
 
-ReActionBarConfigDefaults = {
-  visibility = true,
-  size = 36,
-  spacing = 6,
-  rows = 1,
-  columns = 12,
-  pages = 1,
-  opacity = 100,
-  anchor = { 
-    to = "UIParent",
-    point = "CENTER",
-    relPoint = "CENTER",
-    x = 0,
-    y = 0
-  },
-  btnConfig = ReActionButtonConfigDefaults
-}
-
-
-
--- default variables
-ReActionProfileDefaults = {
-
-  -- global options
-  hideArt = false,
-
-
-  -- default layout replicates Blizzard layout
-  bars = {
-
-    -- main paged action bar
-    [1] = {
-      visibility = true,
+ReAction_DefaultBarConfig = {
+  ["ReAction"] = {
+    ["Action"] = {
+      visible = true,
       size = 36,
       spacing = 6,
       rows = 1,
       columns = 12,
-      pages = 5,
+      pages = nil,
       opacity = 100,
-      anchor = { 
-        to = "UIParent",
-        point = "BOTTOMRIGHT",
-        relPoint = "BOTTOM",
-        x = 2,
-        y = -4
+      anchor = {
+        frame = "UIParent",
+        point = "CENTER",
+        relPoint = "CENTER",
+        x = 0,
+        y = 0,
       },
-      btnConfig = {
-        type = "ReActionButton",
-        actionIDs = { },
-        keyBindLoc = "TOPLEFT",
-        keyBindColorCode = true,
-        stackCountLoc = "BOTTOMRIGHT",
-        showKeyBind = true,
-        showNumericCooldown = false,
-        showStackCount = true,
-        showMacroName = true,
-        showGrid = true,
-      }
+      btnConfig = tcopy(defaultActionConfig)
+    },
+    ["Pet Action"] = {
+      visible = true,
+      parent = "PetActionBarFrame",
+      size = 30,
+      spacing = 8,
+      rows = 1,
+      columns = 10,
+      pages = nil,
+      opacity = 100,
+      anchor = {
+        frame = "UIParent",
+        point = "CENTER",
+        relPoint = "CENTER",
+        x = 0,
+        y = 0,
+      },
+      btnConfig = tcopy(defaultPetActionConfig),
+    }
+  },
+}
 
+-- startup layout replicates Blizzard layout (only set on first-run or reset)
+ReAction_DefaultBlizzardBars = {
+
+  -- main paged action bar
+  [1] = {
+    visible = true,
+    size = 36,
+    spacing = 6,
+    rows = 1,
+    columns = 12,
+    growLeft = false,
+    growUp = false,
+    columnMajor = false,
+    pages = {
+      n = 6,
+      showControls = true,
+      controlsLoc = "Blizzard",
+      autoStanceSwitch = true,   -- priests will get a shadowform bar switch, unlike blizzard's
+      autoStealthSwitch = true,  -- this is different from blizzard's layout, only for druids
     },
+    opacity = 100,
+    anchor = { 
+      frame = "MainMenuBarArtFrame",
+      point = "BOTTOMLEFT",
+      relPoint = "BOTTOMLEFT",
+      x = 3,
+      y = 0,
+    },
+    btnConfig = tcopy(defaultActionConfig),
+  },
+
+  -- multibar right
+  [2] = {
+    visible = false,
+    size = 36,
+    spacing = 6,
+    rows = 12,
+    columns = 1,
+    growLeft = true,
+    growUp = false,
+    columnMajor = true,
+    pages = nil,
+    opacity = 100,
+    anchor = { 
+      frame = "UIParent",
+      point = "BOTTOMRIGHT",
+      relPoint = "BOTTOMRIGHT",
+      x = -4,
+      y = 95,
+    },
+    btnConfig = tcopy(defaultActionConfig),
+  },
+
+  -- multibar left
+  [3] = {
+    visible = false,
+    size = 36,
+    spacing = 6,
+    rows = 12,
+    columns = 1,
+    growLeft = true,
+    growUp = false,
+    columnMajor = true,
+    pages = nil,
+    opacity = 100,
+    anchor = { 
+      frame = "UIParent",
+      point = "BOTTOMRIGHT",
+      relPoint = "BOTTOMRIGHT",
+      x = -51,
+      y = 95,
+    },
+    btnConfig = tcopy(defaultActionConfig),
+  },
+
+  -- multibar bottom right
+  [4] = {
+    visible = false,
+    size = 36,
+    spacing = 6,
+    rows = 1,
+    columns = 12,
+    growLeft = false,
+    growUp = false,
+    columnMajor = false,
+    pages = nil,
+    opacity = 100,
+    anchor = { 
+      frame = "MainMenuBarArtFrame",
+      point = "BOTTOMLEFT",
+      relPoint = "BOTTOMLEFT",
+      x = 514,
+      y = 53,
+    },
+    btnConfig = tcopy(defaultActionConfig),
+  },
+
+  -- multibar bottom left
+  [5] = {
+    visible = false,
+    size = 36,
+    spacing = 6,
+    rows = 1,
+    columns = 12,
+    growLeft = false,
+    growUp = false,
+    columnMajor = false,
+    pages = nil,
+    opacity = 100,
+    anchor = { 
+      frame = "MainMenuBarArtFrame",
+      point = "BOTTOMLEFT",
+      relPoint = "BOTTOMLEFT",
+      x = 3,
+      y = 53,
+    },
+    btnConfig = tcopy(defaultActionConfig),
+  },
+
+  -- pet action bar
+  [6] = {
+    visible = true,
+    parent = "PetActionBarFrame",
+    size = 30,
+    spacing = 8,
+    rows = 1,
+    columns = 10,
+    growLeft = false,
+    growUp = false,
+    columnMajor = false,
+    pages = nil,
+    opacity = 100,
+    anchor = {
+      frame = "PetActionBarFrame",
+      point = "BOTTOMLEFT",
+      relPoint = "BOTTOMLEFT",
+      x = 31,
+      y = -1,
+    },
+    btnConfig = tcopy(defaultPetActionConfig),
+  },
+
+}
+
+-- default settings for action IDs match Blizzard's settings... 
+-- ... except on the main bar extra pages, which map directly to the default shapeshift IDs
+-- rather than mirroring the multi action bars, to give access to all 120 actions
+local bars = ReAction_DefaultBlizzardBars
+
+for i = 1, 12 do
+  bars[1].btnConfig.ids[i] = {
+    i, 
+    72+i, 
+    84+i,
+    96+i,
+    108+i,
+    12+i
   }
-}
+end
+
+for b = 2, 5 do
+  for i = 1, 12 do
+    bars[b].btnConfig.ids[i] = { 12*(b-1) + i }
+  end
+end
+
+for i = 1, 10 do
+  bars[6].btnConfig.ids[i] = { i }
+end
+
+
--- a/Options.lua	Tue Mar 20 21:20:20 2007 +0000
+++ b/Options.lua	Tue Mar 20 21:25:29 2007 +0000
@@ -1,4 +1,19 @@
--- Ace2 Options table for ReAction
+-- Ace2 global options tables for ReAction
+
+
+-- autogenerate the NewBar sub-types table
+local function GenerateNewBarArgs()
+  local args = { }
+  for _, name in pairs(ReAction:GetButtonTypeList() ) do
+    args[name] = {
+      type="execute",
+      name=name,
+      desc=name,
+    }
+  end
+  return args
+end
+
 
 ReActionConsoleOptions = {
   type="group",
@@ -21,7 +36,7 @@
       type = "execute",
       name = "bindings",
       desc = "Launches keybinding setup mode",
-      func = function() ReBinder:Enable() end,
+      func = function() ReBound:Enable() end,
     },
 
     hideart = {
@@ -31,27 +46,29 @@
       get  = "IsArtHidden",
       set  = "ToggleHideArt",
     },
-    
-    new = {
-      type = "execute",
-      name = "new",
-      desc = "Create a new bar with default settings",
-      func = "NewBar"
-    },
-    
+
     showid = {
       type = "toggle",
       name = "showid",
       desc = "Show ActionIDs on buttons",
-      get  = "IsActionIDVisible",
-      set  = "ToggleActionID",
+      get  = "AreIdsVisible",
+      set  = "ToggleIds",
+    },
+
+    create = {
+      type = "group",
+      name = "create",
+      pass = true,
+      func = "NewBar",
+      args = GenerateNewBarArgs(),
+      desc = "Create a new bar",
     },
 
     resetall = {
       type = "execute",
       name = "resetall",
       desc = "Resets to single bar in the default position",
-      func = "ResetBars"
+      func = "ResetBars",
     },
     
     --[[
@@ -59,7 +76,8 @@
       type = "execute",
       name = "resync",
       desc = "Re-orders action IDs sequentially amongst bars",
-      func = "ResyncActionIDs"
+      func = "ResyncActionIDs",
+      disabled = true -- not yet implemented
     },
     ]]
   }
@@ -67,213 +85,320 @@
 
 
 ReActionGlobalMenuOptions = {
-  type="group",
+  type = "group",
+  -- handler = nil, -- NOTE: this variable isn't defined yet, must be added later
   args={ 
     lockbars = {
       type = "toggle",
       name = "Lock Bars",
       desc = "Locks action bars and disables rearrangement",
-      get = function() return ReAction:IsLocked() end,
-      set = function() ReAction:ToggleLocked() end,
+      get = "IsLocked",
+      set = "ToggleLocked",
       order = 1,
     },
 
     lockbtns = {
       type = "toggle",
       name = "Lock Buttons",
-      desc = "Locks action bars and disables rearrangement",
+      desc = "Prevents buttons from being dragged off accidentally. Shift-drag instead.",
       get = function() return LOCK_ACTIONBAR == "1" end,
       set = function() LOCK_ACTIONBAR = (LOCK_ACTIONBAR == "1" and "0" or "1") end,
       order = 2,
     },
 
+    new = {
+      type = "group",
+      name = "New bar",
+      pass = true,
+      func = "NewBar",
+      args = GenerateNewBarArgs(),
+      desc = "Create a new bar",
+      order = 3,
+    },
+
     bindings = {
       type = "execute",
       name = "Set Key Bindings",
       desc = "Launches keybinding setup mode",
-      func = function() ReBinder:Enable() end,
-      order = 3,
+      func = function() ReBound:Enable() end,
+      order = 4,
     },
 
-    new = {
-      type = "execute",
-      name = "New Bar",
-      desc = "Create a new bar with default settings",
-      func = function() ReAction:NewBar() end,
-      order = 4,
-    },
-    
     showid = {
       type = "toggle",
       name = "Show Action IDs",
       desc = "Show ActionIDs on buttons",
-      get  = function() return ReAction:IsActionIDVisible() end,
-      set  = function() ReAction:ToggleActionID() end,
+      get  = "AreIdsVisible",
+      set  = "ToggleIds",
       order = 5,
     },
 
+    hidedefault = {
+      type = "toggle",
+      name = "Hide Default Main Menu Bar",
+      desc = "Hides default Blizzard main menu bar, including bag bar, micro menu bar, shapeshift bar, lag meter, and XP bar",
+      get  = "IsArtHidden",
+      set  = "ToggleHideArt",
+      order = 6,
+    },
+    
     --[[
     resync = {
       type = "execute",
       name = "Re-sync Action IDs",
       desc = "Re-orders action IDs sequentially amongst bars",
-      func = function() ReAction:ResyncActionIDs() end,
-      order = 6,
+      func = "ResyncActionIDs",
+      disabled = true, -- not yet implemented
     },
     ]]
 
-    hideart = {
-      type = "toggle",
-      name = "Hide Default Art",
-      desc = "Hide default Blizzard action bar artwork and XP bar",
-      get  = function() return ReAction:IsArtHidden() end,
-      set  = function() return ReAction:ToggleHideArt() end,
-      order = 7,
-    },
-    
-    --[[
-    reset = {
-      type = "execute",
-      name = "Reset Bars",
-      desc = "Resets to single bar in the default position",
-      func = function() ReAction:ResetBars() end,
-      order = 8,
-    },
-   ]]
-    
   }
 }
 
-function GenerateReActionBarOptions( bar )
-  return {
+
+
+  
+
+function GenerateReActionBarOptions( bar, main )
+  local opts = {
     type = "group",
+    handler = bar,
     args = { 
     
       sep1 = {
         type = "header",
         name = " ",
         desc = " ",
-        order = 9,
+        order = 1,
       },
       
       hdr1 = {
         type = "header",
-        name = "Bar Options",
-        des = "Bar Options",
-        order = 10,
+        name = "Options for Bar #"..bar.barID,
+        desc = "Options for Bar #"..bar.barID,
+        order = 2,
+      },
+
+      layout = {
+        type = "group",
+        name = "Layout",
+        desc = "Button ordering options",
+        order = 3,
+        args = {
+          growLeft = {
+            type = "toggle",
+            name = "Right to Left",
+            desc = "Lay out buttons right-to-left rather than left-to-right",
+            get  = "GetGrowLeft",
+            set  = "SetGrowLeft",
+            order = 3,
+          },
+
+          growUp = {
+            type = "toggle",
+            name = "Bottom to Top",
+            desc = "Lay out buttons bottom-to-top rather than top-to-bottom",
+            get  = "GetGrowUp",
+            set  = "SetGrowUp",
+            order = 4,
+          },
+
+          columnMajor = {
+            type = "toggle",
+            name = "Arrange in Columns",
+            desc = "Lay out buttons sequentially in columns, rather than in rows",
+            get  = "GetColumnMajor",
+            set  = "SetColumnMajor",
+            order = 5,
+          },
+
+          flip = {
+            type = "execute",
+            name = "Flip rows/columns",
+            desc = "Swaps the number of rows and columns, and inverts the button numbering",
+            func = "FlipRowsColumns",
+            order = 6,
+          },
+        },
       },
       
-      --[[
-      hidden = {
-        type = "toggle",
-        name = "Hidden",
-        desc = "Hides the bar except when rearranging bars",
-        get = function() return not bar:GetVisibility() end,
-        set = function() bar:ToggleVisibility() end,
-        order = 11,
+      paging = {
+        type = "group",
+        name = "Paging",
+        desc = "Multi-page options",
+        order = 4,
+        args = {
+          pages = {
+            type = "range",
+            name = "Number of Pages",
+            desc = "Sets the number of pages",
+            get = "GetPages",
+            set = "SetPages",
+            min = 1,
+            max = 10,
+            step = 1,
+            order = 1,
+          },
+
+          autostance = {
+            type = "toggle",
+            name = "Auto Stance Switch",
+            desc = "Automatically switch pages when changing stance or shapeshift form.",
+            get  = "GetAutoStanceSwitch",
+            set  = "ToggleAutoStanceSwitch",
+            order = 2,
+          },
+
+          autostealth = {
+            type = "toggle",
+            name = "Auto Stealth Switch",
+            desc = "Automatically switch pages when stealthing/unstealthing.",
+            get  = "GetAutoStealthSwitch",
+            set  = "ToggleAutoStealthSwitch",
+            order = 3,
+          },
+
+          hidecontrols = {
+            type = "toggle",
+            name = "Hide Paging Controls",
+            desc = "Hide the page up/down controls",
+            get  = "ArePageControlsHidden",
+            set  = "TogglePageControlsHidden",
+            order = 4,
+            disabled = "IsPagingDisabled",
+          },
+
+          controlsloc = {
+            type = "text",
+            name = "Control location",
+            desc = "Location of the page up/down controls",
+            get  = function() return bar:GetPageControlsLoc() or "Blizzard" end,
+            set  = function(loc) bar:SetPageControlsLoc(loc) end,
+            order = 5,
+            disabled = function() return bar:IsPagingDisabled() or bar:ArePageControlsHidden() end,
+            validate = { "Blizzard", "LEFT", "RIGHT", "TOP", "BOTTOM" }
+          },
+        },
       },
-      ]]
+
+      visibility = {
+        type = "group",
+        name = "Visibility",
+        desc = "Set bar visibility options",
+        order = 5,
+        args = {
+          visible = {
+            type = "toggle",
+            name = "Always Visible",
+            desc = "The bar will always be visible.|n This setting overrides conditional settings below.",
+            get = "GetVisibility",
+            set = function() bar:SetVisibility(true) end,
+            order = 1,
+          },
+
+          hidden = {
+            type = "toggle",
+            name = "Always Hidden",
+            desc = "The bar will always be hidden, except when bars are unlocked.|n This setting overrides conditional settings below.",
+            get  = function() return not bar:GetVisibility() end,
+            set  = function() bar:SetVisibility(false) end,
+            order = 2,
+          },
+
+          spring = {
+            type = "toggle",
+            name = "Spring Bar Mode",
+            desc = "The bar is collapsed to a single button, which displays the last action used. Mousing over the button shows the entire bar.|n This setting overrides conditional settings below.",
+            get  = function() end,
+            set  = function() end,
+            hidden = true,
+            disabled = true, -- not yet implemented
+            order = 3,
+          },
+
+          conditional = {
+            type = "group",
+            name = "Auto Show",
+            desc = "Dynamically hide and show the entire bar based on certain conditions.",
+            hidden = true,
+            disabled = true, -- not yet implemented
+            order = 4,
+            args = {
+
+              mouseover = {
+                type = "toggle",
+                name = "Show On Mouseover",
+                desc = "Show the bar only when the mouse is over it.",
+                disabled = true, -- not yet implemented
+                get  = function() end,
+                set  = function() end,
+                order = 1,
+              },
+
+              combat = {
+                type = "toggle",
+                name = "Show In Combat",
+                desc = "Show the bar only when in combat.",
+                disabled = true, -- not yet implemented
+                get  = function() end,
+                set  = function() end,
+                order = 2,
+              },
+
+              nocombat = {
+                type = "toggle",
+                name = "Hide in Combat",
+                desc = "Show the bar only when not in combat.",
+                disabled = true, -- not yet implemented
+                get = function() end,
+                set = function() end,
+                order = 3,
+              }
+            }
+          },
+
+        }
+      },
       
       opacity = {
         type = "range",
         name = "Opacity",
-        desc = "Set bar opacity",
-        get = function() return bar:GetOpacity() end,
-        set = function(o) bar:SetOpacity(o) end,
+        desc = "Set the bar alpha value, from fully transparent (0) to fully opaque (100).",
+        get = "GetOpacity",
+        set = "SetOpacity",
         min = 0,
         max = 100,
         step = 1,
-        order = 12
+        order = 6,
       },
       
       delete = {
         type = "execute",
-        name = "Delete Bar",
-        desc = "Deletes the bar",
-        func = function() ReAction:DeleteBar(bar.barID) end,
-        order = 13,
+        name = "Delete Bar #"..bar.barID,
+        desc = "Deletes bar #"..bar.barID,
+        func = function() main:DeleteBar(bar.barID) end,
+        order = 7
       },
     }
   }
+
+  -- generate the auto-hide options for shapeshift forms. Note that this will 
+  -- only show forms that the character has learned, and the ordering may (? did this get fixed?)
+  -- vary from character to character of the same class
+  local args = opts.args.visibility.args.conditional.args
+  for i = 1, GetNumShapeshiftForms() do
+    local _, name = GetShapeshiftFormInfo(i)
+    args["stance"..i] = {
+      type = "toggle",
+      name = "Show In "..name,
+      desc = "Show the bar when in "..name.."." ,
+      get = function() end,
+      set = function() end,
+      order = #args + 1,
+      disabled = true,
+    }
+  end
+
+  return opts
 end
 
-
-local function setButtonConfig( bar, field, value )
-  if bar and bar.config and bar.config.btnConfig then
-    bar.config.btnConfig[field] = value
-    for _, b in ipairs(bar.buttons) do
-      b:ApplyLayout()
-      b:UpdateDisplay()
-    end
-  end
-end
-
-local function getButtonConfig( bar, field )
-  if bar and bar.config and bar.config.btnConfig then
-    return bar.config.btnConfig[field]
-  end
-end
-
-local function toggleButtonConfig( bar, field )
-  if bar and bar.config and bar.config.btnConfig then
-    bar.config.btnConfig[field] = not bar.config.btnConfig[field]
-    for _, b in ipairs(bar.buttons) do
-      b:ApplyLayout()
-      b:UpdateDisplay()
-    end
-  end
-end
-
-
-function GenerateReActionButtonOptions( bar )
-  return {
-    type = "group",
-    args = { 
-        
-      sep2 = {
-        type = "header",
-        name = "  ",
-        desc = "  ",
-        order = 14,
-      },
-      
-      hdr2 = {
-        type = "header",
-        name = "Button Options",
-        desc = "Button Options",
-        order = 15,
-      },
-      
-      showgrid = {
-        type = "toggle",
-        name = "Always Show Buttons",
-        desc = "Show button placeholders when no action is assigned or on the cursor. Note that buttons are always shown when bars are unlocked.",
-        get  = function() return getButtonConfig(bar, "showGrid") end,
-        set  = function() toggleButtonConfig(bar, "showGrid") end,
-        order = 16,
-      },
-      
-      colorkeys = {
-        type = "toggle",
-        name = "Color Hotkeys",
-        desc = "Enables/disables colorizing hotkeys by key modifier",
-        get  = function() return getButtonConfig(bar, "keyBindColorCode") end,
-        set  = function() toggleButtonConfig(bar, "keyBindColorCode", c) end,
-        order = 17,
-      },
-  
-      keyloc = {
-        type = "text",
-        name = "Hotkey Location",
-        desc = "Sets hotkey location",
-        get  = function() return getButtonConfig(bar, "keyBindLoc") end,
-        set  = function(loc) setButtonConfig(bar, "keyBindLoc", loc) end,
-        validate = { "TOP", "BOTTOM", "TOPLEFT", "TOPRIGHT", "BOTTOMLEFT", "BOTTOMRIGHT" },
-        order = 18,
-      },
-      
-    }    
-  }
-end
-
-
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/README.html	Tue Mar 20 21:25:29 2007 +0000
@@ -0,0 +1,202 @@
+<html>
+<head>
+<title>ReAction: README</title>
+<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
+</head>
+
+<body bgcolor="#FFFFFF" text="#000000">
+<h1 align="center">ReAction</h1>
+<h3 align="center">AddOn for World of Warcraft</h3>
+<p><font size="2">Current Version: 0.3 (beta)<br>
+  Released: 29 Jan 2007</font><font size="2"><br>
+  WoW Version Compatibility/TOC: 2.0.6 / TOC 20003</font></p>
+<h2>The Basics</h2>
+<p>ReAction is a replacement for the default Blizzard action bars. It allows you 
+  redefine your action button layout any way you like.</p>
+<h2>Features</h2>
+<ul>
+  <li>Move, resize, create, and arrange as many action bars as you want. Each 
+    bar can contain any number of buttons arranged in a grid layout of any size 
+    and spacing. The full complement of 120 action slots is supported.</li>
+  <li>Works with all types of actions, including abilities, items, and macros</li>
+  <li>Layout is done with the mouse, by dragging bars and bar edges on screen. 
+    Automatically anchor bars to each other or the screen edges by holding shift 
+    down as you drag.</li>
+  <li>Context menus provide independent configuration options for each bar.</li>
+  <li>Point and click keybinding interface.</li>
+  <li>Supports multiple 'pages' per bar, configured independently. Automatic stance, 
+    form, and stealth switching among pages is supported, including Shadowform.</li>
+  <li>Pet action bar is fully supported and configurable.</li>
+  <li>Settings can be saved per account, realm, character, class, or independent 
+    profile. </li>
+  <li>Compatible with OmniCC</li>
+</ul>
+<p>ReAction is built using the <a href="http://www.wowace.com/Wiki/Ace2">Ace2</a> 
+  development framework.</p>
+<h2>Using ReAction</h2>
+<h3>Installation</h3>
+<p>To install ReAction, drag the ReAction folder to World of Warcraft/Interface/AddOns. 
+  Exit World of Warcraft if it's running, then restart.</p>
+<h3>Welcome to ReAction</h3>
+<p>When you first enter World of Warcraft with ReAction installed, it will look 
+  very similar to the default Blizzard UI, depending on what other AddOns are 
+  installed. Any keybindings you have made to the action bars, however, are not 
+  in effect. Also, only the main menu bar is shown by default. The bottom left, 
+  bottom right, and two right action bars are hidden.</p>
+<p>If you have FuBar installed, on the left side you should see the ReAction plugin 
+  icon and label. If not, there should be a button on your minimap. In either 
+  case, that button (the ReAction Control Button) is the gateway to configuring 
+  ReAction.</p>
+<p>ReAction has three modes of operation:</p>
+<ul>
+  <li>Normal (&quot;locked&quot;)</li>
+  <li>Configuration (&quot;unlocked&quot;)</li>
+  <li>Keybinding</li>
+</ul>
+<p>You can toggle Configuration mode by shift-clicking the ReAction Control Button 
+  and toggle Keybinding mode by alt-clicking the button. Global configuration 
+  options are also available by right-clicking the ReAction button. There are 
+  also a limited set of console commands that can be accessed with the &quot;/reaction&quot; 
+  or &quot;/rxn&quot; slash-commands. Type /rxn in the chat box to print a list 
+  of commands.</p>
+<p>You can also set key bindings to toggle Configuration and Keybinding modes, 
+  in the standard UI keybindings panel.</p>
+<h3>Configuring ReAction</h3>
+<p>Start by shift-clicking the ReAction button to enter Configuration mode. All 
+  bars, including hidden bars, are shown in this mode and <b>normal button click 
+  operation is disabled </b>(though keybindings still work). Mouse over the bars, 
+  edges, and corners to see tooltip instructions for moving, resizing, and rearranging 
+  them. Right click each bar for a menu of options pertaining to that bar. For 
+  example, to change a hidden bar to a visible bar, right-click on a bar and choose 
+  Visibility -&gt; Always Visible. Tooltips for each menu option provide details.</p>
+<p>When you're done configuring ReAction, shift-click the ReAction button again 
+  to go back to Normal mode.</p>
+<p>Configuration mode is not available if you are in combat, and is automatically 
+  cancelled if you enter combat.</p>
+<h3>Setting Keybindings</h3>
+<p><b>ReAction buttons don't use the regular keybindings of the default UI.</b> 
+  In order to get your buttons to use hotkeys, enter ReAction Keybinding Setup 
+  mode by alt-clicking the ReAction Control Button (or choosing 'keybinding mode' 
+  from the right-click menu). <b>ReAction button keybindings are not available 
+  from the standard keybindings menu!</b> You <i>must</i> use the ReAction keybinding 
+  interface.</p>
+<p>Like configuration mode, normal button operation is disabled while in keybinding 
+  mode (though again, any hotkeys you have assigned will work).</p>
+<p>With the keybinding interface out, you can mouseover any ReAction button to 
+  see what its current keybinding is. You can set the keybinding on a button by 
+  pressing the key, then clicking the button. To clear a keybinding from a ReAction 
+  button, right-click the button. If you've set up bars with multiple pages, you 
+  can also bind a key to the page up/down buttons on a per-bar basis. Just make 
+  sure the buttons are showing (they can be hidden later) and assign the keybinding 
+  like any other ReAction button.</p>
+<p>Keybindings are saved on a per-character basis. Keybinding mode is not available 
+  if you are in combat, and is automatically cancelled if you enter combat.</p>
+<h3>Playing with ReAction</h3>
+<p>Once you get your keybindings and configuration set up, you're ready to go. 
+  The buttons behave just like regular Blizzard buttons. However, at any time 
+  when you're not in combat, you can quickly switch to configuration mode and 
+  create a new bar, rearrange bars, hide/show a special-use bar, etc. Perfect 
+  for when you get some goofy quest item that has to be used temporarily, or get 
+  a new skill that doesn't fit on your current layout.</p>
+<h2>Limitations</h2>
+<ul>
+  <li>Due to a lack of certain functionality being made available by Blizzard, 
+    dragging and dropping actions onto action bars while in combat is a little 
+    funky. You have to click the destination button rather than just release drag 
+    on it.</li>
+  <li>You can't configure the pet bar unless you actually have a pet out at the 
+    moment. </li>
+  <li>No support for shapeshift/stance bar (yet)</li>
+  <li>If you have a bar with empty buttons, even though they're invisible they 
+    still block mouse input from reaching whatever might be under the button. 
+    This is a workaround so that you can drag and drop actions onto hidden slots 
+    during combat.</li>
+  <li>If you hide the default main menu bar, there is no substitute for the XP 
+    bar, bag bar, or micro menu bar (although the hotkeys for them still work)</li>
+  <li>The pet bar, by default, appears underneath bar #6 (lower left). You'll 
+    have to move bar 6 out of the way if you want to move the pet bar. Also, it 
+    does not</li>
+</ul>
+<h2>Known Issues</h2>
+<ul>
+  <li>There may or may not be a particular case in which turning off a pet's attack 
+    while using Eyes of the Beast doesn't work. Further investigation (and leveling 
+    a hunter to get Eyes of the Beast :-P) is required.</li>
+  <li>The initial mapping of action IDs for shapeshift forms doesn't exactly match 
+    Blizzard's mapping. When first starting up as a warrior, druid, or rogue, 
+    you will probably need to move your actions around.</li>
+  <li>Action IDs may not always be laid out in sequence, meaning that similar 
+    layouts on different machines may put the actions in a different order.</li>
+  <li>Keybindings are only saved per character, which causes problems when switching 
+    profiles.</li>
+  <li>There seems to be a big performance hit when first dragging a bar after 
+    entering configuration mode, and when first enabling keybinding mode.</li>
+</ul>
+<h2>Future Plans</h2>
+<ul>
+  <li>Use override bindings for the keybindings, and automatically switch with 
+    profiles. Also &quot;steal&quot; bindings from the default UI on first run 
+    so that they're populated with something reasonable.</li>
+  <li>Add support for bag bar, shapeshift bar, and micro menu bar. May also add 
+    XP bar to default unit frames for those who don't want to use a unit frame 
+    addon to get the XP bar back.</li>
+  <li>More dynamic bar support: auto show/hide/fade on stance switch, in combat, 
+    key pressed, etc.</li>
+  <li>&quot;pop-up&quot; bar support, in which a single button shows/hides a bar 
+    when moused over</li>
+  <li>Better and more flexible interface for defining page transitions on stance/form 
+    shift </li>
+  <li>Provide a method for normalizing the action ID layout, permitting better 
+    compatibility when used on different computers.</li>
+  <li>Provide some additional console commands so that enterprising users can 
+    macro certain configuration functionality (notably hiding and showing individual 
+    bars) </li>
+  <li>Yet more configurable button display options (disable cooldown flash, different 
+    borders, etc)</li>
+  <li>Localization</li>
+</ul>
+<h2>Version History</h2>
+<p>Version 0.3</p>
+<ul>
+  <li>Complete rewrite of the innards for better modularity</li>
+  <li>Lots of bug fixes</li>
+  <li>Multi-paged bar support</li>
+  <li>Pet bar support</li>
+  <li>Auto stance/form/stealth switching</li>
+  <li>Lots and lots of new configuration options</li>
+</ul>
+<p>Version 0.2</p>
+<ul>
+  <li>Bug fixes from 0.1</li>
+  <li>new keybinding interface</li>
+</ul>
+<p>Version 0.1</p>
+<ul>
+  <li>Initial concept, button arrangement</li>
+</ul>
+<h2>Credits</h2>
+<p>Huge credit to the <a href="http://www.wowace.com">Ace 2</a> development team, 
+  whose framework provides a level of functionality that I consider critical to 
+  the success of this addon.</p>
+<p>Similarly, enormous credit goes to <a href="http://ckknight.wowinterface.com">ckknight</a>, 
+  whose Dewdrop menuing system and FuBar plugin system are ridiculously easy to 
+  use.</p>
+<p>Finally, thanks to my alpha testers and stalwart adventuring companions: Deor, 
+  Nogrim, Sorabel, and Yngvi.</p>
+<h2>Copyright</h2>
+<p>ReAction &copy; 2007 by Ryan Findley.</p>
+<p>You are free to distribute this AddOn package without notice to the author, 
+  as long as the package is unmodified, this readme.html file accompanies the 
+  distribution, and no claim of authorship of the contents of the package is made. 
+  Derived works are expressly permitted but must be clearly labeled as not the 
+  work of the original author and packaged separately, with credit given to the 
+  original author. Inclusion of this package within a compilation is expressly 
+  permitted.</p>
+<p>The author expressly disclaims any warranty of any kind for the material contained 
+  in this package. USE AT YOUR OWN RISK.</p>
+<p>All files in this package under the 'libs/' directory are libraries made available 
+  by <a href="http://www.wowace.com">WoWAce</a>, and are not the work of this 
+  author.</p>
+<p>World of Warcraft is a registered trademark of Blizzard Entertainment, Inc.</p>
+</body>
+</html>
--- a/ReAction.toc	Tue Mar 20 21:20:20 2007 +0000
+++ b/ReAction.toc	Tue Mar 20 21:25:29 2007 +0000
@@ -1,15 +1,16 @@
-## Interface: 20000
+## Interface: 20003
 ## Title: ReAction |cff7fff7f -Ace2-|r
-## Notes: An action button layout tool
+## Notes: An action bar and button layout tool
 ## DefaultState: enabled
 ## LoadOnDemand: 0
 ## Author: Flick
-## Version: 0.1
+## Version: 0.3
 ## X-Description: An action bar and button layout tool
 ## X-Category: Action Bars
 ## SavedVariables: ReActionDB
 ## SavedVariablesPerCharacter: ReActionDBPC
-## OptionalDeps: Ace2
+## OptionalDeps: Ace2, FuBar
+## X-Embeds: Ace2, DewdropLib, TabletLib
 
 libs\AceLibrary\AceLibrary.lua
 libs\AceOO-2.0\AceOO-2.0.lua
@@ -17,22 +18,29 @@
 libs\AceDB-2.0\AceDB-2.0.lua
 libs\AceConsole-2.0\AceConsole-2.0.lua
 libs\AceEvent-2.0\AceEvent-2.0.lua
+libs\AceLocale-2.2\AceLocale-2.2.lua
 libs\Dewdrop-2.0\Dewdrop-2.0.lua
 libs\Tablet-2.0\Tablet-2.0.lua
 libs\FuBarPlugin-2.0\FuBarPlugin-2.0.lua
 
-ReBinder.lua
-ReBinder.xml
+locale-enUS.lua
 
+classes\ReAnchor.xml
+classes\ReAnchor.lua
+classes\ReBound.xml
+classes\ReBound.lua
+classes\ReBar.xml
+classes\ReBar.lua
+
+classes\ReAction.lua
+classes\ReAction_ActionType.lua
+classes\ReAction_ActionDisplay.lua
+classes\ReAction_ColorScheme.lua
+classes\ReAction_PetActionType.lua
+classes\ReAction_PetActionDisplay.lua
+
+Buttons.lua
 Defaults.lua
 Options.lua
 
-ReBar.lua
-ReBar.xml
-
-Button.lua
-Button.xml
-
-ReAction.lua
-
-
+main.lua
--- 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
-
-
-
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/classes/ReAction_ActionDisplay.lua	Tue Mar 20 21:25:29 2007 +0000
@@ -0,0 +1,391 @@
+-- The ReAction.ActionDisplay mixin defines 'regular' action button display functionality
+-- and is an implementation of the ReAction.IDisplay and ReAction.ActionType.IDisplay interfaces.
+--
+-- This Mixin assumes that it has been mixed in with a ReAction-derived class which implements
+-- the ReAction.IActionType and ReAction.IColorScheme interfaces.
+--
+-- This mixin uses properties of self.config to define display elements:
+--
+-- self.config = {
+--   keyBindLoc = "POSITION",       -- keybind anchor location
+--   stackCountLoc = "POSITION",    -- stack count anchor location
+--   showKeyBind = true/false,      -- show keybind labels
+--   showStackCount = true/false,   -- show stack count labels
+--   showMacroText = true/false,    -- show macro name labels
+--   showGrid = true/false,         -- always show empty buttons
+-- }
+--
+
+local AceOO = AceLibrary("AceOO-2.0")
+
+ReAction.ActionDisplay = AceOO.Mixin {
+  -- ReAction.IDisplay interface
+  "SetupDisplay",
+  "UpdateDisplay",
+  "TempShow",
+  "GetActionFrame",
+  "GetBaseButtonSize",
+  "DisplayID",
+  "DisplayHotkey",
+
+  -- ReAction.ActionType.IDisplay interface
+  "DisplayUsable",
+  "DisplayEquipped",
+  "DisplayAutoRepeat",
+  "DisplayInUse",
+  "DisplayIcon",
+  "DisplayName",
+  "DisplayCount",
+  "DisplayCooldown",
+
+  -- Event handlers
+  "PostClick",
+  "OnDragStart",
+  "OnReceiveDrag",
+  "OnEnter",
+  "OnLeave",
+  "OnUpdate",
+
+  -- internal functions
+  "ApplyLayout",
+  "ApplyStyle",
+  "StartFlash",
+  "StopFlash",
+  "IsFlashing",
+  "DisplayVisibility",
+}
+
+local RAAD = ReAction.ActionDisplay
+
+
+-- private constants
+local _G = getfenv(0)
+
+local equippedActionBorderColor  = { r=0.00,  g=1.00,  b=0.00,  a=0.35 } -- transparent green
+local actionIDColor              = { r=1.00,  g=0.82,  b=0.00,  a=1.00 } -- gold
+
+-- private functions
+-- extract and return color fields from a table, to be fed into SetVertexColor()/SetTextColor()
+local function tcolor(c)
+  return c.r, c.g, c.b, c.a
+end
+
+
+-----------------------------------
+-- Interface Implementation Methods
+-----------------------------------
+function RAAD:SetupDisplay( name )
+  -- create the button widget
+  local b = CreateFrame("CheckButton", name, nil, "SecureActionButtonTemplate, ActionButtonTemplate")
+
+  -- store references to the various sub-frames of ActionButtonTemplate so we don't have to look it up all the time
+  self.frames = {
+    button         = b,
+    hotkey         = _G[name.."HotKey"],
+    count          = _G[name.."Count"],
+    cooldown       = _G[name.."Cooldown"],
+    macro          = _G[name.."Name"],
+    icon           = _G[name.."Icon"],
+    border         = _G[name.."Border"],
+    flash          = _G[name.."Flash"],
+    normalTexture  = _G[name.."NormalTexture"],
+    actionID       = nil,    -- defer creating actionID font string until it's actually requested
+  }
+
+  -- ??? odd: why do we have to increment the cooldown frame level to get it to show? 
+  -- (otherwise it's behind the icon). The default UI doesn't have to (or at least I can't
+  -- find where it does) but for some reason we have to here.
+  self.frames.cooldown:SetFrameLevel(self.frames.cooldown:GetFrameLevel() + 1)
+
+  b:EnableMouse()
+  b:RegisterForDrag("LeftButton", "RightButton")
+  b:RegisterForClicks("AnyUp")
+  b:SetScript("PostClick",           function(arg1) self:PostClick(arg1) end)
+  b:SetScript("OnDragStart",         function(arg1) self:OnDragStart(arg1) end)
+  b:SetScript("OnReceiveDrag",       function() self:OnReceiveDrag() end)
+  b:SetScript("OnEnter",             function() self:OnEnter() end)
+  b:SetScript("OnLeave",             function() self:OnLeave() end)
+  -- defer setting OnUpdate until actions are actually attached
+
+  self.tmpShow_ = 0
+end
+
+function RAAD:UpdateDisplay()
+  self:ApplyLayout()
+  self:ApplyStyle()
+  self:DisplayVisibility()
+  -- refresh the action ID display
+  if ReAction.showIDs_ then
+    self:DisplayID(self:GetID())
+  end
+end
+
+function RAAD:TempShow( visible )
+  visible = visible and true or false -- force data integrity
+  self.showTmp_ = max(0, (self.showTmp_ or 0) + (visible and 1 or -1))
+  self:DisplayVisibility()
+end
+
+function RAAD:GetActionFrame()
+  return self.frames.button
+end
+
+function RAAD:GetBaseButtonSize()
+  return 36
+end
+
+function RAAD:DisplayID( id )
+  local f = self.frames.actionID
+  if id then
+    if not f then
+      -- create the actionID label
+      f = self.frames.button:CreateFontString(nil,"ARTWORK","NumberFontNormalSmall")
+      f:SetPoint("BOTTOMLEFT")
+      f:SetTextColor( tcolor(actionIDColor) )
+      self.frames.actionID = f
+    end
+    f:SetText(tostring(id))
+    f:Show()
+  elseif f then
+    f:Hide()
+  end
+end
+
+function RAAD:DisplayHotkey(txt)
+  self.frames.hotkey:SetText(string.upper(txt or ""))
+  self:UpdateUsable()
+end
+
+function RAAD:DisplayUsable( isUsable, notEnoughMana, outOfRange )
+  local f = self.frames
+  f.icon:SetVertexColor(          self:GetIconColor(isUsable, notEnoughMana, outOfRange) )
+  f.button:GetNormalTexture():SetVertexColor( self:GetBorderColor(isUsable, notEnoughMana, outOfRange) )
+  f.hotkey:SetTextColor(          self:GetHotkeyColor(isUsable, notEnoughMana, outOfRange, f.hotkey:GetText()) )
+end
+
+function RAAD:DisplayEquipped( equipped )
+  local b = self.frames.border
+  if equipped then
+    b:Show()
+  else
+    b:Hide()
+  end
+end
+
+function RAAD:DisplayAutoRepeat( r )
+  if r then
+    self:StartFlash()
+  else
+    self:StopFlash()
+  end
+end
+
+function RAAD:DisplayInUse( inUse )
+  self.frames.button:SetChecked( inUse and 1 or 0 )
+end
+
+function RAAD:DisplayIcon( texture )
+  local f = self.frames.button
+  local icon = self.frames.icon
+  if texture then
+    icon:SetTexture(texture)
+    icon:Show()
+    self.rangeTimer = -1
+    f:SetNormalTexture("Interface\\Buttons\\UI-Quickslot2")
+    if f:GetScript("OnUpdate") == nil then
+      f:SetScript("OnUpdate", function(frame, elapsed) self:OnUpdate(elapsed) end)
+    end
+  else
+    icon:Hide()
+    self.rangeTimer = nil
+    f:SetNormalTexture("Interface\\Buttons\\UI-Quickslot")
+    f:SetScript("OnUpdate",nil)
+  end
+  self:DisplayVisibility()
+end
+
+function RAAD:DisplayCooldown( start, duration, enable )
+  CooldownFrame_SetTimer(self.frames.cooldown, start, duration, enable)
+end
+
+function RAAD:DisplayName( name )
+  self.frames.macro:SetText(name and tostring(name) or "")
+end
+
+function RAAD:DisplayCount( count )
+  self.frames.count:SetText(count and tostring(count) or "")
+end
+
+
+
+
+
+----------------------
+-- Event Handlers
+----------------------
+function RAAD:PostClick()
+  self:UpdateInUse()
+end
+
+function RAAD:OnDragStart()
+  if LOCK_ACTIONBAR ~= "1" or IsShiftKeyDown() then
+    self:PickupAction()
+  end
+end
+
+function RAAD:OnReceiveDrag()
+  self:PlaceAction()
+end
+
+function RAAD:OnEnter()
+  self:SetTooltip() -- from ReAction base class
+  self.tooltipTime = TOOLTIP_UPDATE_TIME
+end
+
+function RAAD:OnLeave()
+  self:ClearTooltip() -- from ReAction base class
+  self.tooltipTime = nil
+end
+
+function RAAD:OnUpdate(elapsed)
+  -- 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
+			
+			local f = self.frames.flash
+			if f then
+        if f:IsVisible() then
+          f:Hide()
+        else
+          f:Show()
+        end
+      end
+		end
+	end
+	
+	-- Handle range indicator
+	if self.rangeTimer then
+		self.rangeTimer = self.rangeTimer - elapsed
+		if self.rangeTimer <= 0 then
+      self:UpdateUsable()
+			self.rangeTimer = TOOLTIP_UPDATE_TIME
+		end
+	end
+
+  -- handle tooltip update
+  if self.tooltipTime then
+    self.tooltipTime = self.tooltipTime - elapsed
+    if self.tooltipTime <= 0 then
+      if GameTooltip:IsOwned(self.frames.button) then
+        self:UpdateTooltip()
+        self.tooltipTime = TOOLTIP_UPDATE_TIME
+      else
+        self.tooltipTime = nil
+      end
+    end
+  end
+end
+
+
+
+----------------------
+-- Internal methods
+----------------------
+
+local function placeLabel( label, anchor )
+  local top = string.match(anchor,"TOP")
+  local bottom = string.match(anchor, "BOTTOM")
+  label:ClearAllPoints()
+  label:SetWidth(40)
+  label:SetPoint(top or bottom or "CENTER",0,top and 2 or bottom and -2 or 0)
+  local j
+  if string.match(anchor,"LEFT") then
+    j = "LEFT"
+  elseif string.match(anchor,"RIGHT") then
+    j = "RIGHT"
+  else
+    j = "CENTER"
+  end
+  label:SetJustifyH(j)
+end
+
+
+function RAAD:ApplyLayout()
+  local f = self.frames
+
+  if self.config.keyBindLoc then
+    placeLabel(f.hotkey, self.config.keyBindLoc)
+  end
+
+  if self.config.stackCountLoc then
+    placeLabel(f.count, self.config.stackCountLoc)
+  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.showMacroName then
+    f.macro:Show()
+  else
+    f.macro:Hide()
+  end
+
+end
+
+function RAAD:ApplyStyle()
+  local f = self.frames
+  -- for now, just a static style
+  f.hotkey:SetFontObject(NumberFontNormal)
+  f.count:SetFontObject(NumberFontNormalYellow)
+  f.border:SetVertexColor( tcolor(equippedActionBorderColor) )
+end
+
+function RAAD:StartFlash()
+  self.flashing = true
+  self.flashtime = 0
+end
+
+function RAAD:StopFlash()
+  self.flashing = false
+  self.frames.flash:Hide()
+end
+
+function RAAD:IsFlashing()
+  return self.flashing
+end
+
+function RAAD:DisplayVisibility()
+  local b = self.frames.button
+
+  if b:GetAttribute("statehidden") then
+    -- can't hide/show in combat
+    if not InCombatLockdown() then
+      b:Hide()
+    end
+  elseif not self:IsActionEmpty() then
+    b:GetNormalTexture():SetAlpha(1.0)
+    b:SetAlpha(1)
+  elseif self.showTmp_ and self.showTmp_ > 0 or self.config.showGrid then
+    b:GetNormalTexture():SetAlpha(0.5)
+    self.frames.cooldown:Hide()
+    b:SetAlpha(1)
+  else
+    b:SetAlpha(0)
+  end
+end
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/classes/ReAction_ActionType.lua	Tue Mar 20 21:25:29 2007 +0000
@@ -0,0 +1,361 @@
+-- The ReAction.ActionType mixin defines 'regular' action button functionality 
+-- and is an implementation of the ReAction.IActionType interface.
+--
+-- The Mixin assumes that it is being mixed in with a ReAction-derived class
+-- which implements the ReAction.IDisplay and ReAction.ActionType.IDisplay interfaces.
+--
+-- This mixin using the following configuration properties:
+--
+-- self.config = {
+--   selfcast = nil / "alt" / "ctrl" / "shift" / "auto" / "none". nil (default) = use Interface Options menu setting.
+-- }
+--
+
+local AceOO = AceLibrary("AceOO-2.0")
+
+ReAction.ActionType = AceOO.Mixin {
+  -- ReAction.IActionType interface
+  "SetID",
+  "GetID",
+  "SetupAction",
+  "UpdateAction",
+  "PickupAction",
+  "PlaceAction",
+  "IsActionEmpty",
+  "UpdateTooltip",
+
+  -- event handlers
+  "ACTIONBAR_SLOT_CHANGED",
+  "PLAYER_ENTERING_WORLD",
+  "ACTIONBAR_SHOWGRID",
+  "ACTIONBAR_HIDEGRID",
+  "ACTIONBAR_UPDATE_STATE",
+  "ACTIONBAR_UPDATE_USABLE",
+  "UNIT_INVENTORY_CHANGED",
+  "CRAFT_SHOW",
+  "PLAYER_ENTER_COMBAT",
+  "PLAYER_LEAVE_COMBAT",
+  "START_AUTOREPEAT_SPELL",
+  "STOP_AUTOREPEAT_SPELL",
+  "OnAttributeChanged",
+  
+  -- internal functions
+  "UpdateSelfcast",
+  "UpdateIcon",
+  "UpdateInUse",
+  "UpdateCount",
+  "UpdateMacroText",
+  "UpdateUsable",
+  "UpdateCooldown",
+  "UpdateActionEvents",
+}
+
+local RAAT = ReAction.ActionType
+
+
+-- Required display elements
+RAAT.IDisplay = AceOO.Interface {
+  DisplayUsable      = "function", -- DisplayUsable(bool, notEnoughMana, outOfRange), change display to indicate action usable/unusable
+  DisplayEquipped    = "function", -- DisplayEquipped(bool)
+  DisplayAutoRepeat  = "function", -- DisplayAutoRepeat(bool)
+  DisplayInUse       = "function", -- DisplayInUse(bool)
+  DisplayIcon        = "function", -- DisplayIcon(texture), nil means empty slot
+  DisplayName        = "function", -- DisplayName(text), macro names
+  DisplayCount       = "function", -- DisplayCount(number), stack count
+  DisplayCooldown    = "function", -- DisplayCooldown(start,duration,enable)
+}
+
+
+---------------------------------------
+-- ReAction.IActionType interface implementation
+---------------------------------------
+function RAAT:SetID( id, page )
+  local f = self:GetActionFrame()
+  id = tonumber(id) -- force data integrity
+  page = tonumber(page)
+  local button = page and ("-page"..page) or "*"
+  if id then
+    f:SetAttribute("*action"..button, id)
+    if page and page > 1 and self.config.selfcast == "right-click" then
+      -- disable right-click auto-cast
+      self.config.selfcast = nil
+    end
+  elseif page then
+    f:SetAttribute("*action"..button, ATTRIBUTE_NOOP)
+  end
+  self:UpdateSelfcast()
+end
+
+function RAAT:GetID(page)
+  local f = self:GetActionFrame()
+  page = tonumber(page)
+  local button = page and ("page"..page) or SecureStateChild_GetEffectiveButton(f)
+  return SecureButton_GetModifiedAttribute(f, "action", button)
+end
+
+function RAAT:SetupAction()
+  local f = self:GetActionFrame()
+	f:SetAttribute("useparent*", true)
+  f:SetAttribute("type", "action")
+
+  self:RegisterEvent("PLAYER_ENTERING_WORLD")
+	self:RegisterEvent("ACTIONBAR_SLOT_CHANGED")
+  self:RegisterEvent("ACTIONBAR_SHOWGRID")
+  self:RegisterEvent("ACTIONBAR_HIDEGRID")
+
+  self:UpdateSelfcast()
+
+  f:SetScript("OnAttributeChanged",  function(frame,name,value) self:OnAttributeChanged(name,value) end)
+end
+
+function RAAT:UpdateAction()
+  self:UpdateIcon()
+  self:UpdateCount()
+  self:UpdateMacroText()
+  self:UpdateUsable()
+  self:UpdateCooldown()
+  self:UpdateActionEvents()
+end
+
+function RAAT:PickupAction()
+  PickupAction(self:GetID())
+  self:UpdateAction()
+end
+
+function RAAT:PlaceAction()
+  if not InCombatLockdown() then
+    -- PlaceAction() is protected. However the user can still drop a new action
+    -- onto a button while in combat by dragging then clicking, because
+    -- UseAction() appears to swap the cursor action for the current action if
+    -- an action is on the cursor.
+    PlaceAction(self:GetID())
+  end
+  self:UpdateActionEvents()
+  self:UpdateIcon()
+end
+
+function RAAT:IsActionEmpty()
+  local slot = self:GetID()
+  return not(slot and HasAction(slot))
+end
+
+function RAAT:UpdateTooltip()
+  local action = self:GetID()
+	if action and GameTooltip:IsOwned(self:GetActionFrame()) then
+    GameTooltip:SetAction(action)
+  end
+end
+
+
+
+
+
+
+
+-----------------------------
+-- Event Handling
+-----------------------------
+function RAAT:ACTIONBAR_SLOT_CHANGED(slot)
+  if slot == 0 or slot == self:GetID() then
+    self:UpdateAction()
+  end
+end
+
+function RAAT:PLAYER_ENTERING_WORLD()
+  self:UpdateAction()
+end
+
+function RAAT:ACTIONBAR_SHOWGRID()
+  self:TempShow(true)
+end
+
+function RAAT:ACTIONBAR_HIDEGRID()
+  self:TempShow(false)
+end
+
+function RAAT:ACTIONBAR_UPDATE_STATE()
+  self:UpdateInUse()
+end
+
+function RAAT:ACTIONBAR_UPDATE_USABLE()
+  self:UpdateUsable()
+  self:UpdateCooldown()
+end
+
+function RAAT:UNIT_INVENTORY_CHANGED(unit)
+  if unit == "player" then
+    self:UpdateIcon()
+    self:UpdateCount()
+  end
+end
+
+function RAAT:CRAFT_SHOW()
+  self:UpdateInUse()
+end
+
+function RAAT:PLAYER_ENTER_COMBAT()
+  if IsAttackAction(self:GetID()) then
+    self:DisplayAutoRepeat(true)
+    self:UpdateInUse()
+  end
+end
+
+function RAAT:PLAYER_LEAVE_COMBAT()
+  if IsAttackAction(self:GetID()) then
+    self:DisplayAutoRepeat(false)
+    self:UpdateInUse()
+  end
+end
+
+function RAAT:START_AUTOREPEAT_SPELL()
+  if IsAutoRepeatAction(self:GetID()) then
+    self:DisplayAutoRepeat(true)
+  end
+end
+
+function RAAT:STOP_AUTOREPEAT_SPELL()
+  if not IsAttackAction(self:GetID()) then
+    self:DisplayAutoRepeat(false)
+    self:UpdateInUse()
+  end
+end
+
+function RAAT:OnAttributeChanged(name, value)
+  if self.config then
+    self:UpdateAction()
+    self:UpdateDisplay()
+  end
+end
+
+
+
+
+---------------------------------
+-- Internal methods
+---------------------------------
+function RAAT:UpdateSelfcast()
+  if not InCombatLockdown() then
+    local c = self.config and self.config.selfcast
+    local f = self:GetActionFrame()
+
+    f:SetAttribute("alt-unit*",nil)
+    f:SetAttribute("ctrl-unit*",nil)
+    f:SetAttribute("shift-unit*",nil)
+    f:SetAttribute("*unit2",nil)
+    if c == nil then
+      f:SetAttribute("unit",nil)
+      f:SetAttribute("checkselfcast", true)
+    else
+      f:SetAttribute("checkselfcast",ATTRIBUTE_NOOP)
+      f:SetAttribute("unit","none") -- "none" gives you the glowing cast hand if no target selected, or casts on target if target selected
+      if c == "none" then
+        -- nothing to do
+      elseif c == "alt" or c == "ctrl" or c == "shift" then
+        f:SetAttribute(c.."-unit*","player")
+      elseif c == "right-click" then
+        if f:GetAttribute("*action-page2") then
+          -- right-click modifier not supported with multipage
+          self.config.selfcast = nil
+          f:SetAttribute("unit",nil)
+          f:SetAttribute("checkselfcast",true)
+        else
+          f:SetAttribute("*unit2","player")
+        end
+      end
+    end
+  end
+end
+
+function RAAT:UpdateIcon()
+  local action = self:GetID()
+  local texture = action and GetActionTexture(action)
+
+  self:DisplayIcon(action and texture)
+  self:DisplayEquipped(action and IsEquippedAction(action))
+  self:UpdateInUse()
+  self:UpdateUsable()
+  self:UpdateCooldown()
+end
+
+function RAAT:UpdateInUse()
+  local action = self:GetID()
+	if action and (IsCurrentAction(action) or IsAutoRepeatAction(action)) then
+		self:DisplayInUse(true)
+	else
+    self:DisplayInUse(false)
+	end
+end
+
+function RAAT:UpdateCount()
+  local action = self:GetID()
+  if action and (IsConsumableAction(action) or IsStackableAction(action)) then
+    self:DisplayCount(GetActionCount(action)) -- will display a 0 if none remaining
+  else
+    self:DisplayCount(nil) -- will display nothing
+  end
+end
+
+function RAAT:UpdateMacroText()
+  local action = self:GetID()
+  self:DisplayName(action and GetActionText(action))
+end
+
+function RAAT:UpdateUsable()
+  local action = self:GetID()
+  if action and HasAction(action) then
+    local isUsable, notEnoughMana = IsUsableAction(action)
+    local outOfRange = IsActionInRange(action) == 0
+    self:DisplayUsable(isUsable and not outOfRange, notEnoughMana, outOfRange)
+  else
+    self:DisplayUsable(false, false, false)
+  end
+end
+
+function RAAT:UpdateCooldown()
+  local action = self:GetID()
+  if action then
+    self:DisplayCooldown(GetActionCooldown(action))
+  end
+end
+
+function RAAT:UpdateActionEvents()
+  local action = self:GetID()
+	if action and HasAction(action) then
+    if not self.actionEventsRegistered then
+      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")
+      self:RegisterEvent("PLAYER_LEAVE_COMBAT")
+      self:RegisterEvent("START_AUTOREPEAT_SPELL")
+      self:RegisterEvent("STOP_AUTOREPEAT_SPELL")
+      self.actionEventsRegistered = true
+    end
+  elseif self.actionEventsRegistered then
+    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.actionEventsRegistered = false
+	end
+end
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/classes/ReAction_ColorScheme.lua	Tue Mar 20 21:25:29 2007 +0000
@@ -0,0 +1,107 @@
+-- ReAction.IColorScheme is an interface to describe color schemes for hotkey labels and
+-- icon/border colorization, depending on the current state.
+--
+
+local AceOO = AceLibrary("AceOO-2.0")
+
+ReAction.IColorScheme = AceOO.Interface {
+  GetHotkeyColor = "function", -- r,g,b,a = GetHotkeyColor( isUsable, notEnoughMana, outOfRange, [bindingText] )
+  GetIconColor   = "function", -- r,g,b,a = GetIconColor( isUsable, notEnoughMana, outOfRange )
+  GetBorderColor = "function", -- r,g,b,a = GetBorderColor( isUsable, notEnoughMana, outOfRange )
+}
+
+
+
+-- ReAction.DefaultColorScheme is a Mixin implementation of ReAction.IColorScheme which
+-- supports the standard Blizzard colorization plus optional hotkey modifier-driven colorization
+-- and colorizing icons red (in addition to hotkeys) when out of range.
+--
+-- DefaultColorScheme makes use of self.config as follows:
+--
+-- self.config = {
+--   keyBindColorCode = true/false, -- color-code keybindings based on modifier key
+-- }
+
+
+ReAction.DefaultColorScheme = AceOO.Mixin {
+  "GetHotkeyColor",
+  "GetModifiedHotkeyColor",  -- returns the default modified color (if configured)
+  "GetIconColor",
+  "GetBorderColor"
+}
+
+
+-- private variables
+local hotKeyDefaultColor         = { r=1.00,  g=1.00,  b=1.00,  a=1.00 } -- white
+local hotKeyDisabledColor        = { r=0.60,  g=0.60,  b=0.60,  a=1.00 } -- 60% gray
+local hotKeyOutOfRangeColor      = { r=1.00,  g=0.20,  b=0.20,  a=1.00 } -- red
+
+local actionUsableColor          = { r=1.00,  g=1.00,  b=1.00,  a=1.00 } -- white
+local actionNotUsableColor       = { r=0.40,  g=0.40,  b=0.40,  a=1.00 } -- 40% gray
+local actionNotEnoughManaColor   = { r=0.20,  g=0.20,  b=0.70,  a=1.00 } -- medium blue
+local actionOutOfRangeColor      = { r=1.00,  g=0.20,  b=0.20,  a=1.00 } -- red
+
+local hotKeyModifierColors = {
+  S = { r=0.60, g=0.60, b=1.00, a=1.00 },  -- shift (blue)
+  C = { r=1.00, g=0.82, b=0.00, a=1.00 },  -- ctrl  (gold)
+  A = { r=0.10, g=1.00, b=0.10, a=1.00 },  -- alt   (green)
+  M = { r=0.90, g=0.30, b=1.00, a=1.00 },  -- mouse (purple)
+}
+
+-- build list of modifier keys (as a string) from table above
+local hotKeyModifiers = ""
+for k, _ in pairs(hotKeyModifierColors) do
+  hotKeyModifiers = hotKeyModifiers..k
+end
+
+-- private functions
+-- extract and return color fields from a table, to be fed into SetVertexColor()/SetTextColor()
+local function tcolor(c)
+  return c.r, c.g, c.b, c.a
+end
+
+
+
+-- mixin methods
+
+local RADCS = ReAction.DefaultColorScheme
+
+function RADCS:GetHotkeyColor( isUsable, notEnoughMana, outOfRange, bindingTxt )
+	if isUsable or notEnoughMana then
+    return tcolor(self:GetModifiedHotkeyColor(bindingTxt))
+  elseif outOfRange then
+    return tcolor(hotKeyOutOfRangeColor)
+  else
+    return tcolor(hotKeyDisabledColor)
+  end
+end
+
+function RADCS:GetModifiedHotkeyColor(txt)
+  local c = hotKeyDefaultColor
+  if txt and self.config.keyBindColorCode then
+    local modKey = string.match( txt or "", "(["..hotKeyModifiers.."])%-")
+    c = modKey and hotKeyModifierColors[modKey] or c
+  end
+  return c
+end
+
+function RADCS:GetIconColor( isUsable, notEnoughMana, outOfRange )
+	if isUsable then
+    return tcolor(actionUsableColor)
+  elseif notEnoughMana then
+    return tcolor(actionNotEnoughManaColor)
+  elseif outOfRange then
+    return tcolor(actionOutOfRangeColor)
+  else
+    return tcolor(actionNotUsableColor)
+  end
+end
+
+function RADCS:GetBorderColor( isUsable, notEnoughMana, outOfRange )
+	if isUsable or notEnoughMana or outOfRange then
+    return tcolor(actionUsableColor)
+  else
+    return tcolor(actionNotUsableColor)
+  end
+end
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/classes/ReAction_PetActionDisplay.lua	Tue Mar 20 21:25:29 2007 +0000
@@ -0,0 +1,322 @@
+-- The ReAction.PetActionDisplay mixin defines 'regular' action button display functionality
+-- and is an implementation of the ReAction.IDisplay and ReAction.ActionType.IDisplay interfaces.
+--
+-- This Mixin assumes that it has been mixed in with a ReAction-derived class which implements
+-- the ReAction.IActionType and ReAction.IColorScheme interfaces.
+--
+-- This mixin uses properties of self.config to define display elements:
+--
+-- self.config = {
+--   keyBindLoc = "POSITION",       -- keybind anchor location
+--   showKeyBind = true/false,      -- show keybind labels
+--   showGrid = true/false,         -- always show empty buttons
+-- }
+--
+
+local AceOO = AceLibrary("AceOO-2.0")
+
+ReAction.PetActionDisplay = AceOO.Mixin {
+  -- ReAction.IDisplay interface
+  "SetupDisplay",
+  "UpdateDisplay",
+  "TempShow",
+  "GetActionFrame",
+  "GetBaseButtonSize",
+  "DisplayID",
+  "DisplayHotkey",
+
+  -- ReAction.PetActionType.IDisplay interface
+  "DisplayAutoCast",
+  "DisplayAutoCastable",
+  "DisplayInUse",
+  "DisplayUsable",
+  "DisplayIcon",
+  "DisplayCooldown",
+
+  -- Event handlers
+  "PreClick",
+  "PostClick",
+  "OnDragStart",
+  "OnReceiveDrag",
+  "OnEnter",
+  "OnLeave",
+
+  -- internal functions
+  "ApplyLayout",
+  "ApplyStyle",
+  "DisplayVisibility",
+}
+
+local RAPAD = ReAction.PetActionDisplay
+
+
+-- private constants
+local _G = getfenv(0)
+
+local actionIDColor = { r=1.00,  g=0.82,  b=0.00,  a=1.00 } -- gold
+
+
+-- private functions
+-- extract and return color fields from a table, to be fed into SetVertexColor()/SetTextColor()
+local function tcolor(c)
+  return c.r, c.g, c.b, c.a
+end
+
+
+-----------------------------------
+-- Interface Implementation Methods
+-----------------------------------
+function RAPAD:SetupDisplay( name )
+  -- create the button widget
+  local b = CreateFrame("CheckButton", name, nil, "ActionButtonTemplate, SecureActionButtonTemplate")
+  b:EnableMouse()
+  b:RegisterForDrag("LeftButton", "RightButton")
+  b:RegisterForClicks("AnyUp")
+  b:SetScript("PreClick",      function(arg1) self:PreClick(arg1) end)
+  b:SetScript("PostClick",     function(arg1) self:PostClick(arg1) end)
+  b:SetScript("OnDragStart",   function(arg1) self:OnDragStart(arg1) end)
+  b:SetScript("OnReceiveDrag", function() self:OnReceiveDrag() end)
+  b:SetScript("OnEnter",       function() self:OnEnter() end)
+  b:SetScript("OnLeave",       function() self:OnLeave() end)
+
+
+  b:SetWidth(30)
+  b:SetHeight(30)
+  
+  local tx = b:GetNormalTexture()
+  tx:ClearAllPoints()
+  tx:SetWidth(54)
+  tx:SetHeight(54)
+  tx:SetPoint("CENTER",0,-1)
+
+  local autoCastable = b:CreateTexture(nil, "OVERLAY")
+  autoCastable:SetTexture("Interface\\Buttons\\UI-AutoCastableOverlay")
+  autoCastable:SetWidth(58)
+  autoCastable:SetHeight(58)
+  autoCastable:SetPoint("CENTER")
+  autoCastable:Hide()
+
+  local autoCast = CreateFrame("Model",nil,b)
+  autoCast:SetModel("Interface\\Buttons\\UI-AutoCastButton.mdx")
+  autoCast:SetScale(1.2)
+  autoCast:SetAllPoints()
+  autoCast:SetSequence(0)
+  autoCast:SetSequenceTime(0,0)
+  autoCast:SetFrameStrata("HIGH") -- Otherwise it won't appear on top of the icon for some reason
+  autoCast:Hide()
+
+
+  local cd = _G[name.."Cooldown"]
+	cd:SetScale(1);
+	cd:ClearAllPoints();
+	cd:SetWidth(33);
+	cd:SetHeight(33);
+	cd:SetPoint("CENTER", -2, -1);
+
+  -- store references to the various sub-frames of ActionButtonTemplate so we don't have to look it up all the time
+  self.frames      = {
+    button         = b,
+    autoCastable   = autoCastable, --_G[name.."AutoCastable"],
+    autoCast       = autoCast, --_G[name.."AutoCast"],
+    hotkey         = _G[name.."HotKey"],
+    cooldown       = cd,
+    icon           = _G[name.."Icon"],
+    normalTexture  = tx, --_G[name.."NormalTexture2"],
+    actionID       = nil,    -- defer creating actionID font string until it's actually requested
+  }
+
+  self.tmpShow_ = 0
+end
+
+function RAPAD:UpdateDisplay()
+  self:ApplyLayout()
+  self:ApplyStyle()
+  self:DisplayVisibility()
+  -- refresh the action ID display
+  if ReAction.showIDs_ then
+    self:DisplayID(self:GetID())
+  end
+end
+
+function RAPAD:TempShow( visible )
+  visible = visible and true or false -- force data integrity
+  self.showTmp_ = max(0, (self.showTmp_ or 0) + (visible and 1 or -1))
+  self:DisplayVisibility()
+end
+
+function RAPAD:GetActionFrame()
+  return self.frames.button
+end
+
+function RAPAD:GetBaseButtonSize()
+  return 30
+end
+
+function RAPAD:DisplayID( id )
+  local f = self.frames.actionID
+  if id then
+    if not f then
+      -- create the actionID label
+      f = self.frames.button:CreateFontString(nil,"ARTWORK","NumberFontNormalSmall")
+      f:SetPoint("BOTTOMLEFT")
+      f:SetTextColor( tcolor(actionIDColor) )
+      self.frames.actionID = f
+    end
+    f:SetText(tostring(id))
+    f:Show()
+  elseif f then
+    f:Hide()
+  end
+end
+
+function RAPAD:DisplayHotkey(txt)
+  self.frames.hotkey:SetText(string.upper(txt or ""))
+end
+
+function RAPAD:DisplayInUse( inUse )
+  self.frames.button:SetChecked( inUse and 1 or 0 )
+end
+
+function RAPAD:DisplayUsable( usable )
+  SetDesaturation(self.frames.icon, (not usable) and 1 or nil) -- argument is backward
+  self.frames.hotkey:SetTextColor(self:GetHotkeyColor(usable, false, false, self.frames.hotkey:GetText()) )
+end
+
+function RAPAD:DisplayAutoCast( show )
+  if show then 
+    self.frames.autoCast:Show()
+  else
+    self.frames.autoCast:Hide()
+  end
+end
+
+function RAPAD:DisplayAutoCastable( show )
+  if show then 
+    self.frames.autoCastable:Show()
+  else
+    self.frames.autoCastable:Hide()
+  end
+end
+
+function RAPAD:DisplayIcon( texture )
+  local f = self.frames.button
+  local icon = self.frames.icon
+  if texture then
+    icon:SetTexture(texture)
+    icon:Show()
+    f:SetNormalTexture("Interface\\Buttons\\UI-Quickslot2")
+  else
+    f:SetScript("OnUpdate",nil)
+    icon:Hide()
+    f:SetNormalTexture("Interface\\Buttons\\UI-Quickslot")
+  end
+  self:DisplayVisibility()
+end
+
+function RAPAD:DisplayCooldown( start, duration, enable )
+  CooldownFrame_SetTimer(self.frames.cooldown, start, duration, enable)
+end
+
+
+
+
+
+
+----------------------
+-- Event Handlers
+----------------------
+function RAPAD:PreClick()
+  -- not sure why we have to do this
+  self.frames.button:SetChecked(0)
+end
+
+function RAPAD:PostClick()
+  self:UpdateAction()
+end
+
+function RAPAD:OnDragStart()
+  if LOCK_ACTIONBAR ~= "1" or IsShiftKeyDown() then
+    self:PickupAction()
+  end
+end
+
+function RAPAD:OnReceiveDrag()
+  self:PlaceAction()
+end
+
+function RAPAD:OnEnter()
+  self:SetTooltip() -- from ReAction base class
+end
+
+function RAPAD:OnLeave()
+  self:ClearTooltip() -- from ReAction base class
+end
+
+
+
+----------------------
+-- Internal methods
+----------------------
+
+local function placeLabel( label, anchor )
+  local top = string.match(anchor,"TOP")
+  local bottom = string.match(anchor, "BOTTOM")
+  label:ClearAllPoints()
+  label:SetWidth(40)
+  label:SetPoint(top or bottom,0,top and 2 or -2)
+  local j
+  if string.match(anchor,"LEFT") then
+    j = "LEFT"
+  elseif string.match(anchor,"RIGHT") then
+    j = "RIGHT"
+  else
+    j = "CENTER"
+  end
+  label:SetJustifyH(j)
+end
+
+
+function RAPAD:ApplyLayout()
+  local f = self.frames
+
+  if self.config.keyBindLoc then
+    placeLabel(f.hotkey, self.config.keyBindLoc)
+  end
+
+  if self.config.showKeyBind then
+    f.hotkey:Show()
+  else
+    f.hotkey:Hide()
+  end
+
+  if self.config.showBorder then
+    f.normalTexture:SetAlpha(1)
+  else
+    f.normalTexture:SetAlpha(0)
+  end
+end
+
+function RAPAD:ApplyStyle()
+
+end
+
+function RAPAD:DisplayVisibility()
+  local b = self.frames.button
+
+  -- can't hide/show in combat
+  if not InCombatLockdown() then
+    if b:GetAttribute("statehidden") then
+      b:Hide()
+    elseif not self:IsActionEmpty() then
+      b:GetNormalTexture():SetAlpha(1.0)
+      b:Show()
+    elseif self.showTmp_ and self.showTmp_ > 0 or self.config.showGrid then
+      b:GetNormalTexture():SetAlpha(0.5)
+      self.frames.cooldown:Hide()
+      b:Show()
+    else
+      b:Hide()
+    end
+  end
+end
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/classes/ReAction_PetActionType.lua	Tue Mar 20 21:25:29 2007 +0000
@@ -0,0 +1,222 @@
+-- The ReAction.PetActionType mixin defines Pet action button functionality 
+-- and is an implementation of the ReAction.IActionType interface.
+--
+-- The Mixin assumes that it is being mixed in with a ReAction-derived class
+-- which implements the ReAction.IDisplay and ReAction.PetActionType.IDisplay interfaces.
+
+local AceOO = AceLibrary("AceOO-2.0")
+
+ReAction.PetActionType = AceOO.Mixin {
+  -- ReAction.IDisplay interface
+  "SetID",
+  "GetID",
+  "SetupAction",
+  "UpdateAction",
+  "PickupAction",
+  "PlaceAction",
+  "IsActionEmpty",
+  "UpdateTooltip",
+
+  -- Event handlers
+  "PLAYER_ENTERING_WORLD",
+  "PLAYER_CONTROL_LOST",
+  "PLAYER_CONTROL_GAINED",
+  "PLAYER_FARSIGHT_FOCUS_CHANGED",
+  "UNIT_PET",
+  "UNIT_FLAGS",
+  "UNIT_AURA",
+  "PET_BAR_UPDATE",
+  "PET_BAR_UPDATE_COOLDOWN",
+  "PET_BAR_SHOWGRID",
+  "PET_BAR_HIDEGRID",
+
+  -- Internal functions
+  "UpdateCooldown",
+}
+
+local RAPAT = ReAction.PetActionType
+
+-- Required display elements
+RAPAT.IDisplay = AceOO.Interface {
+  DisplayAutoCast     = "function", -- DisplayAutoCast(bool)
+  DisplayAutoCastable = "function", -- DisplayAutoCastable(bool)
+  DisplayIcon         = "function", -- DisplayIcon(texture), Display the icon texture (nil for empty slot)
+  DisplayCooldown     = "function", -- DisplayCooldown(start, duration, enable), display cooldown timer
+  DisplayInUse        = "function", -- DisplayInUse(bool), display whether the action is in use
+  DisplayUsable       = "function", -- DisplayUsable(bool), display whether the action can be used now
+}
+
+
+-- private constants
+local actionIDColor = { r=1.00,  g=0.82,  b=0.00,  a=1.00 } -- gold
+
+-- private functions
+-- extract and return color fields from a table, to be fed into SetVertexColor()/SetTextColor()
+local function tcolor(c)
+  return c.r, c.g, c.b, c.a
+end
+
+---------------------------------------
+-- ReAction.IActionType interface implementation
+---------------------------------------
+function RAPAT:SetID( id )  -- paging not supported
+  id = tonumber(id)  -- force data integrity
+  if id then
+    self.config.ids[self.barIdx] = { id }
+    local f = self:GetActionFrame()
+    f:SetAttribute("action",id)
+    -- the following is the goofy hack to work around Blizzard not exporting the pet functions securely
+    f:SetAttribute("clickbutton2",getglobal("PetActionButton"..id))
+  end
+end
+
+function RAPAT:GetID()
+  return SecureButton_GetModifiedAttribute(self:GetActionFrame(), "action", button)
+end
+
+function RAPAT:SetupAction()
+  local b = self:GetActionFrame()
+  -- Blizzard didn't support TogglePetAutocast functionality in
+  -- SecureButton_OnClick(), so we have to spoof it
+  -- by delegating right-clicks to the hidden default action buttons
+  b:SetAttribute("type", "pet")
+  b:SetAttribute("type2", "click") 
+
+  -- shift-clicking to drag locked buttons off
+	b:SetAttribute("checkselfcast", true)
+	b:SetAttribute("useparent-unit", true)
+	
+	self:RegisterEvent("PLAYER_ENTERING_WORLD")
+	self:RegisterEvent("PLAYER_CONTROL_LOST");
+	self:RegisterEvent("PLAYER_CONTROL_GAINED");
+	self:RegisterEvent("PLAYER_FARSIGHT_FOCUS_CHANGED");
+	self:RegisterEvent("UNIT_PET");
+	self:RegisterEvent("UNIT_FLAGS");
+	self:RegisterEvent("UNIT_AURA");
+	self:RegisterEvent("PET_BAR_UPDATE");
+	self:RegisterEvent("PET_BAR_UPDATE_COOLDOWN");
+	self:RegisterEvent("PET_BAR_SHOWGRID");
+	self:RegisterEvent("PET_BAR_HIDEGRID");
+  
+  self:UpdateAction()
+end
+
+function RAPAT:UpdateAction()
+  local id = self:GetID()
+  if id then
+    local name, subtext, texture, isToken, isActive, autoCastAllowed, autoCastEnabled = GetPetActionInfo(id);
+    self:DisplayIcon( isToken and getglobal(texture) or texture )
+    self:DisplayInUse( isActive )
+    self:DisplayAutoCastable( autoCastAllowed )
+    self:DisplayAutoCast( autoCastEnabled )
+    self:DisplayCooldown( GetPetActionCooldown(id) )
+    self:DisplayUsable(GetPetActionsUsable())
+    -- If it's a 'token' save away the tooltip name for updateTooltip
+    self.isToken = isToken
+    if isToken then
+      self.tooltipName = getglobal(name)
+      self.tooltipSubtext = subtext
+    end
+  end
+end
+
+function RAPAT:PickupAction()
+  PickupPetAction(self:GetID())
+  self:UpdateAction()
+end
+
+function RAPAT:PlaceAction()
+  if not InCombatLockdown() then
+    PlacePetAction(self:GetID())
+  end
+  self:UpdateAction()
+end
+
+function RAPAT:IsActionEmpty()
+  local id = self:GetID()
+  return not(id and GetPetActionInfo(id))
+end
+
+function RAPAT:UpdateTooltip()
+  local id = self:GetID()
+	if GameTooltip:IsOwned(self:GetActionFrame()) and id then
+    if self.isToken then
+      GameTooltip:SetText(self.tooltipName, 1.0, 1.0, 1.0)
+      if ( self.tooltipSubtext ) then
+        GameTooltip:AddLine(self.tooltipSubtext, "", 0.5, 0.5, 0.5);
+      end
+      GameTooltip:Show();
+    else
+      if GameTooltip:SetPetAction(id) then
+    		self.tooltipTime = TOOLTIP_UPDATE_TIME
+      end
+    end
+	else
+		self.tooltipTime = nil
+	end
+end
+
+
+-----------------------------
+-- Event Handling
+-----------------------------
+function RAPAT:PLAYER_ENTERING_WORLD()
+  self:UpdateAction()
+end
+
+function RAPAT:PLAYER_CONTROL_LOST()
+  self:UpdateAction()
+end
+
+function RAPAT:PLAYER_CONTROL_GAINED()
+  self:UpdateAction()
+end
+
+function RAPAT:PLAYER_FARSIGHT_FOCUS_CHANGED()
+  self:UpdateAction()
+end
+
+function RAPAT:UNIT_PET(unit)
+  if unit == "player" then
+    self:UpdateAction()
+  end
+end
+
+function RAPAT:UNIT_FLAGS(unit)
+  if unit == "pet" then
+    self:UpdateAction()
+  end
+end
+
+function RAPAT:UNIT_AURA(unit)
+  if unit == "pet" then
+    self:UpdateAction()
+  end
+end
+
+function RAPAT:PET_BAR_UPDATE()
+  self:UpdateAction()
+end
+
+function RAPAT:PET_BAR_UPDATE_COOLDOWN()
+  self:UpdateCooldown()
+end
+
+function RAPAT:PET_BAR_SHOWGRID()
+  self:TempShow(true)
+end
+
+function RAPAT:PET_BAR_HIDEGRID()
+  self:TempShow(false)
+end
+
+
+-------------------------------------
+-- Internal functions
+-------------------------------------
+function RAPAT:UpdateCooldown()
+  local id = self:GetID()
+  if id then
+    self:DisplayCooldown( GetPetActionCooldown(id) )
+  end
+end
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/classes/ReAnchor.lua	Tue Mar 20 21:25:29 2007 +0000
@@ -0,0 +1,314 @@
+--
+-- ReAnchor.lua
+--
+-- Provides drag-placement facilities for frames.
+--
+
+-- local constants
+local AceOO = AceLibrary("AceOO-2.0")
+
+local edges = { "BOTTOM", "TOP", "LEFT", "RIGHT" }
+
+local pointsOnEdge = {
+  BOTTOM = { "BOTTOM", "BOTTOMLEFT",  "BOTTOMRIGHT",  },
+  TOP    = { "TOP",    "TOPLEFT",     "TOPRIGHT",     },
+  RIGHT  = { "RIGHT",  "BOTTOMRIGHT", "TOPRIGHT",     },
+  LEFT   = { "LEFT",   "BOTTOMLEFT",  "TOPLEFT",      },
+}
+
+local edgeSelector = {
+  BOTTOM = 1,  -- select x of x,y
+  TOP    = 1,  -- select x of x,y
+  LEFT   = 2,  -- select y of x,y
+  RIGHT  = 2,  -- select y of x,y  
+}
+
+local oppositePoints = {
+  BOTTOMLEFT  = "TOPRIGHT",
+  BOTTOM      = "TOP",
+  BOTTOMRIGHT = "TOPLEFT",
+  RIGHT       = "LEFT",
+  TOPRIGHT    = "BOTTOMLEFT",
+  TOP         = "BOTTOM",
+  TOPLEFT     = "BOTTOMRIGHT",
+  LEFT        = "RIGHT",
+  CENTER      = "CENTER",
+}
+
+local insidePointOffsetFuncs = {
+  BOTTOMLEFT  = function(x, y) return x, y end,
+  BOTTOM      = function(x, y) return 0, y end,
+  BOTTOMRIGHT = function(x, y) return -x, y end,
+  RIGHT       = function(x, y) return -x, 0 end,
+  TOPRIGHT    = function(x, y) return -x, -y end,
+  TOP         = function(x, y) return 0, -y end,
+  TOPLEFT     = function(x, y) return x, -y end,
+  LEFT        = function(x, y) return x, 0 end,
+  CENTER      = function(x, y) return 0, 0 end,
+}
+
+local pointCoordFuncs = {
+  BOTTOMLEFT  = function(f) return f:GetLeft(),  f:GetBottom() end,
+  BOTTOM      = function(f) return nil,          f:GetBottom() end,
+  BOTTOMRIGHT = function(f) return f:GetRight(), f:GetBottom() end,
+  RIGHT       = function(f) return f:GetRight(), nil end,
+  TOPRIGHT    = function(f) return f:GetRight(), f:GetTop() end,
+  TOP         = function(f) return nil,          f:GetTop() end,
+  TOPLEFT     = function(f) return f:GetLeft(),  f:GetTop() end,
+  LEFT        = function(f) return f:GetLeft(),  nil end,
+  CENTER      = function(f) return f:GetCenter() end,
+}
+
+local edgeBoundsFuncs = {
+  BOTTOM = function(f) return f:GetLeft(), f:GetRight() end,
+  LEFT   = function(f) return f:GetBottom(), f:GetTop() end
+}
+edgeBoundsFuncs.TOP   = edgeBoundsFuncs.BOTTOM
+edgeBoundsFuncs.RIGHT = edgeBoundsFuncs.LEFT
+
+
+-- local utility functions
+
+-- Returns absolute coordinates x,y of the named point 'p' of frame 'f'
+local function GetPointCoords( f, p )
+  local x, y = pointCoordFuncs[p](f)
+  if not(x and y) then
+    local cx, cy = f:GetCenter()
+    x = x or cx
+    y = y or cy
+  end
+  return x, y
+end
+
+
+-- Returns true if frame 'f1' can be anchored to frame 'f2'
+local function CheckAnchorable( f1, f2 )
+  -- can't anchor a frame to itself or to nil
+  if f1 == f2 or f2 == nil then
+    return false
+  end
+  
+  -- can always anchor to UIParent
+  if f2 == UIParent then
+    return true
+  end
+  
+  -- also can't do circular anchoring of frames 
+  -- walk the anchor chain, which generally shouldn't be that expensive
+  -- (who nests draggables that deep anyway?)
+  for i = 1, f2:GetNumPoints() do
+    local _, f = f2:GetPoint(i)
+    return CheckAnchorable(f1,f)
+  end
+  
+  return true
+end
+
+-- Returns true if frames f1 and f2 specified edges overlap
+local function CheckEdgeOverlap( f1, f2, e )
+  local l1, u1 = edgeBoundsFuncs[e](f1)
+  local l2, u2 = edgeBoundsFuncs[e](f2)
+  return l1 <= l2 and l2 <= u1 or l2 <= l1 and l1 <= u2
+end
+
+-- Returns true if point p1 on frame f1 overlaps edge e2 on frame f2
+local function CheckPointEdgeOverlap( f1, p1, f2, e2 )
+  local l, u = edgeBoundsFuncs[e2](f2)
+  local x, y = GetPointCoords(f1,p1)
+  x = select(edgeSelector[e2], x, y)
+  return l <= x and x <= u
+end
+
+-- Returns the distance between corresponding edges. It is 
+-- assumed that the passed in edges e1 and e2 are the same or opposites
+local function GetEdgeDistance( f1, f2, e1, e2 )
+  local x1, y1 = pointCoordFuncs[e1](f1)
+  local x2, y2 = pointCoordFuncs[e2](f2)
+  return math.abs((x1 or y1) - (x2 or y2))
+end
+
+-- Returns interior offsets (specified absolutely) from a point
+local function GetInteriorOffsetsToPoint(p, x, y)
+  return insidePointOffsetFuncs[p](x,y)
+end
+
+
+
+
+
+-- ReAnchor is a Mixin which provides some anchoring and 
+-- placement methods for frames.
+-- An object with the ReAnchor mixin must support the 
+-- IAnchorable interface (implicitly or explicitly).
+-- The mixin methods also require arguments to support
+-- that interface.
+
+-- In the method prototypes, 'IRObjs' is used to refer to a
+-- table of objects which support the IAnchorable interface.
+
+
+ReAnchor = AceOO.Mixin {
+  "GetClosestVisibleEdge",
+  "GetClosestVisiblePoint",
+  "GetClosestPointSnapped",
+  "DisplaySnapIndicator",
+  "HideSnapIndicator",
+}
+
+---------------------------------------------------------
+-- Constants and classes that are not exported via mixin
+---------------------------------------------------------
+ReAnchor.IAnchorable = AceOO.Interface {
+  GetFrame = "function",
+  GetAnchorage = "function",  -- return ReAnchor.anchorInside or .anchorOutside
+}
+
+ReAnchor.anchorInside  = { inside = true }
+ReAnchor.anchorOutside = { outside = true }
+
+ReAnchor.snapIndicator1 = CreateFrame("Frame",nil,UIParent,"ReAnchorSnapIndicatorTemplate")
+ReAnchor.snapIndicator2 = CreateFrame("Frame",nil,UIParent,"ReAnchorSnapIndicatorTemplate")
+
+
+
+
+--------------------
+-- Mixin methods
+--------------------
+
+-- returns:
+--  (1) o : the closest IRObj
+--  (2) e1 : the point (edge) on self:GetFrame()
+--  (3) e2 : the point (edge) on o:GetFrame()
+function ReAnchor:GetClosestVisibleEdge( IRObjs )
+  local f1 = self:GetFrame()
+  local r, o, e1, e2
+  for _, o2 in pairs(IRObjs) do
+    local f2 = o2:GetFrame()
+    local a = o2:GetAnchorage()
+    if f2:IsVisible() and CheckAnchorable(f1,f2) then
+      for _, e in pairs(edges) do
+        local opp = a.inside and e or oppositePoints[e]
+        if CheckEdgeOverlap(f1,f2,e) then
+          local d = GetEdgeDistance(f1, f2, e, opp)
+          if not r or d < r then
+            r, o, e1, e2 = d, o2, e, opp
+          end
+        end
+      end
+    end
+  end
+  return o, e1, e2
+end
+
+-- returns:
+--  (1) o:  the closest IRObj
+--  (1) p:  the point on self:GetFrame()
+--  (2) rp: the relativePoint on o:GetFrame()
+--  (3) x:  x offset
+--  (4) y:  y offset
+-- such that self:GetFrame():SetPoint(p,o:GetFrame(),rp,x,y) preserves the current location
+function ReAnchor:GetClosestVisiblePoint( IRObjs )
+  local f1 = self:GetFrame()
+  local o, e1, e2 = self:GetClosestVisibleEdge( IRObjs )
+  local f2 = o:GetFrame()
+  local rsq, p, rp, x, y
+  -- iterate pointsOnEdge in order and use < to prefer edge centers to corners
+  for _, p1 in ipairs(pointsOnEdge[e1]) do
+    if CheckPointEdgeOverlap(f1,p1,f2,e2) then
+      local p2 = o:GetAnchorage().outside and oppositePoints[p1] or p1
+      local x1, y1 = GetPointCoords(f1,p1)
+      local x2, y2 = GetPointCoords(f2,p2)
+      local dx = x1 - x2
+      local dy = y1 - y2
+      local rsq2 = dx*dx + dy*dy
+      if not rsq or rsq2 < rsq then
+        rsq, p, rp, x, y = rsq2, p1, p2, dx, dy
+      end
+    end
+  end
+  return o, p, rp, x, y
+end
+
+
+-- Calls self:GetClosestVisiblePoint() and then snaps to the specified
+-- offsets if within the given range in x and y (as appropriate)
+-- Return semantic is the same as GetClosestVisiblePoint(). Returns nil
+-- if no snap can be done.
+function ReAnchor:GetClosestPointSnapped(IRObjs, r, xOff, yOff)
+  local f1 = self:GetFrame()
+  local o, p, rp, x, y = self:GetClosestVisiblePoint(IRObjs)
+  local s = false
+  
+  if r then
+    local sx, sy = GetInteriorOffsetsToPoint(p, xOff or 0, yOff or 0)
+    local xx, yy = pointCoordFuncs[p](f1)
+    if xx and yy then
+      if math.abs(x) <= r then
+        x = sx
+        s = true
+      end
+      if math.abs(y) <= r then
+        y = sy
+        s = true
+      end
+    elseif xx then
+      if math.abs(x) <= r then
+        x = sx
+        s = true
+        if math.abs(y) <= r then
+          y = sy
+        end
+      end
+    elseif yy then
+      if math.abs(y) <= r then
+        y = sy
+        s = true
+        if math.abs(x) <= r then
+          x = sx
+        end
+      end
+    end
+  end
+  
+  if s then
+    return o, p, rp, x, y
+  end
+end
+
+
+
+-- shows anchor-indicators on the associated frame and the target frame
+-- when a snap is warranted.
+function ReAnchor:DisplaySnapIndicator( IRObjs, r, xOff, yOff )
+  local o, p, rp, x, y, snap = self:GetClosestPointSnapped(IRObjs, r, xOff, yOff)
+  local si1 = ReAnchor.snapIndicator1
+  local si2 = ReAnchor.snapIndicator2
+  if o then
+    si1:ClearAllPoints()
+    si2:ClearAllPoints()
+    si1:SetPoint("CENTER", self:GetFrame(), p, 0, 0)
+    local xx, yy = pointCoordFuncs[rp](o:GetFrame())
+    x = math.abs(x) <=r and xx and 0 or x
+    y = math.abs(y) <=r and yy and 0 or y
+    si2:SetPoint("CENTER", o:GetFrame(), rp, x, y)
+    si1:Show()
+    si2:Show()
+  else
+    if si1:IsVisible() then
+      si1:Hide()
+      si2:Hide()
+    end
+  end
+end
+
+
+function ReAnchor:HideSnapIndicator()
+  local si1 = ReAnchor.snapIndicator1
+  local si2 = ReAnchor.snapIndicator2
+  if si1:IsVisible() then
+    si1:Hide()
+    si2:Hide()
+  end
+end
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/classes/ReAnchor.xml	Tue Mar 20 21:25:29 2007 +0000
@@ -0,0 +1,18 @@
+<Ui xmlns="http://www.blizzard.com/wow/ui/" 
+  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+  xsi:schemaLocation="http://www.blizzard.com/wow/ui/..\FrameXML\UI.xsd">
+
+  <Frame name="ReAnchorSnapIndicatorTemplate" virtual="true" frameStrata="HIGH" enableMouse="false" hidden="true">
+    <Size>
+      <AbsDimension x="8" y="8"/>
+    </Size>
+    <Layers>
+      <Layer level="OVERLAY">
+        <Texture alphaMode="ADD">
+          <Color r="1.0" g="0.82" b="0" a="0.8"/>
+        </Texture>
+      </Layer>
+    </Layers>
+  </Frame>
+
+</Ui>
--- a/classes/ReBar.lua	Tue Mar 20 21:20:20 2007 +0000
+++ b/classes/ReBar.lua	Tue Mar 20 21:25:29 2007 +0000
@@ -1,55 +1,155 @@
+-- private constants
+local anchoredLabelColor    = { r = 1.0, g = 0.82, b = 0.0 }
+local nonAnchoredLabelColor = { r = 1.0, g = 1.0,  b = 1.0 }
 
--- private constants
-local insideFrame  = 1
-local outsideFrame = 2
+local DRAG_UPDATE_RATE = 0.125  -- cap at 8 Hz
 
-local pointFindTable = {
-  BOTTOMLEFT  = function(f) return f:GetLeft(),  f:GetBottom() end,
-  BOTTOM      = function(f) return nil,          f:GetBottom() end,
-  BOTTOMRIGHT = function(f) return f:GetRight(), f:GetBottom() end,
-  RIGHT       = function(f) return f:GetRight(), nil end,
-  TOPRIGHT    = function(f) return f:GetRight(), f:GetTop() end,
-  TOP         = function(f) return nil,          f:GetTop() end,
-  TOPLEFT     = function(f) return f:GetLeft(),  f:GetTop() end,
-  LEFT        = function(f) return f:GetLeft(),  nil end,
+local nStancePages = {
+  WARRIOR = 3,
+  ROGUE   = 0,
+  HUNTER  = 0,
+  DRUID   = 3, -- updated to 4 if talented
+  PALADIN = 0,
+  SHAMAN  = 0, -- As far as I know, ghost wolf is not a proper shapeshift form, it's just a buff
+  MAGE    = 0,
+  PRIEST  = 0, -- updated to 1 if talented
+  WARLOCK = 0,
 }
 
-local oppositePointTable = {
-  BOTTOMLEFT  = "TOPRIGHT",
-  BOTTOM      = "TOP",
-  BOTTOMRIGHT = "TOPLEFT",
-  RIGHT       = "LEFT",
-  TOPRIGHT    = "BOTTOMLEFT",
-  TOP         = "BOTTOM",
-  TOPLEFT     = "BOTTOMRIGHT",
-  LEFT        = "RIGHT"
+local stanceMaps = {
+  WARRIOR = {
+    -- note: warriors are never in shapeshift form 0.
+    [1] = "*:1", -- battle stance. All states go to state (page) 1.
+    [2] = "*:2", -- defensive stance. All states go to state (page) 2.
+    [3] = "*:3", -- berserker stance. All states go to state (page) 3.
+  },
+  ROGUE   = {},
+  HUNTER  = {},
+  DRUID   = {
+    [0] = "*:1", -- humanoid form. All states go to state (page) 1.
+    [1] = "*:2", -- bear/dire bear form. All states go to state (page) 2.
+    [2] = "*:1", -- aquatic form. All states to go state (page) 1 (same as humanoid)
+    [3] = "*:3", -- cat form. All states go to state (page) 3
+    [4] = "*:1", -- travel form. All states go to state (page) 1 (same as humanoid)
+    [5] = "*:4", -- oomkin/tree form [talent only]. All states go to state (page) 4
+    [6] = "*:1", -- flight form. All states go to state (page) 1 (same as humanoid) (??? How does this work with oomkin/tree?)
+  },
+  PALADIN = {},
+  SHAMAN  = {},
+  MAGE    = {},
+  PRIEST  = {
+    [0] = "*:1", -- normal form. All states go to page 1.
+    [1] = "*:2", -- shadowform (talent only). All states go to page 2.
+  },
+  WARLOCK = {},
 }
 
-local anchoredLabelColor = { r =0.6, g = 0.2, b = 1.0 }
-local nonAnchoredLabelColor = { r = 1.0, g = 0.82, b = 0.0 }
-
--- private variables
-local stickyTargets = {
-  [UIParent] = insideFrame,
-  [WorldFrame] = insideFrame
+local stealthMaps = { 
+  WARRIOR = "",
+  ROGUE   = "1:2",  -- go to page 2 for rogues
+  HUNTER  = "",
+  DRUID   = "3:5",  -- we'll replace with 1:2 if stance mapping is not enabled, and page 5 with 6 if oomkin/tree
+  PALADIN = "",
+  SHAMAN  = "",
+  MAGE    = "",
+  PRIEST  = "",
 }
 
+local unstealthMaps = {
+  WARRIOR = "",
+  ROGUE   = "2:1",  -- go to page 1 for rogues
+  HUNTER  = "",
+  DRUID   = "5:3",  -- we'll replace with 2:1 if stance mapping is not enabled, and page 5 with 6 if oomkin/tree
+  PALADIN = "",
+  SHAMAN  = "",
+  MAGE    = "",
+  PRIEST  = "",
+}
+
+-- Immediately fix if a druid with oomkin or tree (note: can't have both)
+local _, playerClass = UnitClass("player")
+if playerClass == "DRUID" and GetNumShapeshiftForms() > 4 then
+  nStancePages.DRUID = 4
+  stanceMaps.DRUID[5] = "*:4"
+  stealthMaps.DRUID = "3:6"
+  unstealthMaps.DRUID ="6:3"
+end
+
+-- Immediately fix if a priest with shadowform
+if playerClass == "PRIEST" and GetNumShapeshiftForms() > 1 then
+  nStancePages.PRIEST = 2
+end
+
+local AceOO = AceLibrary("AceOO-2.0")
+
 -- ReBar is an Ace 2 class prototype object.
-ReBar = AceLibrary("AceOO-2.0").Class("AceEvent-2.0")
+ReBar = AceOO.Class("AceEvent-2.0", ReAnchor, ReAnchor.IAnchorable)
 
-local dewdrop = AceLibrary("Dewdrop-2.0")
 
+----------------------
+-- Static members
+----------------------
+-- IButtonClass is an interface required of any button class type (not the class objects themselves) to be used with the bar
+ReBar.IButtonClass = AceOO.Interface {
+  Acquire = "function",       -- btn = Acquire(config, barIdx, pages, buttonsPerPage)
+                              -- analogous to new(), this should re-use recycled frames for memory efficiency
+  Release = "function",       -- Release(btn) should hide and dispose of the button (and recycle it)
+}
+
+-- IButton is an interface required of any button objects to be used with the bar
+ReBar.IButton = AceOO.Interface {
+  GetActionFrame = "function",  -- obtains the action frame, presumably inherited from SecureActionButtonTemplate or similar.
+  BarLocked      = "function",  -- BarLocked() called when the bar is locked.
+  BarUnlocked    = "function",  -- BarUnlocked() called when the bar is unlocked.
+  PlaceButton    = "function",  -- PlaceButton(parent, anchorPoint, offsetX, offsetY, squareSz), one-stop position and size setting
+  SetPages       = "function",  -- SetPages(n): sets number of pages for multi-paging. Can error if paging not supported.
+}
+
+-- wrap UIParent and WorldFrame in objects which implement the ReAnchor.IAnchorable interface
+local UIParentPlaceable = {
+  GetFrame = function() return UIParent end,
+  GetAnchorage = function() return ReAnchor.anchorInside end,
+}
+
+local WorldFramePlaceable = {
+  GetFrame = function() return WorldFrame end,
+  GetAnchorage = function() return ReAnchor.anchorInside end,
+}
+
+ReBar.anchorTargets = {
+  UIParentPlaceable,
+  WorldFramePlaceable
+}
+
+ReBar.UIParentAnchorTarget = {
+  UIParentPlaceable
+}
+
+
+
+
+
+
+--------------------
+-- instance methods
+--------------------
+-- construction and destruction
 function ReBar.prototype:init( config, id )
   ReBar.super.prototype.init(self)
 
-  local buttonClass = config and config.btnConfig and config.btnConfig.type and getglobal(config.btnConfig.type)
   self.config  = config
   self.barID   = id
-  self.class   = { button = buttonClass }
   self.buttons = { }
+  self.locked  = true
+
+  self.buttonClass = config.btnConfig and config.btnConfig.type and getglobal(config.btnConfig.type)
+  if not AceOO.inherits(self.buttonClass, ReBar.IButton) then
+    error("ReBar: Supplied Button type does not meet required interface.")
+  end
+   
 
   -- create the bar and control widgets
-  self.barFrame     = CreateFrame("Frame", "ReBar_"..self.barID, UIParent, "ReBarTemplate")
+  self.barFrame     = CreateFrame("Frame", "ReBar"..id, config.parent and getglobal(config.parent) or UIParent, "ReBarTemplate")
   self.controlFrame = getglobal(self.barFrame:GetName().."Controls")
   self.controlFrame.reBar = self
   self.barFrame:SetClampedToScreen(true)
@@ -58,82 +158,113 @@
   self.labelString = getglobal(self.controlFrame:GetName().."LabelString")
   self.labelString:SetText(id)
 
+  -- initial stateheader state
+  self.barFrame.StateChanged = function() self:StateChanged() end
+  self.barFrame:SetAttribute("state",config.pages and config.pages.currentPage or 1) -- initial state
+
   -- initialize the bar layout
   self:ApplySize()
   self:ApplyAnchor()
+  self:AcquireButtons()
   self:LayoutButtons()
   self:ApplyVisibility()
 
-  -- add bar to stickyTargets list
-  stickyTargets[self.barFrame] = outsideFrame
-  
-  -- initialize dewdrop menu
-	dewdrop:Register(self.controlFrame, 'children', function()
-	    dewdrop:FeedAceOptionsTable(ReActionGlobalMenuOptions)
-	    dewdrop:FeedAceOptionsTable(GenerateReActionBarOptions(self))
-	    dewdrop:FeedAceOptionsTable(GenerateReActionButtonOptions(self))
-	  end,
-	  'cursorX', true, 
-	  'cursorY', true
-	)
+  if self.config.pages then
+    self:SetPages(self.config.pages.n)
+    self:ApplyAutoStanceSwitch()
+    self:ApplyAutoStealthSwitch()
+    self:RefreshPageControls()
+  end
+
+  -- register page up/down buttons with ReBound for keybinding
+  if ReBound then
+    ReBound:AddKeybindTarget(getglobal(self.barFrame:GetName().."PageUp"))
+    ReBound:AddKeybindTarget(getglobal(self.barFrame:GetName().."PageDown"))
+  end
+
+  -- add bar to anchorTargets list
+  table.insert(ReBar.anchorTargets, self)
 end
 
+function ReBar.prototype:Destroy()
+  local f = self.barFrame
 
-function ReBar.prototype:Destroy()
-  if self.barFrame == dewdrop:GetOpenedParent() then
-    dewdrop:Close()
-    dewdrop:Unregister(self.barFrame)
+  self:HideControls()
+  f:Hide()
+  f:ClearAllPoints()
+  f:SetParent(nil)
+  f:SetPoint("BOTTOMRIGHT", UIParent, "TOPLEFT", 0, 0)
+  
+  while #self.buttons > 0 do
+    self.buttonClass:Release(table.remove(self.buttons))
   end
 
-  self:HideControls()
-  self.barFrame:Hide()
-  self.barFrame:ClearAllPoints()
-  self.barFrame:SetParent(nil)
-  self.barFrame:SetPoint("BOTTOMRIGHT", UIParent, "TOPLEFT", 0, 0)
+  -- remove from anchorTargets table
+  for idx, b in ipairs(ReBar.anchorTargets) do
+    if b == self then
+      table.remove(ReBar.anchorTargets, idx)
+      break
+    end
+  end
   
-  -- need to keep around self.config for dewdrop menus in the process of deleting self 
+  -- remove from globals
+  local n = f:GetName()
+  setglobal(n, nil)
+  setglobal(n.."Control", nil)
+  setglobal(n.."Controls", nil)
+  setglobal(n.."ControlsLabelString", nil)
+  setglobal(n.."PageUp", nil)
+  setglobal(n.."PageDown", nil)
+  setglobal(n.."Page", nil)
+  setglobal(n.."PageNumber", nil)
+  setglobal(n.."PageNumberLabel", nil)
+  setglobal(n.."PageNumberLabelText", nil)
+end
 
-  while #self.buttons > 0 do
-    self.class.button:release(table.remove(self.buttons))
-  end
 
-  -- remove from sticky targets table
-  stickyTargets[self.barFrame] = nil
-  
-  -- remove from global table
-  -- for some reason after a destroy/recreate the globals still reference
-  -- the old frames
-  setglobal(self.barFrame:GetName(), nil)
-  setglobal(self.barFrame:GetName().."Controls", nil)
-  setglobal(self.controlFrame:GetName().."LabelString", nil)
+
+
+-- ReAnchor.IAnchorable interface implementation
+function ReBar.prototype:GetFrame()
+  return self.barFrame
 end
 
+function ReBar.prototype:GetAnchorage()
+  return ReAnchor.anchorOutside
+end
+
+
+
 
 -- show/hide the control frame
 function ReBar.prototype:ShowControls()
+  self.locked = false
   self.controlFrame:Show()
   for _, b in ipairs(self.buttons) do
     b:BarUnlocked()
+    b:DisplayVisibility()
   end
+  self:ApplyVisibility()
 end
 
 function ReBar.prototype:HideControls()
+  self.locked = true
   local b = self.barFrame
   if b.isMoving or b.resizing then
     b:StopMovingOrSizing()
     b:SetScript("OnUpdate",nil)
   end
-  -- close any dewdrop menu owned by us
-  if self.barFrame == dewdrop:GetOpenedParent() then
-    dewdrop:Close()
-  end
   for _, b in ipairs(self.buttons) do
     b:BarLocked()
+    b:DisplayVisibility()
   end
   self.controlFrame:Hide()
+  self:ApplyVisibility()
 end
 
-
+function ReBar.prototype:GetControlFrame()
+  return self.controlFrame
+end
 
 
 -- accessors
@@ -142,20 +273,265 @@
 end
 
 function ReBar.prototype:ToggleVisibility()
-  self.config.visible = not self.config.visible
+  self:SetVisibility( not self:GetVisibility() )
+end
+
+function ReBar.prototype:SetVisibility(v)
+  self.config.visible = v and true or false -- force data integrity
   self:ApplyVisibility()
 end
 
 function ReBar.prototype:GetOpacity()
-  return self.config.opacity or 100
+  return tonumber(self.config.opacity) or 100
 end
 
 function ReBar.prototype:SetOpacity( o )
-  self.config.opacity = tonumber(o)
-  self:ApplyVisibility()
-  return self.config.opacity
+  o = tonumber(o)
+  if o then
+    self.config.opacity = o
+    self:ApplyVisibility()
+    return self.config.opacity
+  end
 end
 
+function ReBar.prototype:GetButtonList()
+ return self.buttons
+end
+
+function ReBar.prototype:GetGrowLeft()
+  return self.config.growLeft
+end
+
+function ReBar.prototype:SetGrowLeft(g)
+  self.config.growLeft = g and true or false
+  self:LayoutButtons()
+end
+
+function ReBar.prototype:GetGrowUp()
+  return self.config.growUp
+end
+
+function ReBar.prototype:SetGrowUp(g)
+  self.config.growUp = g and true or false
+  self:LayoutButtons()
+end
+
+function ReBar.prototype:GetColumnMajor()
+  return self.config.columnMajor
+end
+
+function ReBar.prototype:SetColumnMajor(m)
+  self.config.columnMajor = m and true or false
+  self:LayoutButtons()
+end
+
+
+-- paging methods
+function ReBar.prototype:IsPagingDisabled()
+  return not (self.config.pages and self.config.pages.n > 1)
+end
+
+function ReBar.prototype:GetPages()
+  return self.config.pages and self.config.pages.n or 1
+end
+
+function ReBar.prototype:SetPages(n)
+  n = tonumber(n)
+  if n and n >= 1 then
+    self.config.pages = self.config.pages or { }
+    self.config.pages.n = n
+    for _, btn in pairs(self.buttons) do
+      btn:SetPages(n)
+    end
+    if n > 1 then
+      local statebutton = "0:page1;"
+      -- map states 1-n to 'page1'-'pagen'
+      -- page 0 is the same as page 1.
+      for i = 1, n do
+        statebutton = statebutton..i..":page"..i..";"
+      end
+      self.barFrame:SetAttribute("statebutton",statebutton)
+    else
+      self.barFrame:SetAttribute("statebutton",ATTRIBUTE_NOOP)
+    end
+    self.barFrame:SetAttribute("statemap-anchor-next","1-"..n)
+    self.barFrame:SetAttribute("statemap-anchor-prev",tostring(n).."-1")
+    self:RefreshPageControls()
+  end
+end
+
+function ReBar.prototype:GetAutoStanceSwitch()
+  return self.config.pages and self.config.pages.autoStanceSwitch
+end
+
+function ReBar.prototype:SetAutoStanceSwitch( s )
+  if not self.config.pages then
+    self.config.pages = { n = 1 }
+  end
+  self.config.pages.autoStanceSwitch = s and true or false
+  self:ApplyAutoStanceSwitch()
+end
+
+function ReBar.prototype:ToggleAutoStanceSwitch()
+  self:SetAutoStanceSwitch( not self:GetAutoStanceSwitch() )
+end
+
+function ReBar.prototype:ApplyAutoStanceSwitch()
+  local switch = self:GetAutoStanceSwitch()
+  local _, class = UnitClass("player")
+  if switch then
+    -- check that the number of pages available is sufficient
+    local totalPages = nStancePages[class] + (self:GetAutoStealthSwitch() and 1 or 0)
+    if self:GetPages() < totalPages then
+      self:SetPages(totalPages)
+    end
+    for form, spec in pairs(stanceMaps[class]) do
+      self.barFrame:SetAttribute("statemap-stance-"..form,spec)
+    end
+    -- set initial value
+    self.barFrame:SetAttribute("state-stance",GetShapeshiftForm(true))
+  else
+    for form, _ in pairs(stanceMaps[class]) do
+      self.barFrame:SetAttribute("statemap-stance-"..form, ATTRIBUTE_NOOP)
+    end
+  end
+end
+
+function ReBar.prototype:GetAutoStealthSwitch()
+  return self.config.pages and self.config.pages.autoStealthSwitch
+end
+
+function ReBar.prototype:SetAutoStealthSwitch( s )
+  if not self.config.pages then
+    self.config.pages = { n = 1 }
+  end
+  self.config.pages.autoStealthSwitch = s and true or false
+  self:ApplyAutoStealthSwitch()
+end
+
+function ReBar.prototype:ToggleAutoStealthSwitch()
+  self:SetAutoStealthSwitch( not self:GetAutoStealthSwitch() )
+end
+
+function ReBar.prototype:ApplyAutoStealthSwitch()
+  local switch = self:GetAutoStealthSwitch()
+  local _, class = UnitClass("player")
+  if switch then
+    -- check that the number of pages available is sufficient
+    local totalPages = (self:GetAutoStanceSwitch() and nStancePages[class] > 0 and nStancePages[class] or 1) + 1
+    if self:GetPages() < totalPages then
+      self:SetPages(totalPages)
+    end
+    local s, s2
+    if class == "DRUID" and not self:GetAutoStanceSwitch() then
+      -- change mapping for cat->prowl and prowl->cat to 1:2 and 2:1 since no stance mapping
+      s = "1:2"
+      s2 = "2:1"
+    end
+    self.barFrame:SetAttribute("statemap-stealth-1",s or stealthMaps[class])
+    self.barFrame:SetAttribute("statemap-stealth-0",s2 or unstealthMaps[class])
+    -- set initial value
+    self.barFrame:SetAttribute("state-stealth",IsStealthed() or 0)
+  else
+    self.barFrame:SetAttribute("statemap-stealth-1",ATTRIBUTE_NOOP)
+    self.barFrame:SetAttribute("statemap-stealth-0",ATTRIBUTE_NOOP)
+  end
+end
+
+function ReBar.prototype:ArePageControlsHidden()
+  return not ( self.config.pages and self.config.pages.showControls )
+end
+
+function ReBar.prototype:TogglePageControlsHidden()
+  if self.config.pages then
+    self.config.pages.showControls = not self.config.pages.showControls
+    self:RefreshPageControls()
+  end
+end
+
+function ReBar.prototype:GetPageControlsLoc()
+  return self.config.pages and self.config.pages.controlsLoc
+end
+
+function ReBar.prototype:SetPageControlsLoc(loc)
+  if self.config.pages then
+    self.config.pages.controlsLoc = loc
+    self:RefreshPageControls()
+  end
+end
+
+function ReBar.prototype:RefreshPageControls()
+  local b = self.barFrame;
+  local upArrow   = getglobal(b:GetName().."PageUp")
+  local downArrow = getglobal(b:GetName().."PageDown")
+  local pageNum   = getglobal(b:GetName().."PageNumber")
+
+  if self:GetPages() > 1 and self.config.pages.showControls then
+    local loc = self.config.pages.controlsLoc
+
+    pageNum:ClearAllPoints()
+    upArrow:ClearAllPoints()
+    downArrow:ClearAllPoints()
+
+    local vertical = { 0,0,0,1,1,0,1,1 }
+    local horizontal = { 0,1,1,1,0,0,1,0 }
+    local textures = { 
+      upArrow:GetNormalTexture(),
+      upArrow:GetPushedTexture(),
+      upArrow:GetDisabledTexture(),
+      upArrow:GetHighlightTexture(),
+      downArrow:GetNormalTexture(),
+      downArrow:GetPushedTexture(),
+      downArrow:GetDisabledTexture(),
+      downArrow:GetHighlightTexture(),
+    }
+
+    local offset = 10
+    local mult = (loc == "RIGHT" or loc == "TOP") and 1 or -1
+    local pageNumOffset = mult * (self.config.spacing/2 - offset)
+    local arrowOffset = mult * offset
+
+    if loc == "Blizzard" or loc == nil then
+      pageNum:SetPoint("LEFT", b, "RIGHT", 28, 0)
+      upArrow:SetPoint("LEFT", b, "RIGHT", -2, 9)
+      downArrow:SetPoint("LEFT", b, "RIGHT", -2, -10)
+      for _, tex in ipairs(textures) do
+        tex:SetTexCoord(unpack(vertical))
+      end
+    elseif loc == "RIGHT" or loc == "LEFT" then
+      local relPoint = loc == "RIGHT" and "LEFT" or "RIGHT"
+      pageNum:SetPoint(relPoint, b, loc, pageNumOffset, 0)
+      upArrow:SetPoint("BOTTOM",pageNum,"TOP",arrowOffset, -12)
+      downArrow:SetPoint("TOP",pageNum,"BOTTOM",arrowOffset, 12)
+      for _, tex in ipairs(textures) do
+        tex:SetTexCoord(unpack(vertical))
+      end
+    else
+      pageNum:SetPoint(loc == "BOTTOM" and "TOP" or "BOTTOM", b, loc, 0, pageNumOffset)
+      upArrow:SetPoint("LEFT",pageNum,"RIGHT",-12,arrowOffset)
+      downArrow:SetPoint("RIGHT",pageNum,"LEFT",12,arrowOffset)
+      for _, tex in ipairs(textures) do
+        tex:SetTexCoord(unpack(horizontal))
+      end
+    end
+    self:StateChanged()
+    upArrow:Show()
+    downArrow:Show()
+    pageNum:Show()
+  else
+    upArrow:Hide()
+    downArrow:Hide()
+    pageNum:Hide()
+  end
+end
+
+function ReBar.prototype:StateChanged()
+  local page = self.barFrame:GetAttribute("state") or 1
+  getglobal(self.barFrame:GetName().."PageNumberLabelText"):SetText(page)
+  if self.config.pages then self.config.pages.currentPage = page end
+end
+
+
 
 -- layout methods
 function ReBar.prototype:ApplySize()
@@ -163,14 +539,14 @@
   local spacing  = self.config.spacing or 4
   local rows     = self.config.rows or 1
   local columns  = self.config.columns or 12
-  local w = buttonSz * columns + spacing * (columns + 1)
-  local h = buttonSz * rows + spacing * (rows + 1)
+  local w = buttonSz * columns + spacing * columns
+  local h = buttonSz * rows + spacing * rows
   local f = self.barFrame
 
-  -- +1: avoid resizing oddities caused by fractional UI scale setting
-  f:SetMinResize(buttonSz + spacing*2 + 1, buttonSz + spacing*2 + 1)
-  f:SetWidth(w + 1)
-  f:SetHeight(h + 1)
+  -- + 0.1: avoid issues with UI scaling
+  f:SetMinResize(buttonSz + spacing + 0.1, buttonSz + spacing + 0.1)
+  f:SetWidth(w + 0.1)
+  f:SetHeight(h + 0.1)
 end
 
 function ReBar.prototype:ApplyAnchor()
@@ -178,9 +554,9 @@
   local f = self.barFrame
   if a then
     f:ClearAllPoints()
-    f:SetPoint(a.point,getglobal(a.to),a.relPoint,a.x,a.y)
+    f:SetPoint(a.point,getglobal(a.frame),a.relPoint,a.x,a.y)
     local color = anchoredLabelColor
-    if a.to == "UIParent" or a.to == "WorldFrame" then
+    if a.frame == "UIParent" or a.frame == "WorldFrame" then
       color = nonAnchoredLabelColor
     end
     self.labelString:SetTextColor(color.r, color.g, color.b)
@@ -188,18 +564,9 @@
 end
 
 function ReBar.prototype:ApplyVisibility()
-  local v = self.config.visibility
-  if type(v) == "table" then
-    if v.class then
-      local _, c = UnitClass("player")
-      v = v.class[c]
-    end
-  elseif type(v) == "string" then
-    local value = getglobal(v)
-    v = value
-  end
+  local v = self.config.visible or not self.locked
   
-  if self.config.opacity then
+  if tonumber(self.config.opacity) then
     self.barFrame:SetAlpha(self.config.opacity / 100)
   end
 
@@ -213,26 +580,55 @@
 function ReBar.prototype:LayoutButtons()
   local r = self.config.rows
   local c = self.config.columns
-  local n = r * c
   local sp = self.config.spacing
   local sz = self.config.size
   local gSize = sp + sz
+  local point = (self.config.growUp and "BOTTOM" or "TOP")..(self.config.growLeft and "RIGHT" or "LEFT")
+  local major = self.config.columnMajor and r or c
+
+  for i, b in ipairs(self.buttons) do
+    local x = sp/2 + gSize * math.fmod(i-1,major)
+    local y = sp/2 + gSize * math.floor((i-1)/major)
+    if self.config.columnMajor then  x,y = y,x end
+    if self.config.growLeft then x = -x end
+    if not self.config.growUp then y = -y end
+    b:PlaceButton(self.barFrame, point, x, y, sz)
+  end
+end
+
+function ReBar.prototype:FlipRowsColumns()
+  self.config.rows, self.config.columns = self.config.columns, self.config.rows
+  self.config.columnMajor = not self.config.columnMajor
+  self:ApplySize()
+  self:LayoutButtons()
+end
+
+function ReBar.prototype:AcquireButtons()
+  local n = self.config.rows * self.config.columns
   
   for i = 1, n do
     if self.buttons[i] == nil then
-      table.insert(self.buttons, self.class.button:acquire(self.barFrame, self.config.btnConfig, i))
+      local b = self.buttonClass:Acquire(self.config.btnConfig, i, self.config.pages and self.config.pages.n, n)
+      if b then
+        if not AceOO.inherits(b, ReBar.IButton) then
+          error("ReBar: specified Button class object did not meet required interface")
+        end
+        if self.locked == false then
+          b:BarUnlocked() -- buttons assume they are created in a barlocked state
+        end
+        self.buttons[i] = b
+        self.barFrame:SetAttribute("addchild",b:GetActionFrame())
+      end
     end
-    local b = self.buttons[i]
-    if b == nil then
-      break -- handling for button types that support limited numbers
-    end
-    b:PlaceButton("TOPLEFT", sp + gSize * math.fmod(i-1,c), - (sp + gSize * math.floor((i-1)/c)), sz)
   end
 
-  -- b == nil, above, should always be the case if and only if i == n. ReBar never monkeys
-  -- with buttons in the middle of the sequence: it always adds or removes on the array end
-  while #self.buttons > n do
-    self.class.button:release(table.remove(self.buttons))
+  local maxn = table.maxn(self.buttons)
+  for i = n+1, table.maxn(self.buttons) do
+    local b = self.buttons[i]
+    self.buttons[i] = nil
+    if b then
+      self.buttonClass:Release(b)
+    end
   end
   
 end
@@ -243,7 +639,7 @@
   -- no point if we can't store the name or the offsets are incomplete
   if name and x and y then
     self.config.anchor = { 
-      to = name,
+      frame = name,
       point = p,
       relPoint = rp or p,
       x = x,
@@ -254,45 +650,53 @@
 
 
 
+function ReBar.prototype:StickyIndicatorUpdate()
+  if IsShiftKeyDown() then
+    local snapRange = self.config.size + self.config.spacing
+    self:DisplaySnapIndicator(ReBar.anchorTargets, snapRange, 0, 0)
+  else
+    self:HideSnapIndicator()
+  end
+end
+
+
 -- mouse event handlers (clicking/dragging/resizing the bar)
 function ReBar.prototype:BeginDrag()
   local f = self.barFrame
   f:StartMoving()
   f.isMoving = true
-  f:SetScript("OnUpdate", function() self:StickyIndicatorUpdate() end)
+  self.updateTime = DRAG_UPDATE_RATE
+  f:SetScript("OnUpdate", function(frame, elapsed) self:StickyIndicatorUpdate(elapsed) end)
 end
 
 function ReBar.prototype:FinishDrag()
-  local f, p, rp, x, y 
+  local o, p, rp, x, y 
   local bf = self.barFrame
+  local snapRange = self.config.size + self.config.spacing
 
   bf:StopMovingOrSizing()
   bf.isMoving = false
 
   bf:SetScript("OnUpdate",nil)
   if IsShiftKeyDown() then
-    f, p, rp, x, y = self:GetStickyAnchor()
-    ReBarStickyIndicator1:Hide()
-    ReBarStickyIndicator2:Hide()
+    o, p, rp, x, y = self:GetClosestPointSnapped(ReBar.anchorTargets, snapRange, 0, 0)
   end
   
-  if f == nil then
-    f = UIParent
-    local _
-    _, p,rp,x,y = self:GetClosestPointTo(f)
+  if o == nil then
+    o, p, rp, x, y = self:GetClosestVisiblePoint(ReBar.UIParentAnchorTarget, snapRange, 0, 0)
   end
-
-  if f then
-    self:StoreAnchor(f,p,rp,x,y)
-    self:ApplyAnchor()
-  end
+  
+  self:HideSnapIndicator()
+  self:StoreAnchor(o:GetFrame(), p, rp, x, y)
+  self:ApplyAnchor()
 end
 
 function ReBar.prototype:BeginBarResize( sizingPoint )
   local f = self.barFrame
   f:StartSizing(sizingPoint)
   f.resizing = true
-  f:SetScript("OnUpdate",function() self:ReflowButtons() end)
+  self.updateTime = DRAG_UPDATE_RATE
+  f:SetScript("OnUpdate",function(frame, elapsed) self:ReflowButtons(elapsed) end)
 end
 
 function ReBar.prototype:BeginButtonResize( sizingPoint, mouseBtn )
@@ -303,12 +707,13 @@
   local c = self.config.columns
   local s = self.config.spacing
   local sz = self.config.size
+  self.updateTime = DRAG_UPDATE_RATE
   if mouseBtn == "LeftButton" then
-    f:SetMinResize(c*(12 + 2*s) +1, r*(12 + 2*s) +1)
-    f:SetScript("OnUpdate",function() self:DragSizeButtons() end)
+    f:SetMinResize(c*(12 + s) +0.1, r*(12 + s) +0.1)
+    f:SetScript("OnUpdate",function(frame, elapsed) self:DragSizeButtons(elapsed) end)
   elseif mouseBtn == "RightButton" then
-    f:SetMinResize(c*sz+1, r*sz+1)
-    f:SetScript("OnUpdate",function() self:DragSizeSpacing() end)
+    f:SetMinResize(c*sz+0.1, r*sz+0.1)
+    f:SetScript("OnUpdate",function(frame, elapsed) self:DragSizeSpacing(elapsed) end)
   end
 end
 
@@ -323,122 +728,6 @@
 
 
 
--- sticky anchoring functions
-function ReBar.prototype:StickyIndicatorUpdate()
-  local si1 = ReBarStickyIndicator1
-  local si2 = ReBarStickyIndicator2
-  if IsShiftKeyDown() then
-    local f, p, rp, x, y = self:GetStickyAnchor()
-    if f then
-      si1:ClearAllPoints()
-      si2:ClearAllPoints()
-      si1:SetPoint("CENTER",self.barFrame,p,0,0)
-      si2:SetPoint("CENTER",f,rp,x,y)
-      si1:Show()
-      si2:Show()
-      return nil
-    end
-  end
-  si1:Hide()
-  si2:Hide()
-  si1:ClearAllPoints()
-  si2:ClearAllPoints()
-end
-
-function ReBar.prototype:CheckAnchorable(f)
-  -- can't anchor to self or to a hidden frame
-  if f == self.barFrame or not(f:IsShown()) then return false end
-
-  -- also can't anchor to frames that are anchored to self
-  for i = 1, f:GetNumPoints() do
-    local _, f2 = f:GetPoint(i)
-    if f2 == self.barFrame then return false end
-  end
-
-  return true
-end
-
-
-function ReBar.prototype:GetStickyAnchor()
-  local snapRange = (self.config.size + self.config.spacing)
-  local r2, f, p, rp, x, y = self:GetClosestAnchor()
-
-  if f and p then
-    local xx, yy = pointFindTable[p](f) 
-    if r2 and r2 < (snapRange*snapRange) then
-      if xx or math.abs(x) < snapRange then x = 0 end
-      if yy or math.abs(y) < snapRange then y = 0 end
-    elseif not(yy) and math.abs(x) < snapRange then
-      x = 0
-    elseif not(xx) and math.abs(y) < snapRange then
-      y = 0
-    else
-      f = nil -- nothing in range
-    end
-  end
-  return f, p, rp, x, y
-end
-
-function ReBar.prototype:GetClosestAnchor()
-  -- choose the closest anchor point on the list of target frames
-  local range2, frame, point, relPoint, offsetX, offsetY
-
-  for f, tgtRegion in pairs(stickyTargets) do
-    if self:CheckAnchorable(f) then
-      local r2 ,p, rp, x, y = self:GetClosestPointTo(f,tgtRegion)
-      if r2 then
-        if not(range2 and range2 < r2) then
-          range2, frame, point, relPoint, offsetX, offsetY = r2, f, p, rp, x, y
-        end
-      end
-    end
-  end
-
-  return range2, frame, point, relPoint, offsetX, offsetY
-end
-
-function ReBar.prototype:GetClosestPointTo(f,inside)
-  local range2, point, relPoint, offsetX, offsetY
-  local pft = pointFindTable
-  local cx, cy = self.barFrame:GetCenter()
-  local fcx, fcy = f:GetCenter()
-  local fh = f:GetHeight()
-  local fw = f:GetWidth()
-
-  -- compute whether edge bisector intersects target edge
-  local dcx = math.abs(cx-fcx) < fw/2 and (cx-fcx)
-  local dcy = math.abs(cy-fcy) < fh/2 and (cy-fcy)
-  
-  for p, func in pairs(pft) do
-    local rp, x, y
-    if inside == outsideFrame then
-      rp = oppositePointTable[p]
-      x, y = self:GetOffsetToPoint(f, func, pft[rp])
-    else
-      rp = p
-      x, y = self:GetOffsetToPoint(f, func, func)
-    end
-
-    -- if anchoring to an edge, only anchor if the center point overlaps the other edge
-    if (x or dcx) and (y or dcy) then
-      local r2 = (x or 0)^2 + (y or 0)^2
-      if range2 == nil or r2 < range2 then
-        range2, point, relPoint, offsetX, offsetY = r2, p, rp, x or dcx, y or dcy
-      end
-    end
-  end
-  return range2, point, relPoint, offsetX, offsetY
-end
-
-function ReBar.prototype:GetOffsetToPoint(f,func,ffunc)
-  local x, y = func(self.barFrame)  -- coordinates of the point on this frame
-  local fx, fy = ffunc(f)  -- coordinates of the point on the target frame
-  -- guarantees: if x then fx, if y then fy
-  return x and (x-fx), y and (y-fy)
-end
-
-
-
 
 
 
@@ -449,47 +738,64 @@
   return f:GetWidth(), f:GetHeight(), c.size, c.rows, c.columns, c.spacing
 end
 
+
 -- add and remove buttons dynamically as the bar is resized
-function ReBar.prototype:ReflowButtons()
-  local w, h, sz, r, c, sp = self:GetLayout()
+function ReBar.prototype:ReflowButtons( elapsed )
+  self.updateTime = self.updateTime - elapsed
+  if self.updateTime <= 0 then
+    self.updateTime = DRAG_UPDATE_RATE
+  
+    local w, h, sz, r, c, sp = self:GetLayout()
 
-  self.config.rows = math.floor( (h - sp) / (sz + sp) )
-  self.config.columns = math.floor( (w - sp) / (sz + sp) )
+    self.config.rows = math.floor( (h+1) / (sz + sp) )
+    self.config.columns = math.floor( (w+1) / (sz + sp) )
 
-  if self.config.rows ~= r or self.config.columns ~= c then
-    self:LayoutButtons()
+    if self.config.rows ~= r or self.config.columns ~= c then
+      self:AcquireButtons()
+      self:LayoutButtons()
+    end
   end
 end
 
 
 -- change the size of buttons as the bar is resized
-function ReBar.prototype:DragSizeButtons()
-  local w, h, sz, r, c, sp = self:GetLayout()
+function ReBar.prototype:DragSizeButtons( elapsed )
+  self.updateTime = self.updateTime - elapsed
+  if self.updateTime <= 0 then
+    self.updateTime = DRAG_UPDATE_RATE
+  
+    local w, h, sz, r, c, sp = self:GetLayout()
 
-  local newSzW = math.floor((w - (c+1)*sp)/c)
-  local newSzH = math.floor((h - (r+1)*sp)/r)
-  
-  self.config.size = math.max(12, math.min(newSzW, newSzH))
+    local newSzW = math.floor((w - c*sp)/c)
+    local newSzH = math.floor((h - r*sp)/r)
+    
+    self.config.size = math.max(12, math.min(newSzW, newSzH))
 
-  if self.config.size ~= sz then
-    self:LayoutButtons()
-    self:UpdateResizeTooltip()
+    if self.config.size ~= sz then
+      self:LayoutButtons()
+      self:UpdateResizeTooltip()
+    end
   end
 end
 
 
 -- change the spacing of buttons as the bar is resized
-function ReBar.prototype:DragSizeSpacing()
-  local w, h, sz, r, c, sp = self:GetLayout()
+function ReBar.prototype:DragSizeSpacing( elapsed )
+  self.updateTime = self.updateTime - elapsed
+  if self.updateTime <= 0 then
+    self.updateTime = DRAG_UPDATE_RATE
+  
+    local w, h, sz, r, c, sp = self:GetLayout()
 
-  local newSpW = math.floor((w - c*sz)/(c+1))
-  local newSpH = math.floor((h - r*sz)/(r+1))
+    local newSpW = math.floor((w - c*sz)/c)
+    local newSpH = math.floor((h - r*sz)/r)
 
-  self.config.spacing = math.max(0, math.min(newSpW, newSpH))
+    self.config.spacing = math.max(0, math.min(newSpW, newSpH))
 
-  if self.config.spacing ~= sp then
-    self:LayoutButtons()
-    self:UpdateResizeTooltip()
+    if self.config.spacing ~= sp then
+      self:LayoutButtons()
+      self:UpdateResizeTooltip()
+    end
   end
 end
 
@@ -503,7 +809,7 @@
 
 function ReBar.prototype:ShowTooltip()
   GameTooltip:SetOwner(self.barFrame, "ANCHOR_TOPRIGHT")
-  GameTooltip:AddLine("Bar "..self.barID)
+  GameTooltip:AddLine(self.config.btnConfig.subtype.." Bar "..self.barID..(self.config.visible and "" or " (Hidden)"))
   GameTooltip:AddLine("Drag to move")
   GameTooltip:AddLine("Shift-drag for sticky mode")
   GameTooltip:AddLine("Right-click for options")
--- a/classes/ReBar.xml	Tue Mar 20 21:20:20 2007 +0000
+++ b/classes/ReBar.xml	Tue Mar 20 21:25:29 2007 +0000
@@ -65,31 +65,25 @@
   </Frame>
 
 
-
-  <Frame name="ReBarStickyIndicatorTemplate" virtual="true" frameStrata="HIGH" enableMouse="false" hidden="true" parent="UIParent">
+  <Button name="ReBarPageArrowTemplate" inherits="SecureAnchorButtonTemplate" virtual="true">
     <Size>
-      <AbsDimension x="8" y="8"/>
+      <AbsDimension x="32" y="32"/>
     </Size>
     <Layers>
-      <Layer level="OVERLAY">
-        <Texture alphaMode="ADD">
-          <Color r="1.0" g="0.82" b="0" a="0.8"/>
-        </Texture>
+      <Layer level="BACKGROUND">
+        <Color r="1" g="1" b="0" setAllPoints="true"/>
       </Layer>
     </Layers>
-  </Frame>
+    <HitRectInsets>
+      <AbsInset left="6" right="6" top="7" bottom="7"/>
+    </HitRectInsets>
+  </Button>
 
-  <Frame name="ReBarStickyIndicator1" inherits="ReBarStickyIndicatorTemplate"/>
-  <Frame name="ReBarStickyIndicator2" inherits="ReBarStickyIndicatorTemplate"/>
 
-
-  <!-- A ReAction bar is a container for buttons. The bar container itself is invisible and non-responsive to
+  <!-- A ReBar is a container for buttons. The bar container itself is invisible and non-responsive to
        mouse input, but when unlocked a normally invisible child control frame becomes visible and
        consumes mouse events to move, resize, and set bar options. -->
-  <Frame name="ReBarTemplate" virtual="true" toplevel="true" enableMouse="true" movable="true" resizable="true">
-    <Layers>
-      <Layer level="BACKGROUND"/>
-    </Layers>
+  <Frame name="ReBarTemplate" inherits="SecureStateDriverTemplate" frameStrata="MEDIUM" virtual="true" toplevel="true" movable="true" resizable="true">
     <Frames>
       <Frame name="$parentControl" setAllPoints="true">
         <!-- this nesting is to ensure the control frame is on top of the buttons, which will 
@@ -156,6 +150,20 @@
                   <Color r="0.7" g="0.7" b="1.0" a="0.2"/>
                 </Texture>
               </Layer>
+              <Layer level="OVERLAY">
+                <FontString name="$parentLabelString" inherits="GameFontNormalLarge" justifyH="CENTER" text="(barID)" setAllPoints="true" outline="THICK">
+                  <Anchors>
+                    <Anchor point="CENTER"/>
+                  </Anchors>
+                  <Shadow>
+                    <Offset>
+                      <AbsDimension x="2" y="-2"/>
+                    </Offset>
+                    <Color r="0" g="0" b="0" a="1"/>
+                  </Shadow>
+                  <Color r="1" g="1" b="1" a="1"/>
+                </FontString>
+              </Layer>
             </Layers>
             <Frames>
               <!-- edge drag handles -->
@@ -241,54 +249,6 @@
                 </Frames>
               </Frame>
 
-              <Frame name="$parentLabel">
-                <Size>
-                  <AbsDimension x="32" y="24"/>
-                </Size>
-                <Anchors>
-                  <Anchor point="CENTER"/>
-                </Anchors>
-                <Backdrop edgeFile="Interface\Tooltips\UI-Tooltip-Border" tile="true">
-                  <EdgeSize>
-                    <AbsValue val="16"/>
-                  </EdgeSize>
-                  <TileSize>
-                    <AbsValue val="16"/>
-                  </TileSize>
-                  <BackgroundInsets>
-                    <AbsInset left="0" right="0" top="0" bottom="0"/>
-                  </BackgroundInsets>
-                </Backdrop>
-                <Layers>
-                  <Layer level="BACKGROUND">
-                    <Texture>
-                      <Anchors>
-                        <Anchor point="TOPLEFT">
-                          <Offset>
-                            <AbsDimension x="4" y="-4"/>
-                          </Offset>
-                        </Anchor>
-                        <Anchor point="BOTTOMRIGHT">
-                          <Offset>
-                            <AbsDimension x="-4" y="4"/>
-                          </Offset>
-                        </Anchor>
-                      </Anchors>
-                      <Color r="0.0" g="0.0" b="0.0"/>
-                    </Texture>
-                  </Layer>
-                  <Layer level="ARTWORK">
-                    <FontString name="$parentString" inherits="GameFontNormalLarge" justifyH="CENTER" text="(barID)">
-                      <Size>
-                        <AbsDimension x="24" y="18"/>
-                      </Size>
-                      <Anchors>
-                        <Anchor point="CENTER"/>
-                      </Anchors>
-                    </FontString>
-                  </Layer>
-                </Layers>
-              </Frame>
             </Frames>
             <Scripts>
               <OnLoad>
@@ -311,6 +271,77 @@
           </Button>
         </Frames>
       </Frame>
+
+      <Button name="$parentPageUp" inherits="ReBarPageArrowTemplate" hidden="true">
+        <Scripts>
+          <OnLoad>
+            this:SetAttribute("anchorchild",this:GetParent())
+            this:SetAttribute("childstate","^next")
+          </OnLoad>
+        </Scripts>
+        <NormalTexture file="Interface\MainMenuBar\UI-MainMenu-ScrollUpButton-Up"/>
+        <PushedTexture file="Interface\MainMenuBar\UI-MainMenu-ScrollUpButton-Down"/>
+        <DisabledTexture file="Interface\Buttons\UI-ScrollBar-ScrollUpButton-Disabled"/>
+        <HighlightTexture alphaMode="ADD" file="Interface\MainMenuBar\UI-MainMenu-ScrollUpButton-Highlight"/>
+      </Button>
+
+      <Button name="$parentPageDown" inherits="ReBarPageArrowTemplate" hidden="true">
+        <Scripts>
+          <OnLoad>
+            this:SetAttribute("anchorchild",this:GetParent())
+            this:SetAttribute("childstate","^prev")
+          </OnLoad>
+        </Scripts>
+        <NormalTexture file="Interface\MainMenuBar\UI-MainMenu-ScrollDownButton-Up"/>
+        <PushedTexture file="Interface\MainMenuBar\UI-MainMenu-ScrollDownButton-Down"/>
+        <DisabledTexture file="Interface\Buttons\UI-ScrollBar-ScrollDownButton-Disabled"/>
+        <HighlightTexture alphaMode="ADD" file="Interface\MainMenuBar\UI-MainMenu-ScrollDownButton-Highlight"/>
+      </Button>
+
+      <Frame name="$parentPage">
+        <Frames> <!-- nesting so it appears on top of arrows -->
+          <Frame name="$parentNumber">
+            <Size>
+              <AbsDimension x="12" y="12"/>
+            </Size>
+            <Layers>
+              <Layer level="BACKGROUND">
+                <Texture>
+                  <Color r="0.2" g="0.2" b="0.2" a="1.0"/>
+                </Texture>
+              </Layer>
+            </Layers>
+            <Frames>
+              <Frame name="$parentLabel">
+                <Size>
+                  <AbsDimension x="18" y="18"/>
+                </Size>
+                <Anchors>
+                  <Anchor point="CENTER"/>
+                </Anchors>
+                <Backdrop edgeFile="Interface\Tooltips\UI-Tooltip-Border" tile="true">
+                  <EdgeSize>
+                    <AbsValue val="12"/>
+                  </EdgeSize>
+                  <TileSize>
+                    <AbsValue val="12"/>
+                  </TileSize>
+                </Backdrop>
+                <Layers>
+                  <Layer level="ARTWORK">
+                    <FontString name="$parentText" inherits="GameFontNormalSmall" text="0">
+                      <Anchors>
+                        <Anchor point="CENTER"/>
+                      </Anchors>
+                      <Color r="1.0" g="0.82" b="0"/>
+                    </FontString>
+                  </Layer>
+                </Layers>
+              </Frame>
+            </Frames>
+          </Frame>
+        </Frames>
+      </Frame>
     </Frames>
   </Frame>
 
--- a/classes/ReBound.lua	Tue Mar 20 21:20:20 2007 +0000
+++ b/classes/ReBound.lua	Tue Mar 20 21:25:29 2007 +0000
@@ -1,43 +1,49 @@
--- ReBinder.lua
+-- ReBound.lua
 -- 
 
-ReBinder = { }
+
+local AceEvent = AceLibrary("AceEvent-2.0")
+
+ReBound = { }
 
 -- initial values
-ReBinder.active = false
+ReBound.active = false
 
-ReBinder.targets = { }
+ReBound.targets = { }
 
-function ReBinder:AddKeybindTarget( t )
+function ReBound:AddKeybindTarget( t )
   if t then
-    self.targets[t] = CreateFrame("Button", nil, t, "ReBinderClickBindingTemplate")
+    self.targets[t] = CreateFrame("Button", nil, t, "ReBoundClickBindingTemplate")
     self.targets[t].keybindTarget = t:GetName()
   end
 end
 
-function ReBinder:RemoveKeybindTarget( t )
+function ReBound:RemoveKeybindTarget( t )
   if t then
     self.targets[t] = nil
   end
 end
 
-function ReBinder:ShowClickBindingButtons()
-  for _, clickFrame in pairs(self.targets) do
+function ReBound:ShowClickBindingButtons()
+  AceEvent:TriggerEvent("EVENT_REBOUND_KEYBINDING_MODE", true)
+  for tgt, clickFrame in pairs(self.targets) do
     clickFrame:Show()
   end    
 end
 
-function ReBinder:HideClickBindingButtons()
-  for _, clickFrame in pairs(self.targets) do
+function ReBound:HideClickBindingButtons()
+  AceEvent:TriggerEvent("EVENT_REBOUND_KEYBINDING_MODE", false)
+  for tgt, clickFrame in pairs(self.targets) do
     clickFrame:Hide()
   end    
 end
 
-function ReBinder:ClearSelectedKey()
+function ReBound:ClearSelectedKey()
   self.selectedKey = nil
+  SetCursor(nil) -- reset cursor to default state
 end
 
-function ReBinder:ToggleEnabled()
+function ReBound:ToggleEnabled()
   if self:IsEnabled() then
     self:Disable()
   else
@@ -45,20 +51,32 @@
   end
 end
 
-function ReBinder:IsEnabled()
-  return ReBinderFrame:IsVisible()
+function ReBound:IsEnabled()
+  return ReBoundFrame:IsVisible()
 end
 
-function ReBinder:Enable()
-  ReBinderFrame:Show()
+function ReBound:Enable()
+  if InCombatLockdown() then
+    UIErrorsFrame:AddMessage("Can't set keybindings in combat")
+  else
+    ReBoundFrame:Show()
+  end
 end
 
-function ReBinder:Disable()
-  ReBinderFrame:Hide()
+function ReBound:Disable()
+  ReBoundFrame:Hide()
 end
 
 
-function ReBinder:HandleKeyPressed( key )
+local mouseButtonConvert = {
+  LeftButton = "BUTTON1",
+  RightButton = "BUTTON2",
+  MiddleButton = "BUTTON3",
+  Button4 = "BUTTON4",
+  Button5 = "BUTTON5"
+}
+
+function ReBound:HandleKeyPressed( key )
   if key == nil or key == "UNKNOWN" or key == "SHIFT" or key == "CTRL" or key == "ALT" then 
     return
   end
@@ -71,12 +89,16 @@
   if IsAltKeyDown() then
     key = "ALT-"..key
   end
-  if key == "ESCAPE" or GetBindingAction(key) == "REBINDER_TOGGLEBINDINGMODE" then
-    ReBinderFrame:Hide()
+  if key == "ESCAPE" or GetBindingAction(key) == "ReBound_TOGGLEBINDINGMODE" then
+    ReBoundFrame:Hide()
     return nil, nil
   end
-
+  key = mouseButtonConvert[key] or key
+  
   self.selectedKey = key
+  
+  -- change cursor to glowing hand
+  SetCursor("CAST_CURSOR")
 
   local keyTxt = GetBindingText(key, "KEY_")
   local cmd    = GetBindingAction(key)
@@ -97,31 +119,31 @@
 end
 
 -- TODO: move to override-binding model and store data in profile
-function ReBinder:BindSelectedKeyTo( btnName )
+function ReBound:BindSelectedKeyTo( btnName )
   if self.selectedKey and btnName then
     self:ClearBinding(btnName)
     SetBindingClick(self.selectedKey, btnName, "LeftButton")
     SaveBindings(2) -- 2 = character-specific
-    ReBinderFrame.statusMsg:SetText(GetBindingText(self.selectedKey, "KEY_") .. " is now bound to " .. btnName)
-    ReBinderFrame.selectedKey:SetText("(none)")
-    ReBinderFrame.currentAction:SetText("(none)")
-    self.selectedKey = nil
+    ReBoundFrame.statusMsg:SetText(GetBindingText(self.selectedKey, "KEY_") .. " is now bound to " .. btnName)
+    ReBoundFrame.selectedKey:SetText("(none)")
+    ReBoundFrame.currentAction:SetText("(none)")
+    self:ClearSelectedKey()
   end
 end
 
 
-function ReBinder:ClearBinding( btnName )
+function ReBound:ClearBinding( btnName )
   if btnName then
     local current = GetBindingKey("CLICK "..btnName..":LeftButton")
     if current then
       SetBinding(current, nil)
-      ReBinderFrame.statusMsg:SetText("|cFFFF3333"..btnName .. " is now unbound|r")
+      ReBoundFrame.statusMsg:SetText("|cFFFF3333"..btnName .. " is now unbound|r")
     end
   end
 end
 
 
-function ReBinder:UpdateCurrentTarget( btnName )
+function ReBound:UpdateCurrentTarget( btnName )
   local msg = ""
   if btnName then
     msg = btnName.." is currently "
@@ -132,5 +154,5 @@
       msg = msg .. " not bound"
     end
   end
-  ReBinderFrame.statusMsg:SetText(msg)
+  ReBoundFrame.statusMsg:SetText(msg)
 end
--- a/classes/ReBound.xml	Tue Mar 20 21:20:20 2007 +0000
+++ b/classes/ReBound.xml	Tue Mar 20 21:25:29 2007 +0000
@@ -3,7 +3,7 @@
   xsi:schemaLocation="http://www.blizzard.com/wow/ui/..\FrameXML\UI.xsd">
 
 
-  <Button name="ReBinderClickBindingTemplate" virtual="true" hidden="true" toplevel="true" setAllPoints="true">
+  <Button name="ReBoundClickBindingTemplate" virtual="true" hidden="true" toplevel="true" setAllPoints="true">
 		<HighlightTexture alphaMode="ADD" file="Interface\Buttons\ButtonHilight-Square"/>
     <Layers>
       <Layer level="BACKGROUND">
@@ -19,26 +19,26 @@
       <OnClick>
         local mouseBtn = arg1
         if mouseBtn == "LeftButton" then
-          ReBinder:BindSelectedKeyTo(this.keybindTarget)
+          ReBound:BindSelectedKeyTo(this.keybindTarget)
         elseif mouseBtn == "RightButton" then
-          ReBinder:ClearBinding(this.keybindTarget)
+          ReBound:ClearBinding(this.keybindTarget)
         end
       </OnClick>
       <PostClick>
         this:SetButtonState("NORMAL")
       </PostClick>
       <OnEnter>
-        ReBinder:UpdateCurrentTarget(this.keybindTarget)
+        ReBound:UpdateCurrentTarget(this.keybindTarget)
       </OnEnter>
       <OnLeave>
-        ReBinder:UpdateCurrentTarget(nil)
+        ReBound:UpdateCurrentTarget(nil)
       </OnLeave>
     </Scripts>
   </Button>
 
 
   <!-- this frame covers the entire UIParent. It is visible but empty and in the background, so all it does is consume key presses and unhandled mouse clicks -->
-  <Button name="ReBinderFrame" frameStrata="BACKGROUND" movable="false" enableMouse="true" enableKeyboard="true" parent="UIParent" hidden="true" setAllPoints="true">
+  <Button name="ReBoundFrame" frameStrata="BACKGROUND" movable="false" enableMouse="true" enableKeyboard="true" parent="UIParent" hidden="true" setAllPoints="true">
     <Frames>
       <!-- this is a dialog frame that appears to provide user feedback for the outer frame -->
       <Button name="$parentDialog" frameStrata="DIALOG" movable="true" enableMouse="true">
@@ -195,7 +195,7 @@
             this:RegisterForClicks("MiddleButtonUp","Button4Up","Button5Up")
           </OnLoad>
           <OnClick>
-            local k, a = ReBinder:HandleKeyPressed(arg1)
+            local k, a = ReBound:HandleKeyPressed(arg1)
             if k then
               this.selectedKey:SetText(k)
               this.currentAction:SetText(a or "(none)")
@@ -211,31 +211,35 @@
         this.statusMsg     = getglobal(this:GetName().."DialogStatusMsg")
         tinsert(UISpecialFrames,this:GetName())
         this:RegisterForClicks("MiddleButtonUp","Button4Up","Button5Up")
+        this:RegisterEvent("PLAYER_REGEN_DISABLED")
       </OnLoad>
       <OnShow>
         this.selectedKey:SetText("(none)")
         this.currentAction:SetText("(none)")
         this.statusMsg:SetText("")
-        ReBinder:ShowClickBindingButtons()
+        ReBound:ShowClickBindingButtons()
       </OnShow>
       <OnHide>
-        ReBinder:HideClickBindingButtons()
-        ReBinder:ClearSelectedKey()
+        ReBound:HideClickBindingButtons()
+        ReBound:ClearSelectedKey()
       </OnHide>
       <OnKeyDown>
-        local k, a = ReBinder:HandleKeyPressed(arg1)
+        local k, a = ReBound:HandleKeyPressed(arg1)
         if k then
           this.selectedKey:SetText(k)
           this.currentAction:SetText(a or "(none)")
         end
       </OnKeyDown>
       <OnClick>
-        local k, a = ReBinder:HandleKeyPressed(arg1)
+        local k, a = ReBound:HandleKeyPressed(arg1)
         if k then
           this.selectedKey:SetText(k)
           this.currentAction:SetText(a or "(none)")
         end
       </OnClick>
+      <OnEvent>
+        this:Hide() -- only event is enter-combat
+      </OnEvent>
     </Scripts>
   </Button>
 
--- a/libs/AceLibrary/AceLibrary.lua	Tue Mar 20 21:20:20 2007 +0000
+++ b/libs/AceLibrary/AceLibrary.lua	Tue Mar 20 21:25:29 2007 +0000
@@ -1,10 +1,10 @@
 --[[
 Name: AceLibrary
-Revision: $Rev: 19062 $
+Revision: $Rev$
 Developed by: The Ace Development Team (http://www.wowace.com/index.php/The_Ace_Development_Team)
 Inspired By: Iriel (iriel@vigilance-committee.org)
              Tekkub (tekkub@gmail.com)
-             Revision: $Rev: 19062 $
+             Revision: $Rev$
 Website: http://www.wowace.com/
 Documentation: http://www.wowace.com/index.php/AceLibrary
 SVN: http://svn.wowace.com/root/trunk/Ace2/AceLibrary
@@ -14,10 +14,11 @@
              proper error tools. It is handy because all the errors occur in the
              file that called it, not in the library file itself.
 Dependencies: None
+License: LGPL v2.1
 ]]
 
 local ACELIBRARY_MAJOR = "AceLibrary"
-local ACELIBRARY_MINOR = "$Revision: 19062 $"
+local ACELIBRARY_MINOR = "$Revision: 20000 $"
 
 local _G = getfenv(0)
 local previous = _G[ACELIBRARY_MAJOR]
@@ -25,7 +26,7 @@
 
 local function safecall(func,...)
     local success, err = pcall(func,...)
-    if not success then geterrorhandler()(err) end
+    if not success then geterrorhandler()(err:find("%.lua:%d+:") and err or (debugstack():match("\n(.-: )in.-\n") or "") .. err) end
 end
 
 -- @table AceLibrary
@@ -36,15 +37,15 @@
 
 local function error(self, message, ...)
 	if type(self) ~= "table" then
-		return _G.error(string.format("Bad argument #1 to `error' (table expected, got %s)", type(self)), 2)
+		return _G.error(("Bad argument #1 to `error' (table expected, got %s)"):format(type(self)), 2)
 	end
 	
 	local stack = debugstack()
 	if not message then
-		local _,_,second = string.find(stack, "\n(.-)\n")
+		local _,_,second = stack:find("\n(.-)\n")
 		message = "error raised! " .. second
 	else
-		local arg = { ... }
+		local arg = { ... } -- not worried about table creation, as errors don't happen often
 		
 		for i = 1, #arg do
 			arg[i] = tostring(arg[i])
@@ -52,35 +53,35 @@
 		for i = 1, 10 do
 			table.insert(arg, "nil")
 		end
-		message = string.format(message, unpack(arg))
+		message = message:format(unpack(arg))
 	end
 	
 	if getmetatable(self) and getmetatable(self).__tostring then
-		message = string.format("%s: %s", tostring(self), message)
+		message = ("%s: %s"):format(tostring(self), message)
 	elseif type(rawget(self, 'GetLibraryVersion')) == "function" and AceLibrary:HasInstance(self:GetLibraryVersion()) then
-		message = string.format("%s: %s", self:GetLibraryVersion(), message)
+		message = ("%s: %s"):format(self:GetLibraryVersion(), message)
 	elseif type(rawget(self, 'class')) == "table" and type(rawget(self.class, 'GetLibraryVersion')) == "function" and AceLibrary:HasInstance(self.class:GetLibraryVersion()) then
-		message = string.format("%s: %s", self.class:GetLibraryVersion(), message)
+		message = ("%s: %s"):format(self.class:GetLibraryVersion(), message)
 	end
 	
-	local first = string.gsub(stack, "\n.*", "")
-	local file = string.gsub(first, ".*\\(.*).lua:%d+: .*", "%1")
-	file = string.gsub(file, "([%(%)%.%*%+%-%[%]%?%^%$%%])", "%%%1")
+	local first = stack:gsub("\n.*", "")
+	local file = first:gsub(".*\\(.*).lua:%d+: .*", "%1")
+	file = file:gsub("([%(%)%.%*%+%-%[%]%?%^%$%%])", "%%%1")
 	
 	
 	local i = 0
-	for s in string.gmatch(stack, "\n([^\n]*)") do
+	for s in stack:gmatch("\n([^\n]*)") do
 		i = i + 1
-		if not string.find(s, file .. "%.lua:%d+:") and not string.find(s, "%(tail call%)") then
-			file = string.gsub(s, "^.*\\(.*).lua:%d+: .*", "%1")
-			file = string.gsub(file, "([%(%)%.%*%+%-%[%]%?%^%$%%])", "%%%1")
+		if not s:find(file .. "%.lua:%d+:") and not s:find("%(tail call%)") then
+			file = s:gsub("^.*\\(.*).lua:%d+: .*", "%1")
+			file = file:gsub("([%(%)%.%*%+%-%[%]%?%^%$%%])", "%%%1")
 			break
 		end
 	end
 	local j = 0
-	for s in string.gmatch(stack, "\n([^\n]*)") do
+	for s in stack:gmatch("\n([^\n]*)") do
 		j = j + 1
-		if j > i and not string.find(s, file .. "%.lua:%d+:") and not string.find(s, "%(tail call%)") then
+		if j > i and not s:find(file .. "%.lua:%d+:") and not s:find("%(tail call%)") then
 			return _G.error(message, j+1)
 		end
 	end
@@ -91,7 +92,7 @@
 	if not condition then
 		if not message then
 			local stack = debugstack()
-			local _,_,second = string.find(stack, "\n(.-)\n")
+			local _,_,second = stack:find("\n(.-)\n")
 			message = "assertion failed! " .. second
 		end
 		return error(self, message, ...)
@@ -108,9 +109,10 @@
 	local errored = false
 	arg = type(arg)
 	if arg ~= kind and arg ~= kind2 and arg ~= kind3 and arg ~= kind4 and arg ~= kind5 then
-		local _,_,func = string.find(debugstack(), "`argCheck'.-([`<].-['>])")
+		local stack = debugstack()
+		local _,_,func = stack:find("`argCheck'.-([`<].-['>])")
 		if not func then
-			_,_,func = string.find(debugstack(), "([`<].-['>])")
+			_,_,func = stack:find("([`<].-['>])")
 		end
 		if kind5 then
 			return error(self, "Bad argument #%s to %s (%s, %s, %s, %s, or %s expected, got %s)", tonumber(num) or 0/0, func, kind, kind2, kind3, kind4, kind5, arg)
@@ -130,7 +132,8 @@
 do
 	local function check(self, ret, ...)
 		if not ret then
-			return error(self, (string.gsub(..., ".-%.lua:%d-: ", "")))
+			local s = ...
+			return error(self, (s:gsub(".-%.lua:%d-: ", "")))
 		else
 			return ...
 		end
@@ -164,12 +167,12 @@
 
 local function svnRevisionToNumber(text)
 	if type(text) == "string" then
-		if string.find(text, "^%$Revision: (%d+) %$$") then
-			return tonumber((string.gsub(text, "^%$Revision: (%d+) %$$", "%1")))
-		elseif string.find(text, "^%$Rev: (%d+) %$$") then
-			return tonumber((string.gsub(text, "^%$Rev: (%d+) %$$", "%1")))
-		elseif string.find(text, "^%$LastChangedRevision: (%d+) %$$") then
-			return tonumber((string.gsub(text, "^%$LastChangedRevision: (%d+) %$$", "%1")))
+		if text:find("^%$Revision: (%d+) %$$") then
+			return tonumber((text:gsub("^%$Revision: (%d+) %$$", "%1")))
+		elseif text:find("^%$Rev: (%d+) %$$") then
+			return tonumber((text:gsub("^%$Rev: (%d+) %$$", "%1")))
+		elseif text:find("^%$LastChangedRevision: (%d+) %$$") then
+			return tonumber((text:gsub("^%$LastChangedRevision: (%d+) %$$", "%1")))
 		end
 	elseif type(text) == "number" then
 		return text
@@ -367,7 +370,7 @@
 		if m then
 			minor = m
 		else
-			_G.error(string.format("Bad argument #3 to  `IsNewVersion'. Must be a number or SVN revision string. %q is not appropriate", minor), 2)
+			_G.error(("Bad argument #3 to  `IsNewVersion'. Must be a number or SVN revision string. %q is not appropriate"):format(minor), 2)
 		end
 	end
 	argCheck(self, minor, 3, "number")
@@ -393,7 +396,7 @@
 			if m then
 				minor = m
 			else
-				_G.error(string.format("Bad argument #3 to  `HasInstance'. Must be a number or SVN revision string. %q is not appropriate", minor), 2)
+				_G.error(("Bad argument #3 to  `HasInstance'. Must be a number or SVN revision string. %q is not appropriate"):format(minor), 2)
 			end
 		end
 		argCheck(self, minor, 3, "number")
@@ -416,7 +419,7 @@
 
 	local data = self.libs[major]
 	if not data then
-		_G.error(string.format("Cannot find a library instance of %s.", major), 2)
+		_G.error(("Cannot find a library instance of %s."):format(major), 2)
 		return
 	end
 	if minor then
@@ -425,12 +428,12 @@
 			if m then
 				minor = m
 			else
-				_G.error(string.format("Bad argument #3 to  `GetInstance'. Must be a number or SVN revision string. %q is not appropriate", minor), 2)
+				_G.error(("Bad argument #3 to  `GetInstance'. Must be a number or SVN revision string. %q is not appropriate"):format(minor), 2)
 			end
 		end
 		argCheck(self, minor, 2, "number")
 		if data.minor ~= minor then
-			_G.error(string.format("Cannot find a library instance of %s, minor version %d.", major, minor), 2)
+			_G.error(("Cannot find a library instance of %s, minor version %d."):format(major, minor), 2)
 		end
 	end
 	return data.instance
@@ -463,11 +466,11 @@
 	if major ~= ACELIBRARY_MAJOR then
 		for k,v in pairs(_G) do
 			if v == newInstance then
-				geterrorhandler()(string.format("Cannot register library %q. It is part of the global table in _G[%q].", major, k))
+				geterrorhandler()((debugstack():match("(.-: )in.-\n") or "") .. ("Cannot register library %q. It is part of the global table in _G[%q]."):format(major, k))
 			end
 		end
 	end
-	if major ~= ACELIBRARY_MAJOR and not string.find(major, "^[%a%-][%a%d%-]+[%a%-]%-%d+%.%d+$") and not string.find(major, "^[%a%-]+%-%d+%.%d+$") then
+	if major ~= ACELIBRARY_MAJOR and not major:find("^[%a%-][%a%d%-]*%-%d+%.%d+$") then
 		_G.error(string.format("Bad argument #3 to `Register'. Must be in the form of \"Name-1.0\". %q is not appropriate", major), 2)
 	end
 	if type(minor) == "string" then
@@ -475,7 +478,7 @@
 		if m then
 			minor = m
 		else
-			_G.error(string.format("Bad argument #4 to `Register'. Must be a number or SVN revision string. %q is not appropriate", minor), 2)
+			_G.error(("Bad argument #4 to `Register'. Must be a number or SVN revision string. %q is not appropriate"):format(minor), 2)
 		end
 	end
 	argCheck(self, minor, 4, "number")
@@ -526,7 +529,7 @@
 --[[			if major ~= ACELIBRARY_MAJOR then
 				for k,v in pairs(_G) do
 					if v == instance then
-						geterrorhandler()(string.format("Cannot register library %q. It is part of the global table in _G[%q].", major, k))
+						geterrorhandler()((debugstack():match("(.-: )in.-\n") or "") .. ("Cannot register library %q. It is part of the global table in _G[%q]."):format(major, k))
 					end
 				end
 			end]]
@@ -557,7 +560,7 @@
 	local instance = data.instance
 	if minor <= data.minor then
 		-- This one is already obsolete, raise an error.
-		_G.error(string.format("Obsolete library registered. %s is already registered at version %d. You are trying to register version %d. Hint: if not AceLibrary:IsNewVersion(%q, %d) then return end", major, data.minor, minor, major, minor), 2)
+		_G.error(("Obsolete library registered. %s is already registered at version %d. You are trying to register version %d. Hint: if not AceLibrary:IsNewVersion(%q, %d) then return end"):format(major, data.minor, minor, major, minor), 2)
 		return
 	end
 	-- This is an update
@@ -623,13 +626,13 @@
 	if activateFunc then
 		safecall(activateFunc, instance, oldInstance, oldDeactivateFunc)	
 				
-		if major ~= ACELIBRARY_MAJOR then
+--[[		if major ~= ACELIBRARY_MAJOR then
 			for k,v in pairs(_G) do
 				if v == instance then
-					geterrorhandler()(string.format("Cannot register library %q. It is part of the global table in _G[%q].", major, k))
+					geterrorhandler()((debugstack():match("(.-: )in.-\n") or "") .. ("Cannot register library %q. It is part of the global table in _G[%q]."):format(major, k))
 				end
 			end
-		end
+		end]]
 	else
 		safecall(oldDeactivateFunc, oldInstance)
 	end
--- a/libs/AceLocale-2.2/AceLocale-2.2.lua	Tue Mar 20 21:20:20 2007 +0000
+++ b/libs/AceLocale-2.2/AceLocale-2.2.lua	Tue Mar 20 21:25:29 2007 +0000
@@ -1,6 +1,6 @@
 --[[
 Name: AceLocale-2.2
-Revision: $Rev: 18708 $
+Revision: $Rev$
 Developed by: The Ace Development Team (http://www.wowace.com/index.php/The_Ace_Development_Team)
 Inspired By: Ace 1.x by Turan (turan@gryphon.com)
 Website: http://www.wowace.com/
@@ -9,10 +9,11 @@
 Description: Localization library for addons to use to handle proper
              localization and internationalization.
 Dependencies: AceLibrary
+License: LGPL v2.1
 ]]
 
 local MAJOR_VERSION = "AceLocale-2.2"
-local MINOR_VERSION = "$Revision: 18708 $"
+local MINOR_VERSION = "$Revision: 20000 $"
 
 if not AceLibrary then error(MAJOR_VERSION .. " requires AceLibrary.") end
 if not AceLibrary:IsNewVersion(MAJOR_VERSION, MINOR_VERSION) then return end
@@ -216,6 +217,17 @@
 		end
 	end
 	rawset(self, CURRENT_LOCALE, locale)
+	if not rawget(self, 'reverse') then
+		rawset(self, 'reverse', setmetatable({}, { __index = function(self2, key)
+			local self = AceLocale.reverseToBase[self2]
+			if not rawget(self, REVERSE_TRANSLATIONS) then
+				self:GetReverseTranslation(key)
+			end
+			self.reverse = self[REVERSE_TRANSLATIONS]
+			return self.reverse[key]
+		end }))
+		AceLocale.reverseToBase[self.reverse] = self
+	end
 	refixInstance(self)
 	if rawget(self, DEBUGGING) or rawget(self, DYNAMIC_LOCALES) then
 		if not rawget(self, TRANSLATION_TABLES) then
@@ -297,7 +309,9 @@
 end
 
 local function initReverse(self)
-	rawset(self, REVERSE_TRANSLATIONS, {})
+	rawset(self, REVERSE_TRANSLATIONS, setmetatable({}, { __index = function(_, key)
+		AceLocale.error(self, "Reverse translation for %q does not exist", key)
+	end }))
 	local alpha = self[TRANSLATIONS]
 	local bravo = self[REVERSE_TRANSLATIONS]
 	for base, localized in pairs(alpha) do
@@ -380,7 +394,7 @@
 		initReverse(self)
 		x = self[REVERSE_TRANSLATIONS]
 	end
-	return x[text] and true
+	return rawget(x, text) and true
 end
 
 function AceLocale.prototype:Debug()
@@ -393,7 +407,7 @@
 	DEFAULT_CHAT_FRAME:AddMessage("--- AceLocale Debug ---")
 	for _,locale in ipairs(locales) do
 		if not self[TRANSLATION_TABLES][locale] then
-			DEFAULT_CHAT_FRAME:AddMessage(string.format("Locale %q not found", locale))
+			DEFAULT_CHAT_FRAME:AddMessage(("Locale %q not found"):format(locale))
 		else
 			localizations[locale] = self[TRANSLATION_TABLES][locale]
 		end
@@ -435,11 +449,11 @@
 	end
 	for locale, t in pairs(localeDebug) do
 		if not next(t) then
-			DEFAULT_CHAT_FRAME:AddMessage(string.format("Locale %q complete", locale))
+			DEFAULT_CHAT_FRAME:AddMessage(("Locale %q complete"):format(locale))
 		else
-			DEFAULT_CHAT_FRAME:AddMessage(string.format("Locale %q missing:", locale))
+			DEFAULT_CHAT_FRAME:AddMessage(("Locale %q missing:"):format(locale))
 			for word in pairs(t) do
-				DEFAULT_CHAT_FRAME:AddMessage(string.format("    %q", word))
+				DEFAULT_CHAT_FRAME:AddMessage(("    %q"):format(word))
 			end
 		end
 	end
@@ -470,6 +484,7 @@
 	self.NAME = oldLib and oldLib.NAME or {}
 	self.DYNAMIC_LOCALES = oldLib and oldLib.DYNAMIC_LOCALES or {}
 	self.CURRENT_LOCALE = oldLib and oldLib.CURRENT_LOCALE or {}
+	self.reverseToBase = oldLib and oldLib.reverseToBase or {}
 	
 	BASE_TRANSLATIONS = self.BASE_TRANSLATIONS
 	DEBUGGING = self.DEBUGGING
--- a/locale-enUS.lua	Tue Mar 20 21:20:20 2007 +0000
+++ b/locale-enUS.lua	Tue Mar 20 21:25:29 2007 +0000
@@ -1,10 +1,25 @@
 -- English localization file for ReAction
 
-local L = AceLibrary("AceLocale-2.0"):new("ReAction")
+local L = AceLibrary("AceLocale-2.2"):new("ReAction")
 
-L:RegisterTranslations("enUS", function()
-	return {
-		["Lock"] = true,
-  }
-end
-)
+L:RegisterTranslations( "enUS", function()
+return {
+  -- main.lua
+  ["ReAction"] = true,
+  ["Toggle ReAction Bar Lock"] = true,
+  ["Toggle ReBound Keybinding Mode"] = true,
+  ["/reaction"] = true,
+  ["/rxn"] = true,
+  ["ReAction bars locked when in combat"] = true,
+  ["Bar lock"] = true,
+  ["Locked"] = true,
+  ["Unlocked"] = true,
+  ["Button lock"] = true,
+  ["On"] = true,
+  ["Off"] = true,
+  ["Kebinding mode"] = true,
+  ["Tried to create a button of unknown type"] = true,
+  ["|cffffcc00Shift-Click for bar lock|n|cff33ff33Alt-Click|r for keybindings|nRight-click for menu"] = true,
+
+}
+end )
--- a/main.lua	Tue Mar 20 21:20:20 2007 +0000
+++ b/main.lua	Tue Mar 20 21:25:29 2007 +0000
@@ -1,23 +1,16 @@
--- ReAction.lua
+-- main.lua
 -- 
 -- Top-level file for the ReAction Action Bar add-on
 --
--- ReAction is implemented in terms of the Ace 2 library: http://www.wowace.com
+-- implemented in terms of the Ace 2 development framework library: http://www.wowace.com
 --
 
--- key binding label constants
-BINDING_HEADER_REACTION                 = "ReAction"
-BINDING_NAME_REACTION_TOGGLELOCK        = "Lock/Unlock ReAction Bars"
-BINDING_NAME_REBINDER_TOGGLEBINDINGMODE = "Toggle ReAction keybinding mode"
+-- Ace Library local object initialization
+local L       = AceLibrary("AceLocale-2.2"):new("ReAction")
+local dewdrop = AceLibrary("Dewdrop-2.0")
+local tablet  = AceLibrary("Tablet-2.0")
 
--- ReAction addon setup via Ace 2
-ReAction = AceLibrary("AceAddon-2.0"):new(
-  "AceConsole-2.0",
-  "AceEvent-2.0",
-  "AceDB-2.0",
-  "FuBarPlugin-2.0"
-)
-
+-- private functions
 local function tcopy(t)
   local r = { }
   for k, v in pairs(t) do
@@ -26,109 +19,180 @@
   return r
 end
 
--- FuBar plugin setup
-ReAction.hasIcon = false
-ReAction.hasNoColor = true
-ReAction.hideMenuTitle = true
-ReAction.defaultPosition = "RIGHT"
-ReAction.defaultMinimapPosition = 240 -- degrees
-ReAction.OnMenuRequest = tcopy(ReActionGlobalMenuOptions)
+-- private constants
+local EMPTY_BAR_SLOT = -1
+
+-- key binding label constants
+BINDING_HEADER_REACTION                = L["ReAction"]
+BINDING_NAME_REACTION_TOGGLELOCK       = L["Toggle ReAction Bar Lock"]
+BINDING_NAME_REBOUND_TOGGLEBINDINGMODE = L["Toggle ReBound Keybinding Mode"]
+
+
+-- main object
+local main = AceLibrary("AceAddon-2.0"):new(
+  "AceConsole-2.0",
+  "AceEvent-2.0",
+  "AceDB-2.0",
+  "FuBarPlugin-2.0"
+)
 
 -- initial non-persistent state
-ReAction.locked = true
+main.locked = true
 
--- localization
--- local L = AceLibrary("AceLocale-2.0"):new("ReAction")
+-- set a global variable for Bindings.xml
+ReActionAddOn = main
+
+
+-- FuBar plugin setup
+-- Even if FuBar isn't installed, this gives us a nice minimap-button interface.
+main.hasIcon = "Interface\\Icons\\INV_Qiraj_JewelEncased"
+main.hasNoColor = true
+main.hideMenuTitle = true
+main.defaultPosition = "LEFT"
+main.defaultMinimapPosition = 240 -- degrees
+main.OnMenuRequest = tcopy(ReActionGlobalMenuOptions) -- use a copy, or bar menus will have FuBar inserted items
+main.independentProfile = true
+
+-- set the handler for the global bar menu options
+-- have to do this after tcopy() above, otherwise it will try to copy the handler object (bad idea)
+ReActionGlobalMenuOptions.handler = main
+
 
 
 
 -- Event handling
-function ReAction:OnInitialize()
-  self:RegisterChatCommand( {"/reaction", "/rxn"}, ReActionConsoleOptions, "REACTION" )
-
+function main:OnInitialize()
+  self:RegisterChatCommand( {L["/reaction"], L["/rxn"]}, ReActionConsoleOptions, "REACTION" )
   self:RegisterDB("ReActionDB","ReActionDBPC")
-  self:RegisterDefaults("profile", ReActionProfileDefaults)
+  self:RegisterDefaults("profile", ReAction_DefaultProfile)
   self:RegisterEvent("PLAYER_REGEN_DISABLED","CombatLockdown")
   self:RegisterEvent("PLAYER_ENTERING_WORLD","HideDefaultBars")
+  self:RegisterEvent("EVENT_REBOUND_KEYBINDING_MODE")
 end
 
-function ReAction:OnEnable()
+function main:OnEnable()
+  -- this gets called at startup and when the profile is changed
   if self.db.profile.firstRunDone ~= true then
     -- Do some "first-run" setup
+    self:StealKeyBindings()
     self.db.profile.firstRunDone = true
-  elseif self.db.profile.disabled == true then
-    -- print some kind of a warning
+    self.db.profile.bars = tcopy(ReAction_DefaultBlizzardBars)
   end
+  self:DestroyAllBars()
   self:SetupBars()
 end
 
-function ReAction:OnDisable()
+function main:OnDisable()
   self:Lock()
 end
 
-function ReAction:OnProfileEnable()
-  -- handle profile switching
-  self:Lock()
-  self:SetupBars()
+function main:OnProfileEnable()
+  -- for profile switching
+  self:OnEnable()
 end
 
-function ReAction:CombatLockdown()
+function main:CombatLockdown()
   if not self:IsLocked() then
     self:Lock()
-    UIErrorsFrame:AddMessage("ReAction bars locked when in combat")
+    ReBound:Disable()
+    UIErrorsFrame:AddMessage(L["ReAction bars locked when in combat"])
   end
 end
 
+function main:EVENT_REBOUND_KEYBINDING_MODE(enabled)
+  for _, bar in pairs(self.bars) do
+    for __, button in pairs(bar.buttons) do
+      button:TempShow(enabled)
+    end
+  end
+end
 
--- lock/unlock ReAction
-function ReAction:SetLocked( lock )
+
+-- FuBar plugin methods
+function main:OnTooltipUpdate()
+	local c = tablet:AddCategory("columns", 2)
+	c:AddLine("text", L["Bar lock"], "text2", self.locked and ("|cffff0000"..L["Locked"].."|r") or ("|cffffcc00"..L["Unlocked"].."|r"))
+  c:AddLine("text", L["Button lock"], "text2", LOCK_ACTIONBAR == "1" and ("|cffcc0000"..L["Locked"].."|r") or ("|cff00cc00"..L["Unlocked"].."|r"))
+  c:AddLine("text", L["Kebinding mode"], "text2", ReBound:IsEnabled() and ("|cff33ff33"..L["On"].."|r") or ("|cffffcc00"..L["Off"].."|r"))
+	tablet:SetHint(L["|cffffcc00Shift-Click for bar lock|n|cff33ff33Alt-Click|r for keybindings|nRight-click for menu"])
+end
+
+function main:OnClick(button)
+	if IsShiftKeyDown() then
+	  self:ToggleLocked()
+    self:UpdateDisplay()
+	elseif IsAltKeyDown() then
+    ReBound:ToggleEnabled()
+    self:UpdateDisplay()
+  end
+end
+
+
+-- lock/unlock bars
+function main:SetLocked( lock )
   if lock ~= self.locked then
-    if not lock then
-      self:Print("Buttons disabled while unlocked")
-    end
     if not lock and InCombatLockdown() then
       UIErrorsFrame:AddMessage(SPELL_FAILED_AFFECTING_COMBAT)
     else
       self.locked = lock and true or false -- force data integrity
       for _, bar in pairs(self.bars) do
-        if self.locked then bar:HideControls() else bar:ShowControls() end
+        if bar ~= EMPTY_BAR_SLOT then
+          if self.locked then 
+            bar:HideControls()
+            -- close any dewdrop menu owned by the bar
+            if bar:GetControlFrame() == dewdrop:GetOpenedParent() then
+              dewdrop:Close()
+            end
+          else
+            bar:ShowControls() 
+          end
+        end
       end
     end
   end
 end
 
-function ReAction:IsLocked()
+function main:IsLocked()
   return self.locked
 end
 
-function ReAction:Lock()
+function main:Lock()
   self:SetLocked(true)
 end
 
-function ReAction:Unlock()
+function main:Unlock()
   self:SetLocked(false)
 end
 
-function ReAction:ToggleLocked()
-  ReAction:SetLocked( not(self.locked) )
+function main:ToggleLocked()
+  main:SetLocked( not(self.locked) )
 end
 
 
 
 -- Hide the default Blizzard main bar artwork
-function ReAction:HideArt()
+function main:HideArt()
   if self.db.profile.hideArt then
+    -- the pet bar is a child of MainMenuBar, and can't be hidden. Need to reparent it
+    PetActionBarFrame:SetParent(UIParent)
     MainMenuBar:Hide() -- this also hides the bags, xp bar, lag meter, and micro menu buttons.
+    -- these two are the pet bar background
+    -- unfortunately UIParent_ManageFramePositions() shows and hides these too
+    -- so they get reparented to MainMenuBar
+    SlidingActionBarTexture0:SetParent(MainMenuBar)
+    SlidingActionBarTexture1:SetParent(MainMenuBar)
   else
+    SlidingActionBarTexture0:SetParent(PetActionBarFrame)
+    SlidingActionBarTexture1:SetParent(PetActionBarFrame)
     MainMenuBar:Show()
   end
 end
 
-function ReAction:IsArtHidden()
+function main:IsArtHidden()
   return self.db.profile.hideArt
 end
 
-function ReAction:SetHideArt( hide )
+function main:SetHideArt( hide )
   if InCombatLockdown() then
     UIErrorsFrame:AddMessage(SPELL_FAILED_AFFECTING_COMBAT)
   else
@@ -137,27 +201,12 @@
   end
 end
 
-function ReAction:ToggleHideArt()
+function main:ToggleHideArt()
   self:SetHideArt( not self:IsArtHidden() )
 end
 
 
 
--- Keybinding color coding
-function ReAction:SetKeyColorCoding( cc )
-  self.db.profile.keyColorCode = cc
-end
-
-function ReAction:IsKeyColorCodeEnabled()
-  return self.db.profile.keyColorCode
-end
-
-function ReAction:ToggleKeyColorCoding()
-  self:SetKeyColorCoding(not self.db.profile.keyColorCode)
-end
-
-
-
 -- Hide default Blizzard bars
 local blizzDefaultBars = {
   ActionButton1,
@@ -172,143 +221,216 @@
   ActionButton10,
   ActionButton11,
   ActionButton12,
+  PetActionButton1,
+  PetActionButton2,
+  PetActionButton3,
+  PetActionButton4,
+  PetActionButton5,
+  PetActionButton6,
+  PetActionButton7,
+  PetActionButton8,
+  PetActionButton9,
+  PetActionButton10,
+  -- NOT the PetActionBarFrame, though - we need that to auto-hide/show our pet action bars
+  MainMenuBarPageNumber,
+  ActionBarUpButton,
+  ActionBarDownButton,
   BonusActionBarFrame,
+  ShapeshiftBarFrame,
   MultiBarLeft,
   MultiBarRight,
   MultiBarBottomLeft,
-  MultiBarBottomRight
+  MultiBarBottomRight,
 }
 
-function ReAction:HideDefaultBars()
-  for _, f in pairs(blizzDefaultBars) do
-    f:UnregisterAllEvents()
-    f:Hide()
-    f:SetParent(ReActionButtonRecycler) -- I mean it!
-    f:ClearAllPoints()                  -- no, I really mean it!
+function main:StealKeyBindings()
+  -- steal the keybindings of the main action bar and assign them to rebar 1, buttons 1-12
+  for i = 1, 12 do
+    -- TODO: when we convert to override bindings
   end
 end
 
+local function disableUIOptions()
+  -- disable the buttons to hide/show the blizzard multiaction bars
+  -- see UIOptionsFrame.lua and .xml
+  -- This is called every time the options panel is shown, after it is set up
+  for _, idx in pairs( { 33, 34, 35, 36, 37, 40 } ) do
+    local f = getglobal("UIOptionsFrameCheckButton"..idx)
+    f.disabled = true
+    OptionsFrame_DisableCheckBox(f)
+    f:SetChecked(false)
+  end
+end
 
--- Reset bars to defaults
-function ReAction:ResetBars()
+function main:HideDefaultBars()
+  for _, f in pairs(blizzDefaultBars) do
+    f:Hide()
+    f:ClearAllPoints()
+    f:SetParent(ReAction.recycler)
+    f:SetPoint("TOPLEFT")
+  end
+
+  MainMenuBar:SetFrameStrata("LOW") -- otherwise it appears on top of bars, if it isn't hidden
+  hooksecurefunc("UIOptionsFrame_Load",disableUIOptions)
+end
+
+
+-- Reset bars to blizzard defaults
+function main:ResetBars()
   if InCombatLockdown() then
     UIErrorsFrame:AddMessage(SPELL_FAILED_AFFECTING_COMBAT)
   else
-    self.db.profile.bars = ReActionProfileDefaults.bars
+    self:DestroyAllBars()
+    self.db.profile.bars = tcopy(ReAction_DefaultBlizzardBars)
     self:SetupBars()
   end
 end
 
 
 -- re-sync action IDs
-function ReAction:ResyncActionIDs()
+function main:ResyncActionIDs()
   -- TODO
 end
 
 
 
 -- Bar manipulation
-ReAction.bars    = { }
+main.bars    = { }
 
-function ReAction:SetupBars()
+function main:DestroyAllBars()
+  -- destroy any existing bars
+  for id = 1, table.maxn(self.bars) do
+    self:DestroyBar(id)
+  end
+end
+
+
+function main:SetupBars()
   -- hide the default Blizzard art, if configued
   self:HideArt()
-  -- hide the default Blizzard bars
-  self:HideDefaultBars()
 
   -- set up the bars from the profile
   -- note the use of table.maxn rather than # or ipairs: 
   -- our array of bars can in fact contain holes
   for id = 1, table.maxn(self.db.profile.bars) do
     local config = self.db.profile.bars[id]
-    if self.bars[id] then 
-      self.bars[id]:Destroy() -- remove old version of bar if switching profiles 
-    end
     if config then
-      self.bars[id] = ReBar:new(config, id)
-    end
-  end
-  
-  -- remove excess bars
-  for id = table.maxn(self.db.profile.bars) + 1, table.maxn(self.bars) do
-    if self.bars[id] then
-      self.bars[id]:Destroy()
-      self.bars[id] = nil
+      self:CreateBar(config, id)
     end
   end
   
   -- anchor the bars, have to do this in a second pass because
   -- they might be anchored to each other in a non-ordered way
   for _, bar in pairs(self.bars) do
-    bar:ApplyAnchor()
-  end
-end
-
-
-function ReAction:NewBar()
-  if InCombatLockdown() then
-    UIErrorsFrame:AddMessage(SPELL_FAILED_AFFECTING_COMBAT)
-  else
-    local c = tcopy(ReActionBarConfigDefaults)
-    local bar = ReBar:new(c, #self.bars+1)
-    table.insert(self.bars, bar)
-    table.insert(self.db.profile.bars, c) 
-    if not self.locked then
-      bar:ShowControls()
+    if bar ~= EMPTY_BAR_SLOT then
+      bar:ApplyAnchor()
     end
   end
 end
 
+function main:CreateBar( config, id )
+  local bar = ReBar:new(config, id)
+  local buttonType = ReAction:GetButtonType(config.btnConfig.subtype)
 
-function ReAction:DeleteBar(id)
+  if buttonType then
+    self.bars[id] = bar
+    self.db.profile.bars[id] = config
+
+    -- initialize dewdrop menu
+    local cf = bar:GetControlFrame()
+    dewdrop:Register(cf, 
+      'children', 
+      function()
+        dewdrop:FeedAceOptionsTable(ReActionGlobalMenuOptions)
+        dewdrop:FeedAceOptionsTable(GenerateReActionBarOptions(bar,self))
+        dewdrop:FeedAceOptionsTable(buttonType:GenerateOptionsTable(config.btnConfig, function() return bar:GetButtonList() end))
+      end,
+      'cursorX', true, 
+      'cursorY', true
+    )
+
+    bar:GetControlFrame():SetScript("OnClick", 
+      function(btn)
+        if btn == "RightButton" then
+          dewdrop:Open(cf)
+        end
+      end
+    )
+
+    if not self.locked then
+      bar:ShowControls()
+    end
+    return bar
+  else
+    if bar then
+      bar:Destroy()
+    end
+    error(L["Tried to create a button of unknown type"])
+  end
+end
+
+function main:DestroyBar( id )
+  local bar = self.bars[id]
+  if bar and bar ~= EMPTY_BAR_SLOT then 
+    local cf = bar:GetControlFrame()
+    if cf == dewdrop:GetOpenedParent() then
+      dewdrop:Close()
+      dewdrop:Unregister(cf)
+    end
+    bar:Destroy()
+    -- we can't do tremove because each bar ID is encoded into the
+    -- frame names as they're created. Need a blank entry in the table.
+    -- The nice thing is that table.insert in NewBar() will automatically
+    -- find the lowest numbered nil slot.
+    self.bars[id] = EMPTY_BAR_SLOT
+  end
+end
+
+function main:NewBar( type )
+  if InCombatLockdown() then
+    UIErrorsFrame:AddMessage(SPELL_FAILED_AFFECTING_COMBAT)
+  else
+    local t = ReAction:GetButtonType(type)
+    if t then
+      local c = tcopy(ReAction_DefaultBarConfig["ReAction"][type])
+      local id = nil
+      for i = 1, table.maxn(self.bars) + 1 do  -- there may be holes, so #self.bars won't work
+        if self.bars[i] == nil or self.bars[i] == EMPTY_BAR_SLOT then
+          id = i
+          break
+        end
+      end
+      local bar = self:CreateBar(c, id)
+      bar:ApplyAnchor()
+      self:Unlock()
+    end
+  end
+end
+
+
+function main:DeleteBar(id)
   if InCombatLockdown() then
     UIErrorsFrame:AddMessage(SPELL_FAILED_AFFECTING_COMBAT)
   else
     if self.bars[id] then
-      -- we can't do tremove because each bar ID is encoded into the
-      -- frame names as they're created. Need a nil entry in the table.
-      -- The nice thing is that table.insert in NewBar() will automatically
-      -- find the first nil slot.
-      self.bars[id]:Destroy()
-      self.bars[id] = nil
+      self:DestroyBar(id)
       self.db.profile.bars[id] = nil
     end
   end
 end
 
-function ReAction:ToggleActionID()
-  if self.showActionIDs then
-    ReActionButton:HideAllActionIDs()
+function main:ToggleIds()
+  if self.showIds then
+    ReAction:HideAllIds()
   else
-    ReActionButton:ShowAllActionIDs()
+    ReAction:ShowAllIds()
   end
-  self.showActionIDs = not self.showActionIDs
+  self.showIds = not self.showIds
 end
 
-function ReAction:IsActionIDVisible()
-  return self.showActionIDs
+function main:AreIdsVisible()
+  return self.showIds
 end
 
 
 
--- FuBar plugin methods
-local tablet = AceLibrary("Tablet-2.0")
-
-function ReAction:OnTooltipUpdate()
-	local c = tablet:AddCategory("columns", 2)
-	c:AddLine("text", "Bar lock", "text2", self.locked and "|cffcc0000Locked|r" or "|cff00cc00Unlocked|r")
-  c:AddLine("text", "Button lock", "text2", LOCK_ACTIONBAR == "1" and "|cffcc0000Locked|r" or "|cff00cc00Unlocked|r")
-  c:AddLine("text", "Kebinding mode", "text2", ReBinder:IsEnabled() and "|cff33ff33On|r" or "|cffffcc00Off|r")
-	tablet:SetHint("|cffffcc00Shift-Click|r for bar lock|n"..
-                 "|cff33ff33Alt-Click|r for keybindings|n"..
-                 "Right-click for menu")
-end
-
-function ReAction:OnClick(button)
-	if IsShiftKeyDown() then
-	  self:ToggleLocked()
-    self:UpdateDisplay()
-	elseif IsAltKeyDown() then
-    ReBinder:ToggleEnabled()
-  end
-end