changeset 2:8e0ff8ae4c08

Version 0.2
author Flick <flickerstreak@gmail.com>
date Tue, 20 Mar 2007 21:08:31 +0000
parents c11ca1d8ed91
children f5607e135c37
files Bindings.xml Button.lua Button.xml Options.lua ReAction.lua ReAction.toc ReBar.lua ReBar.xml ReBinder.lua ReBinder.xml libs/Dewdrop-2.0/._Dewdrop-2.0.lua
diffstat 11 files changed, 642 insertions(+), 181 deletions(-) [+]
line wrap: on
line diff
--- a/Bindings.xml	Tue Mar 20 21:03:57 2007 +0000
+++ b/Bindings.xml	Tue Mar 20 21:08:31 2007 +0000
@@ -1,5 +1,8 @@
 <Bindings>
-  <Binding name="REACTION_TOGGLELOCK" runOnUp="true" header="REACTION">
+  <Binding name="REACTION_TOGGLELOCK" header="REACTION">
     ReAction:ToggleLocked()
   </Binding>
+  <Binding name="REBINDER_TOGGLEBINDINGMODE">
+    ReBinder:ToggleEnabled()
+  </Binding>
 </Bindings>
--- a/Button.lua	Tue Mar 20 21:03:57 2007 +0000
+++ b/Button.lua	Tue Mar 20 21:08:31 2007 +0000
@@ -1,5 +1,5 @@
 -- private constants
-local namePrefix = "ReActionButton_"
+local namePrefix = "ReActionButton"
 local _G = getfenv(0)
 local ACTION_FREE = { }
 local MAX_ACTIONS = 120
@@ -14,6 +14,16 @@
   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-",
+  ["Spacebar"] = "Sp",
+  ["Num Pad "] = "Num-",
+  ["Page Up"]  = "PgUp",
+  ["Page Down"] = "PgDn",
+  [" 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 }
@@ -60,14 +70,13 @@
 
   t.inUse = true
   if t.button then
-    t.button:Configure(parent,config,barIdx,id)
+    t.button:Configure(parent,config,barIdx)
   else
     t.button = self:new(parent,config,barIdx,id)
   end
 
-  if actionButtonTbl[t.button:GetActionID()].inUse ~= true then
-  end
-
+  -- fix screwy config with overlapping IDs
+  config.actionIDs[barIdx] = id
   return t.button
 end
 
@@ -100,14 +109,9 @@
   ReActionButton.super.prototype.init(self)
 
   -- create the button widget
-  self.name = namePrefix.."_"..id
+  self.name = namePrefix..id
   self.button = CreateFrame("CheckButton", self.name, parentFrame, "ReActionButtonTemplate")
   
-  -- create the actionID label on the control widget
-  local actionIDLabel = self.button:CreateFontString(nil,"ARTWORK", "NumberFontNormalSmall")
-  actionIDLabel:SetPoint("BOTTOMLEFT",0,0)
-  actionIDLabel:Hide()
-
   -- 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"],
@@ -119,30 +123,50 @@
     border         = _G[self.name.."Border"],
     normalTexture  = _G[self.name.."NormalTexture"],
     flash          = _G[self.name.."Flash"],
-    actionID       = actionIDLabel
+    actionID       = _G[self.name.."ActionID"],
   }
 
   -- 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, id)
+  self:Configure(parentFrame, config, barIdx)
+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
 end
 
 function ReActionButton.prototype:Recycle()
   local b = self.button
-  local action = self:GetActionID()
-
-  self.config.actionIDs[self.barIdx] = nil
 
   self:SetKeyBinding(nil)
   self:UpdateDisplay()
   b:UnregisterAllEvents()
+  b:SetParent(ReActionButtonRecycleFrame)
+  b:ClearAllPoints()
+  b:SetPoint("TOPLEFT",0,0)
   b:Hide()
-  b:ClearAllPoints()
-  b:SetParent(ReActionButtonRecycleFrame)
+  self.config = tcopy(self.config) -- ew, but necessary
 end
 
+function ReActionButton.prototype:BarUnlocked()
+  self:ShowGridTmp()
+end
+
+function ReActionButton.prototype:BarLocked()
+  self:HideGridTmp()
+end
 
 
 -- set the button location
@@ -166,10 +190,10 @@
 
 
 -- configuration and setup
-function ReActionButton.prototype:Configure( parentFrame, config, barIdx, id )
+function ReActionButton.prototype:Configure( parentFrame, config, barIdx )
   self.config   = config
   self.barIdx   = barIdx
-  self.showGrid = config.showGrid and 1 or 0
+  self.showGridTmp_ = 0
 
   self.button:ClearAllPoints()
   self.button:SetParent(parentFrame)
@@ -177,12 +201,6 @@
   self:SetupAttributes()
   self:RegisterStaticEvents()
 
-  if id then
-    -- set action ID
-    self:SetActionID(id)
-  else
-    self:ApplyActionID()
-  end
   self:ApplyLayout()
   self:ApplyStyle()
 
