Quantcast

local addonName, addon = ...

_G["StarTip"] = LibStub("AceAddon-3.0"):NewAddon("StarTip", "AceConsole-3.0", "AceHook-3.0", "AceEvent-3.0", "AceTimer-3.0", "AceComm-3.0", "AceSerializer-3.0")
StarTip.version = GetAddOnMetadata("StarTip", "Version") or addonName
StarTip.notes = GetAddOnMetadata("StarTip", "Notes")

local LibDBIcon = LibStub("LibDBIcon-1.0")
local LSM = _G.LibStub("LibSharedMedia-3.0")
local LDB = LibStub:GetLibrary("LibDataBroker-1.1")
local AceConfigDialog = LibStub("AceConfigDialog-3.0")
local L = LibStub("AceLocale-3.0"):GetLocale("StarTip")
StarTip.L = L
local LQT = LibStub:GetLibrary("LibQTip-1.0-fix")
StarTip.LQT = LQT
local LibFlash = LibStub("LibFlash")


local LibCore = LibStub("LibScriptableLCDCoreLite-1.0")
local LibTimer = LibStub("LibScriptableUtilsTimer-1.0")
local PluginTalents = LibStub("LibScriptablePluginTalents-1.0")
local WidgetTimer = LibStub("LibScriptableWidgetTimer-1.0")
local WidgetKey = LibStub("LibScriptableWidgetKey-1.0")
local LibWidget = LibStub("LibScriptableWidget-1.0")

local _G = _G
local GameTooltip = _G.GameTooltip
local ipairs, pairs = _G.ipairs, _G.pairs
local timers = {}
local keys = {}
local timerWidgets = {}
local keyWidgets = {}

local environment = {}
StarTip.environment = environment
environment.StarTip = StarTip
environment._G = _G
environment.L = L

local BugGrabber = BugGrabber

local function disableUT(name, side)
	local UT = StarTip:GetModule("UnitTooltip")
	for k, v in pairs(UT.db.profile.lines) do
		if(v.name == name) then
			UT.db.profile.lines[k].enabled = false
			UT:CreateLines()
			error(format(L["StarTip disabled a tooltip line named %s due to an error in the line's %s segment."], v.name, side))
		end
	end
end

local onError
do
	local lastError = nil
	function onError(event, errorObject)
		for k, v in pairs(addon:GetErrors(BugGrabber:GetSessionId())) do
			if type(v.message) == "string" then
				v.message:gsub('t.*StarTip\.UnitTooltip:(.-):left:.*', function(name)
					disableUT(name, "left")
				end)
				v.message:gsub('t.*StarTip\.UnitTooltip:(.-):right:.*', function(name)
					disableUT(name, "right")
				end)
			end
		end
	end
end


