Flick@276: local _, ns = ... flickerstreak@205: local pcall = pcall flickerstreak@205: local pairs = pairs flickerstreak@205: local type = type flickerstreak@205: local geterrorhandler = geterrorhandler Flick@279: local GetTime = GetTime flickerstreak@33: local L = LibStub("AceLocale-3.0"):GetLocale("ReAction") Flick@230: local LKB = LibStub("LibKeyBound-1.0",true) Flick@230: if not LKB then Flick@230: LoadAddOn("LibKeyBound-1.0") Flick@230: LKB = LibStub("LibKeyBound-1.0") Flick@230: end flickerstreak@33: flickerstreak@205: ------ Utility ------ Flick@241: -- make a deep copy of a table Flick@241: local function tcopy(x) Flick@241: if type(x) ~= "table" then Flick@241: return x flickerstreak@28: end Flick@241: local r = {} Flick@241: for k,v in pairs(x) do Flick@241: r[k] = tcopy(v) Flick@241: end Flick@241: return r flickerstreak@28: end flickerstreak@28: Flick@241: -- traverse a table tree by key list and fetch the result or first nil Flick@241: local function tfetch(t, ...) Flick@241: for i = 1, select('#', ...) do Flick@241: t = t and t[select(i, ...)] Flick@241: end Flick@241: return t Flick@241: end Flick@241: Flick@241: -- traverse a table tree by key list and build tree as necessary Flick@241: local function tbuild(t, ...) Flick@241: for i = 1, select('#', ...) do Flick@241: local key = select(i, ...) Flick@241: if not t[key] then t[key] = { } end Flick@241: t = t[key] Flick@241: end Flick@241: return t Flick@241: end Flick@241: Flick@241: -- return a new array of keys of table 't', sorted by comparing Flick@241: -- sub-fields (obtained via tfetch) of the table values Flick@241: local function fieldsort( t, ... ) Flick@241: local r = { } Flick@241: for k in pairs(t) do Flick@241: table.insert(r,k) Flick@241: end Flick@241: local path = { ... } Flick@241: table.sort(r, function(lhs, rhs) Flick@241: local olhs = tfetch(t[lhs], unpack(path)) or 0 Flick@241: local orhs = tfetch(t[rhs], unpack(path)) or 0 Flick@241: return olhs < orhs Flick@241: end) Flick@241: return r Flick@241: end Flick@241: Flick@241: -- store in the addon table Flick@276: ns.tcopy = tcopy Flick@276: ns.tfetch = tfetch Flick@276: ns.tbuild = tbuild Flick@276: ns.fieldsort = fieldsort Flick@241: flickerstreak@205: ------ Core ------ flickerstreak@205: local ReAction = LibStub("AceAddon-3.0"):NewAddon( "ReAction", flickerstreak@205: "AceEvent-3.0" flickerstreak@205: ) Flick@276: ns.ReAction = ReAction Flick@246: ReAction.version = GetAddOnMetadata("ReAction","Version") flickerstreak@205: ReAction.L = L flickerstreak@205: ReAction.LKB = LKB flickerstreak@28: flickerstreak@205: flickerstreak@223: ReAction.barTypes = { } flickerstreak@223: flickerstreak@205: ------ Handlers ------ flickerstreak@28: function ReAction:OnInitialize() flickerstreak@28: self.db = LibStub("AceDB-3.0"):New("ReAction_DB", flickerstreak@213: self.defaultProfile, flickerstreak@182: true -- use global 'Default' (locale-specific) flickerstreak@28: ) flickerstreak@205: flickerstreak@213: self:UpgradeProfile() flickerstreak@211: flickerstreak@205: self.bars = { } flickerstreak@205: flickerstreak@213: self.LBF = LibStub("LibButtonFacade",true) flickerstreak@213: if self.LBF then flickerstreak@213: self.LBF:RegisterSkinCallback("ReAction", self.OnSkinChanged, self) flickerstreak@213: end flickerstreak@213: flickerstreak@211: -- It's fairly normal to use the Blizzard vehicle bar, and to have flickerstreak@211: -- your regular buttons in the same location. If you do this, and don't flickerstreak@211: -- bother to hide your buttons, they'll obscure some parts of the vehicle bar. Flick@295: -- VehicleMenuBar:SetFrameLevel(VehicleMenuBar:GetFrameLevel()+3) flickerstreak@211: flickerstreak@205: self.callbacks = LibStub("CallbackHandler-1.0"):New(self) Flick@227: flickerstreak@184: LKB.RegisterCallback(self,"LIBKEYBOUND_ENABLED") flickerstreak@184: LKB.RegisterCallback(self,"LIBKEYBOUND_DISABLED") flickerstreak@207: LKB.RegisterCallback(self, "LIBKEYBOUND_MODE_COLOR_CHANGED","LIBKEYBOUND_ENABLED") Flick@227: Flick@227: -- see Profile.lua for these callback implementations Flick@227: self.db.RegisterCallback(self,"OnProfileChanged") Flick@227: self.db.RegisterCallback(self,"OnProfileCopied","OnProfileChanged") Flick@227: self.db.RegisterCallback(self,"OnNewProfile") Flick@227: self.db.RegisterCallback(self,"OnProfileReset", "OnNewProfile") Flick@227: flickerstreak@182: self:RegisterEvent("PLAYER_REGEN_DISABLED") Flick@242: self:RegisterEvent("UPDATE_SHAPESHIFT_FORMS") Flick@227: flickerstreak@182: self:InitializeOptions() flickerstreak@28: end flickerstreak@28: flickerstreak@28: function ReAction:OnEnable() flickerstreak@205: self:InitializeBars() Flick@242: self:UPDATE_SHAPESHIFT_FORMS() -- it doesn't fire on a /reloadui flickerstreak@28: end flickerstreak@28: flickerstreak@28: function ReAction:OnDisable() flickerstreak@205: self:TearDownBars() flickerstreak@28: end flickerstreak@28: flickerstreak@33: function ReAction:PLAYER_REGEN_DISABLED() flickerstreak@205: if self.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@185: self:CloseEditor() flickerstreak@33: end flickerstreak@33: end flickerstreak@33: Flick@243: function ReAction:UPDATE_SHAPESHIFT_FORMS() Flick@242: -- Re-parse the rules table according to the new form list. Flick@242: -- This happens both at initial login (after PLAYER_ENTERING_WORLD) Flick@242: -- as well as when gaining new abilities. Flick@243: self.Bar:InitRuleFormats() Flick@242: for _, bar in self:IterateBars() do Flick@242: bar:ApplyStates() Flick@242: end Flick@242: end Flick@242: 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@213: function ReAction:OnSkinChanged( skinID, gloss, backdrop, group, button, colors ) flickerstreak@213: if group == nil then flickerstreak@213: -- don't store global flickerstreak@213: else flickerstreak@213: -- 'group' is the bar-name flickerstreak@213: local bar = self:GetBar(group) flickerstreak@213: if bar then flickerstreak@213: local c = bar:GetConfig().ButtonFacade flickerstreak@213: if c then flickerstreak@213: c.skinID = skinID flickerstreak@213: c.gloss = gloss flickerstreak@213: c.backdrop = backdrop flickerstreak@213: c.colors = colors flickerstreak@213: end flickerstreak@213: end flickerstreak@213: end flickerstreak@213: end flickerstreak@213: flickerstreak@33: flickerstreak@205: ------ Methods ------ flickerstreak@77: Flick@279: do Flick@279: local lastErrorMessage Flick@279: local lastErrorTime Flick@279: function ReAction:UserError(msg) Flick@279: local t = GetTime() Flick@279: if msg ~= lastErrorMessage or lastErrorTime == nil or (t - lastErrorTime > 10) then -- prevent spam Flick@279: UIErrorsFrame:AddMessage(msg) Flick@279: lastErrorMessage = msg Flick@279: lastErrorTime = t Flick@279: end Flick@279: end flickerstreak@61: end flickerstreak@61: flickerstreak@205: function ReAction:GetBar(arg) flickerstreak@205: if type(arg) == "string" then flickerstreak@205: return self.bars[arg], arg flickerstreak@205: elseif type(arg) == "table" then -- reverse lookup flickerstreak@205: for name, bar in pairs(self.bars) do flickerstreak@205: if arg == bar then flickerstreak@205: return bar, name flickerstreak@205: end flickerstreak@205: end flickerstreak@205: else flickerstreak@205: error("ReAction:GetBar() requires either a name or a bar table arg") flickerstreak@205: end flickerstreak@184: end flickerstreak@184: flickerstreak@205: function ReAction:IterateBars() flickerstreak@205: return pairs(self.bars) flickerstreak@205: end flickerstreak@184: 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@217: name = tostring(name) flickerstreak@217: if not name or name == "" then flickerstreak@217: error("ReAction:CreateBar() - bar name string required") flickerstreak@217: elseif self.bars[name] then flickerstreak@217: self:UserError(format(L["ReAction: name '%s' already in use"],name)) flickerstreak@217: return nil flickerstreak@91: end flickerstreak@91: flickerstreak@223: local class flickerstreak@91: if type(config) == "string" then flickerstreak@223: class = self.barTypes[config] flickerstreak@218: if not class then flickerstreak@218: error(("ReAction:CreateBar() - unknown bar type '%s'"):format(config)) flickerstreak@48: end flickerstreak@218: config = tcopy(class:GetDefaultBarConfig()) flickerstreak@218: config.btnRows = select(1,...) or config.btnRows flickerstreak@218: config.btnColumns = select(2,...) or config.btnColumns flickerstreak@218: config.btnWidth = select(3,...) or config.btnWidth flickerstreak@218: config.btnHeight = select(3,...) or config.btnHeight flickerstreak@218: config.spacing = select(4,...) or config.spacing 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@223: else flickerstreak@223: config = config or profile.bars[name] or { } Flick@248: if not config then Flick@248: error(("ReAction: Unable to fetch config table for bar '%s'"):format(name)) Flick@248: elseif not config.type or not self.barTypes[config.type] then Flick@248: error(("ReAction: Unrecognized bar type '%s' for bar '%s'"):format(tostring(config.type), name)) flickerstreak@223: end flickerstreak@223: class = self.barTypes[config.type] flickerstreak@48: end flickerstreak@91: flickerstreak@91: profile.bars[name] = config Flick@234: local bar = self.Bar:New( name, config, class ) -- ReAction.Bar defined in Bar.lua flickerstreak@205: self.bars[name] = bar flickerstreak@205: self.callbacks:Fire("OnCreateBar", bar, name) flickerstreak@205: if self.configMode then flickerstreak@33: bar:ShowControls(true) flickerstreak@33: end flickerstreak@33: flickerstreak@28: return bar flickerstreak@28: end flickerstreak@28: flickerstreak@205: function ReAction:DestroyBar(x) flickerstreak@205: local bar, name = self:GetBar(x) flickerstreak@63: if bar and name then flickerstreak@205: self.bars[name] = nil flickerstreak@205: self.callbacks:Fire("OnDestroyBar", bar, name) flickerstreak@205: bar:Destroy() flickerstreak@28: end flickerstreak@28: end flickerstreak@28: flickerstreak@205: function ReAction:InitializeBars() flickerstreak@205: if not self.barsInitialized then flickerstreak@211: self:ManageBlizzardBars() flickerstreak@211: flickerstreak@205: for name, config in pairs(self.db.profile.bars) do flickerstreak@205: if config then flickerstreak@205: self:CreateBar(name, config) flickerstreak@205: end flickerstreak@205: end Flick@242: -- re-anchor in case anchor order does not match init order flickerstreak@205: for name, bar in pairs(self.bars) do flickerstreak@205: bar:ApplyAnchor() flickerstreak@205: end flickerstreak@205: self.barsInitialized = true flickerstreak@205: end flickerstreak@205: end flickerstreak@205: flickerstreak@205: function ReAction:TearDownBars() flickerstreak@205: for name, bar in pairs(self.bars) do flickerstreak@205: if bar then flickerstreak@208: self.bars[name] = self:DestroyBar(bar) flickerstreak@205: end flickerstreak@205: end flickerstreak@205: self.barsInitialized = false flickerstreak@205: end flickerstreak@205: flickerstreak@205: function ReAction:RebuildAll() flickerstreak@205: self:TearDownBars() flickerstreak@205: self:InitializeBars() flickerstreak@28: end flickerstreak@28: flickerstreak@28: function ReAction:RenameBar(x, newname) flickerstreak@205: local bar, name = self:GetBar(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@205: if self.bars[newname] then flickerstreak@127: self:UserError(format(L["ReAction: name '%s' already in use"],newname)) flickerstreak@47: else flickerstreak@205: self.bars[newname], self.bars[name] = 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@205: self.callbacks:Fire("OnRenameBar", bar, name, newname) flickerstreak@28: end flickerstreak@28: end flickerstreak@28: end flickerstreak@28: flickerstreak@205: function ReAction:EraseBar(x) flickerstreak@205: local bar, name = self:GetBar(x) flickerstreak@63: if bar and name then flickerstreak@208: self:DestroyBar(bar) flickerstreak@205: self.db.profile.bars[name] = nil flickerstreak@63: end flickerstreak@63: end flickerstreak@63: flickerstreak@211: local blizzFrames = { flickerstreak@211: MainMenuBar, flickerstreak@211: MultiBarLeft, flickerstreak@211: MultiBarRight, flickerstreak@211: MultiBarBottomLeft, flickerstreak@211: MultiBarBottomRight, flickerstreak@211: } flickerstreak@211: flickerstreak@211: local hideFrame = CreateFrame("Frame") flickerstreak@211: hideFrame:Hide() flickerstreak@211: local hiddenParents = { } flickerstreak@211: local function ManageBlizzFrame(f, hide) flickerstreak@211: if hide and not hiddenParents[f] then flickerstreak@211: hiddenParents[f] = f:GetParent() flickerstreak@211: f:SetParent(hideFrame) flickerstreak@211: elseif not hide and hiddenParents[f] then flickerstreak@211: f:SetParent(hiddenParents[f]) flickerstreak@211: hiddenParents[f] = nil flickerstreak@211: if f:IsShown() then flickerstreak@211: f:Show() -- refresh flickerstreak@211: end flickerstreak@211: end flickerstreak@211: end flickerstreak@211: flickerstreak@211: function ReAction:ManageBlizzardBars() flickerstreak@211: for _, f in pairs(blizzFrames) do flickerstreak@211: ManageBlizzFrame(f, self.db.profile.options.hideBlizzardBars) flickerstreak@211: end Flick@295: --ManageBlizzFrame(VehicleMenuBar, self.db.profile.options.hideBlizzardVehicleBar) flickerstreak@211: end flickerstreak@211: flickerstreak@218: function ReAction:RegisterBarType( class, isDefault ) Flick@231: local name = class:GetButtonTypeID() flickerstreak@218: self.barTypes[name] = class flickerstreak@218: if isDefault then flickerstreak@218: self.defaultBarType = name flickerstreak@48: end flickerstreak@48: end flickerstreak@48: flickerstreak@63: function ReAction:IterateBarTypes() flickerstreak@218: return pairs(self.barTypes) flickerstreak@63: end flickerstreak@63: flickerstreak@218: function ReAction:GetDefaultBarConfig(barType) flickerstreak@218: if barType and self.barTypes[barType] then flickerstreak@218: return self.barTypes[barType]:GetDefaultBarConfig() 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@218: return self.defaultBarType flickerstreak@63: end flickerstreak@63: flickerstreak@33: function ReAction:SetConfigMode( mode ) flickerstreak@205: if mode ~= self.configMode then flickerstreak@207: if mode then flickerstreak@207: self:SetKeybindMode(false) flickerstreak@207: end flickerstreak@205: self.configMode = mode flickerstreak@205: self.callbacks:Fire("OnConfigModeChanged", mode) flickerstreak@77: end flickerstreak@63: end flickerstreak@63: flickerstreak@63: function ReAction:GetConfigMode() flickerstreak@205: return self.configMode flickerstreak@33: end flickerstreak@38: flickerstreak@88: function ReAction:SetKeybindMode( mode ) flickerstreak@205: if mode ~= self.kbMode then flickerstreak@88: if mode then flickerstreak@207: self:SetConfigMode(false) flickerstreak@184: LKB:Activate() flickerstreak@88: else flickerstreak@184: LKB:Deactivate() flickerstreak@88: end flickerstreak@207: for _, bar in self:IterateBars() do flickerstreak@207: bar:SetKeybindMode(mode) flickerstreak@207: end flickerstreak@205: self.kbMode = LKB:IsShown() or false flickerstreak@88: end flickerstreak@88: end flickerstreak@88: flickerstreak@88: function ReAction:GetKeybindMode( mode ) flickerstreak@205: return self.kbMode flickerstreak@88: end Flick@225: Flick@225: Flick@225: -- ConfigMode support Flick@225: CONFIGMODE_CALLBACKS = CONFIGMODE_CALLBACKS or {} Flick@225: Flick@225: function CONFIGMODE_CALLBACKS.ReAction( action, mode ) Flick@225: if action == "ON" then Flick@225: ReAction:SetConfigMode(true) Flick@225: elseif action == "OFF" then Flick@225: ReAction:SetConfigMode(false) Flick@225: elseif action == "LISTMODES" then Flick@225: -- no modes Flick@225: end Flick@225: end Flick@225: