Mercurial > wow > reaction
view ReAction.lua @ 122:a2d2f23137c8
- Rearranged and consolidated some files in modules directory
- Added 'classes' directory, moved Bar and Overlay there
- Added Button, ActionButton, and GridProxy classes, not in use yet
author | Flick <flickerstreak@gmail.com> |
---|---|
date | Mon, 23 Feb 2009 18:56:57 +0000 |
parents | fb48811a8736 |
children | 29dacbecdb52 |
line wrap: on
line source
--[[ ReAction.lua The ReAction core manages 4 collections: - modules (via AceAddon) - bars - options - bar-type constructors and publishes events when those collections change. It also implements a couple properties and has a couple convenience methods which drill down to particular modules. Most of the "real work" of the addon happens in Bar.lua, Overlay.lua, State.lua, and the various modules. Events (with handler arguments): -------------------------------- "OnCreateBar" (bar, name) : after a bar object is created "OnDestroyBar" (bar, name) : before a bar object is destroyed "OnEraseBar" (bar, name) : before a bar config is removed from the profile db "OnRenameBar" (bar, oldname, newname) : after a bar is renamed "OnRefreshBar" (bar, name) : after a bar's state has been updated "OnOptionsRefreshed" () : after the global options tree is refreshed "OnConfigModeChanged" (mode) : after the config mode is changed "OnBarOptionGeneratorRegistered" (module, function) : after an options generator function is registered ReAction is also an AceAddon-3.0 and contains an AceDB-3.0, which in turn publish more events. ]]-- local version = GetAddOnMetadata("ReAction","Version") ------ CORE ------ local ReAction = LibStub("AceAddon-3.0"):NewAddon( "ReAction", "AceConsole-3.0", "AceEvent-3.0" ) ReAction.revision = tonumber(("$Revision$"):match("%d+")) ------ GLOBALS ------ _G["ReAction"] = ReAction ------ DEBUGGING ------ ReAction.debug = true local dbprint if ReAction.debug then dbprint = function(msg) DEFAULT_CHAT_FRAME:AddMessage(msg) end else dbprint = function() end end ReAction.dbprint = dbprint ------ LIBRARIES ------ local callbacks = LibStub("CallbackHandler-1.0"):New(ReAction) local KB = LibStub("LibKeyBound-1.0") local L = LibStub("AceLocale-3.0"):GetLocale("ReAction") ReAction.L = L ------ PRIVATE ------ local weak = {__mode="k"} local private = { } local bars = {} local defaultBarConfig = {} local barOptionGenerators = { } local options = { type = "group", name = "ReAction", childGroups = "tab", args = { _desc = { type = "description", name = L["Customizable replacement for Blizzard's Action Bars"], order = 1, }, global = { type = "group", name = L["Global Settings"], desc = L["Global configuration settings"], args = { unlock = { type = "toggle", name = L["Unlock Bars"], desc = L["Unlock bars for dragging and resizing with the mouse"], handler = ReAction, get = "GetConfigMode", set = function(info, value) ReAction:SetConfigMode(value) end, width = "double", disabled = InCombatLockdown, order = 1 }, skipProfileWarning = { type = "toggle", name = L["Skip profile keybind warning"], desc = L["Don't show a warning about updating keybinds when switching profiles"], get = function() return ReAction.db.global.skipKeybindWarning end, set = function(info, value) ReAction.db.global.skipKeybindWarning = value end, width = "double", order = 2, }, }, plugins = { }, order = 2, }, module = { type = "group", childGroups = "select", name = L["Module Settings"], desc = L["Configuration settings for each module"], args = { }, plugins = { }, order = 3, }, }, plugins = { } } ReAction.options = options -- insert an entry into the WoW static popup dialogs list StaticPopupDialogs["REACTION_KB_WARN"] = { text = L["ReAction profile changed: check your keybinds, they may need to be updated."], button1 = L["OK"], hideOnEscape = true, enterClicksFirstButton = true, timeout = 0, showAlert = true, whileDead = true, } local SelectBar, DestroyBar, InitializeBars, TearDownBars, DeepCopy, CallModuleMethod, SlashHandler do local pcall = pcall local geterrorhandler = geterrorhandler local self = ReAction local inited = false function SelectBar(x) local bar, name if type(x) == "string" then name = x bar = self:GetBar(name) else for k,v in pairs(bars) do if v == x then name = k bar = x end end end return bar, name end function DestroyBar(x) local bar, name = SelectBar(x) if bar and name then bars[name] = nil callbacks:Fire("OnDestroyBar", bar, name) bar:Destroy() end end function InitializeBars() if not inited then for name, config in pairs(self.db.profile.bars) do if config then self:CreateBar(name, config) end end -- re-anchor and refresh in case anchor order does not match init order for name, bar in pairs(bars) do bar:ApplyAnchor() callbacks:Fire("OnRefreshBar", bar, name) end inited = true end end function TearDownBars() for name, bar in pairs(bars) do if bar then bars[name] = DestroyBar(bar) end end inited = false end function DeepCopy(x) if type(x) ~= "table" then return x end local r = {} for k,v in pairs(x) do r[k] = DeepCopy(v) end return r end function CallModuleMethod(modulename, method, ...) local m = self:GetModule(modulename,true) if m then if type(m) == "table" and type(m[method]) == "function" then m[method](m,...) else dbprint(("Bad call '%s' to %s module"):format(tostring(method),modulename)); end else self:Print(("Module '%s' not found"):format(tostring(modulename))) end end function SlashHandler(option) if option == "config" then self:ShowConfig() elseif option == "edit" then self:ShowEditor() elseif option == "unlock" then self:SetConfigMode(true) elseif option == "lock" then self:SetConfigMode(false) elseif option == "kb" then self:SetKeybindMode(true) else self:Print(("%3.1f.%d"):format(version,self.revision)) self:Print("/rxn config") self:Print("/rxn edit") self:Print("/rxn lock") self:Print("/rxn unlock") self:Print("/rxn kb") end end end ------ HANDLERS ------ function ReAction:OnInitialize() self.db = LibStub("AceDB-3.0"):New("ReAction_DB", { profile = { bars = { }, defaultBar = { } } }, L["Default"] ) self.db.RegisterCallback(self,"OnProfileChanged") self.db.RegisterCallback(self,"OnProfileReset", "OnProfileChanged") self.db.RegisterCallback(self,"OnProfileCopied","OnProfileChanged") KB.RegisterCallback(self,"LIBKEYBOUND_ENABLED") KB.RegisterCallback(self,"LIBKEYBOUND_DISABLED") options.args.profile = LibStub("AceDBOptions-3.0"):GetOptionsTable(self.db) self:RegisterChatCommand("reaction", SlashHandler) self:RegisterChatCommand("rxn", SlashHandler) self:RegisterEvent("PLAYER_REGEN_DISABLED") end function ReAction:OnEnable() InitializeBars() end function ReAction:OnDisable() TearDownBars() end function ReAction:OnProfileChanged() TearDownBars() InitializeBars() self:PopKeybindWarning() end function ReAction:PLAYER_REGEN_DISABLED() if private.configMode == true then self:UserError(L["ReAction config mode disabled during combat."]) self:SetConfigMode(false) self:SetKeybindMode(false) end end function ReAction:LIBKEYBOUND_ENABLED( evt ) self:SetKeybindMode(true) end function ReAction:LIBKEYBOUND_DISABLED( evt ) return self:SetKeybindMode(false) end ------ API ------ function ReAction:UpdateRevision(str) local revision = tonumber(str:match("%d+")) if revision and revision > ReAction.revision then ReAction.revision = revision end end function ReAction:UserError(msg) -- any user errors should be flashed to the UIErrorsFrame UIErrorsFrame:AddMessage(msg) end -- usage: -- (1) ReAction:CreateBar(name, [cfgTable]) -- (2) ReAction:CreateBar(name, "barType", [nRows], [nCols], [btnSize], [btnSpacing]) function ReAction:CreateBar(name, config, ...) local profile = self.db.profile if not name then local prefix = L["Bar "] local i = 1 repeat name = prefix..i i = i + 1 until bars[name] == nil end if type(config) == "string" then config = defaultBarConfig[config] if not config then error(("ReAction:CreateBar() - unknown bar type '%s'"):format(tostring(select(1,...)))) end config = DeepCopy(config) config.btnRows = select(1,...) or config.btnRows or 1 config.btnColumns = select(2,...) or config.btnColumns or 12 config.btnWidth = select(3,...) or config.btnWidth or 36 config.btnHeight = select(3,...) or config.btnHeight or 36 config.spacing = select(4,...) or config.spacing or 3 config.width = config.width or config.btnColumns*(config.btnWidth + config.spacing) + 1 config.height = config.height or config.btnRows*(config.btnHeight + config.spacing) + 1 config.anchor = config.anchor or "UIParent" config.point = config.point or "BOTTOM" config.relpoint = config.relpoint or "BOTTOM" config.y = config.y or 200 config.x = config.x or 0 end config = config or profile.bars[name] or DeepCopy(profile.defaultBar) profile.bars[name] = config local bar = self.Bar:New( name, config ) -- ReAction.Bar defined in Bar.lua bars[name] = bar callbacks:Fire("OnCreateBar", bar, name) if private.configMode then bar:ShowControls(true) end return bar end function ReAction:EraseBar(x) local bar, name = SelectBar(x) if bar and name then callbacks:Fire("OnEraseBar", bar, name) DestroyBar(bar) self.db.profile.bars[name] = nil end end function ReAction:GetBar(name) return bars[name] end -- returns pairs of name, bar function ReAction:IterateBars() return pairs(bars) end function ReAction:RenameBar(x, newname) local bar, name = SelectBar(x) if type(newname) ~= "string" then error("ReAction:RenameBar() - second argument must be a string") end if bar and name and #newname > 0 then if bars[newname] then self:UserError(("%s ('%s')"):format(L["ReAction: name already in use"],newname)) else bars[newname], bars[name] = bars[name], nil bar:SetName(newname or "") local cfg = self.db.profile.bars cfg[newname], cfg[name] = cfg[name], nil callbacks:Fire("OnRenameBar", bar, name, newname) end end end function ReAction:RefreshBar(x) local bar, name = SelectBar(x) if bar and name then callbacks:Fire("OnRefreshBar", bar, name) end end function ReAction:RegisterBarType( name, config, isDefaultChoice ) defaultBarConfig[name] = config if isDefaultChoice then private.defaultBarConfigChoice = name end self:RefreshOptions() end function ReAction:UnregisterBarType( name ) defaultBarConfig[name] = nil if private.defaultBarConfigChoice == name then private.defaultBarConfigChoice = nil end self:RefreshOptions() end function ReAction:IterateBarTypes() return pairs(defaultBarConfig) end function ReAction:GetBarTypeConfig(name) if name then return defaultBarConfig[name] end end function ReAction:GetBarTypeOptions( fill ) fill = fill or { } for k in self:IterateBarTypes() do fill[k] = k end return fill end function ReAction:GetDefaultBarType() return private.defaultBarConfigChoice end function ReAction:RegisterOptions(module, opts, global) options.args[global and "global" or "module"].plugins[module:GetName()] = opts self:RefreshOptions() end function ReAction:RefreshOptions() callbacks:Fire("OnOptionsRefreshed") end -- -- In addition to global and general module options, options tables -- must be generated dynamically for each bar. -- -- 'func' should be a function or a method string. -- The function or method will be passed the bar as its parameter. -- (methods will of course get the module as the first 'self' parameter) -- -- A generator can be unregistered by passing a nil func. -- function ReAction:RegisterBarOptionGenerator( module, func ) if not module or type(module) ~= "table" then -- doesn't need to be a proper module, strictly error("ReAction:RegisterBarOptionGenerator() : Invalid module") end if type(func) == "string" then if not module[func] then error(("ReAction:RegisterBarOptionGenerator() : Invalid method '%s'"):format(func)) end elseif func and type(func) ~= "function" then error("ReAction:RegisterBarOptionGenerator() : Invalid function") end barOptionGenerators[module] = func callbacks:Fire("OnBarOptionGeneratorRegistered", module, func) end -- builds a table suitable for use as an AceConfig3 group 'plugins' sub-table function ReAction:GenerateBarOptionsTable( bar ) local opts = { } for module, func in pairs(barOptionGenerators) do local success, r if type(func) == "string" then success, r = pcall(module[func], module, bar) else success, r = pcall(func, bar) end if success then if r then opts[module:GetName()] = { [module:GetName()] = r } end else geterrorhandler()(r) end end return opts end function ReAction:SetConfigMode( mode ) if mode ~= private.configMode then private.configMode = mode callbacks:Fire("OnConfigModeChanged", mode) end end function ReAction:GetConfigMode() return private.configMode end function ReAction:ShowConfig() CallModuleMethod("ConfigUI","OpenConfig") end function ReAction:ShowEditor(bar, ...) CallModuleMethod("ConfigUI","LaunchBarEditor",bar, ...) end function ReAction:SetKeybindMode( mode ) if mode ~= private.kbMode then if mode then KB:Activate() else KB:Deactivate() end private.kbMode = KB:IsShown() or false end end function ReAction:GetKeybindMode( mode ) return private.kbMode end function ReAction:PopKeybindWarning() if not self.db.global.skipKeybindWarning then StaticPopup_Show("REACTION_KB_WARN") end end -- Export ReAction launcher to LibDataBroker-aware displays LibStub:GetLibrary("LibDataBroker-1.1"):NewDataObject( "ReAction", { type = "launcher", icon = "Interface\\Icons\\INV_Qiraj_JewelEncased", OnClick = function( frame, button ) if not InCombatLockdown() then if IsAltKeyDown() then ReAction:SetKeybindMode( not ReAction:GetKeybindMode() ) elseif IsShiftKeyDown() then ReAction:SetConfigMode( not ReAction:GetConfigMode() ) elseif button == "RightButton" then ReAction:ShowEditor() else ReAction:ShowConfig() end else ReAction:UserError(L["ReAction: can't configure in combat"]) end end, -- this isn't included in the 'launcher' type LDB spec but it seems all launcher displays use it OnTooltipShow = function( tooltip ) tooltip:AddLine(format("|cffffffff%s|r %s",L["Click"],L["for global configuration"])) tooltip:AddLine(format("|cffffd200%s|r %s",L["Right-click"],L["for bar editor dialog"])) tooltip:AddLine(format("|cff00ff00%s|r %s",L["Shift-click"],L["to unlock bars"])) tooltip:AddLine(format("|cff00cccc%s|r %s",L["Alt-click"],L["for keybind mode"])) end, } )