view modules/ReAction_Action/ReAction_Action.lua @ 77:da8ba8783924

- added revision updater to each code file - Changed button/bar class mechanic to metatable-based - Changed buttons to live within a sub-frame, to play nicely between show-empty-buttons and hidestates - bar frame is now available only via accessor - Changed some semantics with AddButton/PlaceButton - Cleaned up action buttons options, fixed hide-when-empty option - moved show-action-ID-label as a button method - converted drag overlay from nested-frame to :Raise() - fixed ReAction:SetConfigMode() to not call event when mode doesn't change - Fixed ordering for dynamic state tab (always last)
author Flick <flickerstreak@gmail.com>
date Mon, 23 Jun 2008 22:27:50 +0000
parents 06cd74bdc7da
children 502cdb5666e2
line wrap: on
line source
--[[
  ReAction Action button module.

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

  It also provides support for multiple pages (interacting with the State module) as well
  as optional action remapping for possessed targets (mind control).

--]]

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

ReAction:UpdateRevision("$Revision: 103 $")

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

-- Button class declaration
local Button = { }

-- private --
local function RefreshLite(bar)
  local btns = module.buttons[bar]
  if btns then
    for _, b in ipairs(btns) do
      b:Refresh()
    end
  end
end

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

  ReAction:RegisterBarOptionGenerator(self, "GetBarOptions")

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

end

function module:OnEnable()
  ReAction:RegisterBarType(L["Action Bar"], 
    { 
      type = moduleID,
      defaultButtonSize = 36,
      defaultBarRows = 1,
      defaultBarCols = 12,
      defaultBarSpacing = 3
    }, true)
end

function module:OnDisable()
  ReAction:UnregisterBarType(L["Action Bar"])
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
    if profile.bars[name] == nil then
      profile.bars[name] = {}
    end
    local btnCfg = profile.buttons[name]
    local barCfg = profile.bars[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], barCfg)
        btns[i] = b
        bar:AddButton(i,b)
      end
    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
    RefreshLite(bar)
  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
  self.db.profile.bars[name] = nil
end

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

  b = self.db.profile.bars
  b[newname], b[oldname] = b[oldname], nil
end

function module:OnConfigModeChanged(event, mode)
  for _, bar in pairs(self.buttons) do
    for _, b in pairs(bar) do
      b:ShowGrid(mode)
      b:ShowActionIDLabel(mode)
    end
  end
end


---- Options ----
local Handler = { }

local options = {
  hideEmpty = {
    name = L["Hide Empty Buttons"],
    desc = L["Hide buttons when empty. This option is not supported for multi-state bars"],
    order = 1,
    type = "toggle",
    get  = "GetHideEmpty",
    set  = "SetHideEmpty",
  },
}

function module:GetBarOptions(bar)
  return {
    type = "group",
    name = L["Action Buttons"],
    handler = Handler:New(bar),
    hidden = "Hidden",
    args = options
  }
end

-- options handler private
do
  local function GetBarConfig( bar )
    return module.db.profile.bars[bar:GetName()]
  end

  function Handler:New(bar)
    return setmetatable( { bar = bar }, { __index = Handler } )
  end

  function Handler:Hidden()
    return self.bar.config.type ~= moduleID
  end

  function Handler:SetHideEmpty(info, value)
    local c = GetBarConfig(self.bar)
    if value ~= c.hideEmpty then
      for b in self.bar:IterateButtons() do
        b:ShowGrid(not value)
      end
      c.hideEmpty = value
    end
  end

  function Handler:GetHideEmpty()
    return GetBarConfig(self.bar).hideEmpty
  end
end


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

