view modules/ReAction_PetAction/ReAction_PetAction.lua @ 93:567a885cdfad

- pet hotkey support - fixed showgrid when in keybind mode for action buttons - fixed a typo (bad 'self') in overlay.lua
author Flick <flickerstreak@gmail.com>
date Fri, 17 Oct 2008 23:13:44 +0000
parents c2504a8b996c
children 39265b16d208
line wrap: on
line source
--[[
  ReAction Pet Action button module

  The button module implements standard action button functionality by wrapping Blizzard's 
  PetActionButton frame and associated functions.

--]]

-- local imports
local ReAction = ReAction
local L = ReAction.L
local _G = _G
local CreateFrame = CreateFrame

ReAction:UpdateRevision("$Revision$")

-- libraries
local KB = LibStub("LibKeyBound-1.0")

-- module declaration
local moduleID = "PetAction"
local module = ReAction:NewModule( moduleID )

-- Button class declaration
local Button = { }

-- module methods
function module:OnInitialize()
  self.db = ReAction.db:RegisterNamespace( moduleID,
    { 
      profile = {
        buttons = { }
      }
    }
  )
  self.buttons = { }

  ReAction:RegisterBarOptionGenerator(self, "GetBarOptions")

  ReAction.RegisterCallback(self, "OnCreateBar")
  ReAction.RegisterCallback(self, "OnDestroyBar")
  ReAction.RegisterCallback(self, "OnRefreshBar")
  ReAction.RegisterCallback(self, "OnEraseBar")
  ReAction.RegisterCallback(self, "OnRenameBar")
  ReAction.RegisterCallback(self, "OnConfigModeChanged")

  KB.RegisterCallback(self, "LIBKEYBOUND_ENABLED")
  KB.RegisterCallback(self, "LIBKEYBOUND_DISABLED")
  KB.RegisterCallback(self, "LIBKEYBOUND_MODE_COLOR_CHANGED","LIBKEYBOUND_ENABLED")
end

function module:OnEnable()
  ReAction:RegisterBarType(L["Pet Action Bar"], 
    { 
      type = moduleID ,
      defaultButtonSize = 30,
      defaultBarRows = 1,
      defaultBarCols = 10,
      defaultBarSpacing = 8
    })
end

function module:OnDisable()
  ReAction:UnregisterBarType(L["Pet Action Bar"])
end

function module:OnCreateBar(event, bar, name)
  if bar.config.type == moduleID then
    -- auto show/hide when pet exists
    bar:GetFrame():SetAttribute("unit","pet")
    if not ReAction:GetConfigMode() then
      RegisterUnitWatch(bar:GetFrame())
    end
    self:OnRefreshBar(event, bar, name)
  end
end

function module:OnRefreshBar(event, bar, name)
  if bar.config.type == moduleID then
    if self.buttons[bar] == nil then
      self.buttons[bar] = { }
    end
    local btns = self.buttons[bar]
    local profile = self.db.profile
    if profile.buttons[name] == nil then
      profile.buttons[name] = {}
    end
    local btnCfg = profile.buttons[name]

    local r, c = bar:GetButtonGrid()
    local n = r*c
    for i = 1, n do
      if btnCfg[i] == nil then
        btnCfg[i] = {}
      end
      if btns[i] == nil then
        local b = Button:New(bar,i,btnCfg[i])
        btns[i] = b
        bar:AddButton(i,b)
      end
      btns[i]:Refresh()
    end
    for i = n+1, #btns do
      if btns[i] then
        bar:RemoveButton(btns[i])
        btns[i] = btns[i]:Destroy()
        if btnCfg[i] then
          btnCfg[i] = nil
        end
      end
    end
  end
end

function module:OnDestroyBar(event, bar, name)
  if self.buttons[bar] then
    local btns = self.buttons[bar]
    for _,b in pairs(btns) do
      if b then
        b:Destroy()
      end
    end
    self.buttons[bar] = nil
  end
end

function module:OnEraseBar(event, bar, name)
  self.db.profile.buttons[name] = nil
end

function module:OnRenameBar(event, bar, oldname, newname)
  local b = self.db.profile.buttons
  b[newname], b[oldname] = b[oldname], nil
end


function module:OnConfigModeChanged(event, mode)
  for _, buttons in pairs(self.buttons) do
    for _, b in pairs(buttons) do
      b:ShowActionIDLabel(mode)
    end
  end
  for _, bar in ReAction:IterateBars() do
    if bar and self.buttons[bar] then
      local f = bar:GetFrame()
      if mode then
        UnregisterUnitWatch(f)
        f:Show()
      else
        RegisterUnitWatch(f)
      end
    end
  end
end

function module:LIBKEYBOUND_ENABLED(evt)
  for _, buttons in pairs(self.buttons) do
    for _, b in pairs(buttons) do
      b:SetKeybindMode(true)
    end
  end
end

function module:LIBKEYBOUND_DISABLED(evt)
  for _, buttons in pairs(self.buttons) do
    for _, b in pairs(buttons) do
      b:SetKeybindMode(false)
    end
  end
end


---- Options ----
function module:GetBarOptions(bar)
  if bar.config.type == moduleID then
    return {
      type = "group",
      name = L["Pet Buttons"],
      args = {
      }
    }
  end
end



------ Button class ------

-- use-count of action IDs
local nActionIDs = NUM_PET_ACTION_SLOTS
local ActionIDList = setmetatable( {}, {
  __index = function(self, idx)
    if idx == nil then
      for i = 1, nActionIDs do
        if rawget(self,i) == nil then
          rawset(self,i,1)
          return i
        end
      end
      error("ran out of pet action IDs")
    else
      local c = rawget(self,idx) or 0
      rawset(self,idx,c+1)
      return idx
    end
  end,
  __newindex = function(self,idx,value)
    if value == nil then
      value = rawget(self,idx)
      if value == 1 then
        value = nil
      elseif value then
        value = value - 1
      end
    end
    rawset(self,idx,value)
  end
})

local frameRecycler = {}
local trash = CreateFrame("Frame")
local KBAttach, GetActionName, GetHotkey, SetKey, FreeKey, ClearBindings, GetBindings
do
  local buttonLookup = setmetatable({},{__mode="kv"})

  -- Use KeyBound-1.0 for binding, but use Override bindings instead of
  -- regular bindings to support multiple profile use. This is a little
  -- weird with the KeyBound dialog box (which has per-char selector as well
  -- as an OK/Cancel box) but it's the least amount of effort to implement.
  function GetActionName(f)
    local b = buttonLookup[f]
    if b then
      return format("%s:%s", b.bar:GetName(), b.idx)
    end
  end

  function GetHotkey(f)
    local b = buttonLookup[f]
    if b then
      return KB:ToShortKey(b:GetConfig().hotkey)
    end
  end

  function SetKey(f, key)
    local b = buttonLookup[f]
    if b then
      local c = b:GetConfig()
      if c.hotkey then
        SetOverrideBinding(f, false, c.hotkey, nil)
      end
      if key then
        SetOverrideBindingClick(f, false, key, f:GetName(), nil)
      end
      c.hotkey = key
      b:DisplayHotkey(GetHotkey(f))
    end
  end

  function FreeKey(f, key)
    local b = buttonLookup[f]
    if b then
      local c = b:GetConfig()
      if c.hotkey == key then
        local action = f:GetActionName()
        SetOverrideBinding(f, false, c.hotkey, nil)
        c.hotkey = nil
        b:DisplayHotkey(nil)
        return action
      end
    end
    return ReAction:FreeOverrideHotkey(key)
  end

  function ClearBindings(f)
    SetKey(f, nil)
  end

  function GetBindings(f)
    local b = buttonLookup[f]
    if b then
      return b:GetConfig().hotkey
    end
  end

  function KBAttach( button )
    local f = button:GetFrame()
    f.GetActionName = GetActionName
    f.GetHotkey     = GetHotkey
    f.SetKey        = SetKey
    f.FreeKey       = FreeKey
    f.ClearBindings = ClearBindings
    f.GetBindings   = GetBindings
    buttonLookup[f] = button
    f:SetKey(button:GetConfig().hotkey)
    ReAction:RegisterKeybindFrame(f)
    if ReAction:GetKeybindMode() then
      button.border:SetVertexColor(KB:GetColorKeyBoundMode())
      button.border:Show()
    end
  end
end

local meta = { __index = Button }

function Button:New( bar, idx, config )
  -- create new self
  self = setmetatable( 
    { 
      bar = bar,
      idx = idx,
      config = config,
    }, meta )

  local name = config.name or ("ReAction_%s_%s_%d"):format(bar:GetName(),moduleID,idx)
  config.name = name
  self.name = name
  config.actionID = ActionIDList[config.actionID] -- gets a free one if none configured
  
  -- have to recycle frames with the same name:
  -- otherwise you either get references to old textures because named CreateFrame()
  -- doesn't overwrite existing globals. Can't set them to nil in the global table, 
  -- as it causes taint.
  local parent = bar:GetFrame()
  local f = frameRecycler[name]
  if f then
    f:SetParent(parent)
  else
    f = CreateFrame("CheckButton", name, parent, "PetActionButtonTemplate")
    -- ditch the old hotkey text because it's tied in ActionButton_Update() to the
    -- standard binding. We use override bindings.
    local hotkey = _G[name.."HotKey"]
    hotkey:SetParent(trash)
    hotkey = f:CreateFontString(nil, "ARTWORK", "NumberFontNormalSmallGray")
    hotkey:SetWidth(36)
    hotkey:SetHeight(18)
    hotkey:SetJustifyH("RIGHT")
    hotkey:SetJustifyV("TOP")
    hotkey:SetPoint("TOPLEFT",f,"TOPLEFT",-2,-2)
    f.hotkey = hotkey
    f:HookScript("OnDragStart", function() self:Update() end)
    f:HookScript("OnReceiveDrag", function() self:Update() end)
  end
  if config.actionID then
    f:SetID(config.actionID) -- PetActionButtonTemplate isn't a proper SecureActionButton
  end
  f:SetFrameStrata("MEDIUM")
  self.frame    = f
  self.icon     = _G[("%sIcon"):format(name)]
  self.acTex    = _G[("%sAutoCastable"):format(name)]
  self.acModel  = _G[("%sShine"):format(name)]
  self.cooldown = _G[("%sCooldown"):format(name)]
  self.hotkey   = f.hotkey
  self.border   = _G[("%sBorder"):format(name)]


  f:RegisterEvent("PLAYER_CONTROL_LOST");
	f:RegisterEvent("PLAYER_CONTROL_GAINED");
	f:RegisterEvent("PLAYER_FARSIGHT_FOCUS_CHANGED");
	f:RegisterEvent("UNIT_PET");
	f:RegisterEvent("UNIT_FLAGS");
	f:RegisterEvent("UNIT_AURA");
	f:RegisterEvent("PET_BAR_UPDATE");
	f:RegisterEvent("PET_BAR_UPDATE_COOLDOWN");

  f:SetScript("OnEvent",
    function(event,arg1)
      if event =="PET_BAR_UPDATE_COOLDOWN" then
        self:UpdateCooldown()
      elseif event == "UPDATE_BINDINGS" then
        self:UpdateHotkey()
      else
        self:Update()
      end
    end)

  KBAttach(self)

  self:Refresh()
  self:SetKeybindMode(ReAction:GetKeybindMode())

  return self
