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