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 <name> - Copies profile <name> into your current profile.",
+ ["DBSLASH_PROFILE_COPY_PATTERN"] = "^profile copy (.+)$",
+ ["DBSLASH_PROFILE_DELETE_DESC"] = "profile delete <name> - Deletes the profile <name>.",
+ ["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 <name> - Sets the current profile to <name>.",
+ ["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("<xx.xx> <yy.yy> [<desc>] - 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