--[[
	Configator - A library to help you create a gui config
	Version: <%version%> (<%codename%>)
	Revision: $Id$
	URL: http://auctioneeraddon.com/dl/

	License:
		This library is free software; you can redistribute it and/or
		modify it under the terms of the GNU Lesser General Public
		License as published by the Free Software Foundation; either
		version 2.1 of the License, or (at your option) any later version.

		This library is distributed in the hope that it will be useful,
		but WITHOUT ANY WARRANTY; without even the implied warranty of
		MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
		Lesser General Public License for more details.

		You should have received a copy of the GNU Lesser General Public
		License along with this library; if not, write to the Free Software
		Foundation, Inc., 51 Franklin Street, Fifth Floor,
		Boston, MA  02110-1301  USA

	Additional:
		Regardless of any other conditions, you may freely use this code
		within the World of Warcraft game client.

--------------------------------------------------------------------------

USAGE:

	Stub   Configator = LibStub:GetLibrary("Configator")
	Call   myCfg = Configator:Create(setterFunc, getterFunc)
	Call   tabId = myCfg:AddTab(TabName)
	Call   myCfg:AddControl(tabId, controlType, leftPct, ...)
	Wait   for callbacks on your getters and setters

	Your setter will be called with (variableName, value) for you to set
	Your getter will be called with (variableName) for your to return the current value

	The AddControl function's ... varies depending on the controlType:
	"Header" == text
	"Subhead" == text
	"Checkbox" == level, setting, label
	"Slider", "TinySlider", "WideSlider", "NumeriSlider", "NumeriTiny", "NumeriWide"
	   == level, setting, min, max, step, label, fmtfunc
	"Text" = level, setting, label
	"NumberBox", "TinyNumber"
	   == level, setting, minVal, maxVal, label
	"MoneyFrame", "PinnedMoney" = level, setting, label
	"ColorSelect", "ColorSelectAlpha" == level, setting, label


	Settings and configuration system.
]]

local LIBRARY_VERSION_MAJOR = "Configator"
local LIBRARY_VERSION_MINOR = 37
local lib = LibStub:NewLibrary(LIBRARY_VERSION_MAJOR, LIBRARY_VERSION_MINOR)
if not lib then return end

LibStub("LibRevision"):Set("$URL$","$Rev$","5.1.DEV.", 'auctioneer', 'libs')

local kit = {}

if not lib.frames then lib.frames = {} end
if not lib.tmpId then lib.tmpId = 0 end

-- Table management functions:
local function replicate(source, depth, history)
	if type(source) ~= "table" then return source end
	assert(depth==nil or tonumber(depth), "Unknown depth: " .. tostring(depth))
	if not depth then depth = 0 history = {} end
	assert(history, "Have depth but without history")
	assert(depth < 100, "Structure is too deep")
	local dest = {} history[source] = dest
	for k, v in pairs(source) do
		if type(v) == "table" then
			if history[v] then dest[k] = history[v]
			else dest[k] = replicate(v, depth+1, history) end
		else dest[k] = v end
	end
	return dest
end
local function empty(item)
	if type(item) ~= 'table' then return end
	for k,v in pairs(item) do item[k] = nil end
end
local function fill(item, ...)
	if type(item) ~= 'table' then return end
	if (#item > 0) then empty(item) end
	local n = select('#', ...)
	for i = 1,n do item[i] = select(i, ...) end
end
-- End table management functions

function lib:CreateAnonName()
	lib.tmpId = lib.tmpId + 1
	return "ConfigatorAnon"..lib.tmpId
end

local function setFocus(focusType, object, focus)
	if focusType == "NEXT" then
		object.nextFocus = focus
		if (object.isMoneyFrame) then
			MoneyInputFrame_SetNextFocus(object, focus)
		end
	elseif focusType == "PREV" then
		object.previousFocus = focus
		if (object.isMoneyFrame) then
			MoneyInputFrame_SetPreviousFocus(object, focus)
		end
	end
end

function lib:TabLink(frame, el, noHook)
	if not frame.firstFocus then
		frame.firstFocus = el
		setFocus("NEXT", el, el)
		setFocus("PREV", el, el)
	else
		local curLast = frame.lastFocus
		local first = frame.firstFocus

		setFocus("NEXT", curLast, el)
		setFocus("PREV", first, el)
		setFocus("NEXT", el, first)
		setFocus("PREV", el, curLast)
	end
	frame.lastFocus = el

	if not noHook and not el.isMoneyFrame then
		el:SetScript("OnTabPressed", kit.FocusShift)
	end
end

local function proxyHide(self)
	self.gui:Hide()
end

local function proxyIsShown(self)
	return self.gui.Backdrop:IsVisible()
end


function lib:Create(setter, getter, dialogWidth, dialogHeight, gapWidth, gapHeight, topOffset, leftOffset)
	local id = #(lib.frames) + 1
	local name = "ConfigatorDialog_"..id

	if not dialogWidth then dialogWidth = 800 end
	if not dialogHeight then dialogHeight = 450 end
	if not gapWidth then gapWidth = 0 end
	if not gapHeight then gapHeight = 0 end

	local gui = CreateFrame("Frame", name, UIParent)
	table.insert(lib.frames, gui)
	gui.setter = setter
	gui.getter = getter
	gui.dialogWidth  = dialogWidth
	gui.dialogHeight = dialogHeight
	gui.gapWidth     = gapWidth
	gui.gapHeight    = gapHeight
	gui.topOffset    = topOffset
	gui.leftOffset   = leftOffset
	gui.heightDelta  = 0
	gui.buttonTop    = 0

	local top = getter("configator.top")
	local left = getter("configator.left")
	if (top and left) then
		gui:SetPoint("TOPLEFT", "UIParent", "BOTTOMLEFT", left, top)
	else
		gui:SetPoint("CENTER", "UIParent", "CENTER")
	end
	gui:Hide()
	gui:SetFrameStrata("HIGH")
	gui:SetToplevel(true)
	gui:SetMovable(true)
	gui:SetWidth(dialogWidth)
	gui:SetHeight(dialogHeight)
	gui:EnableMouse(true)

	gui.Backdrop = CreateFrame("Frame", name.."Backdrop", gui, BackdropTemplateMixin and "BackdropTemplate")
	gui.Backdrop:SetAllPoints(gui)
	gui.Backdrop:SetBackdrop({
		bgFile = "Interface/Tooltips/ChatBubble-Background",
		edgeFile = "Interface/Tooltips/ChatBubble-BackDrop",
		tile = true, tileSize = 32, edgeSize = 32,
		insets = { left = 32, right = 32, top = 32, bottom = 32 }
	})
	gui.Backdrop:SetBackdropColor(0, 0, 0, 1)

	-- Create a proxy to see if we should react to global CloseWindow() calls
	gui.Proxy = CreateFrame("Frame", name.."Proxy", gui)
	gui.Proxy.gui = gui
	table.insert(UISpecialFrames, name.."Proxy") -- make frames Esc Sensitive by default
	gui.Proxy.Hide = proxyHide
	gui.Proxy.IsShown = proxyIsShown

	gui.Done = CreateFrame("Button", nil, gui.Backdrop, "UIPanelButtonTemplate")
	gui.Done:SetSize(90, 21)
	gui.Done:SetPoint("BOTTOMRIGHT", gui, "BOTTOMRIGHT", -10, 10)
	gui.Done:SetScript("OnClick", function() gui:Hide() end)
	gui.Done:SetText(DONE)

	gui.DragTop = CreateFrame("Button", nil, gui.Backdrop)
	gui.DragTop:SetPoint("TOPLEFT", gui.Backdrop, "TOPLEFT", 10,-5)
	gui.DragTop:SetPoint("TOPRIGHT", gui.Backdrop, "TOPRIGHT", -10,-5)
	gui.DragTop:SetHeight(6)
	gui.DragTop:SetHighlightTexture("Interface\\FriendsFrame\\UI-FriendsFrame-HighlightBar")

	gui.DragTop:SetScript("OnMouseDown", function() gui:StartMoving() end)
	gui.DragTop:SetScript("OnMouseUp", function() gui:StopMovingOrSizing() setter("configator.left", gui:GetLeft()) setter("configator.top", gui:GetTop()) end)

	gui.DragBottom = CreateFrame("Button", nil, gui.Backdrop)
	gui.DragBottom:SetPoint("BOTTOMLEFT", gui.Backdrop, "BOTTOMLEFT", 10,5)
	gui.DragBottom:SetPoint("BOTTOMRIGHT", gui.Backdrop, "BOTTOMRIGHT", -10,5)
	gui.DragBottom:SetHeight(6)
	gui.DragBottom:SetHighlightTexture("Interface\\FriendsFrame\\UI-FriendsFrame-HighlightBar")

	gui.DragBottom:SetScript("OnMouseDown", function() gui:StartMoving() end)
	gui.DragBottom:SetScript("OnMouseUp", function() gui:StopMovingOrSizing() setter("configator.left", gui:GetLeft()) setter("configator.top", gui:GetTop()) end)

	gui:RegisterEvent("PLAYER_LOGOUT")
	gui:SetScript("OnEvent", function() gui:SetClampedToScreen(1) setter("configator.left", gui:GetLeft()) setter("configator.top", gui:GetTop()) end)

	gui.config = {
		current = "",
		order = { "" },
		tabs = { [""] = {}, },
		cats = { [""] = {}, },
	}
	gui.tabs = {}
	gui.elements = {}

	for k,v in pairs(kit) do
		gui[k] = v
	end

	return gui
end


-- this shows the game toolip for a specified link
function lib:SetLinkTip(frame, link)
	if not frame or link == nil then
		GameTooltip:Hide()
		return
	end
	GameTooltip:SetOwner(frame, "ANCHOR_NONE")
	GameTooltip:SetHyperlink(link)
	GameTooltip:ClearAllPoints()
	GameTooltip:SetPoint("TOPRIGHT", frame, "TOPLEFT", -10, -20)
	GameTooltip:Show()
end

-- Create a special tooltip just for us
if not lib.tooltip then
	lib.tooltip = CreateFrame("GameTooltip", "ConfigatorTipTooltip", UIParent, "GameTooltipTemplate")
	local function hide_tip()
		lib.tooltip:Hide()
	end
	lib.tooltip.fadeInfo = {}
	function lib:SetTip(frame, ...)
		local n = select("#", ...)
		if n == 1 then
			-- Allow passing of tip lines as a single table
			local tip = select(1, ...)
			if type(tip) == "table" then
				lib:SetTip(frame, unpack(tip))
				return
			end
		end

		if not frame or n == 0 then
			lib.tooltip.fadeInfo.finishedFunc = hide_tip
			local curAlpha = lib.tooltip:GetAlpha()
			UIFrameFadeOut(lib.tooltip, 0.25, curAlpha, 0)
			lib.tooltip:SetAlpha(curAlpha)
			lib.tooltip.schedule = nil
			return
		end

		if lib.tooltip:GetAlpha() > 0 then
			-- Speed up this fade
			UIFrameFadeOut(lib.tooltip, 0.01, 0, 0)
			lib.tooltip:SetAlpha(0)
		end

		lib.tooltip:SetOwner(frame, "ANCHOR_NONE")
		lib.tooltip:ClearLines()

		local tip
		for i=1, n do
			tip = select(i, ...)
			lib.tooltip:AddLine(tostring(tip) or "", 1,1,0.5, 1)
		end
		lib.tooltip:Show()
		lib.tooltip:SetAlpha(0)
		lib.tooltip:SetPoint("TOP", frame, "BOTTOM", 10, -5)
		lib.tooltip.schedule = GetTime() + 1
	end
	lib.tooltip:SetScript("OnUpdate", function()
		if lib.tooltip.schedule and GetTime() > lib.tooltip.schedule then
			local curAlpha = lib.tooltip:GetAlpha()
			UIFrameFadeIn(lib.tooltip, 0.33, curAlpha, 1)
			lib.tooltip:SetAlpha(curAlpha) -- Tooltips set alpha when they are shown, and UIFrameFadeIn does a :Show()
			lib.tooltip.schedule = nil
		end
	end)
	if lib.tooltip.NineSlice then
		local layout = NineSliceUtil.GetLayout("ChatBubble")
		if layout then
			NineSliceUtil.ApplyLayout(lib.tooltip.NineSlice, layout)
		end
	elseif lib.tooltip.SetBackdrop then
		lib.tooltip:SetBackdrop({
			bgFile = "Interface/Tooltips/ChatBubble-Background",
			edgeFile = "Interface/Tooltips/ChatBubble-BackDrop",
			tile = true, tileSize = 32, edgeSize = 32,
			insets = { left = 32, right = 32, top = 32, bottom = 32 }
		})
	end
	lib.tooltip:SetClampedToScreen(true)
end

-- Create our help window
if not lib.help then
	lib.help = CreateFrame("Frame", "ConfigatorHelpFrame", UIParent, BackdropTemplateMixin and "BackdropTemplate")
	lib.help:SetBackdrop({
		bgFile = "Interface/Stationery/StationeryTest1",
		edgeFile = "Interface/TUTORIALFRAME/TUTORIALFRAMEBORDER",
		tile = false, tileSize = 32, edgeSize = 32,
		insets = { left = 4, right = 4, top = 22, bottom = 4 }
	})
	lib.help:SetBackdropColor(0.15,0.15,0.15, 0.98)
	lib.help:SetToplevel(true)
	lib.help:SetPoint("CENTER")
	lib.help:SetWidth(450)
	lib.help:SetHeight(500)
	lib.help:SetMovable(true)
	lib.help:EnableMouse(true)
	lib.help:SetScript("OnUpdate", function() if lib.help.refresh then lib.help.Update() end end)
	lib.help:SetScript("OnEvent", function() if lib.help:IsVisible() then lib.help.Update() end end)
	lib.help:RegisterEvent("UPDATE_FLOATING_CHAT_WINDOWS")
	lib.help:Hide()

	lib.help.title = CreateFrame("Button", nil, lib.help)
	lib.help.title:SetScript("OnMouseDown", function() lib.help:StartMoving() end)
	lib.help.title:SetScript("OnMouseUp", function() lib.help:StopMovingOrSizing() end)
	lib.help.title:SetPoint("TOPLEFT", lib.help, "TOPLEFT", 3,-3)
	lib.help.title:SetPoint("BOTTOMRIGHT", lib.help, "TOPRIGHT", -28,-20)
	lib.help.title:SetHighlightTexture("Interface\\FriendsFrame\\UI-FriendsFrame-HighlightBar")
	if ( lib.help.title.SetTextFontObject ) then lib.help.title.SetNormalFontObject = lib.help.title.SetTextFontObject end -- WotLK Hack
	lib.help.title:SetNormalFontObject("GameFontNormal")
	lib.help.title:SetText("Help Window")

	lib.help.close = CreateFrame("Button", nil, lib.help, "UIPanelCloseButton")
	lib.help.close:SetPoint("TOPRIGHT", lib.help, "TOPRIGHT", 3, 5)
	lib.help.close:SetHeight(32)
	lib.help.close:SetWidth(32)
	lib.help.close:SetScript("OnClick", function () lib.help:Hide() end)

	lib.help.content = CreateFrame("Frame", "ConfigatorHelpContent", lib.help)
	lib.help.content:SetWidth(420)
	lib.help.content:SetHeight(100)
	lib.help.content.totalHeight = 0

	lib.help.rows = {}
	lib.help.fontcache = {}
	function lib.help:AddHelp(question, answer)
		lib.help:AddRow(question, 14, 1,0.9,0, 8)
		lib.help:AddRow(answer, 12, 1,1,1)
	end
	function lib.help:AddRow(text, size, r,g,b, pad)
		if not size then size = 12 end
		if not (r and g and b) then r,g,b = 1,1,1 end
		if not pad then pad = 0 end

		local font
		local remain = #lib.help.fontcache
		if remain > 0 then
			font = lib.help.fontcache[remain]
			table.remove(lib.help.fontcache)
		else
			font = lib.help.content:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
		end
		local file = font:GetFont()

		font:SetFont(file, size)
		font:SetTextColor(r,g,b,1)
		font:SetText(text)
		font.pad = pad

		table.insert(lib.help.rows, font)

		font:SetPoint("TOPLEFT", lib.help.content, "TOPLEFT", 5, -5)
		font:SetPoint("TOPRIGHT", lib.help.content, "TOPRIGHT", -5, -5)
		font:SetWidth(lib.help.content:GetWidth()-10)

		font:SetJustifyV("TOP")
		font:SetJustifyH("LEFT")

		font:Show()

		lib.help.refresh = true
	end
	function lib.help:ClearAllLines()
		lib.help.content.totalHeight = 0
		for i=#lib.help.rows, 1, -1 do
			lib.help.rows[i]:Hide()
			lib.help.rows[i]:ClearAllPoints()
			table.insert(lib.help.fontcache, lib.help.rows[i])
			table.remove(lib.help.rows, i)
		end
		lib.help.refresh = true
	end
	function lib.help:Update()
		local height, top = 0, 0

		for i = 1, #lib.help.rows do
			local font = lib.help.rows[i]
			if i == 1 then
				top = -5
				height = height + 5
			else
				top = -height - 2
				height = height + 2
			end

			top = top - font.pad
			height = height + font.pad

			font:SetPoint("TOPLEFT", lib.help.content, "TOPLEFT", 5, top)
			font:SetPoint("TOPRIGHT", lib.help.content, "TOPRIGHT", -5, top)
			font:SetWidth(lib.help.content:GetWidth()-10)

			height = height + font:GetHeight()
		end
		lib.help.content:SetHeight(height+10)
		lib.help.scroll:Update()
		lib.help.refresh = nil
	end

	function lib.help:Activate()
		local qlist = self.qlist
		local faq = self.faq
		local faa = self.faa
		local qid

		lib.help:ClearAllLines()
		for i = 1, #qlist do
			qid = qlist[i]
			lib.help:AddHelp(faq[qid], faa[qid])
		end
		lib.help:SetFrameStrata(self:GetFrameStrata())
		lib.help.refresh = true
		lib.help:Show()
	end

	local PanelScroller = LibStub:GetLibrary("PanelScroller")
	lib.help.scroll = PanelScroller:Create(lib.CreateAnonName(), lib.help)
	lib.help.scroll:SetPoint("TOPLEFT", lib.help, "TOPLEFT", 10,-25)
	lib.help.scroll:SetPoint("BOTTOMRIGHT", lib.help, "BOTTOMRIGHT", -25,5)
	lib.help.scroll:SetScrollChild(lib.help.content)
	lib.help.scroll:SetScrollBarVisible("HORIZONTAL", "NO")
end

nConf = lib

-- Local function to get the UI object type
local function isGuiObject(obj)
	if not obj then return false end
	if type(obj) ~= "table" then return false end
	if not obj[0] or type(obj[0]) ~= "userdata" then return false end
	if not obj.GetObjectType then return false end
	return obj:GetObjectType()
end

function kit:ClickButton(pos)
	local button = self:GetButton(pos)
	local tabName = button.tabName
	local catId = button.catId
	if button.tabName then
		self:SelectTab(catId, tabName)
	elseif button.catId then
		self:SelectCat(catId)
	end
end

function kit:SelectTab(catId, tabName)
	if self.config.tabs[catId] then
		local id = self.config.tabs[catId][tabName]
		if id then
			self:ActivateTab(id)
		end
	end
end

function kit:SelectCat(catId)
	if self.config.cats[catId] then
		self.config.cats[catId].isOpen = not self.config.cats[catId].isOpen
		self:RegenTabs()
	end
end

local buttonKit = {}
function buttonKit:SetText(text, active)
	self.text:SetText(text)
	if active then
		self.text:SetTextColor(1,0.8,0.1)
	else
		self.text:SetTextColor(0.9,0.9,0.9)
	end
end
function buttonKit:SetArrowDirection(direction)
--	self.text:ClearAllPoints()
	local file = self.text:GetFont()
	if direction == "RIGHT" then
		self.text:SetPoint("LEFT", self, "LEFT", 25,0)
		self.text:SetFont(file, 12)
		self.expand:SetTexCoord(1,1, 0,1, 1,0, 0,0)
		self.expand:Show()
	elseif direction == "DOWN" then
		self.text:SetPoint("LEFT", self, "LEFT", 25,0)
		self.text:SetFont(file, 12)
		self.expand:SetTexCoord(0,1, 0,0, 1,1, 1,0)
		self.expand:Show()
	else
		self.text:SetPoint("LEFT", self, "LEFT", 18,0)
		self.text:SetFont(file, 11)
		self.expand:Hide()
	end
end

function kit:GetButton(pos)
	if not self.buttons then
		self.buttons = {}
	end

	if self.buttons[pos] then return self.buttons[pos] end

	-- Create a button for this tab
	local button = CreateFrame("Button", nil, self)
	button:SetWidth(150)
	button:SetHeight(13)
	button:SetHighlightTexture("Interface\\FriendsFrame\\UI-FriendsFrame-HighlightBar")

	button.text = button:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
	button.text:SetPoint("LEFT", button, "LEFT", 25,0)

	button.expand = button:CreateTexture(nil, "ARTWORK");
	button.expand:SetTexture("Interface\\Minimap\\ROTATING-MINIMAPGUIDEARROW")
	button.expand:SetPoint("LEFT", button, "LEFT", 0,0)
	button.expand:SetWidth(32)
	button.expand:SetHeight(32)
	button.expand:Hide()

	for k,v in pairs(buttonKit) do
		button[k] = v
	end

	button:SetScript("OnClick", function() self:ClickButton(pos) end)
	self.buttons[pos] = button

	if pos == 1 then
		button:SetPoint("TOPLEFT", self, "TOPLEFT", 5, self.buttonTop-15)
	else
		button:SetPoint("TOPLEFT", self:GetButton(pos-1), "BOTTOMLEFT", 0, 0)
	end
	return button
end

function kit:RenderTabs()
	assert(isGuiObject(self), "Must be called on a valid object")
	if (self.config.isZero) then return end

	if not self.render then self:RegenTabs() end

	local offset = 0
	local total = #self.render
	local count = math.floor((self.dialogHeight - 40) / 13)
	for i=1, count do
		local pos = i + offset
		local button = self:GetButton(i)

		if pos <= total then
			local isCat, catId, tabName = unpack(self.render[pos])

			if isCat then
				local isOpen = self.config.cats[catId].isOpen
				if isOpen then
					button:SetArrowDirection("DOWN")
				else
					button:SetArrowDirection("RIGHT")
				end
				button:SetText(self.config.cats[catId].name or catId)
				button.catId = catId
				button.tabName = nil
			else
				button:SetArrowDirection("NONE")
				if catId == self.config.selectedCat
				and tabName == self.config.selectedTab then
					button:SetText(tabName, true)
				else
					button:SetText(tabName)
				end
				button.catId = catId
				button.tabName = tabName
			end
			button:Show()
		else
			button:SetText("")
			button:SetArrowDirection("NONE")
			button.catId = nil
			button.tabName = nil
			button:Hide()
		end
	end
end

function kit:RegenTabs()
	if not self.render then self.render = {} end
	local render = self.render
	empty(render)

	for pos, catId in ipairs(self.config.order) do
		if self.config.cats[catId].hasTabs then
			table.insert(render, {true, catId})
			if self.config.cats[catId].isOpen then
				local list = {}
				for tabName in pairs(self.config.tabs[catId]) do
					table.insert(list, tabName)
				end
				local sortFunction = nil
				if not ( self.config.cats[catId].isSorted ) then
					local tabs = self.config.tabs[catId]
					sortFunction = function(a, b)
						if ( tabs[a] < tabs[b] ) then
							return true
						end
					end
				end
				table.sort(list, sortFunction)
				for pos, tabName in ipairs(list) do
					table.insert(render, {false, catId, tabName})
				end
			end
		end
	end
	self:RenderTabs()
end

function kit:ZeroFrame()
	assert(isGuiObject(self), "Must be called on a valid object")
	assert(self.config.isZero == false, "Cannot zero a frame with tabs")

	local id = 0
	local frame, content

	local myName = lib.CreateAnonName()
	frame = CreateFrame("Frame", myName.."Frame", self, BackdropTemplateMixin and "BackdropTemplate")
	content = CreateFrame("Frame", myName.."Content", frame)

	frame.id = id
	frame:SetPoint("TOPLEFT", self, "TOPLEFT", 10, -10)
	frame:SetPoint("BOTTOMRIGHT", self.Done, "TOPRIGHT", 0, 5)
	frame:SetBackdrop({
		bgFile = "Interface/Tooltips/ChatBubble-Background",
		edgeFile = "Interface/Tooltips/ChatBubble-BackDrop",
		tile = true, tileSize = 32, edgeSize = 32,
		insets = { left = 32, right = 32, top = 32, bottom = 32 }
	})
	frame:SetBackdropColor(0,0,0, 1)

	content:SetPoint("TOPLEFT", frame, "TOPLEFT", 5,-5)
	content:SetPoint("BOTTOMRIGHT", frame, "BOTTOMRIGHT", -5,5)

	self.config = {}
	self.config.current = "zero"
	self.config.tabs = {}
	self.config.tabs.zero = {}
	self.config.tabs.zero.zero = 0
	self.config.cats = {}
	self.config.cats.zero = {}
	self.config.isZero = true

	self.tabs[0] = {
		nil, frame, content, -- For backwards compatability
		catId = nil, --never been defined since configator was built
		tabName = nil,--never been defined since configator was built
		gapWidth = 0,
		gapHeight = 0,
		expandGap = 0,
		leftOffset = 0,
		topOffset = 0,
		frame = frame,
		content = content,
		scroll = nil,
		buttonTop = 0,
	}
	self.tabs.active = 0
	self.config.selectedCat = "zero"
	self.config.selectedTab = "zero"

	return 0
end

function kit:SetPosition(parent, width, height, left, top)
	assert(isGuiObject(self), "Must be called on a valid object")

	parent = parent or UIParent
	top = top or self.getter("configator.top")
	left = left or self.getter("configator.left")
	width = width or self.dialogWidth
	height = height or self.dialogHeight
	self.heightDelta = self.dialogHeight - height

	self:SetParent(parent)
	self:ClearAllPoints()
	if (top and left) then
		self:SetPoint("TOPLEFT", parent, "BOTTOMLEFT", left, top)
	else
		self:SetPoint("CENTER", parent, "CENTER")
	end
	self:SetWidth(width)
	self:SetHeight(height)

	for id, tab in ipairs(self.tabs) do
		if tab and tab.scroll then
			local frame = tab.frame
			local lOfs = frame.leftOffset or 0
			local gWidth = frame.gapWidth or 0
			local cWidth = width - lOfs - gWidth - 210
			tab.content:SetWidth(cWidth)
		end
	end
	local button = self.buttons[1]
	if button then
		button:SetPoint("TOPLEFT", self, "TOPLEFT", 5, self.buttonTop-15)
	end

	local id = self.tabs.active
	if id and self.tabs[id] then
		local tab = self.tabs[id]
		if tab.expanded then
			self:ExpandFrame(id)
		else
			self:ContractFrame(id)
		end
	end
end

function kit:ShowBackdrop()
	assert(isGuiObject(self), "Must be called on a valid object")
	self.Backdrop:Show()
end

function kit:HideBackdrop()
	assert(isGuiObject(self), "Must be called on a valid object")
	self.Backdrop:Hide()
end

function kit:ToggleExpand(id)
	assert(isGuiObject(self), "Must be called on a valid object")
	local tab = self.tabs[id]
	if not tab.expanded then
		self:ExpandFrame(id)
	else
		self:ContractFrame(id)
	end
end
function kit:ExpandFrame(id)
	assert(isGuiObject(self), "Must be called on a valid object")
	local tab = self.tabs[id]
	if tab.gapHeight == 0 then return end
	tab.frame.fullsize:SetNormalTexture("Interface\\Minimap\\UI-Minimap-ZoomOutButton-Up")
	tab.frame.fullsize:SetPushedTexture("Interface\\Minimap\\UI-Minimap-ZoomOutButton-Down")
	tab.frame:SetPoint("BOTTOMRIGHT", self.Done, "TOPRIGHT", 0-tab.gapWidth, 5+tab.expandGap)
	tab.expanded = true
end
function kit:ContractFrame(id)
	assert(isGuiObject(self), "Must be called on a valid object")
	local tab = self.tabs[id]
	if tab.gapHeight == 0 then return end
	tab.frame.fullsize:SetNormalTexture("Interface\\Minimap\\UI-Minimap-ZoomInButton-Up")
	tab.frame.fullsize:SetPushedTexture("Interface\\Minimap\\UI-Minimap-ZoomInButton-Down")
	tab.frame:SetPoint("BOTTOMRIGHT", self.Done, "TOPRIGHT", 0-tab.gapWidth, 5+tab.gapHeight-self.heightDelta)
	tab.expanded = nil
end
function kit:SetExpandGap(id, gap)
	assert(isGuiObject(self), "Must be called on a valid object")
	local tab = self.tabs[id]
	if tab.gapHeight == 0 then return end
	tab.expandGap = gap
end

function kit:AddTab(tabName, catId, gapWidth, gapHeight, topOffset, leftOffset)
	assert(isGuiObject(self), "Must be called on a valid object")
	assert(not self.config.isZero, "Cannot add tabs to a zeroed frame")

	if not catId then catId = self.config.current end

	if not self.config.tabs[catId] then
		self:AddCat(catId)
	end

	local exists, id = self:GetTabByName(tabName, catId)
	if exists then return id end

	local frame, content
	self.config.isZero = false

	local myName = lib.CreateAnonName()
	frame = CreateFrame("Frame", myName.."Frame", self, BackdropTemplateMixin and "BackdropTemplate")
	content = CreateFrame("Frame", myName.."Content", frame)

	if not gapWidth then gapWidth = self.gapWidth or 0 end
	if not gapHeight then gapHeight = self.gapHeight or 0 end
	if not topOffset then topOffset = self.topOffset or 0 end
	if not leftOffset then leftOffset = self.leftOffset or 0 end

	local expandGap = 0
	if self.expandGap then expandGap = self.expandGap end

	local tab = {
		nil, frame, content, -- For backwards compatability
		catId = catId,
		tabName = tabName,
		gapWidth = gapWidth,
		gapHeight = gapHeight,
		expandGap = expandGap,
		topOffset = topOffset,
		leftOffset = leftOffset,
		frame = frame,
		content = content,
		scroll = nil,
		expanded = nil,
	}
	table.insert(self.tabs, tab)
	id = table.getn(self.tabs)

	self.config.tabs[catId][tabName] = id
	tab.id = id
	frame.id = id
	content.id = id

	self.config.cats[catId].hasTabs = true

	frame:ClearAllPoints()
	frame:SetPoint("TOPLEFT", self, "TOPLEFT", 160+leftOffset, -10-topOffset)
	frame:SetPoint("BOTTOMRIGHT", self.Done, "TOPRIGHT", 0-gapWidth, 5+gapHeight)
	frame:SetBackdrop({
		bgFile = "Interface/Tooltips/ChatBubble-Background",
		edgeFile = "Interface/Tooltips/ChatBubble-BackDrop",
		tile = true, tileSize = 32, edgeSize = 32,
		insets = { left = 32, right = 32, top = 32, bottom = 32 }
	})
	frame:SetBackdropColor(0,0,0, 1)
	frame:SetFrameLevel(10)

	frame.fullsize = CreateFrame("Button", nil, frame)
	frame.fullsize:SetPoint("BOTTOMLEFT", frame, "BOTTOMLEFT", -3, -3)
	frame.fullsize:SetWidth(22)
	frame.fullsize:SetHeight(22)
	frame.fullsize:SetNormalTexture("Interface\\Minimap\\UI-Minimap-ZoomInButton-Up")
	frame.fullsize:SetPushedTexture("Interface\\Minimap\\UI-Minimap-ZoomInButton-Down")
	frame.fullsize:SetHighlightTexture("Interface\\Minimap\\UI-Minimap-ZoomButton-Highlight")
	frame.fullsize:SetScript("OnClick", function()
		self:ToggleExpand(id)
	end)

	if gapHeight > 0 then
		frame.fullsize:Show()
	else
		frame.fullsize:Hide()
	end

	content:SetPoint("TOPLEFT", frame, "TOPLEFT", 5,-5)
	content:SetPoint("BOTTOMRIGHT", frame, "BOTTOMRIGHT", -5,5)
	content.gapWidth = gapWidth
	content.gapHeight = gapHeight

	self:RegenTabs()

	if (not self.config.selectedTab) then
		self:ActivateTab(id)
	else
		frame:Hide()
	end

	if (self.autoScrollTabs) then
		self:MakeScrollable(id)
	end

	return id
end

function kit:AddCat(catId, catName, sortedTabs, isOpen)
	assert(isGuiObject(self), "Must be called on a valid object")
	assert(not self.config.isZero, "Cannot add categories to a zeroed frame")
	if self.config.tabs[catId] then return end

	if sortedTabs == nil then sortedTabs = true end

	self.config.isZero = false

	table.insert(self.config.order, catId)
	self.config.tabs[catId] = { }
	self.config.cats[catId] = {
		name = catName,
		isOpen = isOpen,
		isSorted = sortedTabs,
	}
	self.config.current = catId
	if not self.config.selectedCat then self.config.selectedCat = catId end

	self:RegenTabs()
end

local function anchorPoint(frame, el, last, indent, width, height, yofs)
	local clearance = 0
	if (last and last.clearance) then clearance = last.clearance end

	el:SetPoint("LEFT", frame, "LEFT", indent or 15, 0)
	if (width == nil) then
		el:SetPoint("RIGHT", frame, "RIGHT", -5, 0)
	elseif (type(width) == "number") then
		el:SetWidth(width)
	end
	if (type(height) == "number") then
		el:SetHeight(height)
	end
	if (last) then
		el:SetPoint("TOP", last, "BOTTOM", 0, -5 + (yofs or 0) - clearance)
	else
		el:SetPoint("TOP", frame, "TOP", 0, -5 - clearance)
	end
end

function kit:Unfocus()
	assert(isGuiObject(self), "Must be called on a valid object")
	self:Hide()
	self:ClearFocus()
	self:Show()
end

function kit:MouseScroll(direction)
	assert(isGuiObject(self), "Must be called on a valid object")
	local step = 1
	if self:GetObjectType() == "Slider" then
		step = self:GetValueStep()
	end
	self:SetValue(self:GetValue() - direction*step)
end

function kit:CaptureKeys()
	self:EnableKeyboard(true)
end

function kit:ReleaseKeys()
	self:EnableKeyboard(false)
end

function kit:KeyPress(key, ...)
	local dir = 0
	if key == "UP" or key == "RIGHT" then
		dir = 1
	elseif key == "DOWN" or key == "LEFT" then
		dir = -1
	end

	if dir ~= 0 then
		local size = 1
		if IsShiftKeyDown() then
			if IsControlKeyDown() then
				size = 25
			else
				size = 5
			end
		elseif IsControlKeyDown() then
			size = 10
		end

		if self.GetValue then
			local myVal = self:GetValue()
			self:SetValue(myVal + size * dir)
			return
		elseif self.GetNumber then
			local myVal = self:GetNumber()
			self:SetNumber(myVal + size * dir)
			return
		elseif self.GetText then
			local myVal = tonumber(self:GetText())
			if myVal then
				self:SetText(myVal + size * dir)
			end
			return
		end
	end

	if self.subcontrol and self.subcontrol.hasFocus then
		local script = self.subcontrol:GetScript("OnKeyUp")
		if script then
			script(key, ...)
		end
	end
end

function kit:FocusShift(...)
	assert(isGuiObject(self), "Must be called on a valid object")
	local direction
	if (IsShiftKeyDown()) then
		direction = "previousFocus"
	else
		direction = "nextFocus"
	end
	local nextobj = self[direction]
	if nextobj.SetFocus then
		nextobj:SetFocus()
	end
end

function kit:SetControlWidth(width)
	assert(isGuiObject(self), "Must be called on a valid object")
	self.scalewidth = width
end

function kit:MakeScrollable(id)
	assert(isGuiObject(self), "Must be called on a valid object")
	if (self.tabs[id].scroll) then return end
	local frame = self.tabs[id].frame
	local content = self.tabs[id].content
	local oldwidth = content:GetWidth()
	content:ClearAllPoints()
	content:SetWidth(oldwidth)
	content:SetHeight(250)
	local PanelScroller = LibStub:GetLibrary("PanelScroller")
	local scroll = PanelScroller:Create(lib.CreateAnonName(), frame)
	scroll:SetPoint("TOPLEFT", frame, "TOPLEFT", 5,-7)
	scroll:SetPoint("BOTTOMRIGHT", frame, "BOTTOMRIGHT", -25,9)
	scroll:SetScrollChild(content)
	scroll:UpdateScrollChildRect() -- ### todo: according to wiki, may no longer be needed?
	self.tabs[id].scroll = scroll
	self.tabs[id][4] = scroll
end

-- this is a helper function for the tooltip addition
local function AddTipControl(self, control, tip)
	if not self or not control or not tip then return end
	local old_enter = control:GetScript("OnEnter")
	local old_leave = control:GetScript("OnLeave")
	local function help_enter(self, ...)
		if old_enter then old_enter(self, ...) end
		if tip then lib:SetTip(self, tip) end
	end
	local function help_leave(self, ...)
		if old_enter then old_leave(self, ...) end
		if tip then lib:SetTip() end
	end
	control:SetScript("OnEnter", help_enter)
	control:SetScript("OnLeave", help_leave)
end
-- this is a text only tool tip
function kit:AddTip(id, tip)
	assert(isGuiObject(self), "Must be called on a valid object")
	local control
	local idType = type(id)
	if idType == "number" and self.tabs then
		control = self:GetLast(id)
	elseif idType == "string" then
		control = _G[id]
	elseif idType == "table" and type(id[0]) == "userdata" then
		control = id
	end
	assert(isGuiObject(control), "Usage: ConfigatorGui:AddTip(tabId|controlName|control, tip)")
	if control.button and isGuiObject(control.button) then
		control = control.button
	elseif control.control and isGuiObject(control.control) then
		control = control.control
	end
	--MoneyInput frames need a enter/leave  for each sub frame gold/silver/copper
	if control.gold or control.silver or control.copper then
		AddTipControl(self, control.gold, tip)
		AddTipControl(self, control.silver, tip)
		AddTipControl(self, control.copper, tip)
	else
		AddTipControl(self, control, tip)
	end
end

-- this will show the game tooltip for the link
function kit:AddLinkTip(id, link)
	assert(isGuiObject(self), "Must be called on a valid object")
	local control
	local idType = type(id)
	if idType == "number" and self.tabs then
		control = self:GetLast(id)
	elseif idType == "string" then
		control = _G[id]
	elseif idType == "table" and type(id[0]) == "userdata" then
		control = id
	end
	assert(isGuiObject(control), "Usage: ConfigatorGui:AddLinkTip(tabId|controlName|control, link)")

	-- we would really prefer the text labels over the controls themselves (esp. for sliders)
	-- ccox - Feb 8, 2009 - but I can't make that work correctly
	if control.button and isGuiObject(control.button) then
		control = control.button
	elseif control.control and isGuiObject(control.control) then
		control = control.control
	end

	local old_enter = control:GetScript("OnEnter")
	local old_leave = control:GetScript("OnLeave")
	local function help_enter(self, ...)
		if old_enter then old_enter(self, ...) end
		lib:SetLinkTip(self, link)
	end
	local function help_leave(self, ...)
		if old_enter then old_leave(self, ...) end
		lib:SetLinkTip()
	end
	control:SetScript("OnEnter", help_enter)
	control:SetScript("OnLeave", help_leave)
end

function kit:AddHelp(id, qid, question, answer)
	assert(isGuiObject(self), "Must be called on a valid object")
	local content
	local idType = type(id)
	if idType == "number" and self.tabs then
		content = self.tabs[id][2]
	elseif idType == "string" then
		content = _G[id]
	elseif idType == "table" and type(id[0]) == "userdata" then
		content = id
	end
	assert(isGuiObject(content), "Usage: ConfigatorGui:AddHelp(tabId|controlName|control, question, answer)")
	assert(question and answer, "Usage: ConfigatorGui:AddHelp(tabId|controlName|control, question, answer)")

	if question and answer then
		if content and not content.HelpButton then
			content.HelpButton = CreateFrame("BUTTON", content:GetName().."HelpButton", content)
			content.HelpButton:SetPoint("TOPRIGHT", content, "TOPRIGHT", -20,0)
			content.HelpButton:SetWidth(42)
			content.HelpButton:SetHeight(42)
			content.HelpButton:SetNormalTexture("Interface\\TUTORIALFRAME\\TutorialFrame-QuestionMark")
			content.HelpButton:SetHighlightTexture("Interface\\TUTORIALFRAME\\TutorialFrame-QuestionMark")
			content.HelpButton:SetScript("OnClick", lib.help.Activate)
			content.HelpButton.text = content.HelpButton:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
			content.HelpButton.text:SetText("Help")
			content.HelpButton.text:SetPoint("TOP", content.HelpButton, "BOTTOM", 0,5)
			content.HelpButton.qlist = {}
			content.HelpButton.faq = {}
			content.HelpButton.faa = {}
		end
		content.HelpButton.faq[qid] = question
		content.HelpButton.faa[qid] = answer

		for i = 1, #content.HelpButton.qlist do
			if content.HelpButton.qlist[i] == qid then
				return
			end
		end
		table.insert(content.HelpButton.qlist, qid)
	end
end

function kit:GetControl( tabId, controlId )
	if ( controlId ) then
		return self.tabs[tabId].frame.ctrls[controlId]
	else
		local ctrls = self.tabs[tabId].frame.ctrls
		return ctrls.last, ctrls.pos
	end
end

function kit:AddControl(id, cType, column, ...)
	assert(isGuiObject(self), "Must be called on a valid object")
	local frame = self.tabs[id].frame
	if (not frame.ctrls) then
		frame.ctrls = { pos = 0 }
	end
	local cpos = frame.ctrls.pos + 1
	frame.ctrls.pos = cpos
	local ctrl = { kids = {} }
	frame.ctrls[cpos] = ctrl

	local last = frame.ctrls.last
	local control

	local kids = ctrl.kids
	local kpos = 0

	local framewidth = frame:GetWidth() - 25
	if ( self.tabs[id].scroll ) then
		framewidth = framewidth - 14
	end
	column = (column or 0) * framewidth
	local colwidth = nil
	if (self.scalewidth) then
		colwidth = math.min(framewidth-column, (self.scalewidth or 1) * framewidth)
		self.scalewidth = nil
	end

	local content = self.tabs[id].content

	local el
	if (cType == "Header") then
		el = content:CreateFontString(nil, "OVERLAY", "GameFontNormalHuge")
		local fontFile = el:GetFont()
		el:SetFont(fontFile, 15)
		kpos = kpos+1 kids[kpos] = el
		anchorPoint(content, el, last)
		local text = ...
		el:SetText(text)
		last = el
	elseif (cType == "Subhead") then
		el = content:CreateFontString(nil, "OVERLAY", "GameFontNormalLarge")
		local fontFile = el:GetFont()
		el:SetFont(fontFile, 13)
		el:SetJustifyH("LEFT")
		kpos = kpos+1 kids[kpos] = el
		anchorPoint(content, el, last, column+15, colwidth, nil, -10)
		local text = ...
		el:SetText(text)
		last = el
	elseif (cType == "Note") then
		local level, width, height, text = ...
		local indent = 10 * (level or 1)
		el = content:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
		local fontFile = el:GetFont()
		el:SetFont(fontFile, 10)
		el:SetJustifyH("LEFT")
		el:SetJustifyV("TOP")
		kpos = kpos+1 kids[kpos] = el
		anchorPoint(content, el, last, column+indent+15, width, height, nil)
		el:SetText(text)
		control = el
		last = el
	elseif (cType == "Label") then
		local level, setting, text = ...
		local indent = 10 * (level or 1)
		el = content:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
		el:SetJustifyH("LEFT")
		kpos = kpos+1 kids[kpos] = el
		anchorPoint(content, el, last, column+indent+15, colwidth)
		el:SetText(text)
		if (setting) then
			el.hit = CreateFrame("Button", nil, content)
			el.hit.parent = el
			el.hit:SetAlpha(0.3)
			el.hit:SetPoint("TOPLEFT", el, "TOPLEFT", -2, 2)
			el.hit:SetPoint("BOTTOMRIGHT", el, "BOTTOMRIGHT", 2, -2)
			--el.hit:SetHighlightTexture("Interface\\QuestFrame\\UI-QuestTitleHighlight")
			el.hit:SetHighlightTexture("Interface\\FriendsFrame\\UI-FriendsFrame-HighlightBar")
			el.hit.setting = setting
			el.hit.stype = "Button";
			el.hit:SetScript("OnClick", function(...) self:ChangeSetting(...) end)
			el.hit:Show()
		end
		control = el
		last = el
	elseif (cType == "Custom") then
		local level, el = ...
		local indent = 10 * (level or 1)
		kpos = kpos+1 kids[kpos] = el
		anchorPoint(content, el, last, column + indent + 15, colwidth)
		control = el
		last = el
	elseif (cType == "Text") then
		local level, setting, label = ...
		local indent = 10 * (level or 1)
		-- FontString
		el = content:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
		el:SetJustifyH("LEFT")
		kpos = kpos+1 kids[kpos] = el
		anchorPoint(content, el, last, 10 + column + indent)
		el:SetText(label)
		last = el
		-- Editbox
		el = CreateFrame("EditBox", lib.CreateAnonName(), content, "InputBoxTemplate")
		lib:TabLink(frame, el)

		kpos = kpos+1 kids[kpos] = el
		anchorPoint(content, el, last, 20 + column + indent, colwidth or 160, 32, 4)
		el.setting = setting
		el.stype = "EditBox"
		el:SetAutoFocus(false)
		self:GetSetting(el)
		el:SetScript("OnEditFocusGained", function(...)
			self.curFocus = el
		end)
		el:SetScript("OnEditFocusLost", function(...)
			self.curFocus = nil
			self:ChangeSetting(...)
		end)
		el:SetScript("OnEscapePressed", kit.Unfocus)
		el:SetScript("OnEnterPressed", kit.Unfocus)
		self.elements[setting] = el
		el.textEl = last

		control = el
		last = el
	elseif (cType == "Selectbox") then
		local level, list, setting, width, text, maxLabelLength = ...
		local indent = 10 * (level or 1)
		-- Selectbox
		local tmpName = lib.CreateAnonName()

		if (type(list) ~= "function") then
			local listVar = list
			if (type(list) == "table") then
				list = function() return listVar end
			else
				list = function() return self.getter(listVar) end
			end
		end

		if type(width) ~= "number" then
			if type(width) == "string" then
				maxLabelLength = text
				text = width
			end
			width = 140
		end

		local SelectBox = LibStub:GetLibrary("SelectBox")
		el = SelectBox:Create(tmpName, content, width, function(...) self:ChangeSetting(...) end, list, "Default")
		kpos = kpos+1 kids[kpos] = el
		anchorPoint(content, el, last, column+indent - 5, colwidth or width, 22, 4)
		el.list = list
		el.setting = setting
		el.stype = "SelectBox";
		el.clearance = 3
		self.elements[setting] = el
		self:GetSetting(el)
		control = el
		if ( type(text) == "string" ) then
			-- FontString
			el = content:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
			el:SetJustifyH("LEFT")
			kpos = kpos+1 kids[kpos] = el
			if (colwidth) then colwidth = colwidth - 15 end
			anchorPoint(content, el, last, (colwidth or width)+35+column+indent, (colwidth or maxLabelLength), 14, -3)
			el:SetText(text)
			control.textEl = el
		end
		last = control
	elseif (cType == "Button") then
		local level, setting, text = ...
		local indent = 10 * (level or 1)
		-- Button
		el = CreateFrame("Button", nil, content, "UIPanelButtonTemplate")
		kpos = kpos+1 kids[kpos] = el
		anchorPoint(content, el, last, 10 + column + indent, colwidth or 80, 22, 4)
		el.setting = setting
		el.stype = "Button";
		el:SetScript("OnClick", function(...) self:ChangeSetting(...) end)
		el:SetText(text)
		el:SetHeight(21)
		el:SetWidth(math.max(el:GetWidth(), el:GetFontString():GetStringWidth() + 16))
		control = el
		last = el
	elseif (cType == "Checkbox") then
		local level, setting, text, singleLine, maxLabelLength = ...
		if ( maxLabelLength and maxLabelLength <= 1 ) then
			maxLabelLength = maxLabelLength * framewidth - 25
		end
		local indent = 10 * (level or 1)
		-- CheckButton
		el = CreateFrame("CheckButton", nil, content) -- OptionsBaseCheckButtonTemplate is deprecated, build layout in Lua
		el:SetSize(26, 26)
		el:SetNormalTexture("Interface\\Buttons\\UI-CheckBox-Up")
		el:SetPushedTexture("Interface\\Buttons\\UI-CheckBox-Down")
		el:SetHighlightTexture("Interface\\Buttons\\UI-CheckBox-Highlight", "ADD")
		el:SetCheckedTexture("Interface\\Buttons\\UI-CheckBox-Check")
		el:SetDisabledTexture("Interface\Buttons\UI-CheckBox-Check-Disabled")
		kpos = kpos+1 kids[kpos] = el
		anchorPoint(content, el, last, 10 + column + indent, 22, 22, 4)
		el.setting = setting
		el.stype = "CheckButton"
		self:GetSetting(el)
		el:SetScript("OnClick", function(element, ...)
				if element:GetChecked() then
					PlaySound(SOUNDKIT.IG_MAINMENU_OPTION_CHECKBOX_ON)
				else
					PlaySound(SOUNDKIT.IG_MAINMENU_OPTION_CHECKBOX_OFF)
				end
				self:ChangeSetting(element, ...)
			end)
		self.elements[setting] = el
		control = el
		-- FontString
		el = control:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
		el:SetJustifyH("LEFT")
		kpos = kpos+1 kids[kpos] = el
		if (colwidth) then colwidth = colwidth - 15 end
		anchorPoint(content, el, last, 35+column+indent, (colwidth or maxLabelLength), (singleLine and 14))
		el:SetText(text)
		local textWidth = el:GetStringWidth()+25
		control:SetHitRectInsets(-2,-textWidth, -2,-2)
		control.textEl = el
		last = el
	elseif (cType == "Slider" or cType == "WideSlider" or cType == "TinySlider"
	or cType == "NumeriSlider" or cType == "NumeriWide" or cType == "NumeriTiny") then
		local swidth = colwidth or 140
		if (cType == "WideSlider" or cType == "NumeriWide") then swidth = 260 end
		if (cType == "TinySlider" or cType == "NumeriTiny") then swidth = 80 end
		local hasNumber = false
		local nwidth = 0
		if (cType == "NumeriSlider" or cType == "NumeriWide" or cType == "NumeriTiny") then
			hasNumber = true
			nwidth = 40
		end

		local level, setting, min, max, step, text, fmtfunc = ...
		local indent = 10 * (level or 1)
		-- FontString
		el = content:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
		el:SetJustifyH("LEFT")
		kpos = kpos+1 kids[kpos] = el
		anchorPoint(content, el, last, swidth+nwidth + 20 + column+indent)
		el:SetText(text)
		local textElement = el
		-- Slider
		local tmpName = lib.CreateAnonName()
		if DoesTemplateExist("OptionsSliderTemplate") then
			el = CreateFrame("Slider", tmpName, content, "OptionsSliderTemplate") -- Deprecated in 10.0.0: expect to be removed; Valid in 3.4.0 & 1.14.3
		else
			el = CreateFrame("Slider", tmpName, content, "UISliderTemplateWithLabels") -- New in 10.0.0; Does not exist in 3.4.0 & 1.14.3
			el:SetSize(144, 17)
		end
		kpos = kpos+1 kids[kpos] = el
		anchorPoint(content, el, last, 13 + column + indent, swidth, 20, 4)
		_G[tmpName.."Low"]:SetText("")
		_G[tmpName.."High"]:SetText("")
		el.setting = setting
		el.textFmt = text
		el.fmtFunc = fmtfunc
		el.textEl = textElement
		el.stype = "Slider"
		el.step = step * 100
		el:SetMinMaxValues(min * 100, max * 100)
		el:SetValueStep(step*100)
		el:SetObeyStepOnDrag(true)
		el:SetHitRectInsets(0,0,0,0)
		local subcon
		el:EnableMouseWheel(true)
		el:SetScript("OnEnter", kit.CaptureKeys)
		el:SetScript("OnLeave", kit.ReleaseKeys)
		el:SetScript("OnKeyUp", kit.KeyPress)
		el:EnableKeyboard(false)
		el:SetScript("OnMouseWheel", kit.MouseScroll)
		self:GetSetting(el) -- We need to have set or the hasNumber part will always start at 0
		if hasNumber then
			local tmpName2 = lib.CreateAnonName()
			subcon = CreateFrame("EditBox", tmpName2, el, "InputBoxTemplate")
			lib:TabLink(frame, subcon)

			subcon:SetPoint("LEFT", el, "RIGHT", 8,0)
			subcon:SetWidth(40)
			subcon:SetHeight(32)
			subcon:SetScale(0.8)
			subcon:SetScript("OnEditFocusGained", function(...)
				self.curFocus = subcon
				subcon.hasFocus = true
			end)
			subcon:SetScript("OnEditFocusLost", function(...)
				local myMin, myMax = subcon.minValue, subcon.maxValue
				local myVal = math.min(myMax, math.max(myMin, tonumber(subcon:GetNumber()) or 0))
				if (el:GetValue() ~= myVal*100) then
					el:SetValue(myVal*100)
				end
				subcon:SetNumber((el:GetValue())/100)
				subcon.hasFocus = false
				self.curFocus = nil
			end)
			subcon:SetScript("OnEscapePressed", kit.Unfocus)
			subcon:SetScript("OnEnterPressed", kit.Unfocus)
			subcon:SetAutoFocus(false)
			subcon:Show()
			subcon.minValue = min
			subcon.maxValue = max
			subcon.element = el
			subcon:SetNumber((el:GetValue())/100)
			el.subcontrol = subcon
		end
		el:SetScript("OnValueChanged", function(element, value)
			self:ChangeSetting(element, value)
			if (subcon) then
				local myVal = el:GetValue()
				if subcon:GetNumber() ~= myVal/100 then
					subcon:SetNumber(myVal/100)
				end
			end
		end)
		self.elements[setting] = el
		control = el
		last = textElement
	elseif (cType == "NumberBox" or cType == "TinyNumber") then
		local level, setting, minVal, maxVal, label = ...
		local indent = 10 * (level or 1)

		local defWidth = 80
		if (cType == "TinyNumber") then defWidth = 40 end

		-- FontString
		el = content:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
		el:SetJustifyH("LEFT")
		kpos = kpos+1 kids[kpos] = el
		anchorPoint(content, el, last, 10+column+indent)
		el:SetText(label)
		last = el
		-- Editbox
		el = CreateFrame("EditBox", lib.CreateAnonName(), content, "InputBoxTemplate")
		lib:TabLink(frame, el)
		kpos = kpos+1 kids[kpos] = el
		anchorPoint(content, el, last, 20+column+indent, colwidth or defWidth, 32, 4)
		el.setting = setting
		el.stype = "EditBox"
		el:SetAutoFocus(false)
		el:SetNumeric(true);
		el.minValue = minVal;
		el.maxValue = maxVal;
		el.Numeric = true;
		self:GetSetting(el)
		el:SetScript("OnEditFocusGained", function(...)
			self.curFocus = el
		end)
		el:SetScript("OnEditFocusLost", function(...)
			self.curFocus = nil
			self:ChangeSetting(...)
		end)
		el:SetScript("OnEscapePressed", kit.Unfocus)
		el:SetScript("OnEnterPressed", kit.Unfocus)
		self.elements[setting] = el
		el.textEl = last
		control = el
		last = el
	elseif (cType == "MoneyFrame" or cType == "PinnedMoney" or cType == "MoneyFramePinned") then
		local level, setting, minVal, maxVal, label  = ...
		if (cType == "MoneyFrame") then
			label, minVal, maxVal = minVal, nil, nil
		end
		local indent = 10 * (level or 1)
		-- FontString
		if label then
			el = content:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
			el:SetJustifyH("LEFT")
			kpos = kpos+1 kids[kpos] = el
			anchorPoint(content, el, last, 10+column+indent)
			el:SetText(label)
			last = el
		end
		-- MoneyFrame
		local frameName = lib.CreateAnonName();
		el = CreateFrame("Frame", frameName, content, "MoneyInputFrameTemplate")
		el.isMoneyFrame = true

		-- NOTE - ccox - 2007-11-18
		-- TabLink is not working correctly, causes an error when tabbing out of copper field in money input frame
		--     lib:TabLink(frame, el)

		-- for the time being, set to cycle around the fields of the current money frame
		MoneyInputFrame_SetPreviousFocus(el, _G[frameName.."Copper"])
		MoneyInputFrame_SetNextFocus(el, _G[frameName.."Gold"])

		local cur = el
		local MoneyInputFrame_SetOnValueChangedFunc = MoneyInputFrame_SetOnvalueChangedFunc or MoneyInputFrame_SetOnValueChangedFunc -- WotLK Hack
		MoneyInputFrame_SetOnValueChangedFunc(el, function() self:ChangeSetting(cur) end);
		kpos = kpos+1 kids[kpos] = el
		anchorPoint(content, el, last, 20+column+indent, colwidth or 160, 32, 4)
		el.frameName = frameName;
		el.setting = setting;
		el.stype = cType
		el.clearance = -10
		el.minValue = minVal;
		el.maxValue = maxVal;
		self:GetSetting(el)
		self.elements[setting] = el
		el.textEl = last
		control = el
		last = el
	elseif (cType == "ColorSelect" or cType == "ColorSelectAlpha") then
		local level, setting, text = ...
		local indent = 10 * (level or 1)
		-- ColorSelect
		el = CreateFrame("Button", nil, content)
		kpos = kpos+1 kids[kpos] = el
		anchorPoint(content, el, last, 10 + column + indent, 26, 22, -2)
		el.setting = setting
		el.stype = cType
		el:SetWidth(24)
		el:SetHeight(16)
		el.r, el.g, el.b, el.a = 1,1,1,1 --no one notices black on black
		el.bg = el:CreateTexture(nil, "BORDER");
		el.bg:SetAllPoints(el)
		el.bg:SetTexture("Interface\\TargetingFrame\\BarFill2")
		el.bg:SetAlpha(0.7)
		el.tex = el:CreateTexture(nil, "ARTWORK");
		el.tex:SetAllPoints(el)
		el.tex:SetColorTexture(el.r, el.g, el.b, el.a)
		self:GetSetting(el)
		el:SetScript("OnClick", function(obj, ...)
			local f = ColorPickerFrame
			f:SetColorRGB(obj.r, obj.g, obj.b);
			if obj.stype == "ColorSelectAlpha" then
				f.hasOpacity = true
				f.opacity = obj.a
			else
				f.hasOpacity = false
			end
			f.func = function()
				self:ChangeSetting(obj)
			end
			f.cancelFunc = function(previousValues)
				f:SetColorRGB(previousValues.r, previousValues.g, previousValues.b);
				if ( f.hasOpacity ) then
					OpacitySliderFrame:SetValue(previousValues.a)
				end
				self:ChangeSetting(obj, true)
			end
			f.opacityFunc = f.func
			f:SetFrameStrata("TOOLTIP")
			f:SetToplevel("TRUE")
			f.previousValues = {r = obj.r, g = obj.g, b = obj.b, a=obj.a}
			f:Show()
		end)
		self.elements[setting] = el
		control = el
		-- FontString
		el = control:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
		el:SetJustifyH("LEFT")
		kpos = kpos+1 kids[kpos] = el
		if (colwidth) then colwidth = colwidth - 15 end
		anchorPoint(content, el, last, 35+column+indent, colwidth, 16, -2)
		el:SetText(text)
		local textWidth = el:GetStringWidth()+25
		control:SetHitRectInsets(-2,-textWidth, -2,-2)
		control.textEl = el
		last = el
	end

	if last ~= control then
		last.control = control
	end

	self:SetLast(id, last)
	return control
end

function kit:GetLast(id)
	assert(isGuiObject(self), "Must be called on a valid object")
	if (self.tabs[id] and self.tabs[id].frame.ctrls) then
		return self.tabs[id].frame.ctrls.last
	end
end

function kit:SetLast(id, last)
	assert(isGuiObject(self), "Must be called on a valid object")
	if (self.tabs[id] and self.tabs[id].frame.ctrls) then
		self.tabs[id].frame.ctrls.last = last
	end
end

function kit:ActivateTab(id)
	assert(isGuiObject(self), "Must be called on a valid object")
	id = tonumber(id)
	if not id then id = self.id end

	if not self.tabs then self = self:GetParent() end
	if not self.tabs then
		return error("Must call ActivateTab from a valid Configator object")
	end

	if (self.tabs.active) then
		self.tabs[self.tabs.active].frame:Hide()
	end
	local tab = self.tabs[id]
	tab.frame:Show()
	self.tabs.active = id
	self.config.selectedCat = tab.catId
	self.config.selectedTab = tab.tabName

	if not self.config.cats[tab.catId].isOpen then
		self.config.cats[tab.catId].isOpen = true
		self:RegenTabs()
	else
		self:RenderTabs()
	end

	if self.expandOnActivate then
		self:ExpandFrame(id)
	end
end

function kit:GetTabByName(tabName, catId)
	if not catId then
		catId = self.config.current or ""
	end
	if self.config.tabs[catId] then
		local id = self.config.tabs[catId][tabName]
		if id then
			return self.tabs[id], id
		end
	end
end

function kit:GetTabById(id)
	if id then
		return self.tabs[id]
	end
end

function kit:Refresh()
	assert(isGuiObject(self), "Must be called on a valid object")
	for name, el in pairs(self.elements) do
		self:GetSetting(el)
	end
end

function kit:Resave()
	assert(isGuiObject(self), "Must be called on a valid object")
	for name, el in pairs(self.elements) do
		self:ChangeSetting(el)
	end
end

function kit:ClearFocus()
	if self.curFocus then
		self.curFocus:ClearFocus()
	end
end

function kit:GetSetting(element)
	assert(isGuiObject(self), "Must be called on a valid object")
	assert(isGuiObject(element), "You must pass a valid element")
	local setting = element.setting
	local value = self.getter(setting)
	if (element.stype == "CheckButton") then
		element:SetChecked(value or false)
	elseif (element.stype == "EditBox") then
		if (element.Numeric) then
			local oldvalue = value or 0;
			value = oldvalue;
			if (element.minValue) then
				value = math.max( value, element.minValue );
			end
			if (element.maxValue) then
				value = math.min( value, element.maxValue );
			end
			element:SetNumber(value);
			-- notify that we have pinned the value
			if (value ~= oldvalue) then
				self.setter(setting, value);
			end
		else
			element:SetText(value or "")
		end
	elseif (element.stype == "SelectBox") then
		element.value = value
		element:UpdateValue()
	elseif (element.stype == "Button") then
	elseif (element.stype == "Slider") then
		value = tonumber(value) or 0
		element:SetValue(value*100)
		if (element.fmtFunc) then
			element.textEl:SetText(string.format(element.textFmt, element.fmtFunc(value)))
		else
			element.textEl:SetText(string.format(element.textFmt, value))
		end
	elseif (element.stype == "MoneyFrame") then
		MoneyInputFrame_ResetMoney(element)
		MoneyInputFrame_SetCopper(element, tonumber(value) or 0);
	elseif (element.stype == "MoneyFramePinned") then
		local oldvalue = tonumber(value) or 0;
		value = oldvalue;
		if (element.minValue) then
			value = math.max( value, element.minValue );
		end
		if (element.maxValue) then
			value = math.min( value, element.maxValue );
		end
		MoneyInputFrame_ResetMoney(element)
		MoneyInputFrame_SetCopper( element, value );
		-- update with pinned value
		if (oldvalue ~= value) then
			self.setter(setting, value);
		end
	elseif (element.stype == "ColorSelect" or element.stype == "ColorSelectAlpha") then
		local r, g, b, a = strsplit(",", tostring(value) or "0,0,0,1")
		element.r = tonumber(r) or 0
		element.g = tonumber(g) or 0
		element.b = tonumber(b) or 0
		element.a = tonumber(a) or 1
		if element.tex then
			element.tex:SetColorTexture(element.r,element.g,element.b,element.a)
		end
	else
		value = element:GetValue()
	end
end

function kit:ChangeSetting(element, ...)
	assert(isGuiObject(self), "Must be called on a valid object")
	assert(isGuiObject(element), "You must pass a valid element")
	assert(element.stype, "You must pass a valid Configator settings object")
	local setting = element.setting
	local value
	if (element.stype == "CheckButton") then
		value = element:GetChecked()
		if (value) then value = true else value = false end
	elseif (element.stype == "EditBox") then
		if (element.Numeric) then
			local oldvalue = element:GetNumber() or 0;
			value = oldvalue;
			if (element.minValue) then
				value = math.max( value, element.minValue );
			end
			if (element.maxValue) then
				value = math.min( value, element.maxValue );
			end
			-- update the text field with pinned value
			if (oldvalue ~= value) then
				element:SetNumber(value);
			end
		else
			value = element:GetText() or ""
		end
	elseif (element.stype == "SelectBox") then
		value = select(2, ...)
	elseif (element.stype == "Button") then
		value = true
	elseif (element.stype == "Slider") then
		value = (element:GetValue())/100 or 0
		if (element.fmtFunc) then
			element.textEl:SetText(string.format(element.textFmt, element.fmtFunc(value)))
		else
			element.textEl:SetText(string.format(element.textFmt, value))
		end
	elseif (element.stype == "MoneyFrame") then
		value = MoneyInputFrame_GetCopper( element );
	elseif (element.stype == "MoneyFramePinned") then
		local oldvalue = MoneyInputFrame_GetCopper( element );
		value = oldvalue;
		if (element.minValue) then
			value = math.max( value, element.minValue );
		end
		if (element.maxValue) then
			value = math.min( value, element.maxValue );
		end
		-- update with pinned value
		if (oldvalue ~= value) then
			MoneyInputFrame_SetCopper( element, value );
		end
	elseif (element.stype == "ColorSelect" or element.stype == "ColorSelectAlpha") then
		local isCancel = ...
		if (ColorPickerFrame:IsVisible() or isCancel) then
			local r,g,b = ColorPickerFrame:GetColorRGB()
			local a = 1
			if element.stype == "ColorSelectAlpha" then
				a = OpacitySliderFrame:GetValue()
			end
			element.r = tonumber(r) or 0
			element.g = tonumber(g) or 0
			element.b = tonumber(b) or 0
			element.a = tonumber(a) or 1
		end
		value = ("%0.3f,%0.3f,%0.3f,%0.3f"):format(element.r,element.g,element.b,element.a)
		if element.tex then
			element.tex:SetColorTexture(element.r,element.g,element.b,element.a)
		end
	elseif element.GetValue then
		value = element:GetValue()
	else
		return
	end
	self.setter(setting, value)
end

function kit:ColumnCheckboxes(id, cols, options)
	assert(isGuiObject(self), "Must be called on a valid object")
	local last, cont, el, setting, text
	last = self:GetLast(id)
	local optc = table.getn(options)
	local rows = math.ceil(optc / cols)
	local row, col = 0, 0
	cont = nil
	for pos, option in ipairs(options) do
		setting, text = unpack(option)
		col = math.floor(row / rows)
		-- the last agrument (text length) divides by a special factor to improve spacing
		-- a hard coded number would be better, but I'm doing this as a quick fix for now
		el = self:AddControl(id, "Checkbox", col/cols, 1, setting, text, true, 1/cols/1.03)
		row = row + 1
		if (row % rows == 0) then
			if (col == 0) then
				cont = el
			end
			self:SetLast(id, last)
		end
	end
	self:SetLast(id, cont)
end

function kit:SetEscSensitive(setting)
	assert(isGuiObject(self), "Must be called on a valid object")
	local name = self:GetName()
	for i = #UISpecialFrames, 1, -1 do
		if (name == UISpecialFrames[i]) then
			table.remove(UISpecialFrames, i)
		end
	end
	if (setting) then
		table.insert(UISpecialFrames, name)
	end
end

SlashCmdList["LIB_CONFIGATOR"] = function( msg )
	msg = msg:trim():lower()
	if ( msg == "reset" ) then
		for _, dialog in pairs(lib.frames) do
			dialog:ClearAllPoints()
			dialog:SetPoint("CENTER", "UIParent", "CENTER")
		end
	end
end

SLASH_LIB_CONFIGATOR1 = "/configator";
