view modules/ConfigUI.lua @ 174:09f7af2f53f8

Pretty totem bar textures
author Flick <flickerstreak@gmail.com>
date Wed, 20 Oct 2010 00:41:10 +0000
parents 8cc187143acd
children df68b5a40490
line wrap: on
line source
--[[
  ReAction Configuration UI module

  Hooks into Blizzard Interface Options AddOns panel
--]]

-- local imports
local ReAction = ReAction
local L = ReAction.L
local _G = _G
local AceConfigReg = LibStub("AceConfigRegistry-3.0")
local AceConfigDialog = LibStub("AceConfigDialog-3.0")

-- some constants
local configName = "ReAction"

-- module declaration
local moduleID = "ConfigUI"
local module = ReAction:NewModule( moduleID,
  "AceEvent-3.0"
)

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

  self:RegisterEvent("PLAYER_REGEN_DISABLED")
  ReAction.RegisterCallback(self,"OnOptionsRegistered","OnOptionsRefreshed")
  ReAction.RegisterCallback(self,"OnOptionsRefreshed")
  self:InitializeOptions()
end

function module:OnOptionsRefreshed(evt)
  AceConfigReg:NotifyChange(configName)
  if self.editor then self.editor:Refresh() end
end

function module:PLAYER_REGEN_DISABLED()
  if self.editor then
    self.editor:Hide()
  end
end

function module:OpenConfig()
  InterfaceOptionsFrame_OpenToCategory(configName)
end

function module:InitializeOptions()
  ReAction:RegisterOptions(self, {
      _launchEditor = {
        type = "execute",
        handler = self,
        name = L["Edit Bars..."],
        desc = L["Show the ReAction Bar Editor dialogue"],
        func = function()
            self:LaunchBarEditor()
            -- you can't close a dialog in response to an options click, because the end of the 
            -- handler for all the button events calls lib:Open()
            -- So, schedule a close on the next OnUpdate
            if self.db.profile.closeOnLaunch then
              self.editor.closePending = true
            end
          end,
        order = 2,
      },
      _closeThis = {
        type = "toggle",
        name = L["Close on Launch"],
        desc = L["Close the Interface Options window when launching the ReAction Bar Editor"],
        get  = function() return self.db.profile.closeOnLaunch end,
        set  = function(info, val) self.db.profile.closeOnLaunch = val end,
        order = 3,
      },
      _keybind = {
        type = "execute",
        handler = self,
        name = L["Key Bindings"],
        desc = L["Show the keybinding dialogue"],
        func = function()
            ReAction:SetKeybindMode(true)
          end,
        order = 4,
      },
    }, true) -- global

  AceConfigReg:RegisterOptionsTable(configName,ReAction.options)
  self.frame = AceConfigDialog:AddToBlizOptions(configName, configName)
  self.frame.obj:SetCallback("default", 
    function() 
      ReAction.db:ResetProfile()
      ReAction:RefreshOptions()
    end )
end




-- Bar Editor --
local function NewEditor()
  -- private variables
  local editorName = "ReAction-Editor"
  local barOptMap = setmetatable({},{__mode="v"})
  local tmp = { }
  local pointTable = {
    CENTER      = L["Center"], 
    LEFT        = L["Left"],
    RIGHT       = L["Right"],
    TOP         = L["Top"],
    BOTTOM      = L["Bottom"],
    TOPLEFT     = L["Top Left"],
    TOPRIGHT    = L["Top Right"],
    BOTTOMLEFT  = L["Bottom Left"],
    BOTTOMRIGHT = L["Bottom Right"],
  }


  -- use a local GUI container to work around AceConfigDialog closing
  -- both the bar editor and the global options when interface options is closed
  local editor = LibStub("AceGUI-3.0"):Create("Frame")
  local frame = editor.frame
  frame:SetClampedToScreen(true)
  frame:Hide()
  local old_OnUpdate = frame:GetScript("OnUpdate")
  frame:SetScript("OnUpdate", function(dt)
      if old_OnUpdate then
        old_OnUpdate(dt)
      end
      if editor.closePending then
        InterfaceOptionsFrame:Hide()
        editor.closePending = false
      end
      if editor.selfClosePending then
        editor:Hide()
        AceConfigReg:NotifyChange(configName)
        editor.selfClosePending = false
      end
    end )
  editor:SetCallback("OnClose", 
    function() 
      ReAction:SetConfigMode(false)
    end )
  AceConfigDialog:SetDefaultSize(editorName, 700, 540)
  

  local name = ("ReAction - %s"):format(L["Bar Editor"])
  editor:SetTitle(name)
  local options = {
    type = "group",
    name = name,
    handler = editor,
    childGroups = "tree",
    args = {
      desc = {
        type = "description",
        name = L["Use the mouse to arrange and resize the bars on screen. Tooltips on bars indicate additional functionality."],
        order = 1
      },
      launchConfig = {
        type = "execute",
        name = L["Global Config"],
        desc = L["Opens ReAction global configuration settings panel"],
        func = function()
            module:OpenConfig()
            -- you can't close a dialog in response to an options click, because the end of the 
            -- handler for all the button events calls lib:Open()
            -- So, schedule a close on the next OnUpdate
            if module.db.profile.editorCloseOnLaunch then
              editor.selfClosePending = true
            end
          end,
        order = 2
      },
      closeThis = {
        type = "toggle",
        name = L["Close on Launch"],
        desc = L["Close the Bar Editor when opening the ReAction global Interface Options"],
        get  = function() return module.db.profile.editorCloseOnLaunch end,
        set  = function(info, val) module.db.profile.editorCloseOnLaunch = val end,
        order = 3,
      },
      new = {
        type = "group",
        name = L["New Bar..."],
        order = 4,
        args = { 
          desc = {
            type = "description",
            name = L["Choose a name, type, and initial grid for your new action bar:"],
            order = 1,
          },
          name = {
            type = "input",
            name = L["Bar Name"],
            desc = L["Enter a name for your new action bar"],
            get  = function() return tmp.barName or "" end,
            set  = function(info, val) tmp.barName = val end,
            order = 2,
          },
          type = {
            type = "select",
            name = L["Button Type"],
            get  = function() return tmp.barType or ReAction:GetDefaultBarType() or "" end,
            set  = function(info, val) 
                     local c = ReAction:GetBarTypeConfig(val)
                     tmp.barType = val 
                     tmp.barSize = c.defaultButtonSize or tmp.barSize
                     tmp.barRows = c.defaultBarRows or tmp.barRows
                     tmp.barCols = c.defaultBarCols or tmp.barCols
                     tmp.barSpacing = c.defaultBarSpacing or tmp.barSpacing
                   end,
            values = "GetBarTypes",
            order = 3,
          },
          grid = {
            type = "group",
            name = L["Button Grid"],
            inline = true,
            args = {
              hdr = {
                type = "header",
                name = L["Button Grid"],
                order = 1,
              },
              rows = {
                type = "range",
                name = L["Rows"],
                get  = function() return tmp.barRows or 1 end,
                set  = function(info, val) tmp.barRows = val end,
                width = "half",
                min = 1,
                max = 32,
                step = 1,
                order = 2,
              },
              cols = {
                type = "range",
                name = L["Columns"],
                get  = function() return tmp.barCols or 12 end,
                set  = function(info, val) tmp.barCols = val end,
                width = "half",
                min = 1, 
                max = 32,
                step = 1,
                order = 3,
              },
              sz = {
                type = "range",
                name = L["Size"],
                get  = function() return tmp.barSize or 36 end,
                set  = function(info, val) tmp.barSize = val end,
                width = "half",
                min = 10,
                max = 72,
                step = 1,
                order = 4,
              },
              spacing = {
                type = "range",
                name = L["Spacing"],
                get  = function() return tmp.barSpacing or 3 end,
                set  = function(info, val) tmp.barSpacing = val end,
                width = "half",
                min = 0,
                max = 24,
                step = 1,
                order = 5,
              }
            },
            order = 4
          },
          spacer = {
            type = "header",
            name = "",
            width = "full",
            order = -2
          },
          go = {
            type = "execute",
            name = L["Create Bar"],
            func = "CreateBar",
            order = -1,
          }
        }
      }
    }
  }
  AceConfigReg:RegisterOptionsTable(editorName, options)

  function editor:Open(bar, ...)
    if bar then
      AceConfigDialog:SelectGroup(editorName, barOptMap[bar:GetName()], ...)
    end
    AceConfigDialog:Open(editorName,self)
  end

  function editor:Refresh()
    AceConfigReg:NotifyChange(editorName)
  end

  function editor:CreateBarTree(bar)
    local name = bar:GetName()
    -- AceConfig doesn't allow spaces, etc, in arg key names, and they must be
    -- unique strings. So generate a unique key (it can be whatever) for the bar
    local args = options.args
    local key
    local i = 1
    repeat
      key = ("bar%s"):format(i)
      i = i+1
    until args[key] == nil
    barOptMap[name] = key
    args[key] = {
      type = "group",
      name = name,
      childGroups = "tab",
      args = {
        general = {
          type = "group",
          name = L["General"],
          order = 1,
          args = {
            name = {
              type = "input",
              name = L["Rename Bar"],
              get  = function() return bar:GetName() end,
              set  = function(info, value) return ReAction:RenameBar(bar, value) end,
              order = 1,
            },
            delete = {
              type = "execute",
              name = L["Delete Bar"],
              desc = function() return bar:GetName() end,
              confirm = true,
              func = function() ReAction:EraseBar(bar) end,
              order = 2
            },
            anchor = {
              type = "group",
              name = L["Anchor"],
              inline = true,
              args = {
                frame = {
                  type = "input",
                  name = L["Frame"],
                  desc = L["The frame that the bar is anchored to"],
                  get  = function() local _, f = bar:GetAnchor(); return f end,
                  set  = function(info, val) bar:SetAnchor(nil,val) end,
                  validate = function(info, name) 
                      if name then
                        local f = ReAction:GetBar(name)
                        if f then
                          return true
                        else
                          f = _G[name]
                          if f and type(f) == "table" and f.IsObjectType and f:IsObjectType("Frame") then
                            local _, explicit = f:IsProtected()
                            return explicit
                          end
                        end
                      end
                      return false
                    end,
                  width = "double",
                  order = 1
                },
                point = {
                  type = "select",
                  name = L["Point"],
                  desc = L["Anchor point on the bar frame"],
                  style = "dropdown",
                  get  = function() return bar:GetAnchor() end,
                  set  = function(info, val) bar:SetAnchor(val) end,
                  values = pointTable,
                  order = 2,
                },
                relativePoint = {
                  type = "select",
                  name = L["Relative Point"],
                  desc = L["Anchor point on the target frame"],
                  style = "dropdown",
                  get  = function() local p,f,r = bar:GetAnchor(); return r end,
                  set  = function(info, val) bar:SetAnchor(nil,nil,val) end,
                  values = pointTable,
                  order = 3,
                },
                x = {
                  type = "input",
                  pattern = "\-?%d+",
                  name = L["X offset"],
                  get = function() local p,f,r,x = bar:GetAnchor(); return ("%d"):format(x) end,
                  set = function(info,val) bar:SetAnchor(nil,nil,nil,val) end,
                  order = 4
                },
                y = {
                  type = "input",
                  pattern = "\-?%d+",
                  name = L["Y offset"],
                  get = function() local p,f,r,x,y = bar:GetAnchor(); return ("%d"):format(y) end,
                  set = function(info,val) bar:SetAnchor(nil,nil,nil,nil,val) end,
                  order = 5
                },
              },
              order = 3
            },
            alpha = {
              type = "range",
              name = L["Transparency"],
              get  = function() return bar:GetAlpha() end,
              set  = function(info, val) bar:SetAlpha(val) end,
              min = 0, 
              max = 1,
              isPercent = true,
              step = 0.01,
              bigStep = 0.05,
              order = 4,
            },
          },
        },
      }
    }
    self:RefreshBarTree(bar)
  end

  function editor:RefreshBarTree(bar)
    local key = barOptMap[bar:GetName()]
    if key and options.args[key] then
      options.args[key].plugins = ReAction:GenerateBarOptionsTable(bar)
      AceConfigReg:NotifyChange(editorName)
    end
  end

  function editor:OnCreateBar(evt, bar)
    if not tmp.creating then
      -- a bit of hack to work around OnCreateBar event handler ordering
      self:CreateBarTree(bar)
    end
  end

  function editor:OnDestroyBar(evt, bar, name)
    local key = barOptMap[name]
    if key then
      options.args[key] = nil
    end
    self:Refresh()
  end

  function editor:OnEraseBar(evt, name)
    local key = barOptMap[name]
    barOptMap[name] = nil
    if key then
      options.args[key] = nil
      self:Refresh()
    end
  end

  function editor:OnRenameBar(evt, bar, oldname, newname)
    local key = barOptMap[oldname]
    barOptMap[oldname], barOptMap[newname] = nil, key
    if key then
      options.args[key].name = newname
      self:Refresh()
    end
  end
  
  function editor:OnBarOptionGeneratorRegistered(evt)
    for name in pairs(barOptMap) do
      local bar = ReAction:GetBar(name)
      if bar then
        self:RefreshBarTree(bar)
      end
    end
  end

  local _scratch = { }
  function editor:GetBarTypes()
    for k,v in pairs(_scratch) do
      _scratch[k] = nil
    end
    return ReAction:GetBarTypeOptions(_scratch)
  end

  function editor:CreateBar()
    if tmp.barName and tmp.barName ~= "" then
      tmp.creating = true
      local bar = ReAction:CreateBar(tmp.barName, tmp.barType or ReAction:GetDefaultBarType(), tmp.barRows, tmp.barCols, tmp.barSize, tmp.barSpacing)
      if bar then
        self:CreateBarTree(bar)
        AceConfigDialog:SelectGroup(editorName, barOptMap[tmp.barName])
        tmp.barName = nil
      end
      tmp.creating = false
    end
  end

  ReAction.RegisterCallback(editor,"OnCreateBar")
  ReAction.RegisterCallback(editor,"OnDestroyBar")
  ReAction.RegisterCallback(editor,"OnEraseBar")
  ReAction.RegisterCallback(editor,"OnRenameBar")
  ReAction.RegisterCallback(editor,"OnBarOptionGeneratorRegistered")

  for name, bar in ReAction:IterateBars() do
    editor:CreateBarTree(bar)
  end

  return editor
end


function module:LaunchBarEditor(bar, ...)
  if InCombatLockdown() then
    ReAction:UserError(L["ReAction config mode disabled during combat."])
  else
    if not self.editor then
      self.editor = NewEditor()
    end
    self.editor:Open(bar, ...)
    ReAction:SetConfigMode(true)
  end
end