end

function Button:Destroy()
  local f = self.frame
  f:UnregisterAllEvents()
  f:Hide()
  f:SetParent(UIParent)
  f:ClearAllPoints()
  if self.name then
    frameRecycler[self.name] = f
    _G[self.name] = nil
  end
  if self.config.actionID then
    ActionIDList[self.config.actionID] = nil
  end
  self.frame = nil
  self.config = nil
  self.bar = nil
end

function Button:Refresh()
  self.bar:PlaceButton(self, 30, 30)
  self:Update()
  self:UpdateHotkey()
end

function Button:GetFrame()
  return self.frame
end

function Button:GetName()
  return self.name
end

function Button:GetConfig()
  return self.config
end

function Button:GetActionID()
  return self.config.actionID
end

function Button:Update()
  local id = self.frame:GetID()
  local name, subtext, texture, isToken, isActive, autoCastAllowed, autoCastEnabled = GetPetActionInfo(id);
  local f = self.frame
  --ReAction:Print(("id %d: '%s', '%s', '%s', '%s', '%s', '%s', '%s'"):format(tostring(id), tostring(name),tostring(subtext),tostring(texture),tostring(isToken),tostring(isActive),tostring(autoCastAllowed),tostring(autoCastEnabled)))

  if isToken then
    self.icon:SetTexture(_G[texture]);
    f.tooltipName = _G[name];
  else
    self.icon:SetTexture(texture);
    f.tooltipName = name;
  end

  f.isToken = isToken;
	f.tooltipSubtext = subtext;
  f:SetChecked( isActive and 1 or 0);

  if autoCastAllowed then
    self.acTex:Show();
  else
    self.acTex:Hide();
  end

  if autoCastEnabled then
    AutoCastShine_AutoCastStart(self.acModel)
  else
    AutoCastShine_AutoCastStop(self.acModel)
  end

  if texture then
    if GetPetActionSlotUsable(id) then
      SetDesaturation(self.icon,nil)
    else
      SetDesaturation(self.icon,1)
    end
    self.icon:Show();
    f:SetNormalTexture("Interface\\Buttons\\UI-Quickslot2");
  else
    self.icon:Hide();
    f:SetNormalTexture("Interface\\Buttons\\UI-Quickslot");
  end

  self:UpdateCooldown()
end

function Button:UpdateCooldown()
	local start, duration, enable = GetPetActionCooldown(self.frame:GetID());
  CooldownFrame_SetTimer(self.cooldown, start, duration, enable);
end

function Button:UpdateHotkey()
  self:DisplayHotkey(GetHotkey(self.frame))
end

function Button:ShowActionIDLabel(show)
  if show then
    -- store the action ID label in the frame due to frame recycling
    if not self.actionIDLabel and self:GetActionID() then
      local label = self.frame:CreateFontString(nil,"OVERLAY","GameFontNormalLarge")
      label:SetAllPoints()
      label:SetJustifyH("CENTER")
      label:SetShadowColor(0,0,0,1)
      label:SetShadowOffset(2,-2)
      label:SetText(tostring(self:GetActionID()))
      self.actionIDLabel = label
    end
    self.actionIDLabel:Show()
  elseif self.actionIDLabel then
    self.actionIDLabel:Hide()
  end
end


function Button:SetKeybindMode(mode)
  if mode then
    self.border:SetVertexColor(KB:GetColorKeyBoundMode())
    self.border:Show()
  else
    self.border:Hide()
  end
end

function Button:DisplayHotkey( key )
  self.hotkey:SetText(key or "")
end