flickerstreak@109: --[[ flickerstreak@109: ReAction Configuration UI module flickerstreak@109: flickerstreak@109: Hooks into Blizzard Interface Options AddOns panel flickerstreak@109: --]] flickerstreak@109: flickerstreak@109: -- local imports flickerstreak@109: local ReAction = ReAction flickerstreak@109: local L = ReAction.L flickerstreak@109: local _G = _G flickerstreak@109: local AceConfigReg = LibStub("AceConfigRegistry-3.0") flickerstreak@109: local AceConfigDialog = LibStub("AceConfigDialog-3.0") flickerstreak@109: flickerstreak@109: ReAction:UpdateRevision("$Revision$") flickerstreak@109: flickerstreak@109: -- some constants flickerstreak@109: local configName = "ReAction" flickerstreak@109: flickerstreak@109: -- module declaration flickerstreak@109: local moduleID = "ConfigUI" flickerstreak@109: local module = ReAction:NewModule( moduleID, flickerstreak@109: "AceEvent-3.0" flickerstreak@109: ) flickerstreak@109: flickerstreak@109: -- module methods flickerstreak@109: function module:OnInitialize() flickerstreak@109: self.db = ReAction.db:RegisterNamespace( moduleID, flickerstreak@109: { flickerstreak@109: profile = { flickerstreak@109: closeOnLaunch = true, flickerstreak@109: editorCloseOnLaunch = true, flickerstreak@109: } flickerstreak@109: } flickerstreak@109: ) flickerstreak@109: flickerstreak@109: self:RegisterEvent("PLAYER_REGEN_DISABLED") flickerstreak@109: ReAction.RegisterCallback(self,"OnOptionsRegistered","OnOptionsRefreshed") flickerstreak@109: ReAction.RegisterCallback(self,"OnOptionsRefreshed") flickerstreak@109: self:InitializeOptions() flickerstreak@109: end flickerstreak@109: flickerstreak@109: function module:OnOptionsRefreshed(evt) flickerstreak@109: AceConfigReg:NotifyChange(configName) flickerstreak@109: if self.editor then self.editor:Refresh() end flickerstreak@109: end flickerstreak@109: flickerstreak@109: function module:PLAYER_REGEN_DISABLED() flickerstreak@109: if self.editor then flickerstreak@109: self.editor:Hide() flickerstreak@109: end flickerstreak@109: end flickerstreak@109: flickerstreak@109: function module:OpenConfig() flickerstreak@109: InterfaceOptionsFrame_OpenToCategory(configName) flickerstreak@109: end flickerstreak@109: flickerstreak@109: function module:InitializeOptions() flickerstreak@109: ReAction:RegisterOptions(self, { flickerstreak@109: _launchEditor = { flickerstreak@109: type = "execute", flickerstreak@109: handler = self, flickerstreak@109: name = L["Edit Bars..."], flickerstreak@109: desc = L["Show the ReAction Bar Editor dialogue"], flickerstreak@109: func = function() flickerstreak@109: self:LaunchBarEditor() flickerstreak@109: -- you can't close a dialog in response to an options click, because the end of the flickerstreak@109: -- handler for all the button events calls lib:Open() flickerstreak@109: -- So, schedule a close on the next OnUpdate flickerstreak@109: if self.db.profile.closeOnLaunch then flickerstreak@109: self.editor.closePending = true flickerstreak@109: end flickerstreak@109: end, flickerstreak@109: order = 2, flickerstreak@109: }, flickerstreak@109: _closeThis = { flickerstreak@109: type = "toggle", flickerstreak@109: name = L["Close on Launch"], flickerstreak@109: desc = L["Close the Interface Options window when launching the ReAction Bar Editor"], flickerstreak@109: get = function() return self.db.profile.closeOnLaunch end, flickerstreak@109: set = function(info, val) self.db.profile.closeOnLaunch = val end, flickerstreak@109: order = 3, flickerstreak@109: }, flickerstreak@109: _keybind = { flickerstreak@109: type = "execute", flickerstreak@109: handler = self, flickerstreak@109: name = L["Key Bindings"], flickerstreak@109: desc = L["Show the keybinding dialogue"], flickerstreak@109: func = function() flickerstreak@109: ReAction:SetKeybindMode(true) flickerstreak@109: end, flickerstreak@109: order = 4, flickerstreak@109: }, flickerstreak@109: }, true) -- global flickerstreak@109: flickerstreak@109: AceConfigReg:RegisterOptionsTable(configName,ReAction.options) flickerstreak@109: self.frame = AceConfigDialog:AddToBlizOptions(configName, configName) flickerstreak@109: self.frame.obj:SetCallback("default", flickerstreak@109: function() flickerstreak@109: ReAction.db:ResetProfile() flickerstreak@109: ReAction:RefreshOptions() flickerstreak@109: end ) flickerstreak@109: end flickerstreak@109: flickerstreak@109: flickerstreak@109: flickerstreak@109: flickerstreak@109: -- Bar Editor -- flickerstreak@109: local function NewEditor() flickerstreak@109: -- private variables flickerstreak@109: local editorName = "ReAction-Editor" flickerstreak@109: local barOptMap = setmetatable({},{__mode="v"}) flickerstreak@109: local tmp = { } flickerstreak@109: local pointTable = { flickerstreak@109: CENTER = L["Center"], flickerstreak@109: LEFT = L["Left"], flickerstreak@109: RIGHT = L["Right"], flickerstreak@109: TOP = L["Top"], flickerstreak@109: BOTTOM = L["Bottom"], flickerstreak@109: TOPLEFT = L["Top Left"], flickerstreak@109: TOPRIGHT = L["Top Right"], flickerstreak@109: BOTTOMLEFT = L["Bottom Left"], flickerstreak@109: BOTTOMRIGHT = L["Bottom Right"], flickerstreak@109: } flickerstreak@109: flickerstreak@109: flickerstreak@109: -- use a local GUI container to work around AceConfigDialog closing flickerstreak@109: -- both the bar editor and the global options when interface options is closed flickerstreak@109: local editor = LibStub("AceGUI-3.0"):Create("Frame") flickerstreak@109: local frame = editor.frame flickerstreak@109: frame:SetClampedToScreen(true) flickerstreak@109: frame:Hide() flickerstreak@109: local old_OnUpdate = frame:GetScript("OnUpdate") flickerstreak@109: frame:SetScript("OnUpdate", function(dt) flickerstreak@109: if old_OnUpdate then flickerstreak@109: old_OnUpdate(dt) flickerstreak@109: end flickerstreak@109: if editor.closePending then flickerstreak@109: InterfaceOptionsFrame:Hide() flickerstreak@109: editor.closePending = false flickerstreak@109: end flickerstreak@109: if editor.selfClosePending then flickerstreak@109: editor:Hide() flickerstreak@109: AceConfigReg:NotifyChange(configName) flickerstreak@109: editor.selfClosePending = false flickerstreak@109: end flickerstreak@109: end ) flickerstreak@109: editor:SetCallback("OnClose", flickerstreak@109: function() flickerstreak@109: ReAction:SetConfigMode(false) flickerstreak@109: end ) flickerstreak@109: AceConfigDialog:SetDefaultSize(editorName, 700, 540) flickerstreak@109: flickerstreak@109: flickerstreak@109: local name = ("ReAction - %s"):format(L["Bar Editor"]) flickerstreak@109: editor:SetTitle(name) flickerstreak@109: local options = { flickerstreak@109: type = "group", flickerstreak@109: name = name, flickerstreak@109: handler = editor, flickerstreak@109: childGroups = "tree", flickerstreak@109: args = { flickerstreak@109: desc = { flickerstreak@109: type = "description", flickerstreak@109: name = L["Use the mouse to arrange and resize the bars on screen. Tooltips on bars indicate additional functionality."], flickerstreak@109: order = 1 flickerstreak@109: }, flickerstreak@109: launchConfig = { flickerstreak@109: type = "execute", flickerstreak@109: name = L["Global Config"], flickerstreak@109: desc = L["Opens ReAction global configuration settings panel"], flickerstreak@109: func = function() flickerstreak@109: module:OpenConfig() flickerstreak@109: -- you can't close a dialog in response to an options click, because the end of the flickerstreak@109: -- handler for all the button events calls lib:Open() flickerstreak@109: -- So, schedule a close on the next OnUpdate flickerstreak@109: if module.db.profile.editorCloseOnLaunch then flickerstreak@109: editor.selfClosePending = true flickerstreak@109: end flickerstreak@109: end, flickerstreak@109: order = 2 flickerstreak@109: }, flickerstreak@109: closeThis = { flickerstreak@109: type = "toggle", flickerstreak@109: name = L["Close on Launch"], flickerstreak@109: desc = L["Close the Bar Editor when opening the ReAction global Interface Options"], flickerstreak@109: get = function() return module.db.profile.editorCloseOnLaunch end, flickerstreak@109: set = function(info, val) module.db.profile.editorCloseOnLaunch = val end, flickerstreak@109: order = 3, flickerstreak@109: }, flickerstreak@109: new = { flickerstreak@109: type = "group", flickerstreak@109: name = L["New Bar..."], flickerstreak@109: order = 4, flickerstreak@109: args = { flickerstreak@109: desc = { flickerstreak@109: type = "description", flickerstreak@109: name = L["Choose a name, type, and initial grid for your new action bar:"], flickerstreak@109: order = 1, flickerstreak@109: }, flickerstreak@109: name = { flickerstreak@109: type = "input", flickerstreak@109: name = L["Bar Name"], flickerstreak@109: desc = L["Enter a name for your new action bar"], flickerstreak@109: get = function() return tmp.barName or "" end, flickerstreak@109: set = function(info, val) tmp.barName = val end, flickerstreak@109: order = 2, flickerstreak@109: }, flickerstreak@109: type = { flickerstreak@109: type = "select", flickerstreak@109: name = L["Button Type"], flickerstreak@109: get = function() return tmp.barType or ReAction:GetDefaultBarType() or "" end, flickerstreak@109: set = function(info, val) flickerstreak@109: local c = ReAction:GetBarTypeConfig(val) flickerstreak@109: tmp.barType = val flickerstreak@109: tmp.barSize = c.defaultButtonSize or tmp.barSize flickerstreak@109: tmp.barRows = c.defaultBarRows or tmp.barRows flickerstreak@109: tmp.barCols = c.defaultBarCols or tmp.barCols flickerstreak@109: tmp.barSpacing = c.defaultBarSpacing or tmp.barSpacing flickerstreak@109: end, flickerstreak@109: values = "GetBarTypes", flickerstreak@109: order = 3, flickerstreak@109: }, flickerstreak@109: grid = { flickerstreak@109: type = "group", flickerstreak@109: name = L["Button Grid"], flickerstreak@109: inline = true, flickerstreak@109: args = { flickerstreak@109: hdr = { flickerstreak@109: type = "header", flickerstreak@109: name = L["Button Grid"], flickerstreak@109: order = 1, flickerstreak@109: }, flickerstreak@109: rows = { flickerstreak@109: type = "range", flickerstreak@109: name = L["Rows"], flickerstreak@109: get = function() return tmp.barRows or 1 end, flickerstreak@109: set = function(info, val) tmp.barRows = val end, flickerstreak@109: width = "half", flickerstreak@109: min = 1, flickerstreak@109: max = 32, flickerstreak@109: step = 1, flickerstreak@109: order = 2, flickerstreak@109: }, flickerstreak@109: cols = { flickerstreak@109: type = "range", flickerstreak@109: name = L["Columns"], flickerstreak@109: get = function() return tmp.barCols or 12 end, flickerstreak@109: set = function(info, val) tmp.barCols = val end, flickerstreak@109: width = "half", flickerstreak@109: min = 1, flickerstreak@109: max = 32, flickerstreak@109: step = 1, flickerstreak@109: order = 3, flickerstreak@109: }, flickerstreak@109: sz = { flickerstreak@109: type = "range", flickerstreak@109: name = L["Size"], flickerstreak@109: get = function() return tmp.barSize or 36 end, flickerstreak@109: set = function(info, val) tmp.barSize = val end, flickerstreak@109: width = "half", flickerstreak@109: min = 10, flickerstreak@109: max = 72, flickerstreak@109: step = 1, flickerstreak@109: order = 4, flickerstreak@109: }, flickerstreak@109: spacing = { flickerstreak@109: type = "range", flickerstreak@109: name = L["Spacing"], flickerstreak@109: get = function() return tmp.barSpacing or 3 end, flickerstreak@109: set = function(info, val) tmp.barSpacing = val end, flickerstreak@109: width = "half", flickerstreak@109: min = 0, flickerstreak@109: max = 24, flickerstreak@109: step = 1, flickerstreak@109: order = 5, flickerstreak@109: } flickerstreak@109: }, flickerstreak@109: order = 4 flickerstreak@109: }, flickerstreak@109: spacer = { flickerstreak@109: type = "header", flickerstreak@109: name = "", flickerstreak@109: width = "full", flickerstreak@109: order = -2 flickerstreak@109: }, flickerstreak@109: go = { flickerstreak@109: type = "execute", flickerstreak@109: name = L["Create Bar"], flickerstreak@109: func = "CreateBar", flickerstreak@109: order = -1, flickerstreak@109: } flickerstreak@109: } flickerstreak@109: } flickerstreak@109: } flickerstreak@109: } flickerstreak@109: AceConfigReg:RegisterOptionsTable(editorName, options) flickerstreak@109: flickerstreak@109: function editor:Open(bar, ...) flickerstreak@109: if bar then flickerstreak@109: AceConfigDialog:SelectGroup(editorName, barOptMap[bar:GetName()], ...) flickerstreak@109: end flickerstreak@143: AceConfigDialog:Open(editorName,self) flickerstreak@109: end flickerstreak@109: flickerstreak@109: function editor:Refresh() flickerstreak@109: AceConfigReg:NotifyChange(editorName) flickerstreak@109: end flickerstreak@109: flickerstreak@109: function editor:CreateBarTree(bar) flickerstreak@109: local name = bar:GetName() flickerstreak@109: -- AceConfig doesn't allow spaces, etc, in arg key names, and they must be flickerstreak@109: -- unique strings. So generate a unique key (it can be whatever) for the bar flickerstreak@109: local args = options.args flickerstreak@109: local key flickerstreak@109: local i = 1 flickerstreak@109: repeat flickerstreak@109: key = ("bar%s"):format(i) flickerstreak@109: i = i+1 flickerstreak@109: until args[key] == nil flickerstreak@109: barOptMap[name] = key flickerstreak@109: args[key] = { flickerstreak@109: type = "group", flickerstreak@109: name = name, flickerstreak@109: childGroups = "tab", flickerstreak@109: args = { flickerstreak@109: general = { flickerstreak@109: type = "group", flickerstreak@109: name = L["General"], flickerstreak@109: order = 1, flickerstreak@109: args = { flickerstreak@109: name = { flickerstreak@109: type = "input", flickerstreak@109: name = L["Rename Bar"], flickerstreak@109: get = function() return bar:GetName() end, flickerstreak@109: set = function(info, value) return ReAction:RenameBar(bar, value) end, flickerstreak@109: order = 1, flickerstreak@109: }, flickerstreak@109: delete = { flickerstreak@109: type = "execute", flickerstreak@109: name = L["Delete Bar"], flickerstreak@109: desc = function() return bar:GetName() end, flickerstreak@109: confirm = true, flickerstreak@109: func = function() ReAction:EraseBar(bar) end, flickerstreak@109: order = 2 flickerstreak@109: }, flickerstreak@109: anchor = { flickerstreak@109: type = "group", flickerstreak@109: name = L["Anchor"], flickerstreak@109: inline = true, flickerstreak@109: args = { flickerstreak@109: frame = { flickerstreak@109: type = "input", flickerstreak@109: name = L["Frame"], flickerstreak@109: desc = L["The frame that the bar is anchored to"], flickerstreak@109: get = function() local _, f = bar:GetAnchor(); return f end, flickerstreak@109: set = function(info, val) bar:SetAnchor(nil,val) end, flickerstreak@109: validate = function(info, name) flickerstreak@109: if name then flickerstreak@109: local f = ReAction:GetBar(name) flickerstreak@109: if f then flickerstreak@109: return true flickerstreak@109: else flickerstreak@109: f = _G[name] flickerstreak@109: if f and type(f) == "table" and f.IsObjectType and f:IsObjectType("Frame") then flickerstreak@109: local _, explicit = f:IsProtected() flickerstreak@109: return explicit flickerstreak@109: end flickerstreak@109: end flickerstreak@109: end flickerstreak@109: return false flickerstreak@109: end, flickerstreak@109: width = "double", flickerstreak@109: order = 1 flickerstreak@109: }, flickerstreak@109: point = { flickerstreak@109: type = "select", flickerstreak@109: name = L["Point"], flickerstreak@109: desc = L["Anchor point on the bar frame"], flickerstreak@109: style = "dropdown", flickerstreak@109: get = function() return bar:GetAnchor() end, flickerstreak@109: set = function(info, val) bar:SetAnchor(val) end, flickerstreak@109: values = pointTable, flickerstreak@109: order = 2, flickerstreak@109: }, flickerstreak@109: relativePoint = { flickerstreak@109: type = "select", flickerstreak@109: name = L["Relative Point"], flickerstreak@109: desc = L["Anchor point on the target frame"], flickerstreak@109: style = "dropdown", flickerstreak@109: get = function() local p,f,r = bar:GetAnchor(); return r end, flickerstreak@109: set = function(info, val) bar:SetAnchor(nil,nil,val) end, flickerstreak@109: values = pointTable, flickerstreak@109: order = 3, flickerstreak@109: }, flickerstreak@109: x = { flickerstreak@109: type = "input", flickerstreak@109: pattern = "\-?%d+", flickerstreak@109: name = L["X offset"], flickerstreak@109: get = function() local p,f,r,x = bar:GetAnchor(); return ("%d"):format(x) end, flickerstreak@109: set = function(info,val) bar:SetAnchor(nil,nil,nil,val) end, flickerstreak@109: order = 4 flickerstreak@109: }, flickerstreak@109: y = { flickerstreak@109: type = "input", flickerstreak@109: pattern = "\-?%d+", flickerstreak@109: name = L["Y offset"], flickerstreak@109: get = function() local p,f,r,x,y = bar:GetAnchor(); return ("%d"):format(y) end, flickerstreak@109: set = function(info,val) bar:SetAnchor(nil,nil,nil,nil,val) end, flickerstreak@109: order = 5 flickerstreak@109: }, flickerstreak@109: }, flickerstreak@109: order = 3 flickerstreak@109: }, flickerstreak@109: alpha = { flickerstreak@109: type = "range", flickerstreak@109: name = L["Transparency"], flickerstreak@109: get = function() return bar:GetAlpha() end, flickerstreak@109: set = function(info, val) bar:SetAlpha(val) end, flickerstreak@109: min = 0, flickerstreak@109: max = 1, flickerstreak@109: isPercent = true, flickerstreak@109: step = 0.01, flickerstreak@109: bigStep = 0.05, flickerstreak@109: order = 4, flickerstreak@109: }, flickerstreak@109: }, flickerstreak@109: }, flickerstreak@109: } flickerstreak@109: } flickerstreak@109: self:RefreshBarTree(bar) flickerstreak@109: end flickerstreak@109: flickerstreak@109: function editor:RefreshBarTree(bar) flickerstreak@109: local key = barOptMap[bar:GetName()] flickerstreak@109: if key and options.args[key] then flickerstreak@109: options.args[key].plugins = ReAction:GenerateBarOptionsTable(bar) flickerstreak@109: AceConfigReg:NotifyChange(editorName) flickerstreak@109: end flickerstreak@109: end flickerstreak@109: flickerstreak@109: function editor:OnCreateBar(evt, bar) flickerstreak@109: if not tmp.creating then flickerstreak@109: -- a bit of hack to work around OnCreateBar event handler ordering flickerstreak@109: self:CreateBarTree(bar) flickerstreak@109: end flickerstreak@109: end flickerstreak@109: flickerstreak@109: function editor:OnDestroyBar(evt, bar, name) flickerstreak@109: local key = barOptMap[name] flickerstreak@109: if key then flickerstreak@109: options.args[key] = nil flickerstreak@109: end flickerstreak@109: self:Refresh() flickerstreak@109: end flickerstreak@109: flickerstreak@109: function editor:OnEraseBar(evt, name) flickerstreak@109: local key = barOptMap[name] flickerstreak@109: barOptMap[name] = nil flickerstreak@109: if key then flickerstreak@109: options.args[key] = nil flickerstreak@109: self:Refresh() flickerstreak@109: end flickerstreak@109: end flickerstreak@109: flickerstreak@109: function editor:OnRenameBar(evt, bar, oldname, newname) flickerstreak@109: local key = barOptMap[oldname] flickerstreak@109: barOptMap[oldname], barOptMap[newname] = nil, key flickerstreak@109: if key then flickerstreak@109: options.args[key].name = newname flickerstreak@109: self:Refresh() flickerstreak@109: end flickerstreak@109: end flickerstreak@109: flickerstreak@109: function editor:OnBarOptionGeneratorRegistered(evt) flickerstreak@109: for name in pairs(barOptMap) do flickerstreak@109: local bar = ReAction:GetBar(name) flickerstreak@109: if bar then flickerstreak@109: self:RefreshBarTree(bar) flickerstreak@109: end flickerstreak@109: end flickerstreak@109: end flickerstreak@109: flickerstreak@109: local _scratch = { } flickerstreak@109: function editor:GetBarTypes() flickerstreak@109: for k,v in pairs(_scratch) do flickerstreak@109: _scratch[k] = nil flickerstreak@109: end flickerstreak@109: return ReAction:GetBarTypeOptions(_scratch) flickerstreak@109: end flickerstreak@109: flickerstreak@109: function editor:CreateBar() flickerstreak@109: if tmp.barName and tmp.barName ~= "" then flickerstreak@109: tmp.creating = true flickerstreak@109: local bar = ReAction:CreateBar(tmp.barName, tmp.barType or ReAction:GetDefaultBarType(), tmp.barRows, tmp.barCols, tmp.barSize, tmp.barSpacing) flickerstreak@127: if bar then flickerstreak@127: self:CreateBarTree(bar) flickerstreak@127: AceConfigDialog:SelectGroup(editorName, barOptMap[tmp.barName]) flickerstreak@127: tmp.barName = nil flickerstreak@127: end flickerstreak@109: tmp.creating = false flickerstreak@109: end flickerstreak@109: end flickerstreak@109: flickerstreak@109: ReAction.RegisterCallback(editor,"OnCreateBar") flickerstreak@109: ReAction.RegisterCallback(editor,"OnDestroyBar") flickerstreak@109: ReAction.RegisterCallback(editor,"OnEraseBar") flickerstreak@109: ReAction.RegisterCallback(editor,"OnRenameBar") flickerstreak@109: ReAction.RegisterCallback(editor,"OnBarOptionGeneratorRegistered") flickerstreak@109: flickerstreak@109: for name, bar in ReAction:IterateBars() do flickerstreak@109: editor:CreateBarTree(bar) flickerstreak@109: end flickerstreak@109: flickerstreak@109: return editor flickerstreak@109: end flickerstreak@109: flickerstreak@109: flickerstreak@109: function module:LaunchBarEditor(bar, ...) flickerstreak@109: if InCombatLockdown() then flickerstreak@109: ReAction:UserError(L["ReAction config mode disabled during combat."]) flickerstreak@109: else flickerstreak@109: if not self.editor then flickerstreak@109: self.editor = NewEditor() flickerstreak@109: end flickerstreak@109: self.editor:Open(bar, ...) flickerstreak@109: ReAction:SetConfigMode(true) flickerstreak@109: end flickerstreak@109: end flickerstreak@109: