flickerstreak@28: -- ReAction.lua flickerstreak@28: -- See modules/ReAction_ModuleTemplate for Module API listing flickerstreak@28: -- See Bar.lua for Bar object listing flickerstreak@27: flickerstreak@27: ------ CORE ------ flickerstreak@30: local ReAction = LibStub("AceAddon-3.0"):NewAddon( "ReAction", flickerstreak@33: "AceConsole-3.0", flickerstreak@33: "AceEvent-3.0" flickerstreak@30: ) flickerstreak@33: ReAction.version = GetAddOnMetadata("ReAction","Version") flickerstreak@33: ReAction.revision = tonumber(("$Revision$"):match("%d+")) flickerstreak@27: flickerstreak@28: ------ GLOBALS ------ flickerstreak@28: _G["ReAction"] = ReAction flickerstreak@27: flickerstreak@28: ------ DEBUGGING ------ flickerstreak@25: ReAction.debug = true flickerstreak@36: local dbprint flickerstreak@25: if ReAction.debug then flickerstreak@36: dbprint = function(msg) flickerstreak@25: DEFAULT_CHAT_FRAME:AddMessage(msg) flickerstreak@25: end flickerstreak@36: --seterrorhandler(dbprint) flickerstreak@25: else flickerstreak@36: dbprint = function() end flickerstreak@25: end flickerstreak@36: ReAction.dbprint = dbprint flickerstreak@25: flickerstreak@33: ------ LIBRARIES ------ flickerstreak@33: local L = LibStub("AceLocale-3.0"):GetLocale("ReAction") flickerstreak@33: ReAction.L = L flickerstreak@33: flickerstreak@28: ------ PRIVATE ------ flickerstreak@30: local SelectBar, DestroyBar, InitializeBars, TearDownBars, DeepCopy, SafeCall, CheckMethod, SlashHandler flickerstreak@28: do flickerstreak@28: local pcall = pcall flickerstreak@28: local geterrorhandler = geterrorhandler flickerstreak@28: flickerstreak@28: SelectBar = function(x) flickerstreak@28: local bar, name flickerstreak@28: if type(x) == "string" then flickerstreak@28: name = x flickerstreak@28: bar = ReAction:GetBar(name) flickerstreak@50: else flickerstreak@28: for k,v in pairs(ReAction.bars) do flickerstreak@50: if v == x then flickerstreak@28: name = k flickerstreak@50: bar = x flickerstreak@28: end flickerstreak@28: end flickerstreak@28: end flickerstreak@28: return bar, name flickerstreak@28: end flickerstreak@28: flickerstreak@28: DestroyBar = function(x) flickerstreak@28: local bar, name = SelectBar(x) flickerstreak@28: if name and bar then flickerstreak@28: ReAction.bars[name] = nil flickerstreak@28: ReAction:CallMethodOnAllModules("RemoveFromBar", bar) flickerstreak@28: bar:Destroy() flickerstreak@28: end flickerstreak@28: end flickerstreak@28: flickerstreak@28: InitializeBars = function () flickerstreak@28: if not(ReAction.inited) then flickerstreak@28: for name, config in pairs(ReAction.db.profile.bars) do flickerstreak@28: if config then flickerstreak@28: ReAction:CreateBar(name, config) flickerstreak@28: end flickerstreak@28: end flickerstreak@28: ReAction:CallMethodOnAllBars("ApplyAnchor") -- re-anchor in the case of oddball ordering flickerstreak@28: ReAction.inited = true flickerstreak@28: end flickerstreak@28: end flickerstreak@28: flickerstreak@28: TearDownBars = function() flickerstreak@28: for name, bar in pairs(ReAction.bars) do flickerstreak@28: if bar then flickerstreak@28: ReAction.bars[name] = DestroyBar(bar) flickerstreak@28: end flickerstreak@28: end flickerstreak@28: ReAction.inited = false flickerstreak@28: end flickerstreak@28: flickerstreak@28: DeepCopy = function(x) flickerstreak@28: if type(x) ~= "table" then flickerstreak@28: return x flickerstreak@28: end flickerstreak@28: local r = {} flickerstreak@28: for k,v in pairs(x) do flickerstreak@28: r[k] = DeepCopy(v) flickerstreak@28: end flickerstreak@28: return r flickerstreak@28: end flickerstreak@28: flickerstreak@28: SafeCall = function(f, ...) flickerstreak@28: if f then flickerstreak@28: local success, err = pcall(f,...) flickerstreak@28: if not success then flickerstreak@28: geterrorhandler()(err) flickerstreak@28: end flickerstreak@28: end flickerstreak@28: end flickerstreak@28: flickerstreak@28: CheckMethod = function(m) flickerstreak@28: if type(m) == "function" then flickerstreak@28: return m flickerstreak@28: end flickerstreak@28: if type(m) ~= "string" then flickerstreak@28: error("Invalid method") flickerstreak@28: end flickerstreak@28: end flickerstreak@30: flickerstreak@30: SlashHandler = function(option) flickerstreak@30: if option == "config" then flickerstreak@38: ReAction:ShowConfig() flickerstreak@58: elseif option == "edit" then flickerstreak@58: ReAction:ShowEditor() flickerstreak@50: elseif option == "unlock" then flickerstreak@50: ReAction:SetConfigMode(true) flickerstreak@50: elseif option == "lock" then flickerstreak@50: ReAction:SetConfigMode(false) flickerstreak@30: else flickerstreak@36: ReAction:Print(("%3.1f.%d"):format(ReAction.version,ReAction.revision)) flickerstreak@30: ReAction:Print("/reaction config") flickerstreak@58: ReAction:Print("/reaction edit") flickerstreak@50: ReAction:Print("/reaction lock") flickerstreak@50: ReAction:Print("/reaction unlock") flickerstreak@30: end flickerstreak@30: end flickerstreak@28: end flickerstreak@28: flickerstreak@28: flickerstreak@28: ------ HANDLERS ------ flickerstreak@28: function ReAction:OnInitialize() flickerstreak@28: self.db = LibStub("AceDB-3.0"):New("ReAction_DB", flickerstreak@28: { flickerstreak@28: profile = { flickerstreak@28: bars = { }, flickerstreak@28: defaultBar = { } flickerstreak@28: } flickerstreak@28: } flickerstreak@28: -- default profile is character-specific flickerstreak@28: ) flickerstreak@28: self.db.RegisterCallback(self,"OnProfileChanged") flickerstreak@43: self.db.RegisterCallback(self,"OnProfileReset","OnProfileChanged") flickerstreak@30: self.callbacks = LibStub("CallbackHandler-1.0"):New(self) flickerstreak@30: self:RegisterChatCommand("reaction", SlashHandler) flickerstreak@30: self:RegisterChatCommand("rxn", SlashHandler) flickerstreak@33: self:RegisterEvent("PLAYER_REGEN_DISABLED") flickerstreak@28: flickerstreak@28: self.bars = {} flickerstreak@48: self.defaultBarConfig = {} flickerstreak@46: flickerstreak@60: self.options = { flickerstreak@60: type = "group", flickerstreak@60: name = "ReAction", flickerstreak@60: childGroups = "tab", flickerstreak@60: args = { flickerstreak@60: _desc = { flickerstreak@60: type = "description", flickerstreak@60: name = L["Customizable replacement for Blizzard's Action Bars"], flickerstreak@60: order = 1, flickerstreak@60: }, flickerstreak@60: global = { flickerstreak@60: type = "group", flickerstreak@60: name = L["Global Settings"], flickerstreak@60: desc = L["Global configuration settings"], flickerstreak@60: args = { flickerstreak@60: unlock = { flickerstreak@60: type = "toggle", flickerstreak@60: handler = module, flickerstreak@60: name = L["Unlock Bars"], flickerstreak@60: desc = L["Unlock bars for dragging and resizing with the mouse"], flickerstreak@60: get = function() return self.configMode end, flickerstreak@60: set = function(info, value) self:SetConfigMode(value) end, flickerstreak@60: disabled = InCombatLockdown, flickerstreak@60: order = 1 flickerstreak@60: }, flickerstreak@60: }, flickerstreak@60: plugins = { }, flickerstreak@60: order = 2, flickerstreak@60: }, flickerstreak@60: module = { flickerstreak@60: type = "group", flickerstreak@60: childGroups = "select", flickerstreak@60: name = L["Module Settings"], flickerstreak@60: desc = L["Configuration settings for each module"], flickerstreak@60: args = { }, flickerstreak@60: plugins = { }, flickerstreak@60: order = 3, flickerstreak@60: }, flickerstreak@60: profile = LibStub("AceDBOptions-3.0"):GetOptionsTable(self.db) flickerstreak@46: }, flickerstreak@60: plugins = { } flickerstreak@60: } flickerstreak@46: flickerstreak@28: end flickerstreak@28: flickerstreak@28: function ReAction:OnEnable() flickerstreak@28: InitializeBars() flickerstreak@28: end flickerstreak@28: flickerstreak@28: function ReAction:OnDisable() flickerstreak@28: TearDownBars() flickerstreak@28: end flickerstreak@28: flickerstreak@28: function ReAction:OnProfileChanged() flickerstreak@33: TearDownBars() flickerstreak@33: InitializeBars() flickerstreak@28: end flickerstreak@28: flickerstreak@28: function ReAction:OnModuleEnable(module) flickerstreak@28: if module.ApplyToBar then flickerstreak@28: for _, b in pairs(bars) do flickerstreak@28: if b then flickerstreak@28: module:ApplyToBar(b) flickerstreak@28: end flickerstreak@28: end flickerstreak@28: end flickerstreak@28: end flickerstreak@28: flickerstreak@28: function ReAction:OnModuleDisable(module) flickerstreak@28: if module.RemoveFromBar then flickerstreak@28: for _, b in pairs(bars) do flickerstreak@28: if b then flickerstreak@28: module:RemoveFromBar(b) flickerstreak@28: end flickerstreak@28: end flickerstreak@28: end flickerstreak@28: end flickerstreak@28: flickerstreak@33: function ReAction:PLAYER_REGEN_DISABLED() flickerstreak@33: if self.configMode == true then flickerstreak@33: UIErrorsFrame:AddMessage(L["ReAction config mode disabled during combat."]) flickerstreak@33: self:SetConfigMode(false) flickerstreak@33: end flickerstreak@33: end flickerstreak@33: flickerstreak@33: flickerstreak@28: flickerstreak@28: ------ API ------ flickerstreak@61: function ReAction:UserError(msg) flickerstreak@61: -- any user errors should be flashed to the UIErrorsFrame flickerstreak@61: UIErrorsFrame:AddMessage(msg) flickerstreak@61: end flickerstreak@61: flickerstreak@28: function ReAction:CallMethodOnAllModules(method, ...) flickerstreak@28: local m = CheckMethod(method) flickerstreak@28: for _, x in self:IterateModules() do flickerstreak@28: if x then flickerstreak@28: SafeCall(m or x[method], x, ...) flickerstreak@28: end flickerstreak@28: end flickerstreak@28: end flickerstreak@28: flickerstreak@28: function ReAction:CallMethodOnAllBars(method,...) flickerstreak@28: local m = CheckMethod(method) flickerstreak@28: for _, x in pairs(self.bars) do flickerstreak@28: if x then flickerstreak@28: SafeCall(m or x[method], x, ...) flickerstreak@28: end flickerstreak@28: end flickerstreak@28: end flickerstreak@28: flickerstreak@38: function ReAction:CallModuleMethod(modulename, method, ...) flickerstreak@38: local m = self:GetModule(modulename,true) flickerstreak@38: if not m then flickerstreak@38: LoadAddOn(("ReAction_%s"):format(modulename)) flickerstreak@38: m = self:GetModule(modulename,true) flickerstreak@43: if m then flickerstreak@43: dbprint(("succesfully loaded LOD module: %s"):format(modulename)) flickerstreak@43: end flickerstreak@38: end flickerstreak@38: if m then flickerstreak@38: if type(m) == "table" and type(m[method]) == "function" then flickerstreak@38: m[method](m,...) flickerstreak@38: else flickerstreak@38: dbprint(("Bad call '%s' to %s module"):format(tostring(method),modulename)); flickerstreak@38: end flickerstreak@38: else flickerstreak@38: self:Print(("Module '%s' not found"):format(tostring(modulename))) flickerstreak@38: end flickerstreak@38: end flickerstreak@38: flickerstreak@38: flickerstreak@48: function ReAction:CreateBar(name, ...) flickerstreak@48: local config = select(1,...) flickerstreak@48: if config and type(config) ~= "table" then flickerstreak@48: bartype = select(1,...) flickerstreak@48: if type(bartype) ~= "string" then flickerstreak@48: error("ReAction:CreateBar() - first argument must be a config table or a default config type string") flickerstreak@48: end flickerstreak@48: config = self.defaultBarConfig[bartype] flickerstreak@48: if not config then flickerstreak@48: error(("ReAction:CreateBar() - unknown bar type '%s'"):format(bartype)) flickerstreak@48: end flickerstreak@48: config = DeepCopy(config) flickerstreak@48: config.btnRows = select(2,...) or config.btnRows or 1 flickerstreak@48: config.btnColumns = select(3,...) or config.btnColumns or 12 flickerstreak@48: config.btnWidth = select(4,...) or config.btnWidth or 36 flickerstreak@48: config.btnHeight = select(4,...) or config.btnHeight or 36 flickerstreak@48: config.spacing = select(5,...) or config.spacing or 3 flickerstreak@48: config.width = config.width or config.btnColumns*(config.btnWidth + config.spacing) + 1 flickerstreak@48: config.height = config.height or config.btnRows*(config.btnHeight + config.spacing) + 1 flickerstreak@48: config.anchor = config.anchor or "BOTTOM" flickerstreak@48: config.anchorTo = config.anchorTo or "UIParent" flickerstreak@48: config.relativePoint = config.relativePoint or "BOTTOM" flickerstreak@48: config.y = config.y or 200 flickerstreak@48: config.x = config.x or 0 flickerstreak@48: end flickerstreak@28: local profile = self.db.profile flickerstreak@48: config = config or DeepCopy(profile.defaultBar) flickerstreak@28: prefix = prefix or L["Bar "] flickerstreak@28: if not name then flickerstreak@28: i = 1 flickerstreak@28: repeat flickerstreak@28: name = prefix..i flickerstreak@28: i = i + 1 flickerstreak@28: until self.bars[name] == nil flickerstreak@28: end flickerstreak@48: profile.bars[name] = profile.bars[name] or config flickerstreak@28: local bar = self.Bar:new( name, profile.bars[name] ) -- ReAction.Bar defined in Bar.lua flickerstreak@28: self:CallMethodOnAllModules("ApplyToBar", bar) flickerstreak@28: self.bars[name] = bar flickerstreak@30: self.callbacks:Fire("OnCreateBar", bar) flickerstreak@33: if self.configMode then flickerstreak@33: bar:ShowControls(true) flickerstreak@33: end flickerstreak@33: flickerstreak@28: return bar flickerstreak@28: end flickerstreak@28: flickerstreak@28: function ReAction:EraseBar(x) flickerstreak@28: local bar, name = SelectBar(x) flickerstreak@28: if name and bar then flickerstreak@28: DestroyBar(bar) flickerstreak@28: self.db.profile.bars[name] = nil flickerstreak@28: self:CallMethodOnAllModules("EraseBarConfig", name) flickerstreak@30: self.callbacks:Fire("OnEraseBar",name) flickerstreak@28: end flickerstreak@28: end flickerstreak@28: flickerstreak@28: function ReAction:GetBar(name) flickerstreak@28: return self.bars[name] flickerstreak@28: end flickerstreak@28: flickerstreak@28: function ReAction:RenameBar(x, newname) flickerstreak@28: local bar, name = SelectBar(x) flickerstreak@28: if bar and name and newname then flickerstreak@28: if self.bars[newname] then flickerstreak@47: UIErrorsFrame:AddMessage(("%s ('%s')"):format(L["ReAction: name already in use"],newname)) flickerstreak@47: else flickerstreak@47: self.bars[newname] = self.bars[name] flickerstreak@47: self.bars[name] = nil flickerstreak@47: bar:SetName(newname or "") flickerstreak@47: local cfg = self.db.profile.bars flickerstreak@47: cfg[newname], cfg[name] = cfg[name], nil flickerstreak@47: self:CallMethodOnAllModules("RenameBarConfig", name, newname) flickerstreak@47: self.callbacks:Fire("OnRenameBar", name, newname) flickerstreak@28: end flickerstreak@28: end flickerstreak@28: end flickerstreak@28: flickerstreak@53: function ReAction:RegisterBarType( name, config, isDefaultChoice ) flickerstreak@48: self.defaultBarConfig[name] = config flickerstreak@48: if isDefaultChoice then flickerstreak@48: self.defaultBarConfigChoice = name flickerstreak@48: end flickerstreak@48: self:RefreshOptions() flickerstreak@48: end flickerstreak@48: flickerstreak@53: function ReAction:UnregisterBarType( name ) flickerstreak@48: self.defaultBarConfig[name] = nil flickerstreak@48: if self.defaultBarConfigChoice == name then flickerstreak@48: self.defaultBarConfigChoice = nil flickerstreak@48: end flickerstreak@48: self:RefreshOptions() flickerstreak@48: end flickerstreak@48: flickerstreak@60: function ReAction:RegisterOptions(module, options, global) flickerstreak@60: self.options.args[global and "global" or "module"].plugins[module:GetName()] = options flickerstreak@30: end flickerstreak@30: flickerstreak@30: function ReAction:RefreshOptions() flickerstreak@30: self.callbacks:Fire("OnOptionsRefreshed") flickerstreak@30: end flickerstreak@33: flickerstreak@33: function ReAction:SetConfigMode( mode ) flickerstreak@33: self:CallMethodOnAllBars("ShowControls",mode) flickerstreak@33: self:CallMethodOnAllModules("ApplyConfigMode",mode,self.bars) flickerstreak@33: self.configMode = mode flickerstreak@33: end flickerstreak@38: flickerstreak@38: function ReAction:ShowConfig() flickerstreak@38: self:CallModuleMethod("ConfigUI","OpenConfig") flickerstreak@38: end flickerstreak@38: flickerstreak@58: function ReAction:ShowEditor() flickerstreak@58: self:CallModuleMethod("ConfigUI","LaunchBarEditor") flickerstreak@47: end