view main.lua @ 17:639282f3a0e0

More cleanup of main.lua, ReBound-1.0.lua
author Flick <flickerstreak@gmail.com>
date Fri, 23 Mar 2007 19:28:30 +0000
parents 2735edcf9ab7
children 2f3e45fcb9e2
line wrap: on
line source
-- main.lua
-- 
-- Top-level file for the ReAction Action Bar add-on
--
-- implemented in terms of the Ace 2 development framework library: http://www.wowace.com
--

-- 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")
local ReBound = AceLibrary("ReBound-1.0"):new("ReAction")

-- private functions
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

-- 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_REACTION_TOGGLEKEYBIND = L["ReAction Keybinding Mode"]

-- UI panel strings
REACTION_KEYBIND_TITLE    = L["ReAction Keybinding"]
REACTION_KEYBIND_SUBTITLE = L["Click Buttons to Set Keybindings"]
REACTION_KEYBIND_DONE     = L["Save"]
REACTION_KEYBIND_REVERT   = L["Revert"]



------------------------------
-- AceAddon setup
------------------------------
local main = AceLibrary("AceAddon-2.0"):new(
  "AceConsole-2.0",
  "AceEvent-2.0",
  "AceDB-2.0",
  "FuBarPlugin-2.0"
)

function main:OnInitialize()
  self:RegisterChatCommand( {L["/reaction"], L["/rxn"]}, ReActionConsoleOptions, "REACTION" )
  self:RegisterDB("ReActionDB","ReActionDBPC")
  self:RegisterDefaults("profile", ReAction_DefaultProfile)
  self:RegisterEvent("PLAYER_REGEN_DISABLED","CombatLockdown")
  self:DisableDefaultKeybindings()

  -- create update function for keybinding frame
  ReActionKeybindDialog:SetScript("OnHide", function(frame) main:HideKeybindDialog(frame) end)

  -- initial non-persistent state
  self.locked = true
  self.keybindMode = false
  self.bars   = { }
end

-- OnEnable is called at PLAYER_LOGIN or when the addon is enabled.
function main:OnEnable( )
  self:HideDefaultBars()
  self:SetupProfile( )
end

function main:OnDisable()
  self:Lock()
end

-- OnProfileEnable() is only called when switching profiles, NOT for the initial profile at load time.
function main:OnProfileEnable( oldName, oldData )
  self:UnregisterEvent("REBOUND_BIND")
  self:UnregisterEvent("REBOUND_UNBIND")
  ReBound:ClearRegisteredBindings()
  self:SetupProfile()
end

function main:SetupProfile( )
  local profile = self.db.profile
  if profile.firstRunDone ~= true then
    profile.bars = tcopy(ReAction_DefaultBlizzardBars)
  end
  self:DestroyAllBars()
  self:UpgradeProfile()
  self:HideArt()
  self:SetupBars()
  self:SetupKeybindings()
  if profile.firstRunDone ~= true then
    self:Unlock()
    profile.firstRunDone = true
  end
end