@@ -193,7 +211,6 @@
   local b = self.button
   b:SetAttribute("type", "action")
 	b:SetAttribute("shift-type*", ATTRIBUTE_NOOP)
-  b:SetAttribute("alt-type*", ATTRIBUTE_NOOP)
 	b:SetAttribute("checkselfcast", true)
 	b:SetAttribute("useparent-unit", true)
 end
@@ -384,80 +401,19 @@
   return GetBindingKey("CLICK "..self.name..":LeftButton")
 end
 
-function ReActionButton.prototype:ShowAssignKeybinding()
-  local f = ReActionKeybindFrame
-  f:ClearAllPoints()
-  f:SetPoint("BOTTOM", self.button, "TOP", 0, 10)
-  ReActionKeybindFrameButton.keybindTarget = self
-  local k = self:GetKeyBinding()
-  if k then
-    local txt = GetBindingText(k, "KEY_")
-    ReActionKeybindFrameButton:SetText(txt or "")
-  end
-  f:Show()
-end
-
-
-local mouseButtonConvert = {
-  LeftButton = "BUTTON1",
-  RightButton = "BUTTON2",
-  MiddleButton = "BUTTON3",
-  Button4 = "BUTTON4",
-  Button5 = "BUTTON5"
-}
-
-function ReActionButton.prototype:HandleKeybindAssign(button, key, mouseButton)
-  mouseButton = mouseButton and mouseButtonConvert[mouseButton]
-  if mouseButton ~= "BUTTON1" and mouseButton ~= "BUTTON2" then
-    key = key or mouseButton
-    if key == nil or key == "UNKNOWN" or key == "SHIFT" or key == "CTRL" or key == "ALT" then 
-      return
-    end
-    if key == "ESCAPE" then
-      ReActionKeybindFrame:Hide()
-      return
-    end
-		if IsShiftKeyDown() then 
-      key = "SHIFT-"..key
-    end
-		if IsControlKeyDown() then
-			key = "CTRL-"..key
-		end
-		if IsAltKeyDown() then
-			keyPressed = "ALT-"..key
-		end
-    local oldAction = GetBindingAction(key)
-    local oldKey = self:GetKeyBinding()
-    if oldAction then
-      -- can't pop a modal dialog box, will need to think something up
-    end
-    if oldKey == key then
-      SetBinding(key,nil)
-      key = nil
-    end
-    self:SetKeyBinding(key)
-    button:SetText(key and GetBindingText(key, "KEY_") or "")
-    self:UpdateDisplay()
-    SaveBindings(2) -- 2 = character specific bindings... hmm...
-  end
-  button.selected = false
-  this:SetButtonState("NORMAL")
-end
-
-
 -- action ID functions
 function ReActionButton.prototype:SetActionID( id )
-  self.config.actionIDs[self.barIdx] = tonumber(id) -- force data integrity
+  self.actionID = tonumber(id) -- force data integrity
   self:ApplyActionID()
 end
 
 function ReActionButton.prototype:GetActionID()
-  return self.config.actionIDs[self.barIdx]
+  return self.actionID
 end
 
 function ReActionButton.prototype:ApplyActionID()
   local action = tonumber(self:GetActionID())
-  self.button:SetAttribute("action",action or nil)
+  self.button:SetAttribute("action",action)
   self.frames.actionID:SetText(action or "")
 end
 
@@ -488,23 +444,23 @@
 end
 
 function ReActionButton.prototype:ShowGridTmp()
-  self.showGrid = self.showGrid + 1
+  self.showGridTmp_ = self.showGridTmp_ + 1
   self:UpdateVisibility()
 end
 
 function ReActionButton.prototype:HideGridTmp()
-  self.showGrid = self.showGrid - 1
+  self.showGridTmp_ = self.showGridTmp_ - 1
   self:UpdateVisibility()
 end
 
 function ReActionButton.prototype:ShowGrid()
   self.config.showGrid = true
-  self:ShowGridTmp()
+  self:UpdateVisibility()
 end
 
 function ReActionButton.prototype:HideGrid()
   self.config.showGrid = false
-  self:HideGridTmp()
+  self:UpdateVisibility()
 end
 
 
@@ -516,8 +472,11 @@
   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:SetPoint(loc,0,0)
+    h:SetWidth(40)
+    h:SetPoint(top or bottom,0,top and 2 or -2)
     local j
     if string.match(loc,"LEFT") then
       j = "LEFT"
@@ -532,8 +491,11 @@
   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:SetPoint(loc,0,0)
+    c:SetWidth(40)
+    c:SetPoint(top or bottom,0,top and 2 or -2)
     local j
     if string.match(loc,"LEFT") then
       j = "LEFT"
@@ -699,6 +661,11 @@
   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()
@@ -775,7 +742,7 @@
   elseif action and HasAction(action) then
     b:GetNormalTexture():SetAlpha(1.0)
     b:Show()
-  elseif self.showGrid > 0 then
+  elseif self.showGridTmp_ > 0 or self.config.showGrid then
     b:GetNormalTexture():SetAlpha(0.5)
     self.frames.cooldown:Hide()
     b:Show()