-- use-count of action IDs
local nActionIDs = 120
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 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
})

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

  config.name = config.name or ("ReAction_%s_%d"):format(bar:GetName(),idx)
  self.name = config.name
  config.actionID = ActionIDList[config.actionID] -- gets a free one if none configured
  self.nPages = 1
  
  local f = CreateFrame("CheckButton", self.name, bar:GetButtonFrame(), "ActionBarButtonTemplate")

  -- this will probably cause taint and/or performance problems, using right now for display/debugging purposes
  f:SetScript("OnAttributeChanged", ActionButton_UpdateAction)

  f:SetAttribute("action", config.actionID)
  -- install mind control action support for all buttons here just for simplicity
  if self.idx <= 12 then
    f:SetAttribute("action-mc", 120 + self.idx)
  end

  self.frame = f
  self.normalTexture = getglobal(format("%sNormalTexture",f:GetName()))

  -- initialize the hide state
  self:ShowGrid(not barConfig.hideEmpty)
  if ReAction:GetConfigMode() then
    self:ShowGrid(true)
  end

  -- show the ID label if applicable
  self:ShowActionIDLabel(ReAction:GetConfigMode())

  self:Refresh()
  return self
end

function Button:Destroy()
  local f = self.frame
  f:UnregisterAllEvents()
  f:Hide()
  f:SetParent(UIParent)
  f:ClearAllPoints()
  if self.name then
    _G[self.name] = nil
  end
  if self.config.actionID then
    ActionIDList[self.config.actionID] = nil
  end
  if self.config.pages then
    for _, id in ipairs(self.config.pages) do
      ActionIDList[id] = nil
    end
  end
  self.frame = nil
  self.config = nil
  self.bar = nil
end

function Button:Refresh()
  local f = self.frame
  self.bar:PlaceButton(self, 36, 36)
  if self.barConfig.mckeybinds then
    f:SetAttribute("bindings-mc", self.barConfig.mckeybinds[self.idx])
  end
  self:RefreshPages()
end

function Button:GetFrame()
  return self.frame
end

function Button:GetName()
  return self.name
end

function Button:GetActionID()
  return SecureButton_GetModifiedAttribute(self.frame, "action")
end

function Button:RefreshPages()
  local nPages = 1 --self.bar:GetNumPages()
  if nPages ~= self.nPages then
    local f = self:GetFrame()
    local c = self.config.pages
    if nPages > 1 and not c then
      c = { }
      self.config.pages = c
    end
    for i = 1, nPages do
      c[i] = ActionIDList[c[i]] -- gets a free one if none configured
      f:SetAttribute(("action-page%d"):format(i))
    end
    for i = nPages+1, #c do
      ActionIDList[c[i]] = nil
      c[i] = nil
      f:SetAttribute(("action-page%d"):format(i))
    end

    -- TODO:
    -- apply next-page, prev-page, and direct-page keybinds (via bar:SetStateKeybind abstraction)
  end
end

function Button:ShowGrid( show )
  if not InCombatLockdown() then
    -- new in 2.4.1: can't call ActionButton_ShowGrid/HideGrid because they won't update the attribute
    local f = self.frame
    local count = f:GetAttribute("showgrid")
    if show then
      count = count + 1
    else
      count = count - 1
    end
    if count < 0 then
      count = 0
    end
    f:SetAttribute("showgrid",count)

    if count >= 1 and not f:GetAttribute("statehidden") then
      self.normalTexture:SetVertexColor(1.0, 1.0, 1.0, 0.5);
      f:Show()
    elseif count < 1 and not HasAction(self:GetActionID()) then
      f:Hide()
    end
  end
end

function Button:ShowActionIDLabel( show )
  if show then
    local id = self:GetActionID()
    if not self.actionIDLabel and id and id ~= 0 then
      local f = self:GetFrame()
      local label = f:CreateFontString(nil,"OVERLAY","GameFontNormalLarge")
      label:SetAllPoints()
      label:SetJustifyH("CENTER")
      label:SetShadowColor(0,0,0,1)
      label:SetShadowOffset(2,-2)
      label:SetText(tostring(id))
      self.actionIDLabel = label
      f:HookScript("OnAttributeChanged", 
        function(frame, attr, value)
          if attr == "state-parent" then
            label:SetText(tostring(self:GetActionID()))
          end
        end)
    end
    self.actionIDLabel:Show()
  elseif self.actionIDLabel then
    self.actionIDLabel:Hide()
  end
end