-- Set a global variable for Bindings.xml (I use 'setglobal' for clarity, it's not strictly necessary)
setglobal("ReActionAddOn", main)




------------------------------------------------------------
-- Profile conversion functions (from old profiles)
--
-- NOTE: these will be REMOVED when alpha testing is over.
------------------------------------------------------------
function main:UpgradeBindingConfig()
  if #self.db.profile.bindings == 0 then
    for _, bar in pairs(self.bars) do
      for _, button in pairs(bar.buttons) do
        local key = ReBound:GetBinding(button:GetActionFrame(),"LeftButton")
        if key and #key > 0 and not self.db.profile.bindings[key] then
          self:REBOUND_BIND(key,button:GetActionFrame():GetName(),"LeftButton")
        end
      end
    end
  end
end

function main:UpgradeProfile()
end



--------------------------------------------
-- FuBar plugin setup
-- Even if FuBar isn't installed, the plugin 
-- provides 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

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", self:GetKeybindMode() 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
    self:ToggleKeybindMode()
    self:UpdateDisplay()
  end
end




------------------------------
-- Key binding functions
------------------------------
function main:DisableDefaultKeybindings()
  -- change the labels on all actionbar keybindings in the default interface.
  local label = "|cff999999("..L["Use ReAction"]..")|r"
  for i = 1, 12 do
    setglobal("BINDING_NAME_ACTIONBUTTON"..i,label)
    for j = 1, 4 do
      setglobal("BINDING_NAME_MULTIACTIONBAR"..j.."BUTTON"..i,label)
    end
  end
  for i = 1, 6 do
    setglobal("BINDING_NAME_ACTIONPAGE"..i,label)
  end
  for i = 1, 10 do
    setglobal("BINDING_NAME_BONUSACTIONBUTTON"..i,label)
--    setglobal("BINDING_NAME_SHAPESHIFTBUTTON"..i,label)
  end
  BINDING_HEADER_ACTIONBAR = "|cff999999"..L["Action Bar Functions Disabled"].."|r"
  BINDING_HEADER_MULTIACTIONBAR = "|cff999999"..L["Multi-Action Bar Functions Disabled"].."|r"
  BINDING_NAME_NEXTACTIONPAGE = label
  BINDING_NAME_PREVIOUSACTIONPAGE = label
end

function main:SetupKeybindings()
  if self.db.profile.firstRunDone ~= true then
    self:StealKeyBindings()
  else
    self:UpgradeBindingConfig()
    local needsSave = false
    for key, binding in pairs(self.db.profile.bindings) do
      local target = getglobal(binding.target) 
      if target then
        if ReBound:GetBinding(target,binding.button) ~= key then
          ReBound:SetBinding(key,target,binding.button,true)
          needsSave = true
        end
      end
    end
    if needsSave then
      ReBound:SaveBindings()
    end
  end
  self:RegisterEvent("REBOUND_BIND")
  self:RegisterEvent("REBOUND_UNBIND")
end

function main:StealKeyBindings()
  -- steal the keybindings of the main action bar and assign them to rebar 1, buttons 1-12
  local bar = self.bars[1]
  if bar and bar ~= EMPTY_BAR_SLOT then
    for i = 1, 12 do
      local key = GetBindingKey("ACTIONBUTTON"..i)
      if key and #key > 0 then
        local button = bar.buttons[i]
        if button then
          ReBound:SetBinding(key, button:GetActionFrame(),nil,true)
        end
      end
    end
    local key = GetBindingKey("NEXTACTIONPAGE")
    if key and #key > 0 then
      ReBound:SetBinding(key, bar.upArrow,nil,true)
    end
    key = GetBindingKey("PREVIOUSACTIONPAGE")
    if key and #key > 0 then
      ReBound:SetBinding(key, bar.downArrow,nil,true)
    end
    ReBound:SaveBindings()
  end
end

function main:REBOUND_BIND(id, key, target, button)
  if id == "ReAction" and key and target then
    self.db.profile.bindings[key] = { target = target, button = button }
  end
end

function main:REBOUND_UNBIND(id, key)
  if id == "ReAction" and key then
    self.db.profile.bindings[key] = nil
  end
end

function main:ToggleKeybindMode()
  self:SetKeybindMode(not self:GetKeybindMode())
end

function main:GetKeybindMode()
  return self.keybindMode
end

function main:SetKeybindMode(enabled)
  if InCombatLockdown() then
    UIErrorsFrame:AddMessage(ERROR_NOT_IN_COMBAT)
  else
    self.keybindMode = enabled
    for _, bar in pairs(self.bars) do
      if bar and bar ~= EMPTY_BAR_SLOT then
        for __, button in pairs(bar.buttons) do
          if button then
            button:TempShow(enabled)
          end
        end
      end
    end
    if enabled then
      ReBound:ShowRegisteredFrames()
      ReActionKeybindDialog:Show()
    else
      ReBound:HideRegisteredFrames()
      if ReActionKeybindDialog:IsShown() then
        ReActionKeybindDialog:Hide()
      end
    end
  end
end

function main:HideKeybindDialog( frame )
  self:SetKeybindMode(false)
  if frame.save then
    ReBound:SaveBindings()
  else
    ReBound:RevertBindings()
  end
  frame.save = false
end


----------------------------
-- Bar lock/unlock functions
----------------------------
function main:CombatLockdown()
  if not self:IsLocked() then
    self:Lock()
    UIErrorsFrame:AddMessage(L["ReAction bars locked when in combat"])
  end
  ReActionKeybindDialog:Hide()
end

function main:SetLocked( lock )
  if lock ~= self.locked then
    if not lock and InCombatLockdown() then
      UIErrorsFrame:AddMessage(ERROR_NOT_IN_COMBAT)
    else
      self.locked = lock and true or false -- force data integrity
      for _, bar in pairs(self.bars) do
        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 main:IsLocked()
  return self.locked
end

function main:Lock()
  self:SetLocked(true)
end

function main:Unlock()
  self:SetLocked(false)
end

function main:ToggleLocked()
  self:SetLocked( not(self.locked) )
end



--------------------------------------------------------
-- Functions to hide the default Blizzard main bar parts
--------------------------------------------------------
function main:HideArt()
  if self.db.profile.hideArt then
    -- the pet bar is a child of MainMenuBar, but can't be hidden because it will
    -- break automatic pet bar show/hide. Need to reparent it.
    PetActionBarFrame:SetParent(UIParent)
    
    -- 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)

    MainMenuBar:Hide() -- this also hides the bags, xp bar, lag meter, and micro menu buttons.
  else
    SlidingActionBarTexture0:SetParent(PetActionBarFrame)
    SlidingActionBarTexture1:SetParent(PetActionBarFrame)
    PetActionBarFrame:SetParent(MainMenuBar)
    MainMenuBar:Show()
  end
