From fa97cf187cc3ac0671cf72647f11bace224705a7 Mon Sep 17 00:00:00 2001 From: James Whitehead Ii Date: Fri, 30 Mar 2007 03:49:04 +0000 Subject: [PATCH] * Updated to Dongle-Beta1. * Initial release on WoWinterface.com --- Dongle.lua | 2087 +++++++++++++++++++++++++++++++++--------------------------- TomTom.lua | 511 +++++++++------ 2 files changed, 1476 insertions(+), 1122 deletions(-) diff --git a/Dongle.lua b/Dongle.lua index 417716b..e62a5c2 100755 --- a/Dongle.lua +++ b/Dongle.lua @@ -1,921 +1,1166 @@ ---[[------------------------------------------------------------------------- - 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 = "DongleStub-Beta0" -local minor = tonumber(string.match("$Revision: 221 $", "(%d+)") or 1) - -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].instance - else - error("Cannot find a library with name '"..tostring(k).."'", 2) - end - end - }) - - function lib:IsNewerVersion(major, minor) - local versionData = self.versions and self.versions[major] - - if not versionData then return true end - local oldmajor,oldminor = versionData.instance:GetVersion() - - return minor > oldminor - end - - local function NilCopyTable(src, dest) - for k,v in pairs(dest) do dest[k] = nil end - for k,v in pairs(src) do dest[k] = v end - end - - function lib:Register(newInstance, activate, deactivate) - local major,minor = newInstance:GetVersion() - if not self:IsNewerVersion(major, minor) then return false end - if not self.versions then self.versions = {} end - - local versionData = self.versions[major] - if not versionData then - -- New major version - versionData = { - ["instance"] = newInstance, - ["deactivate"] = deactivate, - } - - self.versions[major] = versionData - if type(activate) == "function" then - activate(newInstance) - end - return newInstance - end - - local oldDeactivate = versionData.deactivate - local oldInstance = versionData.instance - - versionData.deactivate = deactivate - - local skipCopy - if type(activate) == "function" then - skipCopy = activate(newInstance, oldInstance) - end - - -- Deactivate the old libary if necessary - if type(oldDeactivate) == "function" then - oldDeactivate(oldInstance, newInstance) - end - - -- Re-use the old table, and discard the new one - if not skipCopy then - NilCopyTable(newInstance, oldInstance) - end - return oldInstance - end - - function lib:GetVersion() return major,minor end - - local function Activate(new, old) - if old then - new.versions = old.versions - end - g.DongleStub = new - end - - -- Actually trigger libary activation here - local stub = g.DongleStub or lib - stub:Register(lib, Activate) -end - ---[[------------------------------------------------------------------------- - Begin Library Implementation ----------------------------------------------------------------------------]] - -local major = "Dongle-Beta0" -local minor = tonumber(string.match("$Revision: 239 $", "(%d+)") or 1) - -assert(DongleStub, string.format("Dongle requires DongleStub.", major)) -assert(DongleStub and DongleStub:GetVersion() == "DongleStub-Beta0", - string.format("Dongle requires DongleStub-Beta0. You are using an older version.", major)) - -if not DongleStub:IsNewerVersion(major, minor) then return end - -local Dongle = {} -local methods = { - "RegisterEvent", "UnregisterEvent", "UnregisterAllEvents", - "RegisterMessage", "UnregisterMessage", "UnregisterAllMessages", "TriggerMessage", - "EnableDebug", "Print", "PrintF", "Debug", "DebugF", - "InitializeDB", - "InitializeSlashCommand", - "NewModule", "HasModule", "IterateModules", -} - -local registry = {} -local lookup = {} -local loadqueue = {} -local loadorder = {} -local events = {} -local databases = {} -local commands = {} -local messages = {} - -local frame - -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 - - 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 function ModuleIterator(t, name) - if not t then return end - local obj - repeat - name,obj = next(t, name) - until type(name) == "string" or not name - - return name,obj -end - -function Dongle:IterateModules() - local reg = lookup[self] - assert(3, reg, "You must call 'IterateModules' from a registered Dongle.") - - return ModuleIterator, reg.modules -end - -local function ADDON_LOADED(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 Dongle.initialized and type(obj.Enable) == "function" then - safecall(obj.Enable, obj) - end - loadqueue[i] = nil - end -end - -local function PLAYER_LOGOUT(event) - self:ClearDBDefaults() - for k,v in pairs(registry) do - local obj = v.obj - if type(obj["Disable"]) == "function" then - safecall(obj["Disable"], obj) - end - end -end - -local function PLAYER_LOGIN() - Dongle.initialized = true - for i,obj in ipairs(loadorder) do - if type(obj.Enable) == "function" then - safecall(obj.Enable, obj) - end - end -end - -local function 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 - safecall(obj[func], obj, event, ...) - end - else - safecall(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") - - local tbl = events[event] - if tbl then - tbl[self] = nil - if not next(tbl) 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:RegisterMessage(msg, func) - local reg = lookup[self] - assert(3, reg, "You must call 'RegisterMessage' from a registered Dongle.") - argcheck(msg, 2, "string") - argcheck(func, 3, "string", "function", "nil") - - -- Name the method the same as the event if necessary - if not func then func = msg end - - if not messages[msg] then - messages[msg] = {} - end - messages[msg][self] = func -end - -function Dongle:UnregisterMessage(msg) - local reg = lookup[self] - assert(3, reg, "You must call 'UnregisterMessage' from a registered Dongle.") - argcheck(msg, 2, "string") - - local tbl = messages[msg] - if tbl then - tbl[self] = nil - if not next(tbl) then - messages[msg] = nil - end - end -end - -function Dongle:UnregisterAllMessages() - assert(3, lookup[self], "You must call 'UnregisterAllMessages' from a registered Dongle.") - - for msg,tbl in pairs(messages) do - tbl[self] = nil - if not next(tbl) then - messages[msg] = nil - end - end -end - -function Dongle:TriggerMessage(msg, ...) - argcheck(msg, 2, "string") - local msgTbl = messages[msg] - if not msgTbl then return end - - for obj,func in pairs(msgTbl) do - if type(func) == "string" then - if type(obj[func]) == "function" then - safecall(obj[func], obj, msg, ...) - end - else - safecall(func, msg, ...) - 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", -} - -local function initdb(parent, name, defaults, defaultProfile, olddb) - 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 - - local profileCreated - if not sv.profiles[profileKey] then sv.profiles[profileKey] = {} profileCreated = true end - - if olddb then - for k,v in pairs(olddb) do olddb[k] = nil end - end - - local db = olddb or {} - db.char = sv.char[char] - db.realm = sv.realm[realm] - db.class = sv.class[class] - db.faction = sv.faction[faction] - db.profile = sv.profiles[profileKey] - db.global = sv.global - db.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 = parent - db.charKey = char - db.realmKey = realm - db.classKey = class - db.factionKey = faction - - databases[db] = true - - if defaults then - db:RegisterDefaults(defaults) - end - - return db,profileCreated -end - -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 db,profileCreated = initdb(self, name, defaults, defaultProfile) - - if profileCreated then - Dongle:TriggerMessage("DONGLE_PROFILE_CREATED", db, self, db.sv_name, db.profileKey) - 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 (dest[k] == nil) 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] - local profileCreated - - if not new then - sv.profiles[name] = {} - new = sv.profiles[name] - profileCreated = true - 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 - - if profileCreated then - Dongle:TriggerMessage("DONGLE_PROFILE_CREATED", db, db.parent, db.sv_name, db.profileKey) - end - - Dongle:TriggerMessage("DONGLE_PROFILE_CHANGED", db, db.parent, db.sv_name, db.profileKey) -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 - Dongle:TriggerMessage("DONGLE_PROFILE_DELETED", db, 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] - - copyDefaults(profile, source, true) - Dongle:TriggerMessage("DONGLE_PROFILE_COPIED", db, 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 - Dongle:TriggerMessage("DONGLE_PROFILE_RESET", db, 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 = db.parent - - initdb(parent, db.sv_name, db.defaults, defaultProfile, db) - Dongle:TriggerMessage("DONGLE_DATABASE_RESET", db, parent, db.sv_name, db.profileKey) - Dongle:TriggerMessage("DONGLE_PROFILE_CREATED", db, db.parent, db.sv_name, db.profileKey) - Dongle:TriggerMessage("DONGLE_PROFILE_CHANGED", db, db.parent, db.sv_name, db.profileKey) - return db -end - -local slashCmdMethods = { - "RegisterSlashHandler", - "PrintUsage", -} - -local function OnSlashCommand(cmd, cmd_line) - if cmd.patterns then - for pattern, tbl in pairs(cmd.patterns) do - if string.match(cmd_line, pattern) then - if type(tbl.handler) == "string" then - cmd.parent[tbl.handler](cmd.parent, string.match(cmd_line, pattern)) - else - tbl.handler(string.match(cmd_line, pattern)) - end - return - end - end - end - cmd:PrintUsage() -end - -function Dongle:InitializeSlashCommand(desc, name, ...) - local reg = lookup[self] - assert(3, reg, "You must call 'InitializeSlashCommand' from a registered Dongle.") - argcheck(desc, 2, "string") - argcheck(name, 3, "string") - argcheck(select(1, ...), 4, "string") - for i = 2,select("#", ...) do - argcheck(select(i, ...), i+2, "string") - end - - local cmd = {} - cmd.desc = desc - cmd.name = name - cmd.parent = self - cmd.slashes = { ... } - for idx,method in pairs(slashCmdMethods) do - cmd[method] = Dongle[method] - end - - local genv = getfenv(0) - - for i = 1,select("#", ...) do - genv["SLASH_"..name..tostring(i)] = "/"..select(i, ...) - end - - genv.SlashCmdList[name] = function(...) OnSlashCommand(cmd, ...) end - - commands[cmd] = true - - return cmd -end - -function Dongle.RegisterSlashHandler(cmd, desc, pattern, handler) - assert(3, commands[cmd], "You must call 'RegisterSlashHandler' from a Dongle slash command object.") - - argcheck(desc, 2, "string") - argcheck(pattern, 3, "string") - argcheck(handler, 4, "function", "string") - - if not cmd.patterns then - cmd.patterns = {} - end - cmd.patterns[pattern] = { - ["desc"] = desc, - ["handler"] = handler, - } -end - -function Dongle.PrintUsage(cmd) - assert(3, commands[cmd], "You must call 'PrintUsage' from a Dongle slash command object.") - - local usage = cmd.desc.."\n".."/"..table.concat(cmd.slashes, ", /")..":\n" - if cmd.patterns then - local descs = {} - for pattern,tbl in pairs(cmd.patterns) do - table.insert(descs, tbl.desc) - end - table.sort(descs) - for _,desc in pairs(descs) do - usage = usage.." - "..desc.."\n" - end - end - cmd.parent:Print(usage) -end - ---[[------------------------------------------------------------------------- - Begin DongleStub required functions and registration ----------------------------------------------------------------------------]] - -function Dongle:GetVersion() return major,minor end - -local function Activate(self, 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 - self.messages = old.messages or messages - - registry = self.registry - lookup = self.lookup - loadqueue = self.loadqueue - loadorder = self.loadorder - events = self.events - databases = self.databases - commands = self.commands - messages = self.messages - - frame = old.frame - - local reg = self.registry[major] - reg.obj = self - lookup[self] = reg - lookup[major] = reg - else - self.registry = registry - self.lookup = lookup - self.loadqueue = loadqueue - self.loadorder = loadorder - self.events = events - self.databases = databases - self.commands = commands - self.messages = messages - - 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", OnEvent) - - -- Register for events using Dongle itself - self:RegisterEvent("ADDON_LOADED", ADDON_LOADED) - self:RegisterEvent("PLAYER_LOGIN", PLAYER_LOGIN) - self:RegisterEvent("PLAYER_LOGOUT", PLAYER_LOGOUT) - - -- 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 - - -- Convert all slash command methods - for cmd in pairs(commands) do - for idx,method in ipairs(slashCmdMethods) do - cmd[method] = self[method] - end - end -end - -local function Deactivate(self, new) - self:UnregisterAllEvents() - lookup[self] = nil -end - -DongleStub:Register(Dongle, Activate, Deactivate) +--[[------------------------------------------------------------------------- + Copyright (c) 2006-2007, 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 = "DongleStub-Beta0" +local minor = tonumber(string.match("$Revision: 221 $", "(%d+)") or 1) + +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].instance + else + error("Cannot find a library with name '"..tostring(k).."'", 2) + end + end + }) + + function lib:IsNewerVersion(major, minor) + local versionData = self.versions and self.versions[major] + + if not versionData then return true end + local oldmajor,oldminor = versionData.instance:GetVersion() + + return minor > oldminor + end + + local function NilCopyTable(src, dest) + for k,v in pairs(dest) do dest[k] = nil end + for k,v in pairs(src) do dest[k] = v end + end + + function lib:Register(newInstance, activate, deactivate) + local major,minor = newInstance:GetVersion() + if not self:IsNewerVersion(major, minor) then return false end + if not self.versions then self.versions = {} end + + local versionData = self.versions[major] + if not versionData then + -- New major version + versionData = { + ["instance"] = newInstance, + ["deactivate"] = deactivate, + } + + self.versions[major] = versionData + if type(activate) == "function" then + activate(newInstance) + end + return newInstance + end + + local oldDeactivate = versionData.deactivate + local oldInstance = versionData.instance + + versionData.deactivate = deactivate + + local skipCopy + if type(activate) == "function" then + skipCopy = activate(newInstance, oldInstance) + end + + -- Deactivate the old libary if necessary + if type(oldDeactivate) == "function" then + oldDeactivate(oldInstance, newInstance) + end + + -- Re-use the old table, and discard the new one + if not skipCopy then + NilCopyTable(newInstance, oldInstance) + end + return oldInstance + end + + function lib:GetVersion() return major,minor end + + local function Activate(new, old) + if old then + new.versions = old.versions + end + g.DongleStub = new + end + + -- Actually trigger libary activation here + local stub = g.DongleStub or lib + stub:Register(lib, Activate) +end + +--[[------------------------------------------------------------------------- + Begin Library Implementation +---------------------------------------------------------------------------]] + +local major = "Dongle-Beta1" +local minor = tonumber(string.match("$Revision: 262 $", "(%d+)") or 1) + +assert(DongleStub, string.format("Dongle requires DongleStub.", major)) + +if not DongleStub:IsNewerVersion(major, minor) then return end + +local Dongle = {} +local methods = { + "RegisterEvent", "UnregisterEvent", "UnregisterAllEvents", "IsEventRegistered", + "RegisterMessage", "UnregisterMessage", "UnregisterAllMessages", "TriggerMessage", "IsMessageRegistered", + "EnableDebug", "IsDebugEnabled", "Print", "PrintF", "Debug", "DebugF", + "InitializeDB", + "InitializeSlashCommand", + "NewModule", "HasModule", "IterateModules", +} + +local registry = {} +local lookup = {} +local loadqueue = {} +local loadorder = {} +local events = {} +local databases = {} +local commands = {} +local messages = {} + +local frame + +--[[------------------------------------------------------------------------- + Message Localization +---------------------------------------------------------------------------]] + +local L = { + ["ADDMESSAGE_REQUIRED"] = "The frame you specify must have an 'AddMessage' method.", + ["ALREADY_REGISTERED"] = "A Dongle with the name '%s' is already registered.", + ["BAD_ARGUMENT"] = "bad argument #%d to '%s' (%s expected, got %s)", + ["BAD_ARGUMENT_DB"] = "bad argument #%d to '%s' (DongleDB expected)", + ["CANNOT_DELETE_ACTIVE_PROFILE"] = "You cannot delete your active profile. Change profiles, then attempt to delete.", + ["DELETE_NONEXISTANT_PROFILE"] = "You cannot delete a non-existant profile.", + ["MUST_CALLFROM_DBOBJECT"] = "You must call '%s' from a Dongle database object.", + ["MUST_CALLFROM_REGISTERED"] = "You must call '%s' from a registered Dongle.", + ["MUST_CALLFROM_SLASH"] = "You must call '%s' from a Dongle slash command object.", + ["PROFILE_DOES_NOT_EXIST"] = "Profile '%s' doesn't exist.", + ["REPLACE_DEFAULTS"] = "You are attempting to register defaults with a database that already contains defaults.", + ["SAME_SOURCE_DEST"] = "Source/Destination profile cannot be the same profile.", + ["EVENT_REGISTER_SPECIAL"] = "You cannot register for the '%s' event. Use the '%s' method instead.", + ["Unknown"] = "Unknown", + ["INJECTDB_USAGE"] = "Usage: DongleCmd:InjectDBCommands(db, ['copy', 'delete', 'list', 'reset', 'set'])", + ["DBSLASH_PROFILE_COPY_DESC"] = "profile copy - Copies profile into your current profile.", + ["DBSLASH_PROFILE_COPY_PATTERN"] = "^profile copy (.+)$", + ["DBSLASH_PROFILE_DELETE_DESC"] = "profile delete - Deletes the profile .", + ["DBSLASH_PROFILE_DELETE_PATTERN"] = "^profile delete (.+)$", + ["DBSLASH_PROFILE_LIST_DESC"] = "profile list - Lists all valid profiles.", + ["DBSLASH_PROFILE_LIST_PATTERN"] = "^profile list$", + ["DBSLASH_PROFILE_RESET_DESC"] = "profile reset - Resets the current profile.", + ["DBSLASH_PROFILE_RESET_PATTERN"] = "^profile reset$", + ["DBSLASH_PROFILE_SET_DESC"] = "profile set - Sets the current profile to .", + ["DBSLASH_PROFILE_SET_PATTERN"] = "^profile set (.+)$", + ["DBSLASH_PROFILE_LIST_OUT"] = "Profile List:", +} + +--[[------------------------------------------------------------------------- + Utility functions for Dongle use +---------------------------------------------------------------------------]] + +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", + string.format(L["BAD_ARGUMENT"], 2, "argcheck", "number", type(level))) + + for i=1,select("#", ...) do + if type(value) == select(i, ...) then return end + end + + local types = strjoin(", ", ...) + local name = string.match(debugstack(2,2,0), ": in function [`<](.-)['>]") + error(string.format(L["BAD_ARGUMENT"], num, name, types, type(value)), 3) +end + +local function safecall(func,...) + local success,err = pcall(func,...) + if not success then + geterrorhandler()(err) + end +end + +--[[------------------------------------------------------------------------- + Dongle constructor, and DongleModule system +---------------------------------------------------------------------------]] + +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(string.format(L["ALREADY_REGISTERED"], name)) + 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, string.format(L["MUST_CALLFROM_REGISTERED"], "NewModule")) + 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 + + return obj,name +end + +function Dongle:HasModule(module) + local reg = lookup[self] + assert(3, reg, string.format(L["MUST_CALLFROM_REGISTERED"], "HasModule")) + argcheck(module, 2, "string", "table") + + return reg.modules[module] +end + +local function ModuleIterator(t, name) + if not t then return end + local obj + repeat + name,obj = next(t, name) + until type(name) == "string" or not name + + return name,obj +end + +function Dongle:IterateModules() + local reg = lookup[self] + assert(3, reg, string.format(L["MUST_CALLFROM_REGISTERED"], "IterateModules")) + + return ModuleIterator, reg.modules +end + +--[[------------------------------------------------------------------------- + Event registration system +---------------------------------------------------------------------------]] + +local function 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 + safecall(obj[func], obj, event, ...) + end + else + safecall(func, event, ...) + end + end + end +end + +local specialEvents = { + ["PLAYER_LOGIN"] = "Enable", + ["PLAYER_LOGOUT"] = "Disable", +} + +function Dongle:RegisterEvent(event, func) + local reg = lookup[self] + assert(3, reg, string.format(L["MUST_CALLFROM_REGISTERED"], "RegisterEvent")) + argcheck(event, 2, "string") + argcheck(func, 3, "string", "function", "nil") + + local special = (self ~= Dongle) and specialEvents[event] + if special then + error(string.format(L["EVENT_REGISTER_SPECIAL"], event, special), 3) + end + + -- 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, string.format(L["MUST_CALLFROM_REGISTERED"], "UnregisterEvent")) + argcheck(event, 2, "string") + + local tbl = events[event] + if tbl then + tbl[self] = nil + if not next(tbl) then + events[event] = nil + frame:UnregisterEvent(event) + end + end +end + +function Dongle:UnregisterAllEvents() + local reg = lookup[self] + assert(3, reg, string.format(L["MUST_CALLFROM_REGISTERED"], "UnregisterAllEvents")) + + 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:IsEventRegistered(event) + local reg = lookup[self] + assert(3, reg, string.format(L["MUST_CALLFROM_REGISTERED"], "IsEventRegistered")) + argcheck(event, 2, "string") + + local tbl = events[event] + return tbl +end + +--[[------------------------------------------------------------------------- + Inter-Addon Messaging System +---------------------------------------------------------------------------]] + +function Dongle:RegisterMessage(msg, func) + local reg = lookup[self] + assert(3, reg, string.format(L["MUST_CALLFROM_REGISTERED"], "RegisterMessage")) + argcheck(msg, 2, "string") + argcheck(func, 3, "string", "function", "nil") + + -- Name the method the same as the message if necessary + if not func then func = msg end + + if not messages[msg] then + messages[msg] = {} + end + messages[msg][self] = func +end + +function Dongle:UnregisterMessage(msg) + local reg = lookup[self] + assert(3, reg, string.format(L["MUST_CALLFROM_REGISTERED"], "UnregisterMessage")) + argcheck(msg, 2, "string") + + local tbl = messages[msg] + if tbl then + tbl[self] = nil + if not next(tbl) then + messages[msg] = nil + end + end +end + +function Dongle:UnregisterAllMessages() + local reg = lookup[self] + assert(3, reg, string.format(L["MUST_CALLFROM_REGISTERED"], "UnregisterAllMessages")) + + for msg,tbl in pairs(messages) do + tbl[self] = nil + if not next(tbl) then + messages[msg] = nil + end + end +end + +function Dongle:TriggerMessage(msg, ...) + argcheck(msg, 2, "string") + local msgTbl = messages[msg] + if not msgTbl then return end + + for obj,func in pairs(msgTbl) do + if type(func) == "string" then + if type(obj[func]) == "function" then + safecall(obj[func], obj, msg, ...) + end + else + safecall(func, msg, ...) + end + end +end + +function Dongle:IsMessageRegistered(msg) + local reg = lookup[self] + assert(3, reg, string.format(L["MUST_CALLFROM_REGISTERED"], "IsMessageRegistered")) + argcheck(msg, 2, "string") + + local tbl = messages[msg] + return tbl[self] +end + +--[[------------------------------------------------------------------------- + Debug and Print utility functions +---------------------------------------------------------------------------]] + +function Dongle:EnableDebug(level, frame) + local reg = lookup[self] + assert(3, reg, string.format(L["MUST_CALLFROM_REGISTERED"], "EnableDebug")) + argcheck(level, 2, "number", "nil") + argcheck(frame, 3, "table", "nil") + + assert(3, type(frame) == "nil" or type(frame.AddMessage) == "function", L["ADDMESSAGE_REQUIRED"]) + reg.debugFrame = frame or ChatFrame1 + reg.debugLevel = level +end + +function Dongle:IsDebugEnabled() + local reg = lookup[self] + assert(3, reg, string.format(L["MUST_CALLFROM_REGISTERED"], "EnableDebug")) + + return reg.debugLevel, reg.debugFrame +end + +local function argsToStrings(a1, ...) + if select("#", ...) > 0 then + return tostring(a1), argsToStrings(...) + else + return tostring(a1) + end +end + +local function printHelp(obj, method, frame, msg, ...) + local reg = lookup[obj] + assert(4, reg, string.format(L["MUST_CALLFROM_REGISTERED"], method)) + + local name = reg.name + msg = "|cFF33FF99"..name.."|r: "..tostring(msg) + if select("#", ...) > 0 then + msg = string.join(", ", msg, argsToStrings(...)) + end + + frame:AddMessage(msg) +end + +local function printFHelp(obj, method, frame, msg, ...) + local reg = lookup[obj] + assert(4, reg, string.format(L["MUST_CALLFROM_REGISTERED"], method)) + + local name = reg.name + local success,txt = pcall(string.format, "|cFF33FF99%s|r: "..msg, name, ...) + if success then + frame:AddMessage(txt) + else + error(string.gsub(txt, "'%?'", string.format("'%s'", method)), 3) + end +end + +function Dongle:Print(msg, ...) + local reg = lookup[self] + assert(3, reg, string.format(L["MUST_CALLFROM_REGISTERED"], "Print")) + argcheck(msg, 2, "number", "string", "boolean", "table", "function", "thread", "userdata") + return printHelp(self, "Print", DEFAULT_CHAT_FRAME, msg, ...) +end + +function Dongle:PrintF(msg, ...) + local reg = lookup[self] + assert(3, reg, string.format(L["MUST_CALLFROM_REGISTERED"], "PrintF")) + argcheck(msg, 2, "number", "string", "boolean", "table", "function", "thread", "userdata") + return printFHelp(self, "PrintF", DEFAULT_CHAT_FRAME, msg, ...) +end + +function Dongle:Debug(level, ...) + local reg = lookup[self] + assert(3, reg, string.format(L["MUST_CALLFROM_REGISTERED"], "Debug")) + argcheck(level, 2, "number") + + if reg.debugLevel and level <= reg.debugLevel then + printHelp(self, "Debug", reg.debugFrame, ...) + end +end + +function Dongle:DebugF(level, ...) + local reg = lookup[self] + assert(3, reg, string.format(L["MUST_CALLFROM_REGISTERED"], "DebugF")) + argcheck(level, 2, "number") + + if reg.debugLevel and level <= reg.debugLevel then + printFHelp(self, "DebugF", reg.debugFrame, ...) + end +end + +--[[------------------------------------------------------------------------- + Database System +---------------------------------------------------------------------------]] + +local dbMethods = { + "RegisterDefaults", "SetProfile", "GetProfiles", "DeleteProfile", "CopyProfile", + "ResetProfile", "ResetDB", + "RegisterNamespace", +} + +local function copyTable(src) + local dest = {} + for k,v in pairs(src) do + if type(k) == "table" then + k = copyTable(k) + end + if type(v) == "table" then + v = copyTable(v) + end + dest[k] = v + end + return dest +end + +local function copyDefaults(dest, src, force) + for k,v in pairs(src) do + if k == "*" then + if type(v) == "table" then + -- Values are tables, need some magic here + local mt = { + __cache = {}, + __index = function(t,k) + local mt = getmetatable(dest) + local cache = rawget(mt, "__cache") + local tbl = rawget(cache, k) + if not tbl then + local parent = t + local parentkey = k + tbl = copyTable(v) + rawset(cache, k, tbl) + local mt = getmetatable(tbl) + local newindex = function(t,k,v) + rawset(parent, parentkey, t) + rawset(t, k, v) + end + rawset(mt, "__newindex", newindex) + end + return tbl + end, + } + setmetatable(dest, mt) + else + -- Values are not tables, so this is just a simple return + local mt = {__index = function() return v end} + setmetatable(dest, mt) + end + elseif type(v) == "table" then + if not dest[k] then dest[k] = {} end + copyDefaults(dest[k], v, force) + else + if (dest[k] == nil) or force then + dest[k] = v + end + end + end +end + +local function removeDefaults(db, defaults) + if not db then return end + for k,v in pairs(defaults) do + if k == "*" and type(v) == "table" then + -- check for any defaults that have been changed + local mt = getmetatable(db) + local cache = rawget(mt, "__cache") + + for cacheKey,cacheValue in pairs(cache) do + removeDefaults(cacheValue, v) + if next(cacheValue) ~= nil then + -- Something's changed + rawset(db, cacheKey, cacheValue) + end + end + elseif 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 + +local function initSection(db, section, svstore, key, defaults) + local sv = rawget(db, "sv") + + local tableCreated + if not sv[svstore] then sv[svstore] = {} end + if not sv[svstore][key] then + sv[svstore][key] = {} + tableCreated = true + end + + local tbl = sv[svstore][key] + + if defaults then + copyDefaults(tbl, defaults) + end + rawset(db, section, tbl) + + return tableCreated, tbl +end + +local dbmt = { + __index = function(t, section) + local keys = rawget(t, "keys") + local key = keys[section] + if key then + local defaultTbl = rawget(t, "defaults") + local defaults = defaultTbl and defaultTbl[section] + + if section == "profile" then + local new = initSection(t, section, "profiles", key, defaults) + if new then + Dongle:TriggerMessage("DONGLE_PROFILE_CREATED", t, rawget(t, "parent"), rawget(t, "sv_name"), key) + end + elseif section == "global" then + local sv = rawget(t, "sv") + if not sv.global then sv.global = {} end + if defaults then + copyDefaults(sv.global, defaults) + end + rawset(t, section, sv.global) + else + initSection(t, section, section, key, defaults) + end + end + + return rawget(t, section) + end +} + +local function initdb(parent, name, defaults, defaultProfile, olddb) + -- This allows us to use an arbitrary table as base instead of saved variable name + local sv + if type(name) == "string" then + sv = getglobal(name) + if not sv then + sv = {} + setglobal(name, sv) + end + elseif type(name) == "table" then + sv = name + end + + -- Generate the database keys for each section + local char = string.format("%s of %s", UnitName("player"), GetRealmName()) + local realm = GetRealmName() + local class = UnitClass("player") + local race = select(2, UnitRace("player")) + local faction = UnitFactionGroup("player") + local factionrealm = string.format("%s - %s", faction, realm) + + -- Make a container for profile keys + if not sv.profileKeys then sv.profileKeys = {} end + + -- Try to get the profile selected from the char db + local profileKey = sv.profileKeys[char] or defaultProfile or char + sv.profileKeys[char] = profileKey + + local keyTbl= { + ["char"] = char, + ["realm"] = realm, + ["class"] = class, + ["race"] = race, + ["faction"] = faction, + ["factionrealm"] = factionrealm, + ["global"] = true, + ["profile"] = profileKey, + } + + -- If we've been passed an old database, clear it out + if olddb then + for k,v in pairs(olddb) do olddb[k] = nil end + end + + -- Give this database the metatable so it initializes dynamically + local db = setmetatable(olddb or {}, dbmt) + + -- 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.profiles = sv.profiles + db.keys = keyTbl + db.sv = sv + db.sv_name = name + db.defaults = defaults + db.parent = parent + + databases[db] = true + + return db +end + +function Dongle:InitializeDB(name, defaults, defaultProfile) + local reg = lookup[self] + assert(3, reg, string.format(L["MUST_CALLFROM_REGISTERED"], "InitializeDB")) + argcheck(name, 2, "string") + argcheck(defaults, 3, "table", "nil") + argcheck(defaultProfile, 4, "string", "nil") + + return initdb(self, name, defaults, defaultProfile) +end + +-- This function operates on a Dongle DB object +function Dongle.RegisterDefaults(db, defaults) + assert(3, databases[db], string.format(L["MUST_CALLFROM_DBOBJECT"], "RegisterDefaults")) + assert(3, db.defaults == nil, L["REPLACE_DEFAUTS"]) + argcheck(defaults, 2, "table") + + for section,key in pairs(db.keys) do + if defaults[section] and rawget(db, section) then + copyDefaults(db[section], defaults[section]) + end + end + + db.defaults = defaults +end + +function Dongle:ClearDBDefaults() + for db in pairs(databases) do + local defaults = db.defaults + local sv = db.sv + + if db and defaults then + for section,key in pairs(db.keys) do + if defaults[section] and rawget(db, section) then + removeDefaults(db[section], defaults[section]) + end + end + + for section,key in pairs(db.keys) do + local tbl = rawget(db, section) + if tbl and not next(tbl) then + sv[section][key] = nil + end + end + end + end +end + +function Dongle.SetProfile(db, name) + assert(3, databases[db], string.format(L["MUST_CALLFROM_DBOBJECT"], "SetProfile")) + argcheck(name, 2, "string") + + local old = db.profile + local defaults = db.defaults and db.defaults.profile + + if defaults then + -- Remove the defaults from the old profile + removeDefaults(old, defaults) + end + + db.profile = nil + db.keys["profile"] = name + + Dongle:TriggerMessage("DONGLE_PROFILE_CHANGED", db, db.parent, db.sv_name, db.keys.profile) +end + +function Dongle.GetProfiles(db, t) + assert(3, databases[db], string.format(L["MUST_CALLFROM_DBOBJECT"], "GetProfiles")) + argcheck(t, 2, "table", "nil") + + t = t or {} + local i = 1 + for profileKey in pairs(db.sv.profiles) do + t[i] = profileKey + i = i + 1 + end + return t, i - 1 +end + +function Dongle.DeleteProfile(db, name) + assert(3, databases[db], string.format(L["MUST_CALLFROM_DBOBJECT"], "DeleteProfile")) + argcheck(name, 2, "string") + + if db.keys.profile == name then + error(L["CANNOT_DELETE_ACTIVE_PROFILE"], 2) + end + + assert(type(db.sv.profiles[name]) == "table", L["DELETE_NONEXISTANT_PROFILE"]) + + db.sv.profiles[name] = nil + Dongle:TriggerMessage("DONGLE_PROFILE_DELETED", db, db.parent, db.sv_name, name) +end + +function Dongle.CopyProfile(db, name) + assert(3, databases[db], string.format(L["MUST_CALLFROM_DBOBJECT"], "CopyProfile")) + argcheck(name, 2, "string") + + assert(3, db.keys.profile ~= name, L["SAME_SOURCE_DEST"]) + assert(3, type(db.sv.profiles[name]) == "table", string.format(L["PROFILE_DOES_NOT_EXIST"], name)) + + local profile = db.profile + local source = db.sv.profiles[name] + + copyDefaults(profile, source, true) + Dongle:TriggerMessage("DONGLE_PROFILE_COPIED", db, db.parent, db.sv_name, name, db.keys.profile) +end + +function Dongle.ResetProfile(db) + assert(3, databases[db], string.format(L["MUST_CALLFROM_DBOBJECT"], "ResetProfile")) + + local profile = db.profile + + for k,v in pairs(profile) do + profile[k] = nil + end + + local defaults = db.defaults and db.defaults.profile + if defaults then + copyDefaults(profile, defaults) + end + Dongle:TriggerMessage("DONGLE_PROFILE_RESET", db, db.parent, db.sv_name, db.keys.profile) +end + + +function Dongle.ResetDB(db, defaultProfile) + assert(3, databases[db], string.format(L["MUST_CALLFROM_DBOBJECT"], "ResetDB")) + argcheck(defaultProfile, 2, "nil", "string") + + local sv = db.sv + for k,v in pairs(sv) do + sv[k] = nil + end + + local parent = db.parent + + initdb(parent, db.sv_name, db.defaults, defaultProfile, db) + Dongle:TriggerMessage("DONGLE_DATABASE_RESET", db, parent, db.sv_name, db.keys.profile) + Dongle:TriggerMessage("DONGLE_PROFILE_CHANGED", db, db.parent, db.sv_name, db.keys.profile) + return db +end + +function Dongle.RegisterNamespace(db, name, defaults) + assert(3, databases[db], string.format(L["MUST_CALLFROM_DBOBJECT"], "RegisterNamespace")) + argcheck(name, 2, "string") + argcheck(defaults, 3, "nil", "string") + + local sv = db.sv + if not sv.namespaces then sv.namespaces = {} end + if not sv.namespaces[name] then + sv.namespaces[name] = {} + end + + local newDB = initdb(db, sv.namespaces[name], defaults, db.keys.profile) + if not db.children then db.children = {} end + table.insert(db.children, newDB) + return newDB +end + +--[[------------------------------------------------------------------------- + Slash Command System +---------------------------------------------------------------------------]] + +local slashCmdMethods = { + "InjectDBCommands", + "RegisterSlashHandler", + "PrintUsage", +} + +local function OnSlashCommand(cmd, cmd_line) + if cmd.patterns then + for idx,tbl in pairs(cmd.patterns) do + local pattern = tbl.pattern + if string.match(cmd_line, pattern) then + local handler = tbl.handler + if type(tbl.handler) == "string" then + local obj + -- Look in the command object before we look at the parent object + if cmd[handler] then obj = cmd end + if cmd.parent[handler] then obj = cmd.parent end + if obj then + obj[handler](obj, string.match(cmd_line, pattern)) + end + else + handler(string.match(cmd_line, pattern)) + end + return + end + end + end + cmd:PrintUsage() +end + +function Dongle:InitializeSlashCommand(desc, name, ...) + local reg = lookup[self] + assert(3, reg, string.format(L["MUST_CALLFROM_REGISTERED"], "InitializeSlashCommand")) + argcheck(desc, 2, "string") + argcheck(name, 3, "string") + argcheck(select(1, ...), 4, "string") + for i = 2,select("#", ...) do + argcheck(select(i, ...), i+2, "string") + end + + local cmd = {} + cmd.desc = desc + cmd.name = name + cmd.parent = self + cmd.slashes = { ... } + for idx,method in pairs(slashCmdMethods) do + cmd[method] = Dongle[method] + end + + local genv = getfenv(0) + + for i = 1,select("#", ...) do + genv["SLASH_"..name..tostring(i)] = "/"..select(i, ...) + end + + genv.SlashCmdList[name] = function(...) OnSlashCommand(cmd, ...) end + + commands[cmd] = true + + return cmd +end + +function Dongle.RegisterSlashHandler(cmd, desc, pattern, handler) + assert(3, commands[cmd], string.format(L["MUST_CALLFROM_SLASH"], "RegisterSlashHandler")) + + argcheck(desc, 2, "string") + argcheck(pattern, 3, "string") + argcheck(handler, 4, "function", "string") + + if not cmd.patterns then + cmd.patterns = {} + end + + table.insert(cmd.patterns, { + ["desc"] = desc, + ["handler"] = handler, + ["pattern"] = pattern, + }) +end + +function Dongle.PrintUsage(cmd) + assert(3, commands[cmd], string.format(L["MUST_CALLFROM_SLASH"], "PrintUsage")) + + local usage = cmd.desc.."\n".."/"..table.concat(cmd.slashes, ", /")..":\n" + if cmd.patterns then + for idx,tbl in ipairs(cmd.patterns) do + usage = usage.." - "..tbl.desc.."\n" + end + end + cmd.parent:Print(usage) +end + +local dbcommands = { + ["copy"] = { + L["DBSLASH_PROFILE_COPY_DESC"], + L["DBSLASH_PROFILE_COPY_PATTERN"], + "CopyProfile", + }, + ["delete"] = { + L["DBSLASH_PROFILE_DELETE_DESC"], + L["DBSLASH_PROFILE_DELETE_PATTERN"], + "DeleteProfile", + }, + ["list"] = { + L["DBSLASH_PROFILE_LIST_DESC"], + L["DBSLASH_PROFILE_LIST_PATTERN"], + }, + ["reset"] = { + L["DBSLASH_PROFILE_RESET_DESC"], + L["DBSLASH_PROFILE_RESET_PATTERN"], + "ResetProfile", + }, + ["set"] = { + L["DBSLASH_PROFILE_SET_DESC"], + L["DBSLASH_PROFILE_SET_PATTERN"], + "SetProfile", + }, +} + +function Dongle.InjectDBCommands(cmd, db, ...) + assert(3, commands[cmd], string.format(L["MUST_CALLFROM_SLASH"], "InjectDBCommands")) + assert(3, databases[db], string.format(L["BAD_ARGUMENT_DB"], 2, "InjectDBCommands")) + local argc = select("#", ...) + assert(3, argc > 0, L["INJECTDB_USAGE"]) + + for i=1,argc do + local cmdname = string.lower(select(i, ...)) + local entry = dbcommands[cmdname] + assert(entry, L["INJECTDB_USAGE"]) + local func = entry[3] + + local handler + if cmdname == "list" then + handler = function(...) + local profiles = db:GetProfiles() + db.parent:Print(L["DBSLASH_PROFILE_LIST_OUT"] .. "\n" .. strjoin("\n", unpack(profiles))) + end + else + handler = function(...) db[entry[3]](db, ...) end + end + + cmd:RegisterSlashHandler(entry[1], entry[2], handler) + end +end + +--[[------------------------------------------------------------------------- + Internal Message/Event Handlers +---------------------------------------------------------------------------]] + +local function PLAYER_LOGOUT(event) + Dongle:ClearDBDefaults() + for k,v in pairs(registry) do + local obj = v.obj + if type(obj["Disable"]) == "function" then + safecall(obj["Disable"], obj) + end + end +end + +local function PLAYER_LOGIN() + Dongle.initialized = true + for i,obj in ipairs(loadorder) do + if type(obj.Enable) == "function" then + safecall(obj.Enable, obj) + end + end +end + +local function ADDON_LOADED(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 Dongle.initialized and type(obj.Enable) == "function" then + safecall(obj.Enable, obj) + end + loadqueue[i] = nil + end +end + +local function DONGLE_PROFILE_CHANGED(msg, db, parent, sv_name, profileKey) + local children = db.children + if children then + for i,namespace in ipairs(children) do + namespace:SetProfile(profileKey) + end + end +end + +--[[------------------------------------------------------------------------- + DongleStub required functions and registration +---------------------------------------------------------------------------]] + +function Dongle:GetVersion() return major,minor end + +local function Activate(self, old) + if old then + registry = old.registry or registry + lookup = old.lookup or lookup + loadqueue = old.loadqueue or loadqueue + loadorder = old.loadorder or loadorder + events = old.events or events + databases = old.databases or databases + commands = old.commands or commands + messages = old.messages or messages + frame = old.frame or CreateFrame("Frame") + + local reg = self.registry[major] + reg.obj = self + else + frame = CreateFrame("Frame") + + local reg = {obj = self, name = "Dongle"} + registry[major] = reg + lookup[self] = reg + lookup[major] = reg + end + + self.registry = registry + self.lookup = lookup + self.loadqueue = loadqueue + self.loadorder = loadorder + self.events = events + self.databases = databases + self.commands = commands + self.messages = messages + self.frame = frame + + local reg = self.registry[major] + lookup[self] = reg + lookup[major] = reg + + frame:SetScript("OnEvent", OnEvent) + + -- Register for events using Dongle itself + self:RegisterEvent("ADDON_LOADED", ADDON_LOADED) + self:RegisterEvent("PLAYER_LOGIN", PLAYER_LOGIN) + self:RegisterEvent("PLAYER_LOGOUT", PLAYER_LOGOUT) + self:RegisterMessage("DONGLE_PROFILE_CHANGED", DONGLE_PROFILE_CHANGED) + + -- 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 + + -- Convert all slash command methods + for cmd in pairs(commands) do + for idx,method in ipairs(slashCmdMethods) do + cmd[method] = self[method] + end + end +end + +local function Deactivate(self, new) + self:UnregisterAllEvents() + lookup[self] = nil +end + +Dongle = DongleStub:Register(Dongle, Activate, Deactivate) diff --git a/TomTom.lua b/TomTom.lua index 51f8dfb..71c2984 100755 --- a/TomTom.lua +++ b/TomTom.lua @@ -1,23 +1,25 @@ -local L = { +local L = setmetatable({ TOOLTIP_TITLE = "TomTom"; TOOLTIP_SUBTITLE = "Zone Coordinates"; TOOLTIP_LOCKED = "This window is locked in place."; TOOLTIP_LEFTCLICK = "Left-click and drag to move this window."; TOOLTIP_RIGHTCLICK = "Right-click to toggle the options panel."; -} +}, {__index=function(t,k) return k end}) -TomTom = DongleStub("Dongle-Beta0"):New("TomTom") +TomTom = DongleStub("Dongle-Beta1"):New("TomTom") local DongleFrames = DongleStub("DongleFrames-1.0") local Astrolabe = DongleStub("Astrolabe-0.3") local profile -function TomTom:Enable() +function TomTom:Initialize() self.defaults = { profile = { + clearwaypoints = true, show = true, lock = false, worldmap = true, cursor = true, + tooltip = true, alpha = 1, notes = { }, @@ -26,161 +28,253 @@ function TomTom:Enable() self.db = self:InitializeDB("TomTomDB", self.defaults) profile = self.db.profile - self:CreateSlashCommands() + self:CreateCoordWindows() + self:RegisterEvent("PLAYER_LEAVING_WORLD") + self:RegisterEvent("PLAYER_ENTERING_WORLD") + self:RegisterEvent("ZONE_CHANGED_NEW_AREA") + self:RegisterEvent("WORLD_MAP_UPDATE") end -local OnMouseDown = function(self,button) - if button == "LeftButton" and not profile.lock then - self:StartMoving() - self.isMoving = true +function TomTom:CreateCoordWindows() + -- Create the draggable frame, as well as the world map coords + local function OnMouseDown(self,button) + if button == "LeftButton" and not profile.lock then + self:StartMoving() + self.isMoving = true + end end -end -local OnMouseUp = function(self,button) - if self.isMoving then - self:StopMovingOrSizing() - self.isMoving = false + local function OnMouseUp(self,button) + if self.isMoving then + self:StopMovingOrSizing() + self.isMoving = false + end + end + + local function OnEnter(self) + if profile.tooltip then + GameTooltip:SetOwner(self, "ANCHOR_CURSOR") + GameTooltip:SetText(L["TOOLTIP_TITLE"]) + GameTooltip:AddLine(L["TOOLTIP_SUBTITLE"]) + if profile.lock then + GameTooltip:AddLine(L["TOOLTIP_LOCKED"]) + else + GameTooltip:AddLine(L["TOOLTIP_LEFTCLICK"]) + end + GameTooltipTextLeft1:SetTextColor(1,1,1); + GameTooltipTextLeft2:SetTextColor(1,1,1); + GameTooltip:Show() + end end -end -local OnEnter = function(self) - if profile.tooltip then - GameTooltip:SetDefaultAnchor(GameTooltip, self) - GameTooltip:SetText(L["TOOLTIP_TITLE"]) - GameTooltip:AddLine(L["TOOLTIP_SUBTITLE"]) - if profile.lock then - GameTooltip:AddLine(L["TOOLTIP_LOCKED"]) + local function OnLeave(self) + GameTooltip:Hide(); + end + + local function CoordFrame_OnUpdate(self, elapsed) + local c,z,x,y = Astrolabe:GetCurrentPlayerPosition() + local text + if not x or not y then + text = "---" else - GameTooltip:AddLine(L["TOOLTIP_LEFTCLICK"]) + self.Text:SetText(string.format("%.2f, %.2f", x*100, y*100)) end - GameTooltipTextLeft1:SetTextColor(1,1,1); - GameTooltipTextLeft2:SetTextColor(1,1,1); - GameTooltip:Show() end -end -local OnLeave = function(self) - GameTooltip:Hide(); -end + local function WorldMap_OnUpdate(self, elapsed) + local c,z,x,y = Astrolabe:GetCurrentPlayerPosition() -local OnUpdate = function(self, elapsed) - local c,z,x,y = Astrolabe:GetCurrentPlayerPosition() + -- Coordinate calculation code taken from CT_MapMod + local cX, cY = GetCursorPosition() + local ceX, ceY = WorldMapFrame:GetCenter(); + local wmfw, wmfh = WorldMapButton:GetWidth(), WorldMapButton:GetHeight(); + + cX = ( ( ( cX / WorldMapFrame:GetScale() ) - ( ceX - wmfw / 2 ) ) / wmfw + 22/10000 ); + cY = ( ( ( ( ceY + wmfh / 2 ) - ( cY / WorldMapFrame:GetScale() ) ) / wmfh ) - 262/10000 ); + + if not x or not y then + self.Player:SetText("Player: ---") + else + self.Player:SetText(string.format("Player: %.2f, %.2f", x*100, y*100)) + end + + if not cX or not cY then + self.Cursor:SetText("Cursor: ---") + else + self.Cursor:SetText(string.format("Cursor: %.2f, %.2f", cX*100, cY*100)) + end + end + + -- Create TomTomFrame, which is the coordinate display + DongleFrames:Create("n=TomTomFrame#p=UIParent#size=100,32#toplevel#strata=HIGH#mouse#movable#clamp", "CENTER", 0, 0) + TomTomFrame.Text = DongleFrames:Create("p=TomTomFrame#t=FontString#inh=GameFontNormal", "CENTER", 0, 0) + TomTomFrame:SetBackdrop({ + bgFile = "Interface\\ChatFrame\\ChatFrameBackground", + edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border", + edgeSize = 16, + insets = {left = 4, right = 4, top = 4, bottom = 4}, + }) + TomTomFrame:SetBackdropColor(0,0,0,0.4) + TomTomFrame:SetBackdropBorderColor(1,0.8,0,0.8) + TomTomFrame:SetScript("OnMouseDown", OnMouseDown) + TomTomFrame:SetScript("OnMouseUp", OnMouseUp) + TomTomFrame:SetScript("OnHide", OnMouseUp) + TomTomFrame:SetScript("OnEnter", OnEnter) + TomTomFrame:SetScript("OnLeave", OnLeave) + TomTomFrame:SetScript("OnUpdate", CoordFrame_OnUpdate) + + -- Create TomTomWorldFrame, which is anchored to the center of the WorldMap + DongleFrames:Create("n=TomTomWorldFrame#p=WorldMapFrame") + TomTomWorldFrame.Player = DongleFrames:Create("p=TomTomWorldFrame#t=FontString#inh=GameFontHighlightSmall", "BOTTOM", WorldMapPositioningGuide, "BOTTOM", -100, 11) + TomTomWorldFrame.Cursor = DongleFrames:Create("p=TomTomWorldFrame#t=FontString#inh=GameFontHighlightSmall", "BOTTOM", WorldMapPositioningGuide, "BOTTOM", 100, 11) + TomTomWorldFrame:SetScript("OnUpdate", WorldMap_OnUpdate) - if not x or not y then - text = "---" + self.frame = CreateFrame("Frame") +end + +local function MinimapIcon_OnEnter(self) + GameTooltip:SetOwner(self, "ANCHOR_CURSOR") + if self.label then + GameTooltip:SetText("TomTom: " .. self.label .. "\n") else - text = string.format("%.2f, %.2f", x*100, y*100) + GameTooltip:SetText("TomTom Waypoint\n") end - - self.Text:SetText(text) -end - -local MinimapSize = { - indoor = { - [0] = 300, -- scale - [1] = 240, -- 1.25 - [2] = 180, -- 5/3 - [3] = 120, -- 2.5 - [4] = 80, -- 3.75 - [5] = 50, -- 6 - }, - outdoor = { - [0] = 466 + 2/3, -- scale - [1] = 400, -- 7/6 - [2] = 333 + 1/3, -- 1.4 - [3] = 266 + 2/6, -- 1.75 - [4] = 200, -- 7/3 - [5] = 133 + 1/3, -- 3.5 - }, -} -local halfpi = math.pi / 2 + local dist,x,y = Astrolabe:GetDistanceToIcon(self) + + GameTooltip:AddLine(self.coord, 1, 1, 1) + GameTooltip:AddLine(("%s yards away"):format(math.floor(dist)), 1, 1 ,1) + GameTooltip:AddLine(self.zone, 0.7, 0.7, 0.7) + GameTooltip:Show() +end -local function updateAngle(icon) +local function MinimapIcon_OnLeave(self) + GameTooltip:Hide() +end + +local halfpi = math.pi / 2 +local function MinimapIcon_UpdateArrow(self, elapsed) + local icon = self.parent local angle = Astrolabe:GetDirectionToIcon(icon) local x = .03875* math.cos(angle + halfpi) + 0.04875 local y = .03875* math.sin(angle + halfpi) + 0.04875 - icon.arrow:SetPosition(x,y,0) - icon.arrow:SetFacing(angle) + self:SetPosition(x,y,0) + self:SetFacing(angle) end -local function iconOnUpdate(icon, elapsed) - if not icon.arrow then return end +local function MinimapIcon_OnUpdate(self, elapsed) + local edge = Astrolabe:IsIconOnEdge(self) + local dot = self.dot:IsShown() + local arrow = self.arrow:IsShown() - local edge = Astrolabe:IsIconOnEdge(icon) + if edge and not arrow then + self.arrow:Show() + self.dot:Hide() + icon.edge = true + elseif not edge and not dot then + self.dot:Show() + self.arrow:Hide() + icon.edge = false + end - if edge then - icon.arrow:Show() - icon.icon:Hide() - elseif icon.arrow:IsVisible() then - icon.arrow:Hide() - icon.icon:Show() + local dist,x,y = Astrolabe:GetDistanceToIcon(self) + if dist < 11 and profile.clearwaypoints then + -- Clear this waypoint + Astrolabe:RemoveIconFromMinimap(self) + TomTom:PrintF("You have arrived at your location (%s)", self.coord) end end -function TomTom:GetMinimapIcon(desc) - self.mmicons = self.mmicons or {} - - local icon = table.remove(self.mmicons) - if not icon then - icon = CreateFrame("Button", nil, Minimap) - icon:SetHeight(12) - icon:SetWidth(12) - - local texture = icon:CreateTexture() - texture:SetTexture("Interface\\Minimap\\ObjectIcons") - texture:SetTexCoord(0.5, 0.75, 0, 0.25) - texture:SetAllPoints() - icon.icon = texture - icon:SetScript("OnUpdate", iconOnUpdate) - - local frame = CreateFrame("Model", nil, Minimap) - frame:SetHeight(140.8) - frame:SetWidth(140.8) - frame:SetPoint("CENTER", Minimap, "CENTER", 0, 0) - frame:SetModel("Interface\\Minimap\\Rotating-MinimapArrow.mdx") - frame:SetFogColor(0.9999977946281433,0.9999977946281433,0.9999977946281433,0.9999977946281433) - frame:SetFogFar(1) - frame:SetFogNear(0) - frame:SetLight(0,1,0,0,0,1,1,1,1,1,1,1,1) - frame:SetModelScale(.600000023841879) - icon.arrow = frame - local total = 0 - frame:SetScript("OnEnter", function() TomTom:Print("enter") end) - frame:SetScript("OnUpdate", function(self, elapsed) - -- Update arrow direction here - updateAngle(icon) - end) - - table.insert(self.points, icon) - return icon - else - return icon +function TomTom:CreateMinimapIcon(label, x, y) + if not self.minimapIcons then + self.minimapIcons = {} end + + -- Return one from the frame pool, if possible + local icon = table.remove(self.minimapIcons) + if icon then + icon.label = label + icon.coord = string.format("%5.2f, %5.2f", x, y) + return icon + end + + -- Create a new icon with arrow + -- icon.dot is the minimap dot texture + -- icon.arrow is the model used on the edge of the map + -- icon.label will contain the mouseover label + -- icon.coord will contain the text of the coordinates + icon = CreateFrame("Button", nil, Minimap) + icon:SetHeight(12) + icon:SetWidth(12) + + icon.label = label + icon.coord = string.format("%5.2f, %5.2f", x, y) + + local texture = icon:CreateTexture() + texture:SetTexture("Interface\\Minimap\\ObjectIcons") + texture:SetTexCoord(0.5, 0.75, 0, 0.25) + texture:SetAllPoints() + icon.dot = texture + icon:SetScript("OnEnter", MinimapIcon_OnEnter) + icon:SetScript("OnLeave", MinimapIcon_OnLeave) + icon:SetScript("OnUpdate", MinimapIcon_OnUpdate) + + local model = CreateFrame("Model", nil, icon) + model:SetHeight(140.8) + model:SetWidth(140.8) + model:SetPoint("CENTER", Minimap, "CENTER", 0, 0) + model:SetModel("Interface\\Minimap\\Rotating-MinimapArrow.mdx") + model:SetFogColor(0.9999977946281433,0.9999977946281433,0.9999977946281433,0.9999977946281433) + model:SetFogFar(1) + model:SetFogNear(0) + model:SetLight(0,1,0,0,0,1,1,1,1,1,1,1,1) + model:SetModelScale(.600000023841879) + model.parent = icon + icon.arrow = model + model:SetScript("OnUpdate", MinimapIcon_UpdateArrow) + model:Hide() + + return icon end -function TomTom:GetWorldMapIcon() - self.wmicons = self.wmicons or {} - - local icon = table.remove(self.wmicons) - if not icon then - icon = CreateFrame("Button", nil, WorldMapFrame) - icon:SetHeight(12) - icon:SetWidth(12) - local texture = icon:CreateTexture() - texture:SetTexture("Interface\\Minimap\\ObjectIcons") - texture:SetTexCoord(0.5, 0.75, 0, 0.25) - texture:SetAllPoints() - icon.icon = texture - icon:SetScript("OnUpdate", iconOnUpdate) - - --table.insert(self.points, icon) - return icon - else - return icon +local function WorldMapIcon_OnEnter(self) + TomTom:Print(self.coord) +end + +function TomTom:CreateWorldMapIcon(label, x, y) + if not self.worldmapIcons then + self.worldmapIcons = {} end + + -- Return one from the frame pool, if possible + local icon = table.remove(self.worldmapIcons) + if icon then + icon.label = label + icon.coord = string.format("%5.2f, %5.2f", x, y) + return icon + end + + -- Create a new icon with arrow + -- icon.dot is the minimap dot texture + -- icon.label will contain the mouseover label + -- icon.coord will contain the text of the coordinates + icon = CreateFrame("Button", nil, WorldMapDetailFrame) + icon:SetHeight(12) + icon:SetWidth(12) + + icon.label = label + icon.coord = string.format("%5.2f, %5.2f", x, y) + + local texture = icon:CreateTexture() + texture:SetTexture("Interface\\Minimap\\ObjectIcons") + texture:SetTexCoord(0.5, 0.75, 0, 0.25) + texture:SetAllPoints() + icon.dot = texture + icon:SetScript("OnEnter", WorldMapIcon_OnEnter) + + return icon end function TomTom:CreateSlashCommands() @@ -204,106 +298,121 @@ function TomTom:CreateSlashCommands() self:PrintF("The coordinate display has been %s.", profile.lock and "locked" or "unlocked") end - self.cmd:RegisterSlashHandler("Show/Hide the coordinate display", "^(coord)$", ToggleDisplay) - self.cmd:RegisterSlashHandler("Show/Hide the world map coordinate display", "^(mapcoord)$", ToggleDisplay) - self.cmd:RegisterSlashHandler("Lock/Unlock the coordinate display", "^lock$", LockDisplay) + self.cmd:RegisterSlashHandler("coord - Show/Hide the coordinate display", "^(coord)$", ToggleDisplay) + self.cmd:RegisterSlashHandler("mapcoord - Show/Hide the world map coordinate display", "^(mapcoord)$", ToggleDisplay) + self.cmd:RegisterSlashHandler("lock - Lock/Unlock the coordinate display", "^lock$", LockDisplay) -- Waypoint placement slash commands self.cmd_way = self:InitializeSlashCommand("TomTom - Waypoints", "TOMTOM_WAY", "way") - local total = 0 - local Way_Update = function(frame,elapsed) - if total >= 0.2 then - total = total + elapsed - return - else - total = 0 - local c,z,x,y = Astrolabe:GetCurrentPlayerPosition() - local zone = self.points and self.points[c] and self.points[c][z] - if not zone then return end - - for idx,entry in ipairs(zone) do - -- Bail out if we're too high - local range = .001 - local xeq = x <= entry.x + range and x >= entry.x - range - local yeq = y <= entry.y + range and y >= entry.y - range - if xeq and yeq then - self:PrintF("You have reached your destination at %.2f, %.2f.", x * 100 , y * 100) - Astrolabe:RemoveIconFromMinimap(entry.icon) - table.insert(self.mmicons, icon) - table.remove(zone, idx) - frame:SetScript("OnUpdate", nil) - end - end - end - end - local sortFunc = function(a,b) if a.x == b.x then return a.y < b.y else return a.x < b.x end end local Way_Set = function(x,y,desc) - if not self.points then self.points = {} end + if not self.m_points then self.m_points = {} end + if not self.w_points then self.w_points = {} end + + local oc,oz = Astrolabe:GetCurrentPlayerPosition() + SetMapToCurrentZone() + local c,z = Astrolabe:GetCurrentPlayerPosition() + SetMapZoom(oc,oz) + + local m_icon = self:CreateMinimapIcon(desc, x, y) + local w_icon = self:CreateWorldMapIcon(desc, x, y) + --local c,z = Astrolabe:GetCurrentPlayerPosition() x = x / 100 y = y / 100 - - local icon = self:GetMinimapIcon(desc) - local c,z = Astrolabe:GetCurrentPlayerPosition() - Astrolabe:PlaceIconOnMinimap(icon, c, z, x, y) - local icon = self:GetWorldMapIcon(desc) - Astrolabe:PlaceIconOnWorldMap(WorldMapFrame, icon, c, z, x, y) - self:PrintF("Setting a waypoint at %.2f, %.2f.", x * 100, y * 100) + Astrolabe:PlaceIconOnMinimap(m_icon, c, z, x, y) + Astrolabe:PlaceIconOnWorldMap(WorldMapDetailFrame, w_icon, c, z, x, y) + + w_icon:Show() - self.points[c] = self.points[c] or {} - self.points[c][z] = self.points[c][z] or {} + local zone = select(z, GetMapZones(c)) + m_icon.zone = zone + w_icon.zone = zone + + self:PrintF("Setting a waypoint at %.2f, %.2f in %s.", x * 100, y * 100, zone) + + self.m_points[c] = self.m_points[c] or {} + self.m_points[c][z] = self.m_points[c][z] or {} + self.m_points.current = self.m_points[c][z] - table.insert(self.points[c][z], {["x"] = x, ["y"] = y, ["icon"] = icon}) - table.sort(self.points[c][z], sortFunc) - self.frame:SetScript("OnUpdate", Way_Update) + table.insert(self.m_points[c][z], {["x"] = x, ["y"] = y, ["icon"] = m_icon}) + table.sort(self.m_points[c][z], sortFunc) + + table.insert(self.w_points, {["c"] = c, ["z"] = z, ["x"] = x, ["y"] = y, ["icon"] = w_icon}) end local Way_Reset = function() - if #self.points == 0 then + if #self.m_points == 0 then self:Print("There are no waypoints to remove.") return end - - for idx,icon in ipairs(self.points) do - Astrolabe:RemoveIconFromMinimap(icon) - icon:Hide() + + if self.m_points then + for cont,ztbl in pairs(self.m_points) do + for zone,ztbl in pairs(ztbl) do + for idx,entry in pairs(ztbl) do + Astrolabe:RemoveIconFromMinimap(entry.icon) + table.insert(self.minimapIcons, entry.icon) + ztbl[idx] = nil + end + end + end end + + if self.w_points then + for k,v in ipairs(self.w_points) do + local icon = v.icon + icon:Hide() + self.w_points[k] = nil + table.insert(self.worldmapIcons, icon) + end + end + self:Print("All waypoints have been removed.") end - self.cmd_way:RegisterSlashHandler("Remove all current waypoints", "^reset$", Way_Reset) - self.cmd_way:RegisterSlashHandler("Add a new waypoint with optional note", "^(%d*%.?%d*)[%s]+(%d*%.?%d*)[%s]*(.*)", Way_Set) + self.cmd_way:RegisterSlashHandler("reset - Remove all current waypoints", "^reset$", Way_Reset) + self.cmd_way:RegisterSlashHandler(" [] - Add a new waypoint with optional note", "^(%d*%.?%d*)[%s]+(%d*%.?%d*)%s*(.*)", Way_Set) end -function TomTom:CreateFrames() - DongleFrames:Create("n=TomTomFrame#p=UIParent#size=100,32#toplevel#strata=HIGH#mouse#movable#clamp", "CENTER", 0, 0) - TomTomFrame.Text = DongleFrames:Create("p=TomTomFrame#t=FontString#inh=GameFontNormal", "CENTER", 0, 0) - TomTomFrame:SetBackdrop({ - bgFile = "Interface\\ChatFrame\\ChatFrameBackground", - edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border", - edgeSize = 16, - insets = {left = 4, right = 4, top = 4, bottom = 4}, - }) - TomTomFrame:SetBackdropColor(0,0,0,0.4) - TomTomFrame:SetBackdropBorderColor(1,0.8,0,0.8) - TomTomFrame:SetScript("OnMouseDown", OnMouseDown) - TomTomFrame:SetScript("OnMouseUp", OnMouseUp) - TomTomFrame:SetScript("OnHide", OnMouseUp) - TomTomFrame:SetScript("OnEnter", OnEnter) - TomTomFrame:SetScript("OnLeave", OnLeave) - TomTomFrame:SetScript("OnUpdate", OnUpdate) - - DongleFrames:Create("n=TomTomWorldFrame#p=WorldMapFrame") - TomTomWorldFrame.Text = DongleFrames:Create("p=TomTomWorldFrame#t=FontString#inh=GameFontHighlightSmall", "BOTTOM", WorldMapPositioningGuide, "BOTTOM", 0, 11) - TomTomWorldFrame:SetScript("OnUpdate", OnUpdate) - - self.frame = CreateFrame("Frame") +function TomTom:ZONE_CHANGED_NEW_AREA() + -- This could clear the minimap, but I won't.. cause.. I don't like that + -- Not sure what to do here + if true then return end +end + +function TomTom:PLAYER_ENTERING_WORLD() + local oc,oz = Astrolabe:GetCurrentPlayerPosition() + SetMapToCurrentZone() + local c,z,x,y = Astrolabe:GetCurrentPlayerPosition() + SetMapZoom(oc,oz) + + if self.m_points and self.m_points[c] then + for zone,v in pairs(self.m_points[c]) do + for idx,entry in pairs(v) do + Astrolabe:PlaceIconOnMinimap(entry.icon, c, zone, entry.x, entry.y) + end + end + end end -TomTom:CreateFrames() +function TomTom:WORLD_MAP_UPDATE() + if not self.w_points then return end + + local c = GetCurrentMapContinent() + + for idx,entry in ipairs(self.w_points) do + local icon = entry.icon + local x,y = Astrolabe:PlaceIconOnWorldMap(WorldMapDetailFrame, icon, entry.c, entry.z, entry.x, entry.y) + if (x and y and (0 < x and x <= 1) and (0 < y and y <= 1)) then + icon:Show() + else + icon:Hide() + end + end +end -- 1.7.9.5