-- Borrowed from BugSack
do
        local errors = {}
        function addon:GetErrors(sessionId)
                -- XXX I've never liked this function, maybe a BugGrabber redesign is in order,
                -- XXX where we have one subtable in the DB per session ID.
                if sessionId then
                        wipe(errors)
                        local db = BugGrabber:GetDB()
                        for i, e in next, db do
                                if sessionId == e.session then
                                        errors[#errors + 1] = e
                                end
                        end
                        return errors
                else
                        return BugGrabber:GetDB()
                end
        end
end

local LDB = LibStub("LibDataBroker-1.1"):NewDataObject("StarTip", {
	type = "data source",
	text = "StarTip",
	icon = "Interface\\Icons\\INV_Chest_Cloth_17",
	OnClick = function() StarTip:OpenConfig() end
})

StarTip.anchors = {
	"CURSOR_TOP",
	"CURSOR_TOPRIGHT",
	"CURSOR_TOPLEFT",
	"CURSOR_BOTTOM",
	"CURSOR_BOTTOMRIGHT",
	"CURSOR_BOTTOMLEFT",
	"CURSOR_LEFT",
	"CURSOR_RIGHT",
	"TOP",
	"TOPRIGHT",
	"TOPLEFT",
	"BOTTOM",
	"BOTTOMRIGHT",
	"BOTTOMLEFT",
	"RIGHT",
	"LEFT",
	"CENTER"
}

StarTip.anchorText = {
	"Cursor Top",
	"Cursor Top-right",
	"Cursor Top-left",
	"Cursor Bottom",
	"Cursor Bottom-right",
	"Cursor Bottom-left",
	"Cursor Left",
	"Cursor Right",
	"Screen Top",
	"Screen Top-right",
	"Screen Top-left",
	"Screen Bottom",
	"Screen Bottom-right",
	"Screen Bottom-left",
	"Screen Right",
	"Screen Left",
	"Screen Center"
}

StarTip.opposites = {
	TOP = "BOTTOM",
	TOPRIGHT = "BOTTOMLEFT",
	TOPLEFT = "BOTTOMRIGHT",
	BOTTOM = "TOP",
	BOTTOMRIGHT = "TOPLEFT",
	BOTTOMLEFT = "TOPRIGHT",
	LEFT = "RIGHT",
	RIGHT = "LEFT",
}

local SINGLETON_CLASSIFICATIONS = {
	"player",
	"pet",
	"pettarget",
	"target",
	"targettarget",
	"targettargettarget",
	"focus",
	"focustarget",
	"focustargettarget"
}
StarTip.SINGLETON_CLASSIFICATIONS = SINGLETON_CLASSIFICATIONS

local UNIT_PARTY_GROUPS = {
	"party",
	"partytarget",
	"partytargettarget",
	"partypet",
	"partypettarget",
	"partypettargettarget"
}
StarTip.UNIT_PARTY_GROUPS = UNIT_PARTY_GROUPS

local UNIT_RAID_GROUPS = {
	"raid",
	"raidtarget",
	"raidtargettarget",
	"raidpet",
	"raidpettarget",
	"raidpettargettarget",
}
StarTip.UNIT_RAID_GROUPS = UNIT_RAID_GROUPS

local defaults = {
	profile = {
		modules = {},
		timers = {},
		keys = {},
		minimap = {hide=true},
		tooltipMain = {frameName="StarTipTooltipMain", intersectFrameName="ChatFrame1", strata=1, level=1, alwaysShown=false, intersect=false, intersectxPad1 = 0, intersectyPad1 = 0, intersectxPad2 = 0, intersectyPad2 = 0, insersectPad = 0, minStrata=5, scriptFrame, hideScript = "self.frame:Hide()", showScript = "self.frame:Show()", hiddenScript = "return not self.frame:IsShown()", shownScript = "return self.frame:IsShown()"},
		modifier = 1,
		unitShow = 1,
		objectShow = 1,
		unitFrameShow = 1,
		otherFrameShow = 1,
		errorLevel = 2,
		throttleVal = 0,
		intersectRate = 300,
		modifierInverse = false,
		message = true,
		backup = true
	}
}

do
	local pool = setmetatable({},{__mode='k'})
	local newCount, delCount = 0, 0
	function StarTip.new(...)
		local t = next(pool)
		local newtbl
		if t then
			pool[t] = nil
			table.wipe(t)
			for i=1, select("#", ...) do
				t[i] = select(i, ...)
			end
		else
			newtbl = true
			t = {...}
		end
		if newtbl then
			--StarTip:Print("new table " .. GetTime(), "new " .. newCount, "del " .. delCount)
		end
		t.__starref__ = true
		newCount = newCount + 1
		return t
	end
	function StarTip.del(...)
		local t = select(1, ...)

		if type(t) ~= "table" or not t.__starref__ then return end

		for i=2, select("#", ...) do
			local t = select(i, ...)
			if type(t) ~= table or t == nil then break end
			StarTip.del(t)
		end
		t.__starref__ = nil
		pool[t] = true
		delCount = delCount + 1
	end
	function StarTip.copy(src, dst)
		local localCopy = dst or {}
		wipe(localCopy)
		for k, v in pairs(src) do
			if type(v) == "table" then
				localCopy[k] = copy(v)
			elseif type(v) == "string" or type(v) == "number"then
				localCopy[k] = v
			end
		end
		localCopy.__starref__ = nil
		return localCopy
	end
end

environment.new = StarTip.new
environment.newDict = StarTip.newDict
environment.del = StarTip.del
environment.copy = StarTip.copy
local new = StarTip.new
local newDict = StarTip.newDict
local del = StarTip.del
local copy = StarTip.copy

local function makeNewTooltip()
	local self = StarTip
	--if self.intersectTimer then self.intersectTimer:Del() end
	self.intersectTimer = LibTimer:New("IntersectTimer", self.db.profile.intersectRate, true, LibWidget.IntersectUpdate, nil, self.db.profile.errorLevel)
	--if self.tooltipMain.widget then self.tooltipMain.widget:Del() end
	self.tooltipMain.widget = LibWidget:New(StarTip.tooltipMain, StarTip, "tooltipMain", StarTip.copy(self.db.profile.tooltipMain), 0, 0, 0, {"generic"}, self.db.profile.errorLevel, StarTip.tooltipMain)
	self.intersectTimer.data = self.tooltipMain.widget

end

local modNames = {L["None"], L["Ctrl"], L["Alt"], L["Shift"]}
local modFuncs = {function() return true end, IsControlKeyDown, IsAltKeyDown, IsShiftKeyDown}

local showChoices = {"Always", "Out of Combat", "Never"}

local options = {
	type = "group",
	args = {
		modules = {
			name = L["Modules"],
			desc = L["Modules"],
			type = "group",
			args = {},
			order = 1
		},
		timers = {
			name = "Timers",
			desc = "Timers",
			type = "group",
			args = {
				add = {
					name = L["Add Timer"],
					desc = L["Add a timer widget"],
					type = "input",
					set = function(info, v)
						tinsert(StarTip.db.profile.timers, {name = v, expression = "return noop", repeating = false, update = 0})
						StarTip:RebuildOpts()
					end,
					order = 1
				},
				restart = {
					name = L["Restart Timers"],
					desc = L["Would you like to restart your timers? Note that this will restart all timers."],
					type = "execute",
					func = function()
						StarTip:RestartTimers()
					end,
					order = 2
				},
				stop = {
					name = L["Stop Keys"],
					desc = L["Would you like to stop your key widgets?"],
					type = "execute",
					func = function()
						StarTip:StopKeys()
					end,
					order = 3
				},
				reset = {
					name = L["Restore Defaults"],
					desc = L["Use this to restore the defaults"],
					type = "execute",
					func = function()
						StarTip.db.profile.timers = {}
						StarTip:RestartTimers()
						StarTip:RebuildOpts()
					end,
					order = 3
				}
			},
			order = 2
		},
		keys = {
			name = L["Keys"],
			desc = L["Keys"],
			type = "group",
			args = {
				add = {
					name = L["Add Key"],
					desc = L["Add a key widget."],
					type = "input",
					set = function(info, v)
						table.insert(StarTip.db.profile.keys, {name = v, expression = "return noop"})
						StarTip:RebuildOpts()
					end,
					order = 1
				},
				reset = {
					name = L["Restore Defaults"],
					desc = L["Use this to restore the defaults"],
					type = "execute",
					func = function()
						StarTip.db.profile.keys = {}
						StarTip:RestartKeys()
						StarTip:RebuildOpts()
					end,
					order = 2
				},
			},
			order = 3
		},
		settings = {
			name = L["Settings"],
			desc = L["Settings"],
			type = "group",
			order = 4,
			args = {
				minimap = {
					name = L["Minimap"],
					desc = L["Toggle showing minimap button"],
					type = "toggle",
					get = function()
						return not StarTip.db.profile.minimap.hide
					end,
					set = function(info, v)
						StarTip.db.profile.minimap.hide = not v
						if not v then
							LibDBIcon:Hide("StarTipLDB")
						else
							LibDBIcon:Show("StarTipLDB")
						end
					end,
					order = 1
				},
				modifier = {
					name = L["Modifier"],
					desc = L["Whether to use a modifier key or not"],
					type = "select",
					values = {L["None"], L["Ctrl"], L["Alt"], L["Shift"]},
					get = function() return StarTip.db.profile.modifier end,
					set = function(info, v) StarTip.db.profile.modifier = v end,
					order = 5
				},
				modifierInverse = {
					name = L["Inverted Modifier"],
					desc = L["Whether to invert what happens when a key is pressed or not, i.e. show and hide."],
					type = "toggle",
					get = function() return StarTip.db.profile.modifierInverse end,
					set = function(info, v) StarTip.db.profile.modifierInverse = v end,
					order = 6
				},
				unitShow = {
					name = L["Unit"],
					desc = L["Whether to show unit tooltips"],
					type = "select",
					values = showChoices,
					get = function() return StarTip.db.profile.unitShow end,
					set = function(info, v) StarTip.db.profile.unitShow = v end,
					order = 7
				},
				objectShow = {
					name = L["Object"],
					desc = L["Whether to show object tooltips"],
					type = "select",
					values = showChoices,
					get = function() return StarTip.db.profile.objectShow end,
					set = function(info, v) StarTip.db.profile.objectShow = v end,
					order = 8
				},
				unitFrameShow = {
					name = L["Unit Frame"],
					desc = L["Whether to show unit frame tooltips"],
					type = "select",
					values = showChoices,
					get = function() return StarTip.db.profile.unitFrameShow end,
					set = function(info, v) StarTip.db.profile.unitFrameShow = v end,
					order = 9
				},
				otherFrameShow = {
					name = L["Other Frame"],
					desc = L["Whether to show other frame tooltips"],
					type = "select",
					values = showChoices,
					get = function() return StarTip.db.profile.otherFrameShow end,
					set = function(info, v) StarTip.db.profile.otherFrameShow = v end,
					order = 10
				},
				errorLevel = {
					name = L["Error Level"],
					desc = L["StarTip's error level"],
					type = "select",
					values = LibStub("LibScriptableUtilsError-1.0").defaultTexts,
					get = function() return StarTip.db.profile.errorLevel end,
					set = function(info, v) StarTip.db.profile.errorLevel = v; StarTip:Print("Note that changing error verbosity requires a UI reload.") end,
					order = 11
				},
				throttleVal = {
					name = L["Throttle Threshold"],
					desc = L["StarTip can throttle your mouseovers, so it doesn't show a tooltip if you mouse over units really fast. There are a few bugs, which is why it's not enabled by default."],
					type = "input",
					pattern = "%d",
					get = function() return tostring(StarTip.db.profile.throttleVal) end,
					set = function(info, v) StarTip.db.profile.throttleVal = tonumber(v) end,
					order = 12
				},
				message = {
					name = L["Greetings"],
					desc = L["Whether the greetings message should be shown or not"],
					type = "toggle",
					get = function() return StarTip.db.profile.message end,
					set = function(info, v) StarTip.db.profile.message = v end,
					order = 13
				},
				intersectRate = {
					name = L["Intersect Check Rate"],
					desc = L["The rate at which intersecting frames will be checked"],
					type = "input",
					pattern = "%d",
					get = function() return tostring(StarTip.db.profile.intersectRate) end,
					set = function(info, v) StarTip.db.profile.intersectRate = tonumber(v); makeNewTooltip() end,
					order = 14
				},
			}
		}
	}
}

local function errorhandler(err)
    return geterrorhandler()(err)
end


StarTip:SetDefaultModuleState(false)

local defaultTimers = {
}

function StarTip:RefreshConfig()
	for k, v in self:IterateModules() do
		if v.ReInit then
			v:ReInit()
		end
	end
	self:RebuildOpts()
	self:Print(L["You may need to reload your UI."])
end

local menuoptions = {
	name = "StarTip",
	type = "group",
	args = {
		open = {
			name = "Open Configuration",
			type = "execute",
			func = function() StarTip:OpenConfig() end
		}
	}
}

function StarTip:OnInitialize()

	if BugGrabber then
		BugGrabber.RegisterCallback(addon, "BugGrabber_BugGrabbed", onError)
		BugGrabber.RegisterCallback(addon, "BugGraabber_EventGrabbed", onError)
	end

	self.db = LibStub("AceDB-3.0"):New("StarTipDB", defaults, "Default")
	self.db.RegisterCallback(self, "OnProfileChanged", "RefreshConfig")
	self.db.RegisterCallback(self, "OnProfileCopied", "RefreshConfig")
	self.db.RegisterCallback(self, "OnProfileReset", "RefreshConfig")

	options.args.settings.args.backup = {
		name = L["Backup"],
		desc = L["Click to generate a backup."],
		type = "execute",
		func = function()
			local tbl = {}
			tbl["StarTip"] = self:Serialize(copy(self.db.profile))
			for k, v in self:IterateModules() do
				if v.db then tbl[k] = self:Serialize(copy(v.db.profile)) end
			end
			self.db.profile.backupText = self:Serialize(tbl)
			self:RebuildOpts()
		end,
		order = 50
	}
	options.args.settings.args.revert = {
		name = L["Revert"],
		desc = L["Click this to revert to the text in the input field labeled \"Replace Database\"."],
		type = "execute",
		func = function()
			local db = self:Unserialize(self.db.profile.replaceDatabase or "")
			self.db.profile.replaceDatabase = nil
			setmetatable(self.db.profile, {__mode="v"})
			self.db.profile = nil
			collectgarbage()
			self.db.profile = self:Unserialize(db["StarTip"] or "")
			db.StarTip = nil
			for k, v in pairs(db) do
				local mod = self:GetModule(k)
				setmetatable(mod.db.profile, {__mode="v"})
				mod.db.profile = self:Unserialize(v)
			end
			StarTip:Print("It is recommended that you /restart the UI.")
		end,
		order = 51
	}
	options.args.settings.args.backupText = {
		name = L["Current Backup"],
		desc = L["Here's your currently stored backup."],
		type = "input",
		get = function() return self.db.profile.backupText end,
		set = function() self.db.profile.backupText = v end,
		order = 52
	}
	options.args.settings.args.replaceDatabase = {
		name = L["Replace Dtatabase"],
		desc = L["Enter serialized databasae string."],
		type = "input",
		get = function() return "" end,
		set = function(info, val)
			self.db.profile.replaceDatabase = val
		end,
		order = 53
	}
	options.args.settings.args.tooltipMain = LibWidget:GetOptions(StarTip.db.profile.tooltipMain, makeNewTooltip)
	options.args.settings.args.tooltipMain.args.add = nil

	LibStub("AceConfig-3.0"):RegisterOptionsTable("StarTip-Addon", options)
	LibStub("AceConfig-3.0"):RegisterOptionsTable("StarTip", menuoptions)
	AceConfigDialog:SetDefaultSize("StarTip-Addon", 800, 450)
	self:RegisterChatCommand("startip", "OpenConfig")
	AceConfigDialog:AddToBlizOptions("StarTip")
	LibDBIcon:Register("StarTipLDB", LDB, self.db.profile.minimap)

	if not options.args.Profiles then
 		options.args.Profiles = LibStub("AceDBOptions-3.0"):GetOptionsTable(self.db)
		self.lastConfig = LibStub("AceConfigDialog-3.0"):AddToBlizOptions("StarTip-Addon", "Profiles", "StarTip-Addon", "Profiles")
	end

	self.leftLines = {}
	self.rightLines = {}
	--self.qtipLines = {}
	for i = 1, 70 do
		GameTooltip:AddDoubleLine(' ', ' ')
		self.leftLines[i] = _G["GameTooltipTextLeft" .. i]
		self.rightLines[i] = _G["GameTooltipTextRight" .. i]
	end
	GameTooltip:ClearLines()

	self.core = LibCore:New(environment, "StarTip", self.db.profile.errorLevel)
	GameTooltip:Show()
	GameTooltip:Hide()

	makeNewTooltip()
end

StarTip.cellProvider, StarTip.cellPrototype = LQT:CreateCellProvider()

function StarTip.cellPrototype:InitializeCell()
	self.fontString = self:CreateFontString()
	self.fontString:SetAllPoints(self)
	self.fontString:SetFontObject(GameTooltipText)
	self.r, self.g, self.b = 1, 1, 1
	local y, x = self:GetPosition()
	if not StarTip.qtipLines[y] then
		StarTip.qtipLines[y] = {}
	end
	StarTip.qtipLines[y][x] = self.fontString
end

function StarTip.cellPrototype:SetupCell(tooltip, value, justification, font, r, g, b)
	local fs = self.fontString
	fs:SetFontObject(font or tooltip:GetFont())
	fs:SetJustifyH(justification)
	fs:SetText(tostring(value))
	self.r, self.g, self.b = r or self.r, g or self.g, b or self.b
	fs:SetTextColor(self.r, self.g, self.b)
	fs:Show()
	return fs:GetStringWidth(), fs:GetStringHeight()
end

function StarTip.cellPrototype:ReleaseCell()
	self.r, self.g, self.b = 1, 1, 1
end

StarTip.tooltipMain = LQT:Acquire("StarTipTooltipMain", 2)
--StarTip.tooltipMain:SetDefaultProvider(StarTip.cellProvider)
_G["StarTipTooltipMain"] = StarTip.tooltipMain
StarTip.tooltipMain:SetParent(UIParent)
StarTip.tooltipMain:ClearAllPoints()
StarTip.tooltipMain:SetPoint("CENTER")
StarTip.tooltipMain.flash = LibFlash:New(StarTip.tooltipMain)
StarTip.tooltipMain.ShowReal = StarTip.tooltipMain.Show
StarTip.tooltipMain.Show = function()
	StarTip.tooltipMain.flash:Stop()
	StarTip.tooltipMain:ShowReal()
	StarTip.tooltipMain:SetAlpha(1)
	if StarTip.tooltipMain.widget.intersect then StarTip.intersectTimer:Start() end
end
StarTip.tooltipMain.HideReal = StarTip.tooltipMain.Hide
StarTip.tooltipMain.Hide = function()
	StarTip.tooltipMain.flash:Stop()
	StarTip.tooltipMain:HideReal()
	StarTip.intersectTimer:Stop()
end
StarTip.tooltipMain.FadeOut = function()
	if StarTip.tooltipMain:IsShown() and StarTip.tooltipMain:GetAlpha() > 0 then
		StarTip.tooltipMain.flash:FadeOut(1, StarTip.tooltipMain:GetAlpha(), 0, StarTip.tooltipMain.Hide)
	end
end

local trunk = {}
local trunkLines = 1
local function trunkUpdate()
	if GameTooltip:NumLines() > trunkLines then
		for i = trunkLines, GameTooltip:NumLines() do
			local r1, g1, b1 = StarTip.leftLines[i]:GetTextColor();
			local r2, g2, b2 = StarTip.rightLines[i]:GetTextColor();
			local txt1 = StarTip.leftLines[i]:GetText()
			local txt2 = StarTip.rightLines[i]:GetText()
			tinsert(trunk, {txt1, r1, g1, b1, txt2, r2, g2, b2})
		end
		trunkLines = GameTooltip:NumLines()
	end
end

function StarTip:TrunkAdd(...)
	tinsert(trunk, StarTip.new(...))
end

function StarTip:TrunkClear()
	for i, v in ipairs(trunk) do
		StarTip.del(v)
	end
	wipe(trunk)
end

StarTip.trunk = trunk
StarTip.trunkTimer = LibTimer:New("Trunk Timer", 300, false, trunkUpdate)

function StarTip:OnEnable()
	if self.db.profile.minimap.hide then
		LibDBIcon:Hide("StarTipLDB")
	else
		LibDBIcon:Show("StarTipLDB")
	end

	GameTooltip:HookScript("OnTooltipSetUnit", self.OnTooltipSetUnit)
	GameTooltip:HookScript("OnTooltipSetItem", self.OnTooltipSetItem)
	GameTooltip:HookScript("OnTooltipSetSpell", self.OnTooltipSetSpell)
	self:RawHookScript(GameTooltip, "OnHide", "OnTooltipHide")
	self:RawHookScript(GameTooltip, "OnShow", "OnTooltipShow")
	self:RawHook(GameTooltip, "FadeOut", "GameTooltipFadeOut", true)
	self:RawHook(GameTooltip, "Hide", "GameTooltipHide", true)
	self:RawHook(GameTooltip, "Show", "GameTooltipShow", true)
	--self:RawHook(GameTooltip, "GetUnit", "GameTooltipGetUnit", true)

	for k,v in self:IterateModules() do
		if (self.db.profile.modules[k]  == nil and not v.defaultOff) or self.db.profile.modules[k] then
			v:Enable()
		end
	end


	self:RestartTimers()
	self:RestartKeys()

	--self:RebuildOpts()

	self:RegisterEvent("MODIFIER_STATE_CHANGED")

	local plugin = {}
	LibStub("LibScriptablePluginColor-1.0"):New(plugin)
	if self.db.profile.message then
		ChatFrame1:AddMessage("|cff751f82" .. L["Welcome to "] .. "|r" .. StarTip.notes .. plugin.Colorize(L[" Type /startip to open config. Alternatively you could press escape and choose the addons menu. Or you can choose to show a minimap icon. You can turn off this message under Settings."], 1, 1, 0))
	end
end

function StarTip:OnDisable()
	LibDBIcon:Hide("StarTipLDB")
	self:Unhook(GameTooltip, "OnTooltipSetUnit")
	self:Unhook(GameTooltip, "OnTooltipSetItem")
	self:Unhook(GameTooltip, "OnTooltipSetSpell")
	self:Unhook(GameTooltip, "OnHide")
	self:Unhook(GameTooltip, "OnShow")
	self:Unhook(GameTooltip, "Show")
	self:Unhook(GameTooltip, "FadeOut")
	self:UnregisterEvent("MODIFIER_STATE_CHANGED")
	for k,v in self:IterateModules() do
		if (self.db.profile.modules[k]  == nil and not v.defaultOff) or self.db.profile.modules[k] then
			v:Disable()
		end
	end
	for i, v in ipairs(timerWidgets) do
		v:Stop()
		v:Del()
	end
	table.wipe(timerWidgets)
	self:StopTimers()
	self:StopKeys()
end

function StarTip:RestartTimers()
	for k, v in ipairs(timerWidgets) do
		v:Stop()
		v:Del()
	end
	table.wipe(timerWidgets)
	for k, v in ipairs(self.db.profile.timers) do
		if v.enabled then
			tinsert(timerWidgets, WidgetTimer:New(self.core, "StarTip.timer." .. k, v, self.db.profile.errorLevel))
			timerWidgets[#timerWidgets]:Start()
		end
	end
end

function StarTip:RestartKeys()
	for k, v in ipairs(keyWidgets) do
		v:Stop()
		v:Del()
	end
	table.wipe(keyWidgets)
	for k, v in ipairs(self.db.profile.keys) do
		if v.enabled then
			local key = WidgetKey:New(self.core, "StarTip.key." .. k, copy(v), self.db.profile.errorLevel)
			table.insert(keyWidgets, key)
			key:Start()
		end
	end
end

function StarTip:StopTimers()
	for k, v in ipairs(timerWidgets) do
		v:Stop()
	end
end

function StarTip:StopKeys()
	for k, v in ipairs(keyWidgets) do
		v:Stop()
	end
end
function StarTip:RebuildOpts()
	for k, v in self:IterateModules() do
		local t = {}
		if type(v.RebuildOpts) == "function" then v:RebuildOpts() end
		options.args.modules.args[v:GetName()] = {
			name = v.name,
			type = "group",
			args = nil
		}

		if v.GetOptions then
			t = v:GetOptions()
			t.optionsHeader = {
				name = L["Settings"],
				type = "header",
				order = 3
			}
			if v.childGroup then
				options.args.modules.args[v:GetName()].childGroups = "tab"
			end
		else
			t = {}
		end

		if v.toggled then
			t.header = {
				name = v.name,
				type = "header",
				order = 1
			}
			t.toggle = {
				name = L["Enable"],
				desc = L["Enable or disable this module"],
				type = "toggle",
				set = function(info,v)
					self.db.profile.modules[k] = v
					if v then
						self:EnableModule(k)
					else
						self:DisableModule(k)
					end
				end,
				get = function() return (self.db.profile.modules[k]  == nil and not v.defaultOff) or self.db.profile.modules[k] end,
				order = 2
			}
			if v.desc then
				t.desc = {
					name = "Description",
					type = "input",
					multiline = true,
					get = function() return v.desc end,
					order = 3
				}
			end
		end
		options.args.modules.args[v:GetName()].args = t
	end
	for k, v in pairs(self.db.profile.timers) do
		options.args.timers.args[v.name or "Timer" .. k] = {
			name = v.name or "Timer" .. k,
			type = "group",
			args = WidgetTimer:GetOptions(v, StarTip.RebuildOpts, StarTip)
		}
		options.args.timers.args[v.name or "Timer" .. k].args.delete = {
			name = L["Delete"],
			desc = L["Delete this timer."],
			type = "execute",
			func = function()
				self.db.profile.timers[k] = nil
				self:RebuildOpts()
			end,
			order = 101
		}
		options.args.timers.args[v.name or "Timer" .. k].args.restart = {
			name = L["Restart Timer"],
			desc = L["Would you like to restart this timer?"],
			type = "execute",
			func = function()
				for i, widget in ipairs(timerWidgets) do
					if v.name == widget.config.name then
						widget:Stop()
						widget:Del()
						wipe(widget)
						timerWidgets[i] = WidgetTimer:New(self.core, "StarTip.timer." .. v.name, copy(v), self.db.profile.errorLevel)
						timerWidgets[i]:Start()
					end
				end
			end,
			order = 100
		}
	end
	for k, v in pairs(self.db.profile.keys) do
		options.args.keys.args[v.name or "Key" .. k] = {
			name = v.name or "Timer" .. k,
			type = "group",
			args = WidgetKey:GetOptions(v, StarTip.RebuildOpts, StarTip)
		}
		options.args.keys.args[v.name or "Key" .. k].args.restart = {
			name = L["Restart Key"],
			desc = L["Would you like to restart this key widget?"],
			type = "execute",
			func = function()
				for i, widget in pairs(keyWidgets) do
					if v.name == widget.config.name then
						widget:Stop()
						widget:Del()
						keyWidgets[i] = WidgetKey:New(self.core, "StarTip.key." .. v.name, copy(v), self.db.profile.errorLevel)
						keyWidgets[i]:Start()
					end
				end
			end,
			order = 100
		}
		options.args.keys.args[v.name or "Key" .. k].args.delete = {
			name = L["Delete"],
			desc = L["Delete this key widget."],
			type = "execute",
			func = function()
				self.db.profile.keys[k] = nil
				self:RebuildOpts()
			end,
			order = 101
		}
	end
	collectgarbage()
end

function StarTip:OpenConfig()
	self:RebuildOpts()
	AceConfigDialog:Open("StarTip-Addon")
end

--[[
function StarTip:HideTooltip()
	GameTooltip:Hide()
	self.tooltipHidden = true
end
--]]

function StarTip:ShowTooltip(unit)
	self.tooltipHidden = false
	unit = unit or StarTip.unit or "mouseover"
	GameTooltip:Hide()
	GameTooltip_SetDefaultAnchor(GameTooltip, UIParent)
	GameTooltip:SetUnit(unit)
	GameTooltip:Show()
end


function StarTip.GameTooltipAddLine(...)
end

local hideTimer
local function hideTooltip()
	if StarTip.unit ~= "mouseover" and GetMouseFocus() == WorldFrame then StarTip.unit = "mouseover"; return end
	hideTimer:Start()
end

--[[
local throttleTimer
local lastTime = GetTime()
local function endThrottle()
	if UnitExists(StarTip.unit or "mouseover") then
		StarTip.OnTooltipSetUnit()
	end
end
]]

function StarTip.OnTooltipSetUnit(...)

	local _, unit = GameTooltip:GetUnit()

	if not unit then return end

	StarTip:TrunkClear()
	trunkLines = GameTooltip:NumLines()

--[[
	hideTimer = hideTimer or LibTimer:New("StarTip.Hide", 100, false, hideTooltip, nil, StarTip.db.profile.errorLevel)
	hideTimer:Start()

	throttleTimer = throttleTimer or LibTimer:New("StarTip.Throttle", StarTip.db.profile.throttleVal, false, endThrottle, nil, StarTip.db.profile.errorLevel)
	if GetTime() < lastTime + StarTip.db.profile.throttleVal and UnitIsPlayer("mouseover") and StarTip.db.profile.throttleVal > 0 then
		throttleTimer:Start();
		GameTooltip:Hide()
		return
	end
	lastTime = GetTime()
]]
	if unit ~= "mouseover" and UnitIsUnit(unit, "mouseover") then
		unit = "mouseover"
	end

	StarTip.owner = GameTooltip:GetOwner()
	StarTip.unit = unit
	environment.unit = unit

	if not StarTip.justSetUnit then
		for k, v in StarTip:IterateModules() do
			if v.SetUnit and v:IsEnabled() then v:SetUnit() end
		end
	end

	StarTip.justSetUnit = false

	StarTip.tooltipMain:Show()
	StarTip.trunkTimer:Start()
end

function StarTip.OnTooltipSetItem(self, ...)
	if StarTip.tooltipHidden then return end
	if not StarTip.justSetItem then
		for k, v in StarTip:IterateModules() do
			if v.SetItem and v:IsEnabled() then v:SetItem(...) end
		end
	end
	StarTip.justSetItem = nil
end

function StarTip.OnTooltipSetSpell(...)
	if StarTip.tooltipHidden then return end
	if not StarTip.justSetSpell then
		for k, v in StarTip:IterateModules() do
			if v.SetSpell and v:IsEnabled() then v:SetSpell(...) end
		end
	end
	StarTip.justSetSpell = nil
end

function StarTip:HideAll()
	for k, v in StarTip:IterateModules() do
		if v.OnHide then
			v:OnHide()
		end
	end
end

function StarTip:GameTooltipHide(...)
	local hide = true

	--[[
	for k, v in StarTip:IterateModules() do
		if v.GameTooltipHide and v:IsEnabled() then
			hide = hide and v:GameTooltipHide(...)
		end
	end
	]]

	if hide then
		StarTip.hooks[GameTooltip].Hide(...)
	end
end

function StarTip:OnTooltipHide(...)
	if not self.justHide then
		for k, v in self:IterateModules() do
			if v.OnHide and v:IsEnabled() then v:OnHide(...) end
		end
	end
	self.justHide = nil
	self.unit = false
	return self.hooks[GameTooltip].OnHide(...)
end


function StarTip:GameTooltipShow(...)

	local show = true
	if StarTip.db.profile.modifier > 1 and type(modFuncs[StarTip.db.profile.modifier]) == "function" then
		if not modFuncs[StarTip.db.profile.modifier]() then
			show = false
		end
	end
	if show ~= false then
			if GameTooltip:IsOwned(UIParent) then
				if GameTooltip:GetUnit() then
					-- world unit
					show = StarTip.db.profile.unitShow
				else
					-- world object
					show = StarTip.db.profile.objectShow
				end
			else
				if GameTooltip:GetUnit() then
					-- unit frame
					show = StarTip.db.profile.unitFrameShow
				else
					-- non-unit frame
					show = StarTip.db.profile.otherFrameShow
				end
			end

			if show == 1 then -- always shown
				show = true
			elseif show == 2 then -- only show out of combat
				if InCombatLockdown() then
					show = false
				else
					show = true
				end
			elseif show == 3 then -- never show
				show = false
			end
	end

	if self.db.profile.modifierInverse then
		--show = not show
	end

	if not show or StarTip.tooltipHidden then GameTooltip:Hide(); return end

	for k, v in StarTip:IterateModules() do
		if v.GameTooltipShow and v:IsEnabled() then
			show = show and v:GameTooltipShow(...)
		end
	end

	if show then
		StarTip.hooks[GameTooltip].Show(...)
	end
end

--[[
function StarTip:GameTooltipGetUnit()
	local name, unit = self.hooks[GameTooltip].GetUnit(GameTooltip)
	if name then
		return name, unit
	end
	if StarTip.unit and UnitExists(StarTip.unit) then
		local name = UnitName(StarTip.unit)
		return name, StarTip.unit
	end
end
]]

function StarTip.OnTooltipShow(...)

	if not StarTip.justShow then
		for k, v in StarTip:IterateModules() do
			if v.OnShow and v:IsEnabled() then v:OnShow(...) end
		end
	end

	StarTip.justShow = false

	return StarTip.hooks[GameTooltip].OnShow(...)
end

function StarTip:GameTooltipFadeOut(...)
	local fadeOut = true
	for k, v in StarTip:IterateModules() do
		if v.OnFadeOut and v:IsEnabled() then
			fadeOut = fadeOut and v:OnFadeOut(...)
		end
	end
	if fadeOut then
		StarTip.hooks[GameTooltip].FadeOut(...)
	end
end

function StarTip:GetLSMIndexByName(category, name)
	for i, v in ipairs(LSM:List(category)) do
		if v == name then
			return i
		end
	end
end

function StarTip:SetOptionsDisabled(t, bool)
	for k, v in pairs(t) do
		if not v.args then
			if k ~= "toggle" then v.disabled = bool end
		else
			self:SetOptionsDisabled(v.args, bool)
		end
	end
end

-- Taken from CowTip
--[[
function StarTip:GetMouseoverUnit()
	local _, tooltipUnit = GameTooltip:GetUnit()
	if not tooltipUnit or not UnitExists(tooltipUnit) or UnitIsUnit(tooltipUnit, "mouseover") then
		return "mouseover"
	else
		return tooltipUnit
	end
end
]]

-- Taken from CowTip and modified a bit
function StarTip:MODIFIER_STATE_CHANGED(ev, modifier, up, ...)
	for i, v in self:IterateModules() do
		if v.MODIFIER_STATE_CHANGED then
			v:MODIFIER_STATE_CHANGED(ev, modifier, up, ...)
		end
	end
	local mod
	if self.db.profile.modifier == 2 then
		mod = (modifier == "LCTRL" or modifier == "RCTRL") and "LCTRL"
		modifier = "LCTRL"
	elseif self.db.profile.modifier == 3 then
		mod = (modifier == "LALT" or modifier == "RALT") and "LALT"
		modifier = "LALT"
	elseif self.db.profile.modifier == 4 then
		mod = (modifier == "LSHIFT" or modifier == "RSHIFT") and "LSHIFT"
		modifier = "LSHIFT"
	end

	if mod ~= modifier then
		return
	end

	if up == 0 then
		if not self.db.profile.modifierInverse then
			StarTip:HideTooltip()
		else
			StarTip:ShowTooltip()
		end
		return
	end

	local mouseover_unit = StarTip:GetMouseoverUnit()

	local frame = GetMouseFocus()
	if frame == WorldFrame or frame == UIParent then
		if not UnitExists(mouseover_unit) then
			GameTooltip:Hide()
			return
		end
		GameTooltip:Hide()
		if not self.db.profile.modifierInverse then
			StarTip:ShowTooltip()
		end
	else
		-- TODO: Translate that into 4.0 standards.
		local OnLeave, OnEnter = frame:GetScript("OnLeave"), frame:GetScript("OnEnter")
		if OnLeave then
			_G.this = frame
			OnLeave(frame)
			_G.this = nil
		end
		if OnEnter then
			_G.this = frame
			OnEnter(frame)
			_G.this = nil
		end
	end
end