end

function main:IsArtHidden()
  return self.db.profile.hideArt
end

function main:SetHideArt( hide )
  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 main:ToggleHideArt()
  self:SetHideArt( not self:IsArtHidden() )
end

-- Hide default Blizzard bars
local blizzDefaultBars = {
  ActionButton1,
  ActionButton2,
  ActionButton3,
  ActionButton4,
  ActionButton5,
  ActionButton6,
  ActionButton7,
  ActionButton8,
  ActionButton9,
  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,
}

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

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




---------------------------------------
-- Bar setup and manipulation functions
---------------------------------------
-- Reset bars to blizzard defaults
function main:ResetBars()
  if InCombatLockdown() then
    UIErrorsFrame:AddMessage(SPELL_FAILED_AFFECTING_COMBAT)
  else
    self.db.profile.bars = tcopy(ReAction_DefaultBlizzardBars)
    self:OnProfileEnable() -- treat it like a profile switch
  end
end

function main:DestroyAllBars()
  -- destroy any existing bars
  for id = 1, table.maxn(self.bars) do
    self:DestroyBar(id)
  end
end

function main:SetupBars()
  -- 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 config then
      self.bars[id] = 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
    if bar ~= EMPTY_BAR_SLOT then
      bar:ApplyAnchor()
    end
  end
end

function main:CreateBar( config, id )
  local buttonType = config.btnConfig and config.btnConfig.type and getglobal(config.btnConfig.type)
  local subtype = buttonType and buttonType:GetButtonType(config.btnConfig.subtype)

  if not subtype then
    self:Print(L["Tried to create a button of unknown type"])
    return
  end

  local bar = ReBar:new(config, id)

  -- initialize dewdrop menu
  dewdrop:Register(bar:GetControlFrame(), 
    'children', 
    function()
      dewdrop:FeedAceOptionsTable(ReActionGlobalMenuOptions)
      dewdrop:FeedAceOptionsTable(GenerateReActionBarOptions(bar,self))
      dewdrop:FeedAceOptionsTable(subtype:GenerateOptionsTable(config.btnConfig, function() return bar:GetButtonList() end))
    end,
    'cursorX', true, 
    'cursorY', true
  )

  -- register page up/down buttons with ReBound for keybinding
  ReBound:Register(bar.upArrow)
  ReBound:Register(bar.downArrow)

  if not self.locked then
    bar:ShowControls()
  end

  return bar
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

-- 
-- this function is a wrapper for CreateBar() which looks up the bar type
-- and constructs a new configuration object of the right type.
function main:NewBar( type )
  if InCombatLockdown() then
    UIErrorsFrame:AddMessage(SPELL_FAILED_AFFECTING_COMBAT)
    return
  end

  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
    self.bars[id] = self:CreateBar(c, id)
    self.db.profile.bars[id] = c
    self.bars[id]:ApplyAnchor()
    self:Unlock()
  end
end

--
-- This function is a wrapper for DestroyBar() which does in-combat 
-- checking and updates the config.
function main:DeleteBar(id)
  if InCombatLockdown() then
    UIErrorsFrame:AddMessage(SPELL_FAILED_AFFECTING_COMBAT)
  else
    if self.bars[id] then
      self:DestroyBar(id)
      self.db.profile.bars[id] = nil
    end
  end
end