@@ -795,7 +762,7 @@
 
 function ReActionButton.prototype:UpdateTooltip()
   local action = self:GetActionID()
-	if action and GameTooltip:SetAction(action) then
+	if GameTooltip:IsOwned(self.button) and action and GameTooltip:SetAction(action) then
 		self.tooltipTime = TOOLTIP_UPDATE_TIME
 	else
 		self.tooltipTime = nil
--- a/Button.xml	Tue Mar 20 21:03:57 2007 +0000
+++ b/Button.xml	Tue Mar 20 21:08:31 2007 +0000
@@ -4,15 +4,22 @@
 
 
   <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 arg1 == "LeftButton" and IsAltKeyDown() then
-          this.rxnBtn:ShowAssignKeybinding()
-        end
         if this.rxnBtn:ShouldPickupAction(button) then
 					PickupAction(this.rxnBtn:GetActionID())
 				end
@@ -41,7 +48,7 @@
 
   <Frame name="ReActionButtonRecyler" parent="UIParent" hidden="true" setAllPoints="true" enableMouse="false"/>
   
-  <Frame name="ReActionKeybindFrame" enableMouse="true" enableKeyboard="true" parent="UIParent" hidden="true">
+  <Frame name="ReActionKeybindFrame" frameStrata="DIALOG" enableMouse="true" enableKeyboard="true" parent="UIParent" hidden="true">
     <Size>
       <AbsDimension x="160" y="52"/>
     </Size>
@@ -98,7 +105,14 @@
             end
             if this.selected and this.keybindTarget then
               this:LockHighlight()
-              this:SetScript("OnKeyDown", function() this.keybindTarget:HandleKeybindAssign(this,arg1) end)
+              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)
--- a/Options.lua	Tue Mar 20 21:03:57 2007 +0000
+++ b/Options.lua	Tue Mar 20 21:08:31 2007 +0000
@@ -17,6 +17,13 @@
       func = "Unlock",
     },
 
+    bindings = {
+      type = "execute",
+      name = "bindings",
+      desc = "Launches keybinding setup mode",
+      func = function() ReBinder:Enable() end,
+    },
+
     hideart = {
       type = "toggle",
       name = "hideart",
@@ -40,9 +47,9 @@
       set  = "ToggleActionID",
     },
 
-    reset = {
+    resetall = {
       type = "execute",
-      name = "reset",
+      name = "resetall",
       desc = "Resets to single bar in the default position",
       func = "ResetBars"
     },
@@ -62,21 +69,38 @@
 ReActionGlobalMenuOptions = {
   type="group",
   args={ 
-    lock = {
+    lockbars = {
       type = "toggle",
-      name = "Locked",
+      name = "Lock Bars",
       desc = "Locks action bars and disables rearrangement",
       get = function() return ReAction:IsLocked() end,
       set = function() ReAction:ToggleLocked() end,
+      order = 1,
+    },
+
+    lockbtns = {
+      type = "toggle",
+      name = "Lock Buttons",
+      desc = "Locks action bars and disables rearrangement",
+      get = function() return LOCK_ACTIONBAR == "1" end,
+      set = function() LOCK_ACTIONBAR = (LOCK_ACTIONBAR == "1" and "0" or "1") end,
       order = 2,
     },
 
+    bindings = {
+      type = "execute",
+      name = "Set Key Bindings",
+      desc = "Launches keybinding setup mode",
+      func = function() ReBinder:Enable() end,
+      order = 3,
+    },
+
     new = {
       type = "execute",
       name = "New Bar",
       desc = "Create a new bar with default settings",
       func = function() ReAction:NewBar() end,
-      order = 3
+      order = 4,
     },
     
     showid = {
@@ -85,7 +109,7 @@
       desc = "Show ActionIDs on buttons",
       get  = function() return ReAction:IsActionIDVisible() end,
       set  = function() ReAction:ToggleActionID() end,
-      order = 4
+      order = 5,
     },
 
     --[[
@@ -94,7 +118,7 @@
       name = "Re-sync Action IDs",
       desc = "Re-orders action IDs sequentially amongst bars",
       func = function() ReAction:ResyncActionIDs() end,
-      order = 5,
+      order = 6,
     },
     ]]
 
@@ -104,21 +128,22 @@
       desc = "Hide default Blizzard action bar artwork and XP bar",
       get  = function() return ReAction:IsArtHidden() end,
       set  = function() return ReAction:ToggleHideArt() end,
-      order = 6,
+      order = 7,
     },
     
+    --[[
     reset = {
       type = "execute",
       name = "Reset Bars",
       desc = "Resets to single bar in the default position",
       func = function() ReAction:ResetBars() end,
-      order = 7
+      order = 8,
     },
+   ]]
     
   }
 }
 
