From 63ce65de860821097fbf07e6deb9a20b4b14fcb2 Mon Sep 17 00:00:00 2001 From: James Whitehead II Date: Fri, 22 Dec 2006 06:18:30 +0000 Subject: [PATCH] * Added Dongle to version control --- Dongle.lua | 759 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ DongleUtils.lua | 479 +++++++++++++++++++++++++++++++++++ 2 files changed, 1238 insertions(+) create mode 100644 Dongle.lua create mode 100644 DongleUtils.lua diff --git a/Dongle.lua b/Dongle.lua new file mode 100644 index 0000000..66ce40a --- /dev/null +++ b/Dongle.lua @@ -0,0 +1,759 @@ +--[[------------------------------------------------------------------------- + Copyright (c) 2006, Dongle Development Team + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of the Dongle Development Team nor the names of + its contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +---------------------------------------------------------------------------]] + +local major,minor = "DongleStub", 20061205.3 +local g = getfenv(0) + +if not g.DongleStub or g.DongleStub:IsNewerVersion(major, minor) then + local lib = setmetatable({}, { + __call = function(t,k) + if type(t.versions) == "table" and t.versions[k] then + return t.versions[k] + else + error("Cannot find a library with name '"..tostring(k).."'", 2) + end + end + }) + + function lib:IsNewerVersion(major, minor) + local entry = self.versions and self.versions[major] + + if not entry then return true end + local oldmajor,oldminor = entry:GetVersion() + + return minor > oldminor + end + + function lib:Register(new) + local major,minor = new:GetVersion() + if not self:IsNewerVersion(major, minor) then return false end + local old = self.versions and self.versions[major] + -- Run the new libraries activation + if type(new.Activate) == "function" then + new:Activate(old) + end + + -- Deactivate the old libary if necessary + if old and type(old.Deactivate) == "function" then + old:Deactivate(new) + end + + self.versions[major] = new + end + + function lib:GetVersion() return major,minor end + + function lib:Activate(old) + if old then + self.versions = old.versions + else + self.versions = {} + end + g.DongleStub = self + end + + -- Actually trigger libary activation here + local stub = g.DongleStub or lib + stub:Register(lib) +end + +--[[------------------------------------------------------------------------- +Begin Library Implementation +---------------------------------------------------------------------------]] + +local major = "Dongle" +local minor = tonumber(select(3,string.find("$Revision: 92 $", "(%d+)")) or 1) + +assert(DongleStub, string.format("%s requires DongleStub.", major)) +if not DongleStub:IsNewerVersion(major, minor) then return end + +Dongle = {} +local methods = { + "RegisterEvent", "UnregisterEvent", "UnregisterAllEvents", "TriggerEvent", + "EnableDebug", "Print", "PrintF", "Debug", "DebugF", + "InitializeDB", + "NewModule", "HasModule", "IterateModules", +} + +local registry = {} +local lookup = {} +local loadqueue = {} +local loadorder = {} +local events = {} +local databases = {} +local commands = {} + +local function assert(level,condition,message) + if not condition then + error(message,level) + end +end + +local function argcheck(value, num, ...) + assert(1, type(num) == "number", + "Bad argument #2 to 'argcheck' (number expected, got " .. type(level) .. ")") + + for i=1,select("#", ...) do + if type(value) == select(i, ...) then return end + end + + local types = strjoin(", ", ...) + local name = string.match(debugstack(), "`argcheck'.-[`<](.-)['>]") or "Unknown" + error(string.format("bad argument #%d to '%s' (%s expected, got %s)", + num, name, types, type(value)), 3) +end + +local function safecall(func,...) + local success,err = pcall(func,...) + if not success then + geterrorhandler()(err) + end +end + +function Dongle:New(name, obj) + argcheck(name, 2, "string") + argcheck(obj, 3, "table", "nil") + + if not obj then + obj = {} + end + + if registry[name] then + error("A Dongle with the name '"..name.."' is already registered.") + end + + local reg = {["obj"] = obj, ["name"] = name} + + registry[name] = reg + lookup[obj] = reg + lookup[name] = reg + + for k,v in pairs(methods) do + obj[v] = self[v] + end + + -- Add this Dongle to the end of the queue + table.insert(loadqueue, obj) + return obj,name +end + +function Dongle:NewModule(name, obj) + local reg = lookup[self] + assert(3, reg, "You must call 'NewModule' from a registered Dongle.") + argcheck(name, 2, "string") + argcheck(obj, 3, "table", "nil") + + obj,name = Dongle:New(name, obj) + + if not reg.modules then reg.modules = {} end + reg.modules[obj] = obj + reg.modules[name] = obj + table.insert(reg.modules, name) + table.sort(reg.modules) + + return obj,name +end + +function Dongle:HasModule(module) + local reg = lookup[self] + assert(3, reg, "You must call 'HasModule' from a registered Dongle.") + argcheck(module, 2, "string", "table") + + return reg.modules[module] +end + +local EMPTY_TABLE = {} + +function Dongle:IterateModules() + local reg = lookup[self] + assert(3, reg, "You must call 'IterateModules' from a registered Dongle.") + + return ipairs(reg.modules or EMPTY_TABLE) +end + +function Dongle:ADDON_LOADED(frame, event, ...) + for i=1, #loadqueue do + local obj = loadqueue[i] + table.insert(loadorder, obj) + + if type(obj.Initialize) == "function" then + safecall(obj.Initialize, obj) + end + + if self.initialized and type(obj.Enable) == "function" then + safecall(obj.Enable, obj) + end + loadqueue[i] = nil + end +end + +function Dongle:PLAYER_LOGIN() + self.initialized = true + for i,obj in ipairs(loadorder) do + if type(obj.Enable) == "function" then + safecall(obj.Enable, obj) + end + end +end + +function Dongle:TriggerEvent(event, ...) + argcheck(event, 2, "string") + local eventTbl = events[event] + if eventTbl then + for obj,func in pairs(eventTbl) do + if type(func) == "string" then + if type(obj[func]) == "function" then + safecall(obj[func], obj, event, ...) + end + else + safecall(func,event,...) + end + end + end +end + +function Dongle:OnEvent(frame, event, ...) + local eventTbl = events[event] + if eventTbl then + for obj,func in pairs(eventTbl) do + if type(func) == "string" then + if type(obj[func]) == "function" then + obj[func](obj, event, ...) + end + else + func(event, ...) + end + end + end +end + +function Dongle:RegisterEvent(event, func) + local reg = lookup[self] + assert(3, reg, "You must call 'RegisterEvent' from a registered Dongle.") + argcheck(event, 2, "string") + argcheck(func, 3, "string", "function", "nil") + + -- Name the method the same as the event if necessary + if not func then func = event end + + if not events[event] then + events[event] = {} + frame:RegisterEvent(event) + end + events[event][self] = func +end + +function Dongle:UnregisterEvent(event) + local reg = lookup[self] + assert(3, reg, "You must call 'UnregisterEvent' from a registered Dongle.") + argcheck(event, 2, "string") + + if events[event] then + events[event][self] = nil + if not next(events[event]) then + events[event] = nil + frame:UnregisterEvent(event) + end + end +end + +function Dongle:UnregisterAllEvents() + assert(3, lookup[self], "You must call 'UnregisterAllEvents' from a registered Dongle.") + + for event,tbl in pairs(events) do + tbl[self] = nil + if not next(tbl) then + events[event] = nil + frame:UnregisterEvent(event) + end + end +end + +function Dongle:AdminEvents(event) + local method + if event == "PLAYER_LOGOUT" then + Dongle:ClearDBDefaults() + method = "Disable" + elseif event == "PLAYER_REGEN_DISABLED" then + method = "CombatLockdown" + elseif event == "PLAYER_REGEN_ENABLED" then + method = "CombatUnlock" + end + + if method then + for k,v in pairs(registry) do + local obj = v.obj + if obj[method] then obj[method](obj) end + end + end +end + +function Dongle:EnableDebug(level) + local reg = lookup[self] + assert(3, reg, "You must call 'EnableDebug' from a registered Dongle.") + argcheck(level, 2, "number", "nil") + + reg.debugLevel = level +end + +do + local function argsToStrings(a1, ...) + if select("#", ...) > 0 then + return tostring(a1), argsToStrings(...) + else + return tostring(a1) + end + end + + local function printHelp(obj, method, msg, ...) + local reg = lookup[obj] + assert(4, reg, "You must call '"..method.."' from a registered Dongle.") + + local name = reg.name + msg = "|cFF33FF99"..name.."|r: "..tostring(msg) + if select("#", ...) > 0 then + msg = string.join(", ", msg, argsToStrings(...)) + end + + ChatFrame1:AddMessage(msg) + end + + local function printFHelp(obj, method, msg, ...) + local reg = lookup[obj] + assert(4, reg, "You must call '"..method.."' from a registered Dongle.") + + local name = reg.name + local success,txt = pcall(string.format, "|cFF33FF99%s|r: "..msg, name, ...) + if success then + ChatFrame1:AddMessage(txt) + else + error(string.gsub(txt, "'%?'", string.format("'%s'", method)), 3) + end + end + + function Dongle:Print(...) + return printHelp(self, "Print", ...) + end + + function Dongle:PrintF(...) + return printFHelp(self, "PrintF", ...) + end + + function Dongle:Debug(level, ...) + local reg = lookup[self] + assert(3, reg, "You must call 'Debug' from a registered Dongle.") + argcheck(level, 2, "number") + + if reg.debugLevel and level >= reg.debugLevel then + printHelp(self, "Debug", ...) + end + end + + function Dongle:DebugF(level, ...) + local reg = lookup[self] + assert(3, reg, "You must call 'DebugF' from a registered Dongle.") + argcheck(level, 2, "number") + + if reg.debugLevel and level >= reg.debugLevel then + printFHelp(self, "DebugF", ...) + end + end +end + +local dbMethods = { + "RegisterDefaults", "SetProfile", "GetProfiles", "DeleteProfile", "CopyProfile", + "ResetProfile", "ResetDB", +} + +function Dongle:InitializeDB(name, defaults, defaultProfile) + local reg = lookup[self] + assert(3, reg, "You must call 'InitializeDB' from a registered Dongle.") + argcheck(name, 2, "string") + argcheck(defaults, 3, "table", "nil") + argcheck(defaultProfile, 4, "string", "nil") + + local sv = getglobal(name) + + if not sv then + sv = {} + setglobal(name, sv) + + -- Lets do the initial setup + sv.char = {} + sv.faction = {} + sv.realm = {} + sv.class = {} + sv.global = {} + sv.profiles = {} + end + + -- Initialize the specific databases + local char = string.format("%s of %s", UnitName("player"), GetRealmName()) + local realm = string.format("%s", GetRealmName()) + local class = UnitClass("player") + local race = select(2, UnitRace("player")) + local faction = UnitFactionGroup("player") + + -- Initialize the containers + if not sv.char then sv.char = {} end + if not sv.realm then sv.realm = {} end + if not sv.class then sv.class = {} end + if not sv.faction then sv.faction = {} end + if not sv.global then sv.global = {} end + if not sv.profiles then sv.profiles = {} end + if not sv.profileKeys then sv.profileKeys = {} end + + -- Initialize this characters profiles + if not sv.char[char] then sv.char[char] = {} end + if not sv.realm[realm] then sv.realm[realm] = {} end + if not sv.class[class] then sv.class[class] = {} end + if not sv.faction[faction] then sv.faction[faction] = {} end + + -- Try to get the profile selected from the char db + local profileKey = sv.profileKeys[char] or defaultProfile or char + sv.profileKeys[char] = profileKey + + if not sv.profiles[profileKey] then sv.profiles[profileKey] = {} end + + local db = { + ["char"] = sv.char[char], + ["realm"] = sv.realm[realm], + ["class"] = sv.class[class], + ["faction"] = sv.faction[faction], + ["profile"] = sv.profiles[profileKey], + ["global"] = sv.global, + ["profiles"] = sv.profiles, + } + + -- Copy methods locally + for idx,method in pairs(dbMethods) do + db[method] = Dongle[method] + end + + -- Set some properties in the object we're returning + db.sv = sv + db.sv_name = name + db.profileKey = profileKey + db.parent = reg.name + db.charKey = char + db.realmKey = realm + db.classKey = class + db.factionKey = faction + + databases[db] = true + + if defaults then + db:RegisterDefaults(defaults) + end + + return db +end + +local function copyDefaults(dest, src, force) + for k,v in pairs(src) do + if type(v) == "table" then + if not dest[k] then dest[k] = {} end + copyDefaults(dest[k], v, force) + else + if not dest[k] or force then + dest[k] = v + end + end + end +end + +-- This function operates on a Dongle DB object +function Dongle.RegisterDefaults(db, defaults) + assert(3, databases[db], "You must call 'RegisterDefaults' from a Dongle database object.") + argcheck(defaults, 2, "table") + + if defaults.char then copyDefaults(db.char, defaults.char) end + if defaults.realm then copyDefaults(db.realm, defaults.realm) end + if defaults.class then copyDefaults(db.class, defaults.class) end + if defaults.faction then copyDefaults(db.faction, defaults.faction) end + if defaults.global then copyDefaults(db.global, defaults.global) end + if defaults.profile then copyDefaults(db.profile, defaults.profile) end + + db.defaults = defaults +end + +local function removeDefaults(db, defaults) + if not db then return end + for k,v in pairs(defaults) do + if type(v) == "table" and db[k] then + removeDefaults(db[k], v) + if not next(db[k]) then + db[k] = nil + end + else + if db[k] == defaults[k] then + db[k] = nil + end + end + end +end + +function Dongle:ClearDBDefaults() + for db in pairs(databases) do + local defaults = db.defaults + local sv = db.sv + + if db and defaults then + if defaults.char then removeDefaults(db.char, defaults.char) end + if defaults.realm then removeDefaults(db.realm, defaults.realm) end + if defaults.class then removeDefaults(db.class, defaults.class) end + if defaults.faction then removeDefaults(db.faction, defaults.faction) end + if defaults.global then removeDefaults(db.global, defaults.global) end + if defaults.profile then + for k,v in pairs(sv.profiles) do + removeDefaults(sv.profiles[k], defaults.profile) + end + end + + -- Remove any blank "profiles" + if not next(db.char) then sv.char[db.charKey] = nil end + if not next(db.realm) then sv.realm[db.realmKey] = nil end + if not next(db.class) then sv.class[db.classKey] = nil end + if not next(db.faction) then sv.faction[db.factionKey] = nil end + if not next(db.global) then sv.global = nil end + end + end +end + +function Dongle.SetProfile(db, name) + assert(3, databases[db], "You must call 'SetProfile' from a Dongle database object.") + argcheck(name, 2, "string") + + local sv = db.sv + local old = sv.profiles[db.profileKey] + local new = sv.profiles[name] + + if not new then + sv.profiles[name] = {} + new = sv.profiles[name] + end + + if db.defaults and db.defaults.profile then + -- Remove the defaults from the old profile + removeDefaults(old, db.defaults.profile) + + -- Inject the defaults into the new profile + copyDefaults(new, db.defaults.profile) + end + + db.profile = new + + -- Save this new profile name + sv.profileKeys[db.charKey] = name + db.profileKey = name + + -- FIRE: DONGLE_PROFILE_CHANGED, "DongleName", "SVName", "ProfileName" + local parent = lookup[db.parent].obj + parent:TriggerEvent("DONGLE_PROFILE_CHANGED", db.parent, db.sv_name, name) +end + +function Dongle.GetProfiles(db, t) + assert(3, databases[db], "You must call 'GetProfiles' from a Dongle database object.") + argcheck(t, 2, "table", "nil") + + t = t or {} + local i = 1 + for profileKey in pairs(db.profiles) do + t[i] = profileKey + i = i + 1 + end + return t, i - 1 +end + +function Dongle.DeleteProfile(db, name) + assert(3, databases[db], "You must call 'DeleteProfile' from a Dongle database object.") + argcheck(name, 2, "string") + + if db.profileKey == name then + error("You cannot delete your active profile. Change profiles, then attempt to delete.", 2) + end + + db.sv.profiles[name] = nil + local parent = lookup[db.parent].obj + parent:TriggerEvent("DONGLE_PROFILE_DELETED", db.parent, db.sv_name, name) +end + +function Dongle.CopyProfile(db, name) + assert(3, databases[db], "You must call 'CopyProfile' from a Dongle database object.") + argcheck(name, 2, "string") + + assert(3, db.profileKey ~= name, "Source/Destination profile cannot be the same profile") + assert(3, type(db.sv.profiles[name]) == "table", "Profile \""..name.."\" doesn't exist.") + + local profile = db.profile + local source = db.sv.profiles[name] + + -- Don't do a destructive copy, just do what we're told + copyDefaults(profile, source, true) + -- FIRE: DONGLE_PROFILE_COPIED, "DongleName", "SVName", "SourceProfile", "DestProfile" + local parent = lookup[db.parent].obj + parent:TriggerEvent("DONGLE_PROFILE_COPIED", db.parent, db.sv_name, name, db.profileKey) +end + +function Dongle.ResetProfile(db) + assert(3, databases[db], "You must call 'ResetProfile' from a Dongle database object.") + + local profile = db.profile + + for k,v in pairs(profile) do + profile[k] = nil + end + if db.defaults and db.defaults.profile then + copyDefaults(profile, db.defaults.profile) + end + -- FIRE: DONGLE_PROFILE_RESET, "DongleName", "SVName", "ProfileName" + local parent = lookup[db.parent].obj + parent:TriggerEvent("DONGLE_PROFILE_RESET", db.parent, db.sv_name, db.profileKey) +end + + +function Dongle.ResetDB(db, defaultProfile) + assert(3, databases[db], "You must call 'ResetDB' from a Dongle database object.") + argcheck(defaultProfile, 2, "nil", "string") + + local sv = db.sv + for k,v in pairs(sv) do + sv[k] = nil + end + + local parent = lookup[db.parent].obj + + local newdb = parent:InitializeDB(db.sv_name, db.defaults, defaultProfile) + newdb:SetProfile(newdb.profileKey) + local parent = lookup[db.parent].obj + parent:TriggerEvent("DONGLE_DATABASE_RESET", newdb.parent, newdb.sv_name, newdb.profileKey) + + -- Remove the old database from the lookup table + databases[db] = nil + return newdb +end + +function Dongle:RegisterSlashCommand(command, prefix, pattern, validator) + local reg = lookup[self] + assert(3, reg, "You must call 'RegisterSlashCommand' from a registered Dongle.") + argcheck(prefix, 2, "string") + argcheck(pattern, 3, "string", "nil") + argcheck(validator, 4, "function", "nil") + + if not reg.cmd then + reg.cmd = {} + end + reg.cmd[prefix] = { + ["pattern"] = pattern, + ["validator"] = validator, + } + + -- Register the slash command here + + +end + +--[[------------------------------------------------------------------------- + Begin DongleStub required functions and registration +---------------------------------------------------------------------------]] + +function Dongle:GetVersion() return major,minor end + +function Dongle:Activate(old) + if old then + self.registry = old.registry or registry + self.lookup = old.lookup or lookup + self.loadqueue = old.loadqueue or loadqueue + self.loadorder = old.loadorder or loadorder + self.events = old.events or events + self.databases = old.databases or databases + self.commands = old.commands or commands + + registry = self.registry + lookup = self.lookup + loadqueue = self.loadqueue + loadorder = self.loadorder + events = self.events + databases = self.databases + commands = self.commands + + frame = old.frame + self.registry[major].obj = self + else + self.registry = registry + self.lookup = lookup + self.loadqueue = loadqueue + self.loadorder = loadorder + self.events = events + self.databases = databases + self.commands = commands + + local reg = {obj = self, name = "Dongle"} + registry[major] = reg + lookup[self] = reg + lookup[major] = reg + end + + if not frame then + frame = CreateFrame("Frame") + end + + self.frame = frame + frame:SetScript("OnEvent", function(...) self:OnEvent(...) end) + + -- Register for events using Dongle itself + self:RegisterEvent("ADDON_LOADED") + self:RegisterEvent("PLAYER_LOGIN") + self:RegisterEvent("PLAYER_LOGOUT", "AdminEvents") + self:RegisterEvent("PLAYER_REGEN_ENABLED", "AdminEvents") + self:RegisterEvent("PLAYER_REGEN_DISABLED", "AdminEvents") + + -- Convert all the modules handles + for name,obj in pairs(registry) do + for k,v in ipairs(methods) do + obj[k] = self[v] + end + end + + -- Convert all database methods + for db in pairs(databases) do + for idx,method in ipairs(dbMethods) do + db[method] = self[method] + end + end +end + +function Dongle:Deactivate(new) + lookup[self] = nil + self:UnregisterAllEvents() +end + +DongleStub:Register(Dongle) diff --git a/DongleUtils.lua b/DongleUtils.lua new file mode 100644 index 0000000..d1c2c26 --- /dev/null +++ b/DongleUtils.lua @@ -0,0 +1,479 @@ +--[[------------------------------------------------------------------------- + Copyright (c) 2006, Dongle Development Team + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of the Dongle Development Team nor the names of + its contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +---------------------------------------------------------------------------]] + +local major,minor = "DongleStub", 20061205.3 +local g = getfenv(0) + +if not g.DongleStub or g.DongleStub:IsNewerVersion(major, minor) then + local lib = setmetatable({}, { + __call = function(t,k) + if type(t.versions) == "table" and t.versions[k] then + return t.versions[k] + else + error("Cannot find a library with name '"..tostring(k).."'", 2) + end + end + }) + + function lib:IsNewerVersion(major, minor) + local entry = self.versions and self.versions[major] + + if not entry then return true end + local oldmajor,oldminor = entry:GetVersion() + + return minor > oldminor + end + + function lib:Register(new) + local major,minor = new:GetVersion() + if not self:IsNewerVersion(major, minor) then return false end + local old = self.versions and self.versions[major] + -- Run the new libraries activation + if type(new.Activate) == "function" then + new:Activate(old) + end + + -- Deactivate the old libary if necessary + if old and type(old.Deactivate) == "function" then + old:Deactivate(new) + end + + self.versions[major] = new + end + + function lib:GetVersion() return major,minor end + + function lib:Activate(old) + if old then + self.versions = old.versions + else + self.versions = {} + end + g.DongleStub = self + end + + -- Actually trigger libary activation here + local stub = g.DongleStub or lib + stub:Register(lib) +end + +--[[------------------------------------------------------------------------- +Begin Library Implementation +---------------------------------------------------------------------------]] + +local majorUtil, majorGrat, majorMetro = "DongleUtils", "GratuityMini", "MetrognomeNano" +local minor = tonumber(select(3,string.find("$Revision: 139 $", "(%d+)"))) or 1 +if not DongleStub:IsNewerVersion(majorUtil, minor) then return end + + +-------------------------------- +-- DongleUtils -- +-- Misc handy utils -- +-------------------------------- + +local DongleUtils = {} + + +function DongleUtils:GetVersion() + return majorUtil, minor +end + + +function DongleUtils:Activate(old) + +end + + +function DongleUtils:Deactivate(new) + DongleUtils = nil +end + + +--------------------------- +-- Common locale strings -- +--------------------------- + +local locale = GetLocale() +-- Localized class names. Index == enUS, value == localized +DongleUtils.classnames = locale == "deDE" and { + ["Warlock"] = "Hexenmeister", + ["Warrior"] = "Krieger", + ["Hunter"] = "Jäger", + ["Mage"] = "Magier", + ["Priest"] = "Priester", + ["Druid"] = "Druide", + ["Paladin"] = "Paladin", + ["Shaman"] = "Schamane", + ["Rogue"] = "Schurke", +} or locale == "frFR" and { + ["Warlock"] = "D\195\169moniste", + ["Warrior"] = "Guerrier", + ["Hunter"] = "Chasseur", + ["Mage"] = "Mage", + ["Priest"] = "Pr\195\170tre", + ["Druid"] = "Druide", + ["Paladin"] = "Paladin", + ["Shaman"] = "Chaman", + ["Rogue"] = "Voleur", +} or { + ["Warlock"] = "Warlock", + ["Warrior"] = "Warrior", + ["Hunter"] = "Hunter", + ["Mage"] = "Mage", + ["Priest"] = "Priest", + ["Druid"] = "Druid", + ["Paladin"] = "Paladin", + ["Shaman"] = "Shaman", + ["Rogue"] = "Rogue", +} + +-- Reversed version of .classnames, for locale -> enUS translation +DongleUtils.classnamesreverse = {} +for i,v in pairs(DongleUtils.classnames) do DongleUtils.classnamesreverse[v] = i end + + +-- Handy method to attach to secure frames, to aid in setting attributes quickly +-- Example: someframe:SetManyAttributes("type1", "spell", "spell", "Innervate", "unit1", "player") +function DongleUtils.SetManyAttributes(self, ...) + for i=1,select("#", ...),2 do + local att,val = select(i, ...) + if not att then return end + self:SetAttribute(att,val) + end +end + + +function DongleUtils.RGBToHex(r, g, b) + return string.format("%02x%02x%02x", r, g, b) +end + + +function DongleUtils.RGBPercToHex(r, g, b) + return string.format("%02x%02x%02x", r*255, g*255, b*255) +end + + +function DongleUtils.HexToRGB(hex) + local rhex, ghex, bhex = string.sub(hex, 1, 2), string.sub(hex, 3, 4), string.sub(hex, 5, 6) + return tonumber(rhex, 16), tonumber(ghex, 16), tonumber(bhex, 16) +end + + +function DongleUtils.HexToRGBPerc(hex) + local rhex, ghex, bhex = string.sub(hex, 1, 2), string.sub(hex, 3, 4), string.sub(hex, 5, 6) + return tonumber(rhex, 16)/255, tonumber(ghex, 16)/255, tonumber(bhex, 16)/255 +end + + +function DongleUtils.ColorGradient(perc, ...) + local num = select("#", ...) + local hexes = type(select(1, ...)) == "string" + + if perc == 1 then + if hexes then return select(num, ...) + else return select(num-2, ...), select(num-1, ...), select(num, ...) end + end + + if not hexes then num = num / 3 end + + local segment, relperc = math.modf(perc*(num-1)) + local r1, g1, b1, r2, g2, b2 + if hexes then + r1, g1, b1 = DongleUtils.HexToRGBPerc(select(segment+1, ...)) + r2, g2, b2 = DongleUtils.HexToRGBPerc(select(segment+2, ...)) + else + r1, g1, b1 = select((segment*3)+1, ...), select((segment*3)+2, ...), select((segment*3)+3, ...) + r2, g2, b2 = select((segment*3)+4, ...), select((segment*3)+5, ...), select((segment*3)+6, ...) + end + + if hexes then + return DongleUtils.RGBToHex(r1 + (r2-r1)*relperc, + g1 + (g2-g1)*relperc, + b1 + (b2-b1)*relperc) + else + return r1 + (r2-r1)*relperc, + g1 + (g2-g1)*relperc, + b1 + (b2-b1)*relperc + end +end + + +function DongleUtils.GetHPSeverity(perc, class) + if not class then return DongleUtils.ColorGradient(perc, 1,0,0, 1,1,0, 0,1,0) + else + local c = RAID_CLASS_COLORS[class] + return DongleUtils.ColorGradient(perc, 1,0,0, 1,1,0, c.r,c.g,c.b) + end +end + + +DongleStub:Register(DongleUtils) + + +--------------------------------------- +-- GratuityMini -- +-- Tooltip parsing library -- +--------------------------------------- + +local GratuityMini = {} +local CreateTooltip + +function GratuityMini:GetVersion() + return majorGrat, minor +end + + +function GratuityMini:Activate(old) + self.tooltip = old and old.tooltip or CreateTooltip() + CreateTooltip = nil +end + + +function GratuityMini:Deactivate(new) + GratuityMini = nil +end + + +function GratuityMini:GetTooltip() + return self.tooltip +end + + +CreateTooltip = function() + local tip = CreateFrame("GameTooltip") + tip:SetOwner(tip, "ANCHOR_NONE") + tip.Llines, tip.Rlines = {}, {} + for i=1,30 do + tip.Llines[i], tip.Rlines[i] = tip:CreateFontString(), tip:CreateFontString() + tip.Llines[i]:SetFontObject(GameFontNormal); tip.Rlines[i]:SetFontObject(GameFontNormal) + tip:AddFontStrings(tip.Llines[i], tip.Rlines[i]) + end + + tip.Erase = function(self) + self:ClearLines() -- Ensures tooltip's NumLines is reset + for i=1,30 do + self.Rlines[i]:SetText() -- Clear text from right side (ClearLines only hides them) + self.L[i], self.R[i] = nil, nil -- Flush the metatable cache + end + if not self:IsOwned(self) then self:SetOwner(self, "ANCHOR_NONE") end + end + + local methods = {"SetMerchantCostItem", "SetBagItem", "SetAction", "SetAuctionItem", "SetAuctionSellItem", "SetBuybackItem", + "SetCraftItem", "SetCraftSpell", "SetHyperlink", "SetInboxItem", "SetInventoryItem", "SetLootItem", "SetLootRollItem", + "SetMerchantItem", "SetPetAction", "SetPlayerBuff", "SetQuestItem", "SetQuestLogItem", "SetQuestRewardSpell", + "SetSendMailItem", "SetShapeshift", "SetSpell", "SetTalent", "SetTrackingSpell", "SetTradePlayerItem", "SetTradeSkillItem", + "SetTradeTargetItem", "SetTrainerService", "SetUnit", "SetUnitBuff", "SetUnitDebuff"} + for _,m in pairs(methods) do + local orig = tip[m] + tip[m] = function(self, ...) + self:Erase() + return orig(self, ...) + end + end + + tip.L, tip.R = {}, {} + setmetatable(tip.L, { + __index = function(t, key) + if tip:NumLines() >= key and tip.Llines[key] then + local v = tip.Llines[key]:GetText() + t[key] = v + return v + end + return nil + end, + }) + setmetatable(tip.R, { + __index = function(t, key) + if tip:NumLines() >= key and tip.Rlines[key] then + local v = tip.Rlines[key]:GetText() + t[key] = v + return v + end + return nil + end, + }) + + return tip +end + + +DongleStub:Register(GratuityMini) + + +-------------------------------------------------- +-- MetrognomeNano -- +-- OnUpdate and delayed event manager -- +-------------------------------------------------- + +local Metrognome = {} +local frame, handlers, eventargs + + +function Metrognome:GetVersion() + return majorMetro, minor +end + + +function Metrognome:Activate(old) + self.eventargs = old and old.eventargs or {} + self.handlers = old and old.handlers or {} + self.frame = old and old.frame or CreateFrame("Frame") + handlers, frame, eventargs = self.handlers, self.frame, self.eventargs + if not old then frame:Hide() end + frame.name = "MetrognomeNano Frame" + frame:SetScript("OnUpdate", self.OnUpdate) + + if old then + self.olds = old.olds or {} + if not getmetatable(self.olds) then setmetatable(self.olds, {__mode = "k"}) end + self.olds[old] = true + + for o in pairs(self.olds) do + for i,v in pairs(self) do + if i ~= "GetVersion" and i ~= "Activate" and i ~= "Deactivate" then o[i] = v end + end + end + end + self.Activate = nil +end + + +function Metrognome:Deactivate(new) + Metrognome, frame, handlers, eventargs = nil, nil, nil, nil + self.Deactivate = nil +end + + +function Metrognome:FireDelayedEvent(event, delay, ...) + local id = event..GetTime() + + self:Register(self, id, "DelayedEventHandler", rate, id, event, ...) + self:Start(id, 1) + + return id +end + + +function Metrognome:DelayedEventHandler(id, event, ...) + self:Unregister(id) + + if Dongle then Dongle:FireEvent(event, ...) end +end + + +function Metrognome:Register(addon, name, func, rate, ...) +--~ self:argCheck(name, 2, "string") +--~ self:argCheck(func, 3, "function") +--~ self:assert(not handlers[name], "A timer with the name "..name.." is already registered") + + handlers[name] = { + handler = type(func) == "string" and addon, + name = name, + func = func, + rate = rate or 0, + ... + } + + return true +end + + +function Metrognome:Unregister(name) +--~ self:argCheck(name, 2, "string") + + if not handlers[name] then return end + handlers[name] = nil + if not next(handlers) then frame:Hide() end + return true +end + + +function Metrognome:Start(name, numexec) +--~ self:argCheck(name, 2, "string") + + if not handlers[name] then return end + handlers[name].limit = numexec + handlers[name].elapsed = 0 + handlers[name].running = true + frame:Show() + return true +end + + +function Metrognome:Stop(name) +--~ self:argCheck(name, 2, "string") + + if not handlers[name] then return end + handlers[name].running = nil + handlers[name].limit = nil + if not next(handlers) then frame:Hide() end + return true +end + + +function Metrognome:ChangeRate(name, newrate) +--~ self:argCheck(name, 2, "string") + + if not handlers[name] then return end + + local t = handlers[name] + t.elapsed = 0 + t.rate = newrate or 0 + return true +end + + +function Metrognome:GetHandlerTable(name) +--~ self:argCheck(name, 2, "string") + + return handlers[name] +end + + +function Metrognome.OnUpdate(frame, elapsed) + for i,v in pairs(handlers) do + if v.running then + v.elapsed = v.elapsed + elapsed + if v.elapsed >= v.rate then + if v.handler then v.handler[v.func](v.handler, v.elapsed, unpack(v)) + else v.func(v.elapsed, unpack(v)) end + v.elapsed = 0 + if v.limit then + v.limit = v.limit - 1 + if v.limit <= 0 then Metrognome:Stop(i) end + end + end + end + end +end + + +DongleStub:Register(Metrognome) -- 1.7.9.5