Quantcast

* Updated to Dongle-Beta1.

James Whitehead Ii [03-30-07 - 03:49]
* Updated to Dongle-Beta1.
* Initial release on WoWinterface.com
Filename
Dongle.lua
TomTom.lua
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