-
 function GenerateReActionBarOptions( bar )
   return {
     type = "group",
@@ -128,14 +153,14 @@
         type = "header",
         name = " ",
         desc = " ",
-        order = 8,
+        order = 9,
       },
       
       hdr1 = {
         type = "header",
         name = "Bar Options",
         des = "Bar Options",
-        order = 9,
+        order = 10,
       },
       
       --[[
@@ -145,7 +170,7 @@
         desc = "Hides the bar except when rearranging bars",
         get = function() return not bar:GetVisibility() end,
         set = function() bar:ToggleVisibility() end,
-        order = 10,
+        order = 11,
       },
       ]]
       
@@ -158,7 +183,7 @@
         min = 0,
         max = 100,
         step = 1,
-        order = 11
+        order = 12
       },
       
       delete = {
@@ -166,7 +191,7 @@
         name = "Delete Bar",
         desc = "Deletes the bar",
         func = function() ReAction:DeleteBar(bar.barID) end,
-        order = 12,
+        order = 13,
       },
     }
   }
@@ -184,7 +209,7 @@
 end
 
 local function getButtonConfig( bar, field )
-  if bar and config and btnConfig then
+  if bar and bar.config and bar.config.btnConfig then
     return bar.config.btnConfig[field]
   end
 end
@@ -209,14 +234,23 @@
         type = "header",
         name = "  ",
         desc = "  ",
-        order = 13,
+        order = 14,
       },
       
       hdr2 = {
         type = "header",
         name = "Button Options",
         desc = "Button Options",
-        order = 14,
+        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 = {
@@ -225,7 +259,7 @@
         desc = "Enables/disables colorizing hotkeys by key modifier",
         get  = function() return getButtonConfig(bar, "keyBindColorCode") end,
         set  = function() toggleButtonConfig(bar, "keyBindColorCode", c) end,
-        order = 15,
+        order = 17,
       },
   
       keyloc = {
@@ -235,21 +269,11 @@
         get  = function() return getButtonConfig(bar, "keyBindLoc") end,
         set  = function(loc) setButtonConfig(bar, "keyBindLoc", loc) end,
         validate = { "TOP", "BOTTOM", "TOPLEFT", "TOPRIGHT", "BOTTOMLEFT", "BOTTOMRIGHT" },
-        order = 16,
+        order = 18,
       },
       
-      sep3 = {
-        type = "header",
-        name = "   ",
-        desc = "   ",
-        order = 17
-      },
     }    
   }
 end
 
 
-ReActionProfileMenuOptions = {
-  type = "group",
-  args = { }
-}
--- a/ReAction.lua	Tue Mar 20 21:03:57 2007 +0000
+++ b/ReAction.lua	Tue Mar 20 21:08:31 2007 +0000
@@ -6,8 +6,9 @@
 --
 
 -- key binding label constants
-BINDING_HEADER_REACTION          = "ReAction"
-BINDING_NAME_REACTION_TOGGLELOCK = "Lock/Unlock ReAction"
+BINDING_HEADER_REACTION                 = "ReAction"
+BINDING_NAME_REACTION_TOGGLELOCK        = "Lock/Unlock ReAction Bars"
+BINDING_NAME_REBINDER_TOGGLEBINDINGMODE = "Toggle ReAction keybinding mode"
 
 -- ReAction addon setup via Ace 2
 ReAction = AceLibrary("AceAddon-2.0"):new(
@@ -17,12 +18,21 @@
   "FuBarPlugin-2.0"
 )
 
+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
+
 -- FuBar plugin setup
 ReAction.hasIcon = false
 ReAction.hasNoColor = true
+ReAction.hideMenuTitle = true
 ReAction.defaultPosition = "RIGHT"
 ReAction.defaultMinimapPosition = 240 -- degrees
-ReAction.OnMenuRequest = ReActionGlobalMenuOptions
+ReAction.OnMenuRequest = tcopy(ReActionGlobalMenuOptions)
 
 -- initial non-persistent state
 ReAction.locked = true
@@ -39,8 +49,7 @@
   self:RegisterDB("ReActionDB","ReActionDBPC")
   self:RegisterDefaults("profile", ReActionProfileDefaults)
   self:RegisterEvent("PLAYER_REGEN_DISABLED","CombatLockdown")
-  
-  AceLibrary("Dewdrop-2.0"):InjectAceOptionsTable(self, ReActionProfileMenuOptions)
+  self:RegisterEvent("PLAYER_ENTERING_WORLD","HideDefaultBars")
 end
 
 function ReAction:OnEnable()
@@ -49,9 +58,8 @@
     self.db.profile.firstRunDone = true
   elseif self.db.profile.disabled == true then
     -- print some kind of a warning
-  else
-    self:SetupBars()
   end
+  self:SetupBars()
 end
 
 function ReAction:OnDisable()
@@ -60,8 +68,8 @@
 
 function ReAction:OnProfileEnable()
   -- handle profile switching
+  self:Lock()
   self:SetupBars()
-  self:Lock()
 end
 
 function ReAction:CombatLockdown()
@@ -74,12 +82,18 @@
 
 -- lock/unlock ReAction
 function ReAction:SetLocked( lock )
-  self.locked = lock and true or false -- force data integrity
-  if not self.locked then
-    self:Print("Buttons disabled while unlocked")
-  end
-  for _, bar in ipairs(self.bars) do
-    if self.locked then bar:HideControls() else bar:ShowControls() end
+  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
+      end
+    end
   end
 end
 
@@ -115,8 +129,12 @@
 end
 
 function ReAction:SetHideArt( hide )
-  self.db.profile.hideArt = hide and true or false -- force data integrity
-  self:HideArt()
+  if InCombatLockdown() then
+    UIErrorsFrame:AddMessage(SPELL_FAILED_AFFECTING_COMBAT)
+  else
+    self.db.profile.hideArt = hide and true or false -- force data integrity
+    self:HideArt()
+  end
 end
 
 function ReAction:ToggleHideArt()
@@ -163,18 +181,22 @@
 
 function ReAction:HideDefaultBars()
   for _, f in pairs(blizzDefaultBars) do
+    f:UnregisterAllEvents()
     f:Hide()
-    f:ClearAllPoints()
-    f:SetParent(ReActionButtonRecycler)
-    f:UnregisterAllEvents()
+    f:SetParent(ReActionButtonRecycler) -- I mean it!
+    f:ClearAllPoints()                  -- no, I really mean it!
   end
 end
 
 
 -- Reset bars to defaults
 function ReAction:ResetBars()
-  self.db.profile.bars = ReActionProfileDefaults.bars
-  self:SetupBars()
+  if InCombatLockdown() then
+    UIErrorsFrame:AddMessage(SPELL_FAILED_AFFECTING_COMBAT)
+  else
+    self.db.profile.bars = ReActionProfileDefaults.bars
+    self:SetupBars()
+  end
 end
 
 
@@ -195,36 +217,62 @@
   self:HideDefaultBars()
 
   -- set up the bars from the profile
-  for id, info in ipairs(self.db.profile.bars) do
-    if self.bars[id] then self.bars[id]:Destroy() end -- remove old version of bar if switching profiles
-    self.bars[id] = ReBar:new(info, id)
+  -- 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
-  while #self.bars > #self.db.profile.bars do
-    table.remove(self.bars):Destroy()
+  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
+    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 ipairs(self.bars) do
+  for _, bar in pairs(self.bars) do
     bar:ApplyAnchor()
   end
 end
 
 
 function ReAction:NewBar()
-  local c = ReActionBarConfigDefaults
-  table.insert(self.bars, ReBar:new(c, #self.bars + 1))
-  table.insert(self.db.profile.bars, c) 
-  self:Unlock()
+  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()
+    end
+  end
 end
 
 
 function ReAction:DeleteBar(id)
-  if self.bars[id] then
-    table.remove(self.bars, id):Destroy()
-    table.remove( self.db.profile.bars, 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.db.profile.bars[id] = nil
+    end
   end
 end
 
@@ -248,13 +296,19 @@
 
 function ReAction:OnTooltipUpdate()
 	local c = tablet:AddCategory("columns", 2)
-	c:AddLine("text", "ReAction bar lock", "text2", self.locked and "|cffcc0000Locked|r" or "|cff00cc00Unlocked|r")
-	tablet:SetHint("|cffcc6600Shift-Click|r to toggle action bar lock. Right-click for options.")
+	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()
-	end
-	self:UpdateDisplay()
+    self:UpdateDisplay()
+	elseif IsAltKeyDown() then
+    ReBinder:ToggleEnabled()
+  end
 end
--- a/ReAction.toc	Tue Mar 20 21:03:57 2007 +0000
+++ b/ReAction.toc	Tue Mar 20 21:08:31 2007 +0000
@@ -21,6 +21,9 @@
 libs\Tablet-2.0\Tablet-2.0.lua
 libs\FuBarPlugin-2.0\FuBarPlugin-2.0.lua
 
+ReBinder.lua
+ReBinder.xml
+
 Defaults.lua
 Options.lua
 
--- a/ReBar.lua	Tue Mar 20 21:03:57 2007 +0000
+++ b/ReBar.lua	Tue Mar 20 21:08:31 2007 +0000
@@ -2,7 +2,6 @@
 -- private constants
 local insideFrame  = 1
 local outsideFrame = 2
-local _G = getfenv(0) -- global variable table
 
 local pointFindTable = {
   BOTTOMLEFT  = function(f) return f:GetLeft(),  f:GetBottom() end,
@@ -26,6 +25,9 @@
   LEFT        = "RIGHT"
 }
 
+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,
@@ -40,7 +42,7 @@
 function ReBar.prototype:init( config, id )
   ReBar.super.prototype.init(self)
 
-  local buttonClass = config and config.btnConfig and config.btnConfig.type and _G[config.btnConfig.type]
+  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 }
@@ -48,11 +50,13 @@
 
   -- create the bar and control widgets
   self.barFrame     = CreateFrame("Frame", "ReBar_"..self.barID, UIParent, "ReBarTemplate")
-  self.controlFrame = _G[self.barFrame:GetName().."Controls"]
+  self.controlFrame = getglobal(self.barFrame:GetName().."Controls")
   self.controlFrame.reBar = self
+  self.barFrame:SetClampedToScreen(true)
 
   -- set the text label on the control widget
-  _G[self.controlFrame:GetName().."LabelString"]:SetText(id)
+  self.labelString = getglobal(self.controlFrame:GetName().."LabelString")
+  self.labelString:SetText(id)
 
   -- initialize the bar layout
   self:ApplySize()
@@ -68,7 +72,6 @@
 	    dewdrop:FeedAceOptionsTable(ReActionGlobalMenuOptions)
 	    dewdrop:FeedAceOptionsTable(GenerateReActionBarOptions(self))
 	    dewdrop:FeedAceOptionsTable(GenerateReActionButtonOptions(self))
-	    dewdrop:FeedAceOptionsTable(ReActionProfileMenuOptions)
 	  end,
 	  'cursorX', true, 
 	  'cursorY', true
@@ -82,9 +85,11 @@
     dewdrop:Unregister(self.barFrame)
   end
 
+  self:HideControls()
   self.barFrame:Hide()
   self.barFrame:ClearAllPoints()
   self.barFrame:SetParent(nil)
+  self.barFrame:SetPoint("BOTTOMRIGHT", UIParent, "TOPLEFT", 0, 0)
   
   -- need to keep around self.config for dewdrop menus in the process of deleting self 
 
@@ -107,6 +112,9 @@
 -- show/hide the control frame
 function ReBar.prototype:ShowControls()
   self.controlFrame:Show()
+  for _, b in ipairs(self.buttons) do
+    b:BarUnlocked()
+  end
 end
 
 function ReBar.prototype:HideControls()
@@ -119,6 +127,9 @@
   if self.barFrame == dewdrop:GetOpenedParent() then
     dewdrop:Close()
   end
+  for _, b in ipairs(self.buttons) do
+    b:BarLocked()
+  end
   self.controlFrame:Hide()
 end
 
@@ -165,8 +176,15 @@
 function ReBar.prototype:ApplyAnchor()
   local a = self.config.anchor
   local f = self.barFrame
-  f:ClearAllPoints()
-  f:SetPoint(a.point,getglobal(a.to),a.relPoint,a.x,a.y)
+  if a then
+    f:ClearAllPoints()
+    f:SetPoint(a.point,getglobal(a.to),a.relPoint,a.x,a.y)
+    local color = anchoredLabelColor
+    if a.to == "UIParent" or a.to == "WorldFrame" then
+      color = nonAnchoredLabelColor
+    end
+    self.labelString:SetTextColor(color.r, color.g, color.b)
+  end
 end
 
 function ReBar.prototype:ApplyVisibility()
--- a/ReBar.xml	Tue Mar 20 21:03:57 2007 +0000
+++ b/ReBar.xml	Tue Mar 20 21:08:31 2007 +0000
@@ -86,7 +86,7 @@
   <!-- A ReAction bar 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" frameStrata="HIGH" enableMouse="true" movable="true" resizable="true">
+  <Frame name="ReBarTemplate" virtual="true" toplevel="true" enableMouse="true" movable="true" resizable="true">
     <Layers>
       <Layer level="BACKGROUND"/>
     </Layers>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ReBinder.lua	Tue Mar 20 21:08:31 2007 +0000
@@ -0,0 +1,136 @@
+-- ReBinder.lua
+-- 
+
+ReBinder = { }
+
+-- initial values
+ReBinder.active = false
+
+ReBinder.targets = { }
+
+function ReBinder:AddKeybindTarget( t )
+  if t then
+    self.targets[t] = CreateFrame("Button", nil, t, "ReBinderClickBindingTemplate")
+    self.targets[t].keybindTarget = t:GetName()
+  end
+end
+
+function ReBinder:RemoveKeybindTarget( t )
+  if t then
+    self.targets[t] = nil
+  end
+end
+
+function ReBinder:ShowClickBindingButtons()
+  for _, clickFrame in pairs(self.targets) do
+    clickFrame:Show()
+  end    
+end
+
+function ReBinder:HideClickBindingButtons()
+  for _, clickFrame in pairs(self.targets) do
+    clickFrame:Hide()
+  end    
+end
+
+function ReBinder:ClearSelectedKey()
+  self.selectedKey = nil
+end
+
+function ReBinder:ToggleEnabled()
+  if self:IsEnabled() then
+    self:Disable()
+  else
+    self:Enable()
+  end
+end
+
+function ReBinder:IsEnabled()
+  return ReBinderFrame:IsVisible()
+end
+
+function ReBinder:Enable()
+  ReBinderFrame:Show()
+end
+
+function ReBinder:Disable()
+  ReBinderFrame:Hide()
+end
+
+
+function ReBinder:HandleKeyPressed( key )
+  if key == nil or key == "UNKNOWN" or key == "SHIFT" or key == "CTRL" or key == "ALT" then 
+    return
+  end
+  if IsShiftKeyDown() then 
+    key = "SHIFT-"..key
+  end
+  if IsControlKeyDown() then
+    key = "CTRL-"..key
+  end
+  if IsAltKeyDown() then
+    key = "ALT-"..key
+  end
+  if key == "ESCAPE" or GetBindingAction(key) == "REBINDER_TOGGLEBINDINGMODE" then
+    ReBinderFrame:Hide()
+    return nil, nil
+  end
+
+  self.selectedKey = key
+
+  local keyTxt = GetBindingText(key, "KEY_")
+  local cmd    = GetBindingAction(key)
+  local cmdTxt
+  
+  if cmd then
+    cmdTxt = GetBindingText(cmd, "BINDING_NAME_")
+  end
+  
+  -- make click-bindings look prettier
+  local btnName
+  if cmdTxt then
+    btnName = string.match(cmdTxt,"CLICK (.+)\:LeftButton")
+    btnName = btnName or string.match(cmdTxt,"CLICK (.+)\:RightButton")
+  end
+  
+  return keyTxt, btnName or cmdTxt
+end
+
+-- TODO: move to override-binding model and store data in profile
+function ReBinder: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
+  end
+end
+
+
+function ReBinder: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")
+    end
+  end
+end
+
+
+function ReBinder:UpdateCurrentTarget( btnName )
+  local msg = ""
+  if btnName then
+    msg = btnName.." is currently "
+    local current = GetBindingKey("CLICK "..btnName..":LeftButton")
+    if current then
+      msg = msg .. "bound to " .. GetBindingText(current, "KEY_")
+    else
+      msg = msg .. " not bound"
+    end
+  end
+  ReBinderFrame.statusMsg:SetText(msg)
+end
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/ReBinder.xml	Tue Mar 20 21:08:31 2007 +0000
@@ -0,0 +1,242 @@
+<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">
+
+
+  <Button name="ReBinderClickBindingTemplate" virtual="true" hidden="true" toplevel="true" setAllPoints="true">
+		<HighlightTexture alphaMode="ADD" file="Interface\Buttons\ButtonHilight-Square"/>
+    <Layers>
+      <Layer level="BACKGROUND">
+        <Texture>
+          <Color r="0" g="0" b="0" a="0"/>
+        </Texture>
+      </Layer>
+    </Layers>
+    <Scripts>
+      <OnLoad>
+        this:RegisterForClicks("LeftButtonUp","RightButtonUp")
+      </OnLoad>
+      <OnClick>
+        local mouseBtn = arg1
+        if mouseBtn == "LeftButton" then
+          ReBinder:BindSelectedKeyTo(this.keybindTarget)
+        elseif mouseBtn == "RightButton" then
+          ReBinder:ClearBinding(this.keybindTarget)
+        end
+      </OnClick>
+      <PostClick>
+        this:SetButtonState("NORMAL")
+      </PostClick>
+      <OnEnter>
+        ReBinder:UpdateCurrentTarget(this.keybindTarget)
+      </OnEnter>
+      <OnLeave>
+        ReBinder: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">
+    <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">
+        <Size>
+          <AbsDimension x="330" y="350"/>
+        </Size>
+        <Anchors>
+          <Anchor point="CENTER"/>
+        </Anchors>
+        <TitleRegion>
+          <Size>
+            <AbsDimension x="350" y="30"/>
+          </Size>
+          <Anchors>
+            <Anchor point="TOP">
+              <Offset>
+                <AbsDimension x="0" y="-10"/>
+              </Offset>
+            </Anchor>
+          </Anchors>
+        </TitleRegion>
+        <Backdrop edgeFile="Interface\Tooltips\UI-Tooltip-Border" tile="true">
+          <EdgeSize>
+            <AbsValue val="16"/>
+          </EdgeSize>
+          <TileSize>
+            <AbsValue val="16"/>
+          </TileSize>
+          <BackgroundInsets>
+            <AbsInset left="4" right="4" top="4" bottom="4"/>
+          </BackgroundInsets>
+        </Backdrop>
+        <Layers>
+          <Layer level="BACKGROUND">
+            <Texture>
+              <Color r="0" g="0" b="0" a="0.7"/>
+            </Texture>
+          </Layer>
+          <Layer level="ARTWORK">
+            <FontString inherits="GameFontNormalLarge" text="Key Binding Setup" justifyH="CENTER">
+              <Size>
+                <AbsDimension x="200" y="30"/>
+              </Size>
+              <Anchors>
+                <Anchor point="TOP">
+                  <Offset>
+                    <AbsDimension x="0" y="-10"/>
+                  </Offset>
+                </Anchor>
+              </Anchors>
+            </FontString>
+            <FontString inherits="GameFontNormal" text="Press a key to ready it for assignment, then click a button to assign the key.|nRight-click a button to clear its binding." justifyH="CENTER">
+              <Size>
+                <AbsDimension x="240" y="70"/>
+              </Size>
+              <Anchors>
+                <Anchor point="TOP">
+                  <Offset>
+                    <AbsDimension x="0" y="-55"/>
+                  </Offset>
+                </Anchor>
+              </Anchors>
+              <Color r="1.0" g="1.0" b="1.0"/>
+            </FontString>
+            <FontString inherits="GameFontNormal" text="Selected Key:" justifyH="RIGHT">
+              <Size>
+                <AbsDimension x="175" y="25"/>
+              </Size>
+              <Anchors>
+                <Anchor point="RIGHT" relativePoint="TOP">
+                  <Offset>
+                    <AbsDimension x="-25" y="-160"/>
+                  </Offset>
+                </Anchor>
+              </Anchors>
+            </FontString>
+            <FontString inherits="GameFontNormal" text="Current Binding:" justifyH="RIGHT">
+              <Size>
+                <AbsDimension x="175" y="25"/>
+              </Size>
+              <Anchors>
+                <Anchor point="RIGHT" relativePoint="TOP">
+                  <Offset>
+                    <AbsDimension x="-25" y="-205"/>
+                  </Offset>
+                </Anchor>
+              </Anchors>
+            </FontString>
+            <FontString name="$parentCurrentActionText" inherits="GameFontNormal" text="(none)" justifyH="CENTER">
+              <Size>
+                <AbsDimension x="140" y="50"/>
+              </Size>
+              <Anchors>
+                <Anchor point="CENTER" relativePoint="TOP">
+                  <Offset>
+                    <AbsDimension x="75" y="-205"/>
+                  </Offset>
+                </Anchor>
+              </Anchors>
+              <Color r="0" g="1" b="0" a="1"/>
+            </FontString>
+            <FontString name="$parentStatusMsg" inherits="GameFontNormal" text="" justifyH="CENTER">
+              <Size>
+                <AbsDimension x="200" y="50"/>
+              </Size>
+              <Anchors>
+                <Anchor point="TOP">
+                  <Offset>
+                    <AbsDimension x="0" y="-230"/>
+                  </Offset>
+                </Anchor>
+              </Anchors>
+              <Color r="0.1" g="1.0" b="0.1"/>
+            </FontString>
+          </Layer>
+        </Layers>
+        <Frames>
+          <Button name="$parentSelectedKey" inherits="UIPanelButtonTemplate2" text="(none)">
+            <Size>
+              <AbsDimension x="140" y="28"/>
+            </Size>
+            <Anchors>
+              <Anchor point="CENTER" relativePoint="TOP">
+                <Offset>
+                  <AbsDimension x="75" y="-160"/>
+                </Offset>
+              </Anchor>
+            </Anchors>
+          </Button>
+          <Button inherits="GameMenuButtonTemplate" text="Done">
+            <Size>
+              <AbsDimension x="112" y="28"/>
+            </Size>
+            <Anchors>
+              <Anchor point="BOTTOM">
+                <Offset>
+                  <AbsDimension x="0" y="10"/>
+                </Offset>
+              </Anchor>
+            </Anchors>
+            <Scripts>
+              <OnClick>
+                this:GetParent():GetParent():Hide()
+              </OnClick>
+            </Scripts>
+          </Button>
+        </Frames>
+        <Scripts>
+          <!-- the dialog frame needs to handle clicks (close button, drag title) so we have to
+            re-implement the behavior of capturing alternate mouse buttons -->
+          <OnLoad>
+            this.selectedKey   = getglobal(this:GetName().."SelectedKeyText")
+            this.currentAction = getglobal(this:GetName().."CurrentActionText")
+            this:RegisterForClicks("MiddleButtonUp","Button4Up","Button5Up")
+          </OnLoad>
+          <OnClick>
+            local k, a = ReBinder:HandleKeyPressed(arg1)
+            if k then
+              this.selectedKey:SetText(k)
+              this.currentAction:SetText(a or "(none)")
+            end
+          </OnClick>
+        </Scripts>
+      </Button>
+    </Frames>
+    <Scripts>
+      <OnLoad>
+        this.selectedKey   = getglobal(this:GetName().."DialogSelectedKeyText")
+        this.currentAction = getglobal(this:GetName().."DialogCurrentActionText")
+        this.statusMsg     = getglobal(this:GetName().."DialogStatusMsg")
+        tinsert(UISpecialFrames,this:GetName())
+        this:RegisterForClicks("MiddleButtonUp","Button4Up","Button5Up")
+      </OnLoad>
+      <OnShow>
+        this.selectedKey:SetText("(none)")
+        this.currentAction:SetText("(none)")
+        this.statusMsg:SetText("")
+        ReBinder:ShowClickBindingButtons()
+      </OnShow>
+      <OnHide>
+        ReBinder:HideClickBindingButtons()
+        ReBinder:ClearSelectedKey()
+      </OnHide>
+      <OnKeyDown>
+        local k, a = ReBinder:HandleKeyPressed(arg1)
+        if k then
+          this.selectedKey:SetText(k)
+          this.currentAction:SetText(a or "(none)")
+        end
+      </OnKeyDown>
+      <OnClick>
+        local k, a = ReBinder:HandleKeyPressed(arg1)
+        if k then
+          this.selectedKey:SetText(k)
+          this.currentAction:SetText(a or "(none)")
+        end
+      </OnClick>
+    </Scripts>
+  </Button>
+
+</Ui>
Binary file libs/Dewdrop-2.0/._Dewdrop-2.0.lua has changed