--[[
Name: Dewdrop-2.0
Revision: $Rev: 44792 $
Author(s): ckknight (ckknight@gmail.com)
Website: http://ckknight.wowinterface.com/
Documentation: http://wiki.wowace.com/index.php/Dewdrop-2.0
SVN: http://svn.wowace.com/root/trunk/DewdropLib/Dewdrop-2.0
Description: A library to provide a clean dropdown menu interface.
Dependencies: AceLibrary
License: LGPL v2.1
]]

local MAJOR_VERSION = "Dewdrop-2.0"
local MINOR_VERSION = "$Revision: 44792 $"

if not AceLibrary then error(MAJOR_VERSION .. " requires AceLibrary") end
if not AceLibrary:IsNewVersion(MAJOR_VERSION, MINOR_VERSION) then return end

local Dewdrop = {}

local SharedMedia

local CLOSE = "Close"
local CLOSE_DESC = "Close the menu."
local VALIDATION_ERROR = "Validation error."
local USAGE_TOOLTIP = "Usage: %s."
local RANGE_TOOLTIP = "Note that you can scroll your mouse wheel while over the slider to step by one."
local RESET_KEYBINDING_DESC = "Hit escape to clear the keybinding."
local KEY_BUTTON1 = "Left Mouse"
local KEY_BUTTON2 = "Right Mouse"
local DISABLED = "Disabled"
local DEFAULT_CONFIRM_MESSAGE = "Are you sure you want to perform `%s'?"

if GetLocale() == "deDE" then
	CLOSE = "Schlie\195\159en"
	CLOSE_DESC = "Men\195\188 schlie\195\159en."
	VALIDATION_ERROR = "Validierungsfehler."
	USAGE_TOOLTIP = "Benutzung: %s."
	RANGE_TOOLTIP = "Beachte das du mit dem Mausrad scrollen kannst solange du \195\188ber dem Schieberegler bist, um 10er Spr\195\188nge zu machen."
	RESET_KEYBINDING_DESC = "Escape dr\195\188cken, um die Tastenbelegung zu l\195\182schen."
	KEY_BUTTON1 = "Linke Maustaste"
	KEY_BUTTON2 = "Rechte Maustaste"
	DISABLED = "Deaktiviert"
	DEFAULT_CONFIRM_MESSAGE = "Bist du sicher das du `%s' machen willst?"
elseif GetLocale() == "koKR" then
	CLOSE = "닫기"
	CLOSE_DESC = "메뉴를 닫습니다."
	VALIDATION_ERROR = "오류 확인."
	USAGE_TOOLTIP = "사용법: %s."
	RANGE_TOOLTIP = "알림 : 슬라이더 위에서 마우스 휠을 사용하면 한단계씩 조절할 수 있습니다."
	RESET_KEYBINDING_DESC = "단축키를 해제하려면 ESC키를 누르세요."
	KEY_BUTTON1 = "왼쪽 마우스"
	KEY_BUTTON2 = "오른쪽 마우스"
	DISABLED = "비활성화됨"
	DEFAULT_CONFIRM_MESSAGE = "정말로 `%s' 실행을 하시겠습니까 ?"
elseif GetLocale() == "frFR" then
	CLOSE = "Fermer"
	CLOSE_DESC = "Ferme le menu."
	VALIDATION_ERROR = "Erreur de validation."
	USAGE_TOOLTIP = "Utilisation : %s."
	RANGE_TOOLTIP = "Vous pouvez aussi utiliser la molette de la souris pour pour modifier progressivement."
	RESET_KEYBINDING_DESC = "Appuyez sur la touche Echappement pour effacer le raccourci."
	KEY_BUTTON1 = "Clic gauche"
	KEY_BUTTON2 = "Clic droit"
	DISABLED = "D\195\169sactiv\195\169"
	DEFAULT_CONFIRM_MESSAGE = "\195\138tes-vous s\195\187r de vouloir effectuer '%s' ?"
elseif GetLocale() == "esES" then
	CLOSE = "Cerrar"
	CLOSE_DESC = "Cierra el menú."
	VALIDATION_ERROR = "Error de validación."
	USAGE_TOOLTIP = "Uso: %s."
	RANGE_TOOLTIP = "Puedes desplazarte verticalmente con la rueda del ratón sobre el desplazador."
	RESET_KEYBINDING_DESC = "Pulsa Escape para borrar la asignación de tecla."
	KEY_BUTTON1 = "Clic Izquierdo"
	KEY_BUTTON2 = "Clic Derecho"
	DISABLED = "Desactivado"
	DEFAULT_CONFIRM_MESSAGE = "¿Estás seguro de querer realizar `%s'?"
elseif GetLocale() == "zhTW" then
	CLOSE = "關閉"
	CLOSE_DESC = "關閉選單。"
	VALIDATION_ERROR = "驗證錯誤。"
	USAGE_TOOLTIP = "用法: %s。"
	RANGE_TOOLTIP = "你可以在捲動條上使用滑鼠滾輪來捲動。"
	RESET_KEYBINDING_DESC = "按Esc鍵清除快捷鍵。"
	KEY_BUTTON1 = "滑鼠左鍵"
	KEY_BUTTON2 = "滑鼠右鍵"
	DISABLED = "停用"
	DEFAULT_CONFIRM_MESSAGE = "是否執行「%s」?"
end

Dewdrop.KEY_BUTTON1 = KEY_BUTTON1
Dewdrop.KEY_BUTTON2 = KEY_BUTTON2

local function new(...)
	local t = {}
	for i = 1, select('#', ...), 2 do
		local k = select(i, ...)
		if k then
			t[k] = select(i+1, ...)
		else
			break
		end
	end
	return t
end

local tmp
do
	local t = {}
	function tmp(...)
		for k in pairs(t) do
			t[k] = nil
		end
		for i = 1, select('#', ...), 2 do
			local k = select(i, ...)
			if k then
				t[k] = select(i+1, ...)
			else
				break
			end
		end
		return t
	end
end
local tmp2
do
	local t = {}
	function tmp2(...)
		for k in pairs(t) do
			t[k] = nil
		end
		for i = 1, select('#', ...), 2 do
			local k = select(i, ...)
			if k then
				t[k] = select(i+1, ...)
			else
				break
			end
		end
		return t
	end
end
local levels
local buttons


-- Secure frame handling:
-- Rather than using secure buttons in the menu (has problems), we have one
-- master secureframe that we pop onto menu items on mouseover. This requires
-- some dark magic with OnLeave etc, but it's not too bad.

local secureFrame = CreateFrame("Button", nil, nil, "SecureActionButtonTemplate")
secureFrame:Hide()

local function secureFrame_Show(self)
  local owner = self.owner
  
  if self.secure then	-- Leftovers from previos owner, clean up! ("Shouldn't" happen but does..)
	  for k,v in pairs(self.secure) do
	    self:SetAttribute(k, nil)
	  end
  end
  self.secure = owner.secure;	-- Grab hold of new secure data
  
  local scale = owner:GetEffectiveScale()
  
  self:SetPoint("TOPLEFT", nil, "BOTTOMLEFT", owner:GetLeft() * scale, owner:GetTop() * scale)
  self:SetPoint("BOTTOMRIGHT", nil, "BOTTOMLEFT", owner:GetRight() * scale, owner:GetBottom() * scale)
  self:EnableMouse(true)
  for k,v in pairs(self.secure) do
    self:SetAttribute(k, v)
  end

	secureFrame:SetFrameStrata(owner:GetFrameStrata())
	secureFrame:SetFrameLevel(owner:GetFrameLevel()+1)
  
  self:Show()
end

local function secureFrame_Hide(self)
  self:Hide()
  if self.secure then
	  for k,v in pairs(self.secure) do
	    self:SetAttribute(k, nil)
	  end
	end
  self.secure = nil
end

secureFrame:SetScript("OnEvent", 
	function()
		if event=="PLAYER_REGEN_ENABLED" then
			this.combat = false
			if not this:IsShown() and this.owner then
				secureFrame_Show(this)
			end
		elseif event=="PLAYER_REGEN_DISABLED" then
			this.combat = true
			if this:IsShown() then
				secureFrame_Hide(this)
			end
		end
	end
)
secureFrame:RegisterEvent("PLAYER_REGEN_ENABLED")
secureFrame:RegisterEvent("PLAYER_REGEN_DISABLED")

secureFrame:SetScript("OnLeave", 
	function() 
		local owner=this.owner
		this:Deactivate() 
		owner:GetScript("OnLeave")()
	end
)

secureFrame:HookScript("OnClick",
	function()
		local realthis = this
		this = this.owner
		this:GetScript("OnClick")()
	end
)

function secureFrame:IsOwnedBy(frame)
	return self.owner == frame
end

function secureFrame:Activate(owner)
	if self.owner then		-- "Shouldn't" happen but apparently it does and I cba to troubleshoot...
		if not self.combat then
			secureFrame_Hide(self)
		end
	end
	self.owner = owner
	if not self.combat then
		secureFrame_Show(self)
	end
end

function secureFrame:Deactivate()
	if not self.combat then
		secureFrame_Hide(self)
	end
	self.owner = nil
end

-- END secure frame utilities


-- Underline on mouseover - use a single global underline that we move around, no point in creating lots of copies
local underlineFrame = CreateFrame("Frame", nil)
underlineFrame.tx = underlineFrame:CreateTexture()
underlineFrame.tx:SetTexture(1,1,0.5,0.75)
underlineFrame:SetScript("OnHide", function(this) this:Hide(); end)
underlineFrame:SetScript("OnShow", function(this) 	-- change sizing on the fly to catch runtime uiscale changes
    underlineFrame.tx:SetPoint("TOPLEFT", -1, -2/this:GetEffectiveScale())
    underlineFrame.tx:SetPoint("RIGHT", 1,0)
    underlineFrame.tx:SetHeight(0.6 / this:GetEffectiveScale()); 
end)
underlineFrame:SetHeight(1)

-- END underline on mouseover


local function GetScaledCursorPosition()
	local x, y = GetCursorPosition()
	local scale = UIParent:GetEffectiveScale()
	return x / scale, y / scale
end

local function StartCounting(self, level)
	for i = level, 1, -1 do
		if levels[i] then
			levels[i].count = 3
		end
	end
end

local function StopCounting(self, level)
	for i = level, 1, -1 do
		if levels[i] then
			levels[i].count = nil
		end
	end
end

local function OnUpdate(self, elapsed)
	for _,level in ipairs(levels) do
		local count = level.count
		if count then
			count = count - elapsed
			if count < 0 then
				level.count = nil
				self:Close(level.num)
			else
				level.count = count
			end
		end
	end
end

local function CheckDualMonitor(self, frame)
	local ratio = GetScreenWidth() / GetScreenHeight()
	if ratio >= 2.4 and frame:GetRight() > GetScreenWidth() / 2 and frame:GetLeft() < GetScreenWidth() / 2 then
		local offsetx
		if GetCursorPosition() / GetScreenHeight() * 768 < GetScreenWidth() / 2 then
			offsetx = GetScreenWidth() / 2 - frame:GetRight()
		else
			offsetx = GetScreenWidth() / 2 - frame:GetLeft()
		end
		local point, parent, relativePoint, x, y = frame:GetPoint(1)
		frame:SetPoint(point, parent, relativePoint, (x or 0) + offsetx, y or 0)
	end
end

local function CheckSize(self, level)
	if not level.buttons then
		return
	end
	local height = 20
	for _, button in ipairs(level.buttons) do
		height = height + button:GetHeight()
	end
	level:SetHeight(height)
	local width = 160
	for _, button in ipairs(level.buttons) do
		local extra = 1
		if button.hasArrow or button.hasColorSwatch then
			extra = extra + 16
		end
		if not button.notCheckable then
			extra = extra + 24
		end
		button.text:SetFont(STANDARD_TEXT_FONT, button.textHeight)
		if button.text:GetWidth() + extra > width then
			width = button.text:GetWidth() + extra
		end
	end
	level:SetWidth(width + 20)
	if level:GetLeft() and level:GetRight() and level:GetTop() and level:GetBottom() and (level:GetLeft() < 0 or level:GetRight() > GetScreenWidth() or level:GetTop() > GetScreenHeight() or level:GetBottom() < 0) then
		level:ClearAllPoints()
		local parent = level.parent or level:GetParent()
		if type(parent) ~= "table" then
			parent = UIParent
		end
		if level.lastDirection == "RIGHT" then
			if level.lastVDirection == "DOWN" then
				level:SetPoint("TOPLEFT", parent, "TOPRIGHT", 5, 10)
			else
				level:SetPoint("BOTTOMLEFT", parent, "BOTTOMRIGHT", 5, -10)
			end
		else
			if level.lastVDirection == "DOWN" then
				level:SetPoint("TOPRIGHT", parent, "TOPLEFT", -5, 10)
			else
				level:SetPoint("BOTTOMRIGHT", parent, "BOTTOMLEFT", -5, -10)
			end
		end
	end
	local dirty = false
	if not level:GetRight() then
		self:Close()
		return
	end
	if level:GetRight() > GetScreenWidth() and level.lastDirection == "RIGHT" then
		level.lastDirection = "LEFT"
		dirty = true
	elseif level:GetLeft() < 0 and level.lastDirection == "LEFT" then
		level.lastDirection = "RIGHT"
		dirty = true
	end
	if level:GetTop() > GetScreenHeight() and level.lastVDirection == "UP" then
		level.lastVDirection = "DOWN"
		dirty = true
	elseif level:GetBottom() < 0 and level.lastVDirection == "DOWN" then
		level.lastVDirection = "UP"
		dirty = true
	end
	if dirty then
		level:ClearAllPoints()
		local parent = level.parent or level:GetParent()
		if type(parent) ~= "table" then
			parent = UIParent
		end
		if level.lastDirection == "RIGHT" then
			if level.lastVDirection == "DOWN" then
				level:SetPoint("TOPLEFT", parent, "TOPRIGHT", 5, 10)
			else
				level:SetPoint("BOTTOMLEFT", parent, "BOTTOMRIGHT", 5, -10)
			end
		else
			if level.lastVDirection == "DOWN" then
				level:SetPoint("TOPRIGHT", parent, "TOPLEFT", -5, 10)
			else
				level:SetPoint("BOTTOMRIGHT", parent, "BOTTOMLEFT", -5, -10)
			end
		end
	end
	if level:GetTop() > GetScreenHeight() then
		local top = level:GetTop()
		local point, parent, relativePoint, x, y = level:GetPoint(1)
		level:ClearAllPoints()
		level:SetPoint(point, parent, relativePoint, x or 0, (y or 0) + GetScreenHeight() - top)
	elseif level:GetBottom() < 0 then
		local bottom = level:GetBottom()
		local point, parent, relativePoint, x, y = level:GetPoint(1)
		level:ClearAllPoints()
		level:SetPoint(point, parent, relativePoint, x or 0, (y or 0) - bottom)
	end
	CheckDualMonitor(self, level)
	if mod(level.num, 5) == 0 then
		local left, bottom = level:GetLeft(), level:GetBottom()
		level:ClearAllPoints()
		level:SetPoint("BOTTOMLEFT", UIParent, "BOTTOMLEFT", left, bottom)
	end
end

local Open
local OpenSlider
local OpenEditBox
local Refresh
local Clear
local function ReleaseButton(self, level, index)
	if not level.buttons then
		return
	end
	if not level.buttons[index] then
		return
	end
	local button = level.buttons[index]
	button:Hide()
	if button.highlight then
		button.highlight:Hide()
	end
--	button.arrow:SetVertexColor(1, 1, 1)
--	button.arrow:SetHeight(16)
--	button.arrow:SetWidth(16)
	table.remove(level.buttons, index)
	table.insert(buttons, button)
	for k in pairs(button) do
		if k ~= 0 and k ~= "text" and k ~= "check" and k ~= "arrow" and k ~= "colorSwatch" and k ~= "highlight" and k ~= "radioHighlight" then
			button[k] = nil
		end
	end
	return true
end

local function Scroll(self, level, down)
	if down then
		if level:GetBottom() < 0 then
			local point, parent, relativePoint, x, y = level:GetPoint(1)
			level:SetPoint(point, parent, relativePoint, x, y + 50)
			if level:GetBottom() > 0 then
				level:SetPoint(point, parent, relativePoint, x, y + 50 - level:GetBottom())
			end
		end
	else
		if level:GetTop() > GetScreenHeight() then
			local point, parent, relativePoint, x, y = level:GetPoint(1)
			level:SetPoint(point, parent, relativePoint, x, y - 50)
			if level:GetTop() < GetScreenHeight() then
				level:SetPoint(point, parent, relativePoint, x, y - 50 + GetScreenHeight() - level:GetTop())
			end
		end
	end
end

local function getArgs(t, str, num, ...)
	local x = t[str .. num]
	if x == nil then
		return ...
	else
		return x, getArgs(t, str, num + 1, ...)
	end
end

local sliderFrame
local editBoxFrame

local normalFont
local lastSetFont
local justSetFont = false
local regionTmp = {}
local function fillRegionTmp(...)
	for i = 1, select('#', ...) do
		regionTmp[i] = select(i, ...)
	end
end

local function showGameTooltip(this)
	if this.tooltipTitle or this.tooltipText then
		GameTooltip_SetDefaultAnchor(GameTooltip, this)
		local disabled = not this.isTitle and this.disabled
		local font
		if this.tooltipTitle then
			if SharedMedia and SharedMedia:IsValid("font", this.tooltipTitle) then
				font = SharedMedia:Fetch("font", this.tooltipTitle)
			end
			if disabled then
				GameTooltip:SetText(this.tooltipTitle, 0.5, 0.5, 0.5, 1)
			else
				GameTooltip:SetText(this.tooltipTitle, 1, 1, 1, 1)
			end
			if this.tooltipText then
				if not font and SharedMedia and SharedMedia:IsValid("font", this.tooltipText) then
					font = SharedMedia:Fetch("font", this.tooltipText)
				end
				if disabled then
					GameTooltip:AddLine(this.tooltipText, (NORMAL_FONT_COLOR.r + 0.5) / 2, (NORMAL_FONT_COLOR.g + 0.5) / 2, (NORMAL_FONT_COLOR.b + 0.5) / 2, 1)
				else
					GameTooltip:AddLine(this.tooltipText, NORMAL_FONT_COLOR.r, NORMAL_FONT_COLOR.g, NORMAL_FONT_COLOR.b, 1)
				end
			end
		else	
			if SharedMedia and SharedMedia:IsValid("font", this.tooltipText) then
				font = SharedMedia:Fetch("font", this.tooltipText)
			end
			if disabled then
				GameTooltip:SetText(this.tooltipText, 0.5, 0.5, 0.5, 1)
			else
				GameTooltip:SetText(this.tooltipText, 1, 1, 1, 1)
			end
		end
		if font then
			fillRegionTmp(GameTooltip:GetRegions())
			lastSetFont = font
			justSetFont = true
			for i,v in ipairs(regionTmp) do
				if v.SetFont then
					local norm,size,outline = v:GetFont()
					v:SetFont(font, size, outline)
					if not normalFont then
						normalFont = norm
					end
				end
				regionTmp[i] = nil
			end
		elseif not normalFont then
			fillRegionTmp(GameTooltip:GetRegions())
			for i,v in ipairs(regionTmp) do
				if v.GetFont and not normalFont then
					normalFont = v:GetFont()
				end
				regionTmp[i] = nil
			end
		end
		GameTooltip:Show()
	end
	if this.tooltipFunc then
		GameTooltip:SetOwner(this, "ANCHOR_NONE")
		GameTooltip:SetPoint("TOPLEFT", this, "TOPRIGHT", 5, 0)
		this.tooltipFunc(getArgs(this, 'tooltipArg', 1))
		GameTooltip:Show()
	end
end

local tmpt = setmetatable({}, {mode='v'})
local numButtons = 0
local function AcquireButton(self, level)
	if not levels[level] then
		return
	end
	level = levels[level]
	if not level.buttons then
		level.buttons = {}
	end
	local button
	if #buttons == 0 then
		numButtons = numButtons + 1
		button = CreateFrame("Button", "Dewdrop20Button" .. numButtons, nil)
		button:SetFrameStrata("FULLSCREEN_DIALOG")
		button:SetHeight(16)
		local highlight = button:CreateTexture(nil, "BACKGROUND")
		highlight:SetTexture("Interface\\QuestFrame\\UI-QuestTitleHighlight")
		button.highlight = highlight
		highlight:SetBlendMode("ADD")
		highlight:SetAllPoints(button)
		highlight:Hide()
		local check = button:CreateTexture(nil, "ARTWORK")
		button.check = check
		check:SetTexture("Interface\\Buttons\\UI-CheckBox-Check")
		check:SetPoint("CENTER", button, "LEFT", 12, 0)
		check:SetWidth(24)
		check:SetHeight(24)
		local radioHighlight = button:CreateTexture(nil, "ARTWORK")
		button.radioHighlight = radioHighlight
		radioHighlight:SetTexture("Interface\\Buttons\\UI-RadioButton")
		radioHighlight:SetAllPoints(check)
		radioHighlight:SetBlendMode("ADD")
		radioHighlight:SetTexCoord(0.5, 0.75, 0, 1)
		radioHighlight:Hide()
		button:SetScript("OnEnter", function()
			if (sliderFrame and sliderFrame:IsShown() and sliderFrame.mouseDown and sliderFrame.level == this.level.num + 1) or (editBoxFrame and editBoxFrame:IsShown() and editBoxFrame.mouseDown and editBoxFrame.level == this.level.num + 1) then
				for i = 1, this.level.num do
					Refresh(self, levels[i])
				end
				return
			end
			self:Close(this.level.num + 1)
			if not this.disabled then
				if this.secure then
					secureFrame:Activate(this)
				elseif this.hasSlider then
					OpenSlider(self, this)
				elseif this.hasEditBox then
					OpenEditBox(self, this)
				elseif this.hasArrow then
					Open(self, this, nil, this.level.num + 1, this.value)
				end
			end
			if not this.level then -- button reclaimed
				return
			end
			StopCounting(self, this.level.num + 1)
			if not this.disabled then
				highlight:Show()
				if this.isRadio then
					button.radioHighlight:Show()
				end
				if this.mouseoverUnderline then
					underlineFrame:SetParent(this)
					underlineFrame:SetPoint("BOTTOMLEFT",this.text,0,0)
					underlineFrame:SetWidth(this.text:GetWidth())
					underlineFrame:Show()
				end
			end
			showGameTooltip(this)
		end)
		button:SetScript("OnHide", function()
			if this.secure and secureFrame:IsOwnedBy(this) then
				secureFrame:Deactivate()
			end
		end)
		button:SetScript("OnLeave", function()
			if this.secure and secureFrame:IsShown() then
				return;	-- it's ok, we didn't actually mouse out of the button, only onto the secure frame on top of it
			end
			underlineFrame:Hide()
			if not this.selected then
				highlight:Hide()
			end
			button.radioHighlight:Hide()
			if this.level then
				StartCounting(self, this.level.num)
			end
			GameTooltip:Hide()
		end)
		local first = true
		button:SetScript("OnClick", function()
			if not this.disabled then
				if this.hasColorSwatch then
					local func = button.colorFunc
					local hasOpacity = this.hasOpacity
					local this = this
					for k in pairs(tmpt) do
						tmpt[k] = nil
					end
					for i = 1, 1000 do
						local x = this['colorArg'..i]
						if x == nil then
							break
						else
							tmpt[i] = x
						end
					end
					ColorPickerFrame.func = function()
						if func then
							local r,g,b = ColorPickerFrame:GetColorRGB()
							local a = hasOpacity and 1 - OpacitySliderFrame:GetValue() or nil
							local n = #tmpt
							tmpt[n+1] = r
							tmpt[n+2] = g
							tmpt[n+3] = b
							tmpt[n+4] = a
							func(unpack(tmpt))
							tmpt[n+1] = nil
							tmpt[n+2] = nil
							tmpt[n+3] = nil
							tmpt[n+4] = nil
						end
					end
					ColorPickerFrame.hasOpacity = this.hasOpacity
					ColorPickerFrame.opacityFunc = ColorPickerFrame.func
					ColorPickerFrame.opacity = 1 - this.opacity
					ColorPickerFrame:SetColorRGB(this.r, this.g, this.b)
					local r, g, b, a = this.r, this.g, this.b, this.opacity
					ColorPickerFrame.cancelFunc = function()
						if func then
							local n = #tmpt
							tmpt[n+1] = r
							tmpt[n+2] = g
							tmpt[n+3] = b
							tmpt[n+4] = a
							func(unpack(tmpt))
							for i = 1, n+4 do
								tmpt[i] = nil
							end
						end
					end
					self:Close(1)
					ShowUIPanel(ColorPickerFrame)
				elseif this.func then
					local level = this.level
					if type(this.func) == "string" then
						if type(this.arg1[this.func]) ~= "function" then
							self:error("Cannot call method %q", this.func)
						end
						this.arg1[this.func](this.arg1, getArgs(this, 'arg', 2))
					else
						this.func(getArgs(this, 'arg', 1))
					end
					if this.closeWhenClicked then
						self:Close()
					elseif level:IsShown() then
						for i = 1, level.num do
							Refresh(self, levels[i])
						end
						local value = levels[level.num].value
						for i = level.num-1, 1, -1 do
							local level = levels[i]
							local good = false
							for _,button in ipairs(level.buttons) do
								if button.value == value then
									good = true
									break
								end
							end
							if not good then
								Dewdrop:Close(i+1)
							end
							value = levels[i].value
						end	
					end
				elseif this.closeWhenClicked then
					self:Close()
				end
			end
		end)
		local text = button:CreateFontString(nil, "ARTWORK")
		button.text = text
		text:SetFontObject(GameFontHighlightSmall)
		button.text:SetFont(STANDARD_TEXT_FONT, UIDROPDOWNMENU_DEFAULT_TEXT_HEIGHT)
		button:SetScript("OnMouseDown", function()
			if not this.disabled and (this.func or this.colorFunc or this.closeWhenClicked) then
				text:SetPoint("LEFT", button, "LEFT", this.notCheckable and 1 or 25, -1)
			end
		end)
		button:SetScript("OnMouseUp", function()
			if not this.disabled and (this.func or this.colorFunc or this.closeWhenClicked) then
				text:SetPoint("LEFT", button, "LEFT", this.notCheckable and 0 or 24, 0)
			end
		end)
		local arrow = button:CreateTexture(nil, "ARTWORK")
		button.arrow = arrow
		arrow:SetPoint("LEFT", button, "RIGHT", -16, 0)
		arrow:SetWidth(16)
		arrow:SetHeight(16)
		arrow:SetTexture("Interface\\ChatFrame\\ChatFrameExpandArrow")
		local colorSwatch = button:CreateTexture(nil, "ARTWORK")
		button.colorSwatch = colorSwatch
		colorSwatch:SetWidth(20)
		colorSwatch:SetHeight(20)
		colorSwatch:SetTexture("Interface\\ChatFrame\\ChatFrameColorSwatch")
		local texture = button:CreateTexture(nil, "OVERLAY")
		colorSwatch.texture = texture
		texture:SetTexture("Interface\\Buttons\\WHITE8X8")
		texture:SetWidth(11.5)
		texture:SetHeight(11.5)
		texture:Show()
		texture:SetPoint("CENTER", colorSwatch, "CENTER")
		colorSwatch:SetPoint("RIGHT", button, "RIGHT", 0, 0)
	else
		button = table.remove(buttons)
	end
	button:ClearAllPoints()
	button:SetParent(level)
	button:SetFrameStrata(level:GetFrameStrata())
	button:SetFrameLevel(level:GetFrameLevel() + 1)
	button:SetPoint("LEFT", level, "LEFT", 10, 0)
	button:SetPoint("RIGHT", level, "RIGHT", -10, 0)
	if #level.buttons == 0 then
		button:SetPoint("TOP", level, "TOP", 0, -10)
	else
		button:SetPoint("TOP", level.buttons[#level.buttons], "BOTTOM", 0, 0)
	end
	button.text:SetPoint("LEFT", button, "LEFT", 24, 0)
	button:Show()
	button.level = level
	table.insert(level.buttons, button)
	if not level.parented then
		level.parented = true
		level:ClearAllPoints()
		if level.num == 1 then
			if level.parent ~= UIParent and type(level.parent) == "table" then
				level:SetPoint("TOPRIGHT", level.parent, "TOPLEFT")
			else
				level:SetPoint("CENTER", UIParent, "CENTER")
			end
		else
			if level.lastDirection == "RIGHT" then
				if level.lastVDirection == "DOWN" then
					level:SetPoint("TOPLEFT", level.parent, "TOPRIGHT", 5, 10)
				else
					level:SetPoint("BOTTOMLEFT", level.parent, "BOTTOMRIGHT", 5, -10)
				end
			else
				if level.lastVDirection == "DOWN" then
					level:SetPoint("TOPRIGHT", level.parent, "TOPLEFT", -5, 10)
				else
					level:SetPoint("BOTTOMRIGHT", level.parent, "BOTTOMLEFT", -5, -10)
				end
			end
		end
		level:SetFrameStrata("FULLSCREEN_DIALOG")
	end
	button:SetAlpha(1)
	return button
end

local numLevels = 0
local function AcquireLevel(self, level)
	if not levels[level] then
		for i = #levels + 1, level, -1 do
			local i = i
			numLevels = numLevels + 1
			local frame = CreateFrame("Button", "Dewdrop20Level" .. numLevels, nil)
			if i == 1 then
				local old_CloseSpecialWindows = CloseSpecialWindows
				function CloseSpecialWindows()
					local found = old_CloseSpecialWindows()
					if levels[1]:IsShown() then
						self:Close()
						return 1
					end
					return found
				end
			end
			levels[i] = frame
			frame.num = i
			frame:SetParent(UIParent)
			frame:SetFrameStrata("FULLSCREEN_DIALOG")
			frame:Hide()
			frame:SetWidth(180)
			frame:SetHeight(10)
			frame:SetFrameLevel(i * 3)
			frame:SetScript("OnHide", function()
				self:Close(level + 1)
			end)
			if frame.SetTopLevel then
				frame:SetTopLevel(true)
			end
			frame:EnableMouse(true)
			frame:EnableMouseWheel(true)
			local backdrop = CreateFrame("Frame", nil, frame)
			backdrop:SetAllPoints(frame)
			backdrop:SetBackdrop(tmp(
				'bgFile', "Interface\\Tooltips\\UI-Tooltip-Background",
				'edgeFile', "Interface\\Tooltips\\UI-Tooltip-Border",
				'tile', true,
				'insets', tmp2(
					'left', 5,
					'right', 5,
					'top', 5,
					'bottom', 5
				),
				'tileSize', 16,
				'edgeSize', 16
			))
			backdrop:SetBackdropBorderColor(TOOLTIP_DEFAULT_COLOR.r, TOOLTIP_DEFAULT_COLOR.g, TOOLTIP_DEFAULT_COLOR.b)
			backdrop:SetBackdropColor(TOOLTIP_DEFAULT_BACKGROUND_COLOR.r, TOOLTIP_DEFAULT_BACKGROUND_COLOR.g, TOOLTIP_DEFAULT_BACKGROUND_COLOR.b)
			frame:SetScript("OnClick", function()
				self:Close(i)
			end)
			frame:SetScript("OnEnter", function()
				StopCounting(self, i)
			end)
			frame:SetScript("OnLeave", function()
				StartCounting(self, i)
			end)
			frame:SetScript("OnMouseWheel", function()
				Scroll(self, frame, arg1 < 0)
			end)
			if i == 1 then
				frame:SetScript("OnUpdate", function(this, arg1)
					OnUpdate(self, arg1)
				end)
				levels[1].lastDirection = "RIGHT"
				levels[1].lastVDirection = "DOWN"
			else
				levels[i].lastDirection = levels[i - 1].lastDirection
				levels[i].lastVDirection = levels[i - 1].lastVDirection
			end
		end
	end
	local fullscreenFrame = GetUIPanel("fullscreen")
	local l = levels[level]
	local strata, framelevel = l:GetFrameStrata(), l:GetFrameLevel()
	if fullscreenFrame then
		l:SetParent(fullscreenFrame)
	else
		l:SetParent(UIParent)
	end
	l:SetFrameStrata(strata)
	l:SetFrameLevel(framelevel)
	l:SetAlpha(1)
	return l
end

local function validateOptions(options, position, baseOptions, fromPass)
	if not baseOptions then
		baseOptions = options
	end
	if type(options) ~= "table" then
		return "Options must be a table.", position
	end
	local kind = options.type
	if type(kind) ~= "string" then
		return '"type" must be a string.', position
	elseif kind ~= "group" and kind ~= "range" and kind ~= "text" and kind ~= "execute" and kind ~= "toggle" and kind ~= "color" and kind ~= "header" then
		return '"type" must either be "range", "text", "group", "toggle", "execute", "color", or "header".', position
	end
	if options.aliases then
		if type(options.aliases) ~= "table" and type(options.aliases) ~= "string" then
			return '"alias" must be a table or string', position
		end
	end
	if not fromPass then
		if kind == "execute" then
			if type(options.func) ~= "string" and type(options.func) ~= "function" then
				return '"func" must be a string or function', position
			end
		elseif kind == "range" or kind == "text" or kind == "toggle" then
			if type(options.set) ~= "string" and type(options.set) ~= "function" then
				return '"set" must be a string or function', position
			end
			if kind == "text" and options.get == false then
			elseif type(options.get) ~= "string" and type(options.get) ~= "function" then
				return '"get" must be a string or function', position
			end
		elseif kind == "group" and options.pass then
			if options.pass ~= true then
				return '"pass" must be either nil, true, or false', position
			end
			if not options.func then
				if type(options.set) ~= "string" and type(options.set) ~= "function" then
					return '"set" must be a string or function', position
				end
				if type(options.get) ~= "string" and type(options.get) ~= "function" then
					return '"get" must be a string or function', position
				end
			elseif type(options.func) ~= "string" and type(options.func) ~= "function" then
				return '"func" must be a string or function', position
			end
		end
	end
	if options ~= baseOptions then
		if kind == "header" then
		elseif type(options.desc) ~= "string" then
			return '"desc" must be a string', position
		elseif options.desc:len() == 0 then
			return '"desc" cannot be a 0-length string', position
		end
	end
	if options ~= baseOptions or kind == "range" or kind == "text" or kind == "toggle" or kind == "color" then
		if options.type == "header" and not options.cmdName and not options.name then
		elseif options.cmdName then
			if type(options.cmdName) ~= "string" then
				return '"cmdName" must be a string or nil', position
			elseif options.cmdName:len() == 0 then
				return '"cmdName" cannot be a 0-length string', position
			end
			if type(options.guiName) ~= "string" then
				if not options.guiNameIsMap then
					return '"guiName" must be a string or nil', position
				end
			elseif options.guiName:len() == 0 then
				return '"guiName" cannot be a 0-length string', position
			end
		else
			if type(options.name) ~= "string" then
				return '"name" must be a string', position
			elseif options.name:len() == 0 then
				return '"name" cannot be a 0-length string', position
			end
		end
	end
	if options.guiNameIsMap then
		if type(options.guiNameIsMap) ~= "boolean" then
			return '"guiNameIsMap" must be a boolean or nil', position
		elseif options.type ~= "toggle" then
			return 'if "guiNameIsMap" is true, then "type" must be set to \'toggle\'', position
		elseif type(options.map) ~= "table" then
			return '"map" must be a table', position
		end
	end
	if options.message and type(options.message) ~= "string" then
		return '"message" must be a string or nil', position
	end
	if options.error and type(options.error) ~= "string" then
		return '"error" must be a string or nil', position
	end
	if options.current and type(options.current) ~= "string" then
		return '"current" must be a string or nil', position
	end
	if options.order then
		if type(options.order) ~= "number" or (-1 < options.order and options.order < 0.999) then
			return '"order" must be a non-zero number or nil', position
		end
	end
	if options.disabled then
		if type(options.disabled) ~= "function" and type(options.disabled) ~= "string" and options.disabled ~= true then
			return '"disabled" must be a function, string, or boolean', position
		end
	end
	if options.cmdHidden then
		if type(options.cmdHidden) ~= "function" and type(options.cmdHidden) ~= "string" and options.cmdHidden ~= true then
			return '"cmdHidden" must be a function, string, or boolean', position
		end
	end
	if options.guiHidden then
		if type(options.guiHidden) ~= "function" and type(options.guiHidden) ~= "string" and options.guiHidden ~= true then
			return '"guiHidden" must be a function, string, or boolean', position
		end
	end
	if options.hidden then
		if type(options.hidden) ~= "function" and type(options.hidden) ~= "string" and options.hidden ~= true then
			return '"hidden" must be a function, string, or boolean', position
		end
	end
	if kind == "text" then
		if type(options.validate) == "table" then
			local t = options.validate
			local iTable = nil
			for k,v in pairs(t) do
				if type(k) == "number" then
					if iTable == nil then
						iTable = true
					elseif not iTable then
						return '"validate" must either have all keys be indexed numbers or strings', position
					elseif k < 1 or k > #t then
						return '"validate" numeric keys must be indexed properly. >= 1 and <= #t', position
					end
				else
					if iTable == nil then
						iTable = false
					elseif iTable then
						return '"validate" must either have all keys be indexed numbers or strings', position
					end
				end
				if type(v) ~= "string" then
					return '"validate" values must all be strings', position
				end
			end
			if options.multiToggle and options.multiToggle ~= true then
				return '"multiToggle" must be a boolean or nil if "validate" is a table', position
			end
		elseif options.validate == "keybinding" then
			-- no other checks
		else
			if type(options.usage) ~= "string" then
				return '"usage" must be a string', position
			elseif options.validate and type(options.validate) ~= "string" and type(options.validate) ~= "function" then
				return '"validate" must be a string, function, or table', position
			end
		end
		if options.multiToggle and type(options.validate) ~= "table" then
			return '"validate" must be a table if "multiToggle" is true', position
		end
	elseif kind == "range" then
		if options.min or options.max then
			if type(options.min) ~= "number" then
				return '"min" must be a number', position
			elseif type(options.max) ~= "number" then
				return '"max" must be a number', position
			elseif options.min >= options.max then
				return '"min" must be less than "max"', position
			end
		end
		if options.step then
			if type(options.step) ~= "number" then
				return '"step" must be a number', position
			elseif options.step < 0 then
				return '"step" must be nonnegative', position
			end
		end
		if options.bigStep then
			if type(options.bigStep) ~= "number" then
				return '"bigStep" must be a number', position
			elseif options.bigStep < 0 then
				return '"bigStep" must be nonnegative', position
			end
		end
		if options.isPercent and options.isPercent ~= true then
			return '"isPercent" must either be nil, true, or false', position
		end
	elseif kind == "toggle" then
		if options.map then
			if type(options.map) ~= "table" then
				return '"map" must be a table', position
			elseif type(options.map[true]) ~= "string" then
				return '"map[true]" must be a string', position
			elseif type(options.map[false]) ~= "string" then
				return '"map[false]" must be a string', position
			end
		end
	elseif kind == "color" then
		if options.hasAlpha and options.hasAlpha ~= true then
			return '"hasAlpha" must be nil, true, or false', position
		end
	elseif kind == "group" then
		if options.pass and options.pass ~= true then
			return '"pass" must be nil, true, or false', position
		end
		if type(options.args) ~= "table" then
			return '"args" must be a table', position
		end
		for k,v in pairs(options.args) do
			if type(k) ~= "number" then
				if type(k) ~= "string" then
					return '"args" keys must be strings or numbers', position
				elseif k:len() == 0 then
					return '"args" keys must not be 0-length strings.', position
				end
			end
			if type(v) ~= "table" then
				return '"args" values must be tables', position and position .. "." .. k or k
			end
			local newposition
			if position then
				newposition = position .. ".args." .. k
			else
				newposition = "args." .. k
			end
			local err, pos = validateOptions(v, newposition, baseOptions, options.pass)
			if err then
				return err, pos
			end
		end
	elseif kind == "execute" then
		if type(options.confirm) ~= "string" and type(options.confirm) ~= "boolean" and type(options.confirm) ~= "nil" then
			return '"confirm" must be a string, boolean, or nil', position
		end
	end
	if options.icon and type(options.icon) ~= "string" then
		return'"icon" must be a string', position
	end
	if options.iconWidth or options.iconHeight then
		if type(options.iconWidth) ~= "number" or type(options.iconHeight) ~= "number" then
			return '"iconHeight" and "iconWidth" must be numbers', position
		end
	end
	if options.iconCoordLeft or options.iconCoordRight or options.iconCoordTop or options.iconCoordBottom then
		if type(options.iconCoordLeft) ~= "number" or type(options.iconCoordRight) ~= "number" or type(options.iconCoordTop) ~= "number" or type(options.iconCoordBottom) ~= "number" then
			return '"iconCoordLeft", "iconCoordRight", "iconCoordTop", and "iconCoordBottom" must be numbers', position
		end
	end
end

local validatedOptions

local values
local mysort_args
local mysort
local othersort
local othersort_validate

local baseFunc, currentLevel

local function confirmPopup(message, func, ...)
	if not StaticPopupDialogs["DEWDROP20_CONFIRM_DIALOG"] then
		StaticPopupDialogs["DEWDROP20_CONFIRM_DIALOG"] = {}
	end
	local t = StaticPopupDialogs["DEWDROP20_CONFIRM_DIALOG"]
	for k in pairs(t) do
		t[k] = nil
	end
	t.text = message
	t.button1 = ACCEPT or "Accept"
	t.button2 = CANCEL or "Cancel"
	t.OnAccept = function()
		func(unpack(t))
	end
	for i = 1, select('#', ...) do
		t[i] = select(i, ...)
	end
	t.timeout = 0
	t.whileDead = 1
	t.hideOnEscape = 1
	
	Dewdrop:Close()
	StaticPopup_Show("DEWDROP20_CONFIRM_DIALOG")
end


local function getMethod(settingname, handler, v, methodName, ...)	-- "..." is simply returned straight out cause you can't do "a,b,c = 111,f(),222"
	assert(v and type(v)=="table")
	assert(methodName and type(methodName)=="string")
	
	local method = v[methodName]
	if type(method)=="function" then
		return method, ...
	elseif type(method)=="string" then
		if not handler then
			Dewdrop:error("[%s] 'handler' is required if providing a method name: %q", tostring(settingname), method)
		elseif not handler[method] then
			Dewdrop:error("[%s] 'handler' method %q not defined", tostring(settingname), method)
		end
		return handler[method], handler, ...
	end
	
	Dewdrop:error("[%s] Missing %q directive", tostring(settingname), methodName)
end

local function callMethod(settingname, handler, v, methodName, ...)
	assert(v and type(v)=="table")
	assert(methodName and type(methodName)=="string")
	
	local method = v[methodName]
	if type(method)=="function" then
		local success, ret,ret2,ret3,ret4 = pcall(v[methodName], ...)
		if not success then
			geterrorhandler()(ret)
			return nil
		end
		return ret,ret2,ret3,ret4
		
	elseif type(method)=="string" then
		
		local neg = method:match("^~(.-)$")
		if neg then
			method = neg
		end
		if not handler then
			Dewdrop:error("[%s] 'handler' is required if providing a method name: %q", tostring(settingname), method)
		elseif not handler[method] then
			Dewdrop:error("[%s] 'handler' (%q) method %q not defined", tostring(settingname), handler.name or "(unnamed)", method)
		end
		local success, ret,ret2,ret3,ret4 = pcall(handler[method], handler, ...)
		if not success then
			geterrorhandler()(ret)
			return nil
		end
		if neg then 
			return not ret
		end
		return ret,ret2,ret3,ret4
	elseif method == false then
		return nil
	end
	
	Dewdrop:error("[%s] Missing %q directive in %q", tostring(settingname), methodName, v.name or "(unnamed)")
end

local function skip1Nil(...)
	if select(1,...)==nil then
		return select(2,...)
	end
	return ...
end

function Dewdrop:FeedAceOptionsTable(options, difference)
	self:argCheck(options, 2, "table")
	self:argCheck(difference, 3, "nil", "number")
	if not currentLevel then
		self:error("Cannot call `FeedAceOptionsTable' outside of a Dewdrop declaration")
	end
	if not difference then
		difference = 0
	end
	if not validatedOptions then
		validatedOptions = {}
	end
	if not validatedOptions[options] then
		local err, position = validateOptions(options)

		if err then
			if position then
				Dewdrop:error(position .. ": " .. err)
			else
				Dewdrop:error(err)
			end
		end

		validatedOptions[options] = true
	end
	local level = levels[currentLevel]
	if not level then
		self:error("Improper level given")
	end
	if not values then
		values = {}
	else
		for k,v in pairs(values) do
			values[k] = nil
		end
	end

	local current = level
	while current do		-- this traverses from higher level numbers to lower, building "values" with leaf nodes first and trunk nodes later
		if current.num == difference + 1 then
			break
		end
		table.insert(values, current.value)
		current = levels[current.num - 1]
	end

	local realOptions = options
	local handler = options.handler
	local passTable
	local passValue
	while #values > 0 do	-- This loop traverses values from the END (trunk nodes first, then onto leaf nodes)
		if options.pass then
			if options.get and options.set then
				passTable = options
			elseif not passTable then
				passTable = options
			end
		else
			passTable = nil
		end
		local value = table.remove(values)
		options = options.args and options.args[value]
		if not options then
			return
		end
		handler = options.handler or handler
		passValue = passTable and value or nil
	end

	if options.type == "group" then
		local hidden = options.hidden
		if type(hidden) == "function" or type(hidden) == "string" then
			hidden = callMethod(options.name or "(options root)", handler, options, "hidden", options.passValue) or false
		end
		if hidden then
			return
		end
		local disabled = options.disabled
		if type(disabled) == "function" or type(disabled) == "string" then
			disabled = callMethod(options.name or "(options root)", handler, options, "disabled", options.passValue) or false
		end
		if disabled then
			self:AddLine(
				'text', DISABLED,
				'disabled', true
			)
			return
		end
		for k in pairs(options.args) do
			table.insert(values, k)
		end
		if options.pass then
			if options.get and options.set then
				passTable = options
			elseif not passTable then
				passTable = options
			end
		else
			passTable = nil
		end
		if not mysort then
			mysort = function(a, b)
				local alpha, bravo = mysort_args[a], mysort_args[b]
				local alpha_order = alpha.order or 100
				local bravo_order = bravo.order or 100
				local alpha_name = alpha.guiName or alpha.name
				local bravo_name = bravo.guiName or bravo.name
				if alpha_order == bravo_order then
					if not alpha_name then
						return bravo_name
					elseif not bravo_name then
						return false
					else
						return alpha_name:gsub("|c%x%x%x%x%x%x%x%x", ""):gsub("|r", ""):upper() < bravo_name:gsub("|c%x%x%x%x%x%x%x%x", ""):gsub("|r", ""):upper()
					end
				else
					if alpha_order < 0 then
						if bravo_order > 0 then
							return false
						end
					else
						if bravo_order < 0 then
							return true
						end
					end
					return alpha_order < bravo_order
				end
			end
		end
		mysort_args = options.args
		table.sort(values, mysort)
		mysort_args = nil
		local hasBoth = #values >= 1 and (options.args[values[1]].order or 100) > 0 and (options.args[values[#values]].order or 100) < 0
		local last_order = 1
		for _,k in ipairs(values) do
			local v = options.args[k]
			local handler = v.handler or handler
			if hasBoth and last_order > 0 and (v.order or 100) < 0 then
				hasBoth = false
				self:AddLine()
			end
			local hidden, disabled = v.guiHidden or v.hidden, v.disabled
			
			if type(hidden) == "function" or type(hidden) == "string" then
				hidden = callMethod(k, handler, v, "hidden", v.passValue) or false
			end
			if not hidden then
				if type(disabled) == "function" or type(disabled) == "string" then
					disabled = callMethod(k, handler, v, "disabled", v.passValue) or false
				end
				local name = (v.guiIconOnly and v.icon) and "" or (v.guiName or v.name)
				local desc = v.guiDesc or v.desc
				local iconHeight = v.iconHeight or 16
				local iconWidth = v.iconWidth or 16
				local iconCoordLeft = v.iconCoordLeft
				local iconCoordRight = v.iconCoordRight
				local iconCoordBottom = v.iconCoordBottom
				local iconCoordTop = v.iconCoordTop
				local tooltipTitle, tooltipText
				tooltipTitle = name
				if name ~= desc then
					tooltipText = desc
				end
				if type(v.usage) == "string" and v.usage:trim():len() > 0 then
					if tooltipText then
						tooltipText = tooltipText .. "\n\n" .. USAGE_TOOLTIP:format(v.usage)
					else
						tooltipText = USAGE_TOOLTIP:format(v.usage)
					end
				end
				local v_p = passTable
				if not v_p or (v.type ~= "execute" and v.get and v.set) or (v.type == "execute" and v.func) then
					v_p = v
				end
				local passValue = v.passValue or (v_p~=v and k) or nil
				if v.type == "toggle" then
					local checked = callMethod(name, handler, v_p, "get", passValue) or false
					local checked_arg = checked
					if type(v_p.get)=="string" and v_p.get:match("^~") then
						checked_arg = not checked
					end
					local func, arg1, arg2, arg3 = getMethod(name, handler, v_p, "set",   skip1Nil(passValue, not checked_arg))
					if v.guiNameIsMap then
						checked = checked and true or false
						name = tostring(v.map and v.map[checked]):gsub("|c%x%x%x%x%x%x%x%x(.-)|r", "%1")
						tooltipTitle = name
						checked = true--nil
					end
					self:AddLine(
						'text', name,
						'checked', checked,
						'isRadio', v.isRadio,
						'func', func,
						'arg1', arg1,
						'arg2', arg2,
						'arg3', arg3,
						'disabled', disabled,
						'tooltipTitle', tooltipTitle,
						'tooltipText', tooltipText
					)
				elseif v.type == "execute" then
					local func, arg1, arg2, arg3, arg4
					local confirm = v.confirm
					if confirm == true then
						confirm = DEFAULT_CONFIRM_MESSAGE:format(tooltipText or tooltipTitle)
						func,arg1,arg2,arg3,arg4 = confirmPopup, confirm, getMethod(name, handler, v_p, "func",    passValue)
					else
						func,arg1,arg2 = getMethod(name, handler, v_p, "func",    passValue)
					end
					self:AddLine(
						'text', name,
						'checked', checked,
						'func', func,
						'arg1', arg1,
						'arg2', arg2,
						'arg3', arg3,
						'arg4', arg4,
						'disabled', disabled,
						'tooltipTitle', tooltipTitle,
						'tooltipText', tooltipText,
						'icon', v.icon,
						'iconHeight', iconHeight,
						'iconWidth', iconWidth,
						'iconCoordLeft', iconCoordLeft,
						'iconCoordRight', iconCoordRight,
						'iconCoordTop', iconCoordTop,
						'iconCoordBottom', iconCoordBottom
					)
				elseif v.type == "range" then
					local sliderValue
					sliderValue = callMethod(name, handler, v_p, "get", passValue) or 0
					local sliderFunc, sliderArg1, sliderArg2 = getMethod(name, handler, v_p, "set",    passValue)
					if tooltipText then
						tooltipText = format("%s\n\n%s", tooltipText, RANGE_TOOLTIP)
					else
						tooltipText = RANGE_TOOLTIP
					end
					self:AddLine(
						'text', name,
						'hasArrow', true,
						'hasSlider', true,
						'sliderMin', v.min or 0,
						'sliderMax', v.max or 1,
						'sliderStep', v.step or 0,
						'sliderBigStep', v.bigStep or nil,
						'sliderIsPercent', v.isPercent or false,
						'sliderValue', sliderValue,
						'sliderFunc', sliderFunc,
						'sliderArg1', sliderArg1,
						'sliderArg2', sliderArg2,
						'fromAceOptions', true,
						'disabled', disabled,
						'tooltipTitle', tooltipTitle,
						'tooltipText', tooltipText,
						'icon', v.icon,
						'iconHeight', iconHeight,
						'iconWidth', iconWidth,
						'iconCoordLeft', iconCoordLeft,
						'iconCoordRight', iconCoordRight,
						'iconCoordTop', iconCoordTop,
						'iconCoordBottom', iconCoordBottom
					)
				elseif v.type == "color" then
					local r,g,b,a = callMethod(name, handler, v_p, "get", passValue)
					if not r then
						r,g,b,a = 0,0,0,0
					end
					local colorFunc, colorArg1, colorArg2 = getMethod(name, handler, v_p, "set",    passValue)
					self:AddLine(
						'text', name,
						'hasArrow', true,
						'hasColorSwatch', true,
						'r', r,
						'g', g,
						'b', b,
						'opacity', v.hasAlpha and a or nil,
						'hasOpacity', v.hasAlpha,
						'colorFunc', colorFunc,
						'colorArg1', colorArg1,
						'colorArg2', colorArg2,
						'disabled', disabled,
						'tooltipTitle', tooltipTitle,
						'tooltipText', tooltipText
					)
				elseif v.type == "text" then
						if type(v.validate) == "table" then
						local func,arg1,arg2
						if v.onClick then
							func,arg1,arg2 = getMethod(name, handler, v, "onClick",    passValue)
						end
						local checked
						if v.isChecked then
							checked = callMethod(name, handler, v, "isChecked",    passValue) or false
						end
						self:AddLine(
							'text', name,
							'hasArrow', true,
							'value', k,
							'func', func,
							'arg1', arg1,
							'arg2', arg2,
							'mouseoverUnderline', func and true or nil,
							'disabled', disabled,
							'checked', checked,
							'tooltipTitle', tooltipTitle,
							'tooltipText', tooltipText,
							'icon', v.icon,
							'iconHeight', iconHeight,
							'iconWidth', iconWidth,
							'iconCoordLeft', iconCoordLeft,
							'iconCoordRight', iconCoordRight,
							'iconCoordTop', iconCoordTop,
							'iconCoordBottom', iconCoordBottom
						)
					else
						local editBoxText
						editBoxText = callMethod(name, handler, v_p, "get", passValue) or ""
						local editBoxFunc, editBoxArg1, editBoxArg2 = getMethod(name, handler, v_p, "set",    passValue)
						
						local editBoxValidateFunc, editBoxValidateArg1

						if v.validate and v.validate ~= "keybinding" then
							if v.validate == "keybinding" then
								if tooltipText then
									tooltipText = format("%s\n\n%s", tooltipText, RESET_KEYBINDING_DESC)
								else
									tooltipText = RESET_KEYBINDING_DESC
								end
							else
								editBoxValidateFunc, editBoxValidateArg1 = getMethod(name, handler, v, "validate") -- no passvalue!
							end
						end
						
						self:AddLine(
							'text', name,
							'hasArrow', true,
							'icon', v.icon,
							'iconHeight', iconHeight,
							'iconWidth', iconWidth,
							'iconCoordLeft', iconCoordLeft,
							'iconCoordRight', iconCoordRight,
							'iconCoordTop', iconCoordTop,
							'iconCoordBottom', iconCoordBottom,
							'hasEditBox', true,
							'editBoxText', editBoxText,
							'editBoxFunc', editBoxFunc,
							'editBoxArg1', editBoxArg1,
							'editBoxArg2', editBoxArg2,
							'editBoxValidateFunc', editBoxValidateFunc,
							'editBoxValidateArg1', editBoxValidateArg1,
							'editBoxIsKeybinding', v.validate == "keybinding",
							'editBoxKeybindingOnly', v.keybindingOnly,
							'editBoxKeybindingExcept', v.keybindingExcept,
							'disabled', disabled,
							'tooltipTitle', tooltipTitle,
							'tooltipText', tooltipText
						)
					end
				elseif v.type == "group" then
					local func,arg1,arg2
					if v.onClick then
						func,arg1,arg2 = getMethod(name, handler, v, "onClick",    passValue)
					end
					local checked
					if v.isChecked then
						checked = callMethod(name, handler, v, "isChecked",    passValue) or false
					end
					self:AddLine(
						'text', name,
						'hasArrow', true,
						'value', k,
						'func', func,
						'arg1', arg1,
						'arg2', arg2,
						'mouseoverUnderline', func and true or nil,
						'disabled', disabled,
						'checked', checked,
						'tooltipTitle', tooltipTitle,
						'tooltipText', tooltipText,
						'icon', v.icon,
						'iconHeight', iconHeight,
						'iconWidth', iconWidth,
						'iconCoordLeft', iconCoordLeft,
						'iconCoordRight', iconCoordRight,
						'iconCoordTop', iconCoordTop,
						'iconCoordBottom', iconCoordBottom
					)
				elseif v.type == "header" then
					if name == "" or not name then
						self:AddLine(
							'isTitle', true,
							'icon', v.icon,
							'iconHeight', iconHeight,
							'iconWidth', iconWidth,
							'iconCoordLeft', iconCoordLeft,
							'iconCoordRight', iconCoordRight,
							'iconCoordTop', iconCoordTop,
							'iconCoordBottom', iconCoordBottom
						)
					else
						self:AddLine(
							'text', name,
							'isTitle', true,
							'icon', v.icon,
							'iconHeight', iconHeight,
							'iconWidth', iconWidth,
							'iconCoordLeft', iconCoordLeft,
							'iconCoordRight', iconCoordRight,
							'iconCoordTop', iconCoordTop,
							'iconCoordBottom', iconCoordBottom
						)
					end
				end
			end
			last_order = v.order or 100
		end
	elseif options.type == "text" and type(options.validate) == "table" then
		local current
		local options_p = passTable
		if not options_p or (options.get and options.set) then
			options_p = options
			passTable = nil
			passValue = nil
		end
		local multiToggle = options.multiToggle
		local passValue = options.passValue or passValue
		if not multiToggle then
			current = callMethod(k, handler, options_p, "get", passValue)
		end
		local indexed = true
		for k,v in pairs(options.validate) do
			if type(k) ~= "number" then
				indexed = false
			end
			table.insert(values, k)
		end
		if not indexed then
			if not othersort then
				othersort = function(alpha, bravo)
					return othersort_validate[alpha]:gsub("|c%x%x%x%x%x%x%x%x", ""):gsub("|r", ""):upper() < othersort_validate[bravo]:gsub("|c%x%x%x%x%x%x%x%x", ""):gsub("|r", ""):upper()
				end
			end
			othersort_validate = options.validate
			table.sort(values, othersort)
			othersort_validate = nil
		end
		for _,k in ipairs(values) do
			local v = options.validate[k]
			if type(k) == "number" then
				k = v
			end
			local func, arg1, arg2, arg3, arg4 = getMethod(k, handler, options_p, "set",    skip1Nil(passValue, k))
			local checked
			if multiToggle then
				checked = callMethod(k, handler, options_p, "get", skip1Nil(passValue, k)) or false
				if arg2 == nil then
					arg2 = not checked
				elseif arg3 == nil then
					arg3 = not checked
				else
					arg4 = not checked
				end
			else
				checked = (k == current or (type(k) == "string" and type(current) == "string" and k:lower() == current:lower()))
				if checked then
					func, arg1, arg2, arg3, arg4 = nil, nil, nil, nil, nil
				end
			end
			local tooltipTitle
			local tooltipText
			if options.validateDesc then
				tooltipTitle = v
				tooltipText = options.validateDesc[k]
			else
				tooltipTitle = options.guiName or options.name
				tooltipText = v
			end
			self:AddLine(
				'text', v,
				'func', func,
				'arg1', arg1,
				'arg2', arg2,
				'arg3', arg3,
				'arg4', arg4,
				'isRadio', not multiToggle,
				'checked',  checked,
				'tooltipTitle', tooltipTitle,
				'tooltipText', tooltipText
			)
		end
		for k in pairs(values) do
			values[k] = nil
		end
	else
		return false
	end
	return true
end

function Dewdrop:FeedTable(s, difference)
	self:argCheck(s, 2, "table")
	self:argCheck(difference, 3, "nil", "number")
	if not currentLevel then
		self:error("Cannot call `FeedTable' outside of a Dewdrop declaration")
	end
	if not difference then
		difference = 0
	end
	local level = levels[currentLevel]
	if not level then
		self:error("Improper level given")
	end
	if not values then
		values = {}
	else
		for k,v in pairs(values) do
			values[k] = nil
		end
	end
	local t = s.subMenu and s or {subMenu = s}
	local current = level
	while current do
		if current.num == difference + 1 then
			break
		end
		table.insert(values, current.value)
		current = levels[current.num - 1]
	end

	while #values > 0 do
		local value = table.remove(values)
		t = t.subMenu and t.subMenu[value]
		if not t then
			return
		end
	end

	if t.subMenu or current.num == 1 then
		for k in pairs(t.subMenu) do
			table.insert(values, k)
		end
		table.sort(values)
		for _,k in ipairs(values) do
			local argTable = {"value", k}
			for key, val in pairs(t.subMenu[k]) do
				table.insert(argTable, key)
				table.insert(argTable, val)
			end
			self:AddLine(unpack(argTable))
		end
		for k in pairs(values) do
			values[k] = nil
		end
		return false
	end
	return true
end

function Refresh(self, level)
	if type(level) == "number" then
		level = levels[level]
	end
	if not level then
		return
	end
	if baseFunc then
		Clear(self, level)
		currentLevel = level.num
		if type(baseFunc) == "table" then
			if currentLevel == 1 then
				local handler = baseFunc.handler
				if handler then
					local name = tostring(handler)
					if not name:find('^table:') and not handler.hideMenuTitle then
						name = name:gsub("|c%x%x%x%x%x%x%x%x(.-)|r", "%1")
						self:AddLine(
							'text', name,
							'isTitle', true
						)
					end
				end
--			elseif level.parentText then
--				self:AddLine(
--					'text', level.parentText,
--					'tooltipTitle', level.parentTooltipTitle,
--					'tooltipText', level.parentTooltipText,
--					'tooltipFunc', level.parentTooltipFunc,
--					'isTitle', true
--				)
			end
			self:FeedAceOptionsTable(baseFunc)
			if currentLevel == 1 then
				self:AddLine(
					'text', CLOSE,
					'tooltipTitle', CLOSE,
					'tooltipText', CLOSE_DESC,
					'closeWhenClicked', true
				)
			end
		else
--			if level.parentText then
--				self:AddLine(
--					'text', level.parentText,
--					'tooltipTitle', level.parentTooltipTitle,
--					'tooltipText', level.parentTooltipText,
--					'tooltipFunc', level.parentTooltipFunc,
--					'isTitle', true
--				)
--			end
			baseFunc(currentLevel, level.value, levels[level.num - 1] and levels[level.num - 1].value, levels[level.num - 2] and levels[level.num - 2].value, levels[level.num - 3] and levels[level.num - 3].value, levels[level.num - 4] and levels[level.num - 4].value)
		end
		currentLevel = nil
		CheckSize(self, level)
	end
end

function Dewdrop:Refresh(level)
	self:argCheck(level, 2, "number", "nil")
	if not level then
		for k,v in pairs(levels) do
			Refresh(self, v)
		end
	else
		Refresh(self, levels[level])
	end
end

function OpenSlider(self, parent)
	if not sliderFrame then
		sliderFrame = CreateFrame("Frame", nil, nil)
		sliderFrame:SetWidth(100)
		sliderFrame:SetHeight(170)
		sliderFrame:SetScale(UIParent:GetScale())
		sliderFrame:SetBackdrop(tmp(
			'bgFile', "Interface\\Tooltips\\UI-Tooltip-Background",
			'edgeFile', "Interface\\Tooltips\\UI-Tooltip-Border",
			'tile', true,
			'insets', tmp2(
				'left', 5,
				'right', 5,
				'top', 5,
				'bottom', 5
			),
			'tileSize', 16,
			'edgeSize', 16
		))
		sliderFrame:SetFrameStrata("FULLSCREEN_DIALOG")
		if sliderFrame.SetTopLevel then
			sliderFrame:SetTopLevel(true)
		end
		sliderFrame:SetBackdropBorderColor(TOOLTIP_DEFAULT_COLOR.r, TOOLTIP_DEFAULT_COLOR.g, TOOLTIP_DEFAULT_COLOR.b)
		sliderFrame:SetBackdropColor(TOOLTIP_DEFAULT_BACKGROUND_COLOR.r, TOOLTIP_DEFAULT_BACKGROUND_COLOR.g, TOOLTIP_DEFAULT_BACKGROUND_COLOR.b)
		sliderFrame:EnableMouse(true)
		sliderFrame:EnableMouseWheel(true)
		sliderFrame:Hide()
		sliderFrame:SetPoint("CENTER", UIParent, "CENTER")
		local slider = CreateFrame("Slider", nil, sliderFrame)
		sliderFrame.slider = slider
		slider:SetOrientation("VERTICAL")
		slider:SetMinMaxValues(0, 1)
		slider:SetValueStep(0.000000001)
		slider:SetValue(0.5)
		slider:SetWidth(16)
		slider:SetHeight(128)
		slider:SetPoint("LEFT", sliderFrame, "LEFT", 15, 0)
		slider:SetBackdrop(tmp(
			'bgFile', "Interface\\Buttons\\UI-SliderBar-Background",
			'edgeFile', "Interface\\Buttons\\UI-SliderBar-Border",
			'tile', true,
			'edgeSize', 8,
			'tileSize', 8,
			'insets', tmp2(
				'left', 3,
				'right', 3,
				'top', 3,
				'bottom', 3
			)
		))
		local texture = slider:CreateTexture()
		slider:SetThumbTexture("Interface\\Buttons\\UI-SliderBar-Button-Vertical")
		local text = slider:CreateFontString(nil, "ARTWORK")
		sliderFrame.topText = text
		text:SetFontObject(GameFontGreenSmall)
		text:SetText("100%")
		text:SetPoint("BOTTOM", slider, "TOP")
		local text = slider:CreateFontString(nil, "ARTWORK")
		sliderFrame.bottomText = text
		text:SetFontObject(GameFontGreenSmall)
		text:SetText("0%")
		text:SetPoint("TOP", slider, "BOTTOM")
		local editBox = CreateFrame("EditBox", nil, sliderFrame)
		sliderFrame.currentText = editBox
		editBox:SetFontObject(ChatFontNormal)
		editBox:SetHeight(13)
		editBox:SetPoint("RIGHT", sliderFrame, "RIGHT", -16, 0)
		editBox:SetPoint("LEFT", slider, "RIGHT", 12, 0)
		editBox:SetText("50%")
		editBox:SetJustifyH("CENTER")
		
		local width = editBox:GetWidth()/2 + 10
		local left = editBox:CreateTexture(nil, "BACKGROUND")
		left:SetTexture("Interface\\ChatFrame\\UI-ChatInputBorder-Left")
		left:SetTexCoord(0, width / 256, 0, 1)
		left:SetWidth(width)
		left:SetHeight(32)
		left:SetPoint("LEFT", editBox, "LEFT", -10, 0)
		local right = editBox:CreateTexture(nil, "BACKGROUND")
		right:SetTexture("Interface\\ChatFrame\\UI-ChatInputBorder-Right")
		right:SetTexCoord(1 - width / 256, 1, 0, 1)
		right:SetWidth(width)
		right:SetHeight(32)
		right:SetPoint("RIGHT", editBox, "RIGHT", 10, 0)
		
		local changed = false
		local inside = false
		slider:SetScript("OnValueChanged", function()
			if sliderFrame.changing then
				return
			end
			changed = true
			local done = false
			if sliderFrame.parent and sliderFrame.parent.sliderFunc then
				local min = sliderFrame.parent.sliderMin or 0
				local max = sliderFrame.parent.sliderMax or 1
				local step
				if sliderFrame.fineStep then
					step = sliderFrame.parent.sliderStep or (max - min) / 100
				else
					step = sliderFrame.parent.sliderBigStep or sliderFrame.parent.sliderStep or (max - min) / 100
				end
				local value = (1 - slider:GetValue()) * (max - min) + min
				if step > 0 then
					value = math.floor((value - min) / step + 0.5) * step + min
					if value > max then
						value = max
					elseif value < min then
						value = min
					end
				end
				if value == sliderFrame.lastValue then
					return
				end
				sliderFrame.lastValue = value
				local text = sliderFrame.parent.sliderFunc(getArgs(sliderFrame.parent, 'sliderArg', 1, value))
				if sliderFrame.parent.fromAceOptions then
					text = nil
				elseif type(text) == "string" or type(text) == "number" then
					sliderFrame.currentText:SetText(text)
					done = true
				end
			end
			if not done then
				local min = sliderFrame.parent.sliderMin or 0
				local max = sliderFrame.parent.sliderMax or 1
				local step
				if sliderFrame.fineStep then
					step = sliderFrame.parent.sliderStep or (max - min) / 100
				else
					step = sliderFrame.parent.sliderBigStep or sliderFrame.parent.sliderStep or (max - min) / 100
				end
				local value = (1 - slider:GetValue()) * (max - min) + min
				if step > 0 then
					value = math.floor((value - min) / step + 0.5) * step + min
					if value > max then
						value = max
					elseif value < min then
						value = min
					end
				end
				if sliderFrame.parent.sliderIsPercent then
					sliderFrame.currentText:SetText(string.format("%.0f%%", value * 100))
				else
					if step < 0.1 then
						sliderFrame.currentText:SetText(string.format("%.2f", value))
					elseif step < 1 then
						sliderFrame.currentText:SetText(string.format("%.1f", value))
					else
						sliderFrame.currentText:SetText(string.format("%.0f", value))
					end
				end
			end
		end)
		local function onEnter()
			StopCounting(self, sliderFrame.level)
			showGameTooltip(sliderFrame.parent)
		end
		local function onLeave()
			GameTooltip:Hide()
		end
		sliderFrame:SetScript("OnEnter", onEnter)
		sliderFrame:SetScript("OnLeave", function()
			GameTooltip:Hide()
			if changed then
				local parent = sliderFrame.parent
				local sliderFunc = parent.sliderFunc
				for i = 1, sliderFrame.level - 1 do
					Refresh(self, levels[i])
				end
				local newParent
				for _,button in ipairs(levels[sliderFrame.level-1].buttons) do
					if button.sliderFunc == sliderFunc then
						newParent = button
						break
					end
				end
				if newParent then
					OpenSlider(self, newParent)
				else
					sliderFrame:Hide()
				end
			end			
		end)
		editBox:SetScript("OnEnter", onEnter)
		editBox:SetScript("OnLeave", onLeave)
		slider:SetScript("OnMouseDown", function()
			sliderFrame.mouseDown = true
			GameTooltip:Hide()
		end)
		slider:SetScript("OnMouseUp", function()
			sliderFrame.mouseDown = false
			if changed--[[ and not inside]] then
				local parent = sliderFrame.parent
				local sliderFunc = parent.sliderFunc
				for i = 1, sliderFrame.level - 1 do
					Refresh(self, levels[i])
				end
				local newParent
				for _,button in ipairs(levels[sliderFrame.level-1].buttons) do
					if button.sliderFunc == sliderFunc then
						newParent = button
						break
					end
				end
				if newParent then
					OpenSlider(self, newParent)
				else
					sliderFrame:Hide()
				end
			end
			if inside then
				showGameTooltip(sliderFrame.parent)
			end
		end)
		slider:SetScript("OnEnter", function()
			inside = true
			StopCounting(self, sliderFrame.level)
			showGameTooltip(sliderFrame.parent)
		end)
		slider:SetScript("OnLeave", function()
			inside = false
			GameTooltip:Hide()
			if changed and not sliderFrame.mouseDown then 
				local parent = sliderFrame.parent
				local sliderFunc = parent.sliderFunc
				for i = 1, sliderFrame.level - 1 do
					Refresh(self, levels[i])
				end
				local newParent
				for _,button in ipairs(levels[sliderFrame.level-1].buttons) do
					if button.sliderFunc == sliderFunc then
						newParent = button
						break
					end
				end
				if newParent then
					OpenSlider(self, newParent)
				else
					sliderFrame:Hide()
				end
				
				changed = false
			end			
		end)
		sliderFrame:SetScript("OnMouseWheel", function(t, a1)
			local arg1 = a1 or arg1
			local up = arg1 > 0
			
			local min = sliderFrame.parent.sliderMin or 0
			local max = sliderFrame.parent.sliderMax or 1
			local step = sliderFrame.parent.sliderStep or (max - min) / 100
			if step <= 0 then
				step = (max - min) / 100
			end
			
			local value = (1 - slider:GetValue()) * (max - min) + min
			if up then
				value = value + step
			else
				value = value - step
			end
			if value > max then
				value = max
			elseif value < min then
				value = min
			end
			sliderFrame.fineStep = true
			if max<=min then
				slider:SetValue(0)
			else
				slider:SetValue(1 - (value - min) / (max - min))
			end
			sliderFrame.fineStep = nil
		end)
		slider:SetScript("OnMouseWheel", sliderFrame:GetScript("OnMouseWheel"))
		editBox:SetScript("OnEnterPressed", function(t, a1)
			local value = editBox:GetNumber()
			
			if sliderFrame.parent.sliderIsPercent then
				value = value / 100
			end
			
			local min = sliderFrame.parent.sliderMin or 0
			local max = sliderFrame.parent.sliderMax or 1
			
			if value > max then
				value = max
			elseif value < min then
				value = min
			end
			sliderFrame.fineStep = true
			if max <= min then
				slider:SetValue(0)
			else
				slider:SetValue(1 - (value - min) / (max - min))
			end
			sliderFrame.fineStep = nil

			StartCounting(self, sliderFrame.level)
		end)
		editBox:SetScript("OnEscapePressed", function()
			self:Close(sliderFrame.level)
			StartCounting(self, sliderFrame.level)
		end)
		editBox:SetAutoFocus(false)
	end
	sliderFrame.parent = parent
	sliderFrame.level = parent.level.num + 1
	sliderFrame.parentValue = parent.level.value
	sliderFrame:SetFrameLevel(parent.level:GetFrameLevel() + 3)
	sliderFrame.slider:SetFrameLevel(sliderFrame:GetFrameLevel() + 1)
	sliderFrame.currentText:SetFrameLevel(sliderFrame:GetFrameLevel() + 1)
	sliderFrame.currentText:ClearFocus()
	sliderFrame.changing = true
	if not parent.sliderMin or not parent.sliderMax then
		return
	end

	if parent.arrow then
--		parent.arrow:SetVertexColor(0.2, 0.6, 0)
--		parent.arrow:SetHeight(24)
--		parent.arrow:SetWidth(24)
		parent.selected = true
		parent.highlight:Show()
	end

	sliderFrame:SetClampedToScreen(false)
	if not parent.sliderValue then
		parent.sliderValue = (parent.sliderMin + parent.sliderMax) / 2
	end
	if parent.sliderMax <= parent.sliderMin then
		sliderFrame.slider:SetValue(0)
	else		
		sliderFrame.slider:SetValue(1 - (parent.sliderValue - parent.sliderMin) / (parent.sliderMax - parent.sliderMin))
	end
	sliderFrame.changing = false
	sliderFrame.bottomText:SetText(parent.sliderMinText or "0")
	sliderFrame.topText:SetText(parent.sliderMaxText or "1")
	local text
	if parent.sliderFunc and not parent.fromAceOptions then
		text = parent.sliderFunc(getArgs(parent, 'sliderArg', 1, parent.sliderValue))
	end
	if type(text) == "number" or type(text) == "string" then
		sliderFrame.currentText:SetText(text)
	elseif parent.sliderIsPercent then
		sliderFrame.currentText:SetText(string.format("%.0f%%", parent.sliderValue * 100))
	else
		if parent.sliderStep < 0.1 then
			sliderFrame.currentText:SetText(string.format("%.2f", parent.sliderValue))
		elseif parent.sliderStep < 1 then
			sliderFrame.currentText:SetText(string.format("%.1f", parent.sliderValue))
		else
			sliderFrame.currentText:SetText(string.format("%.0f", parent.sliderValue))
		end
	end
	

	sliderFrame.lastValue = parent.sliderValue
	
	local level = parent.level
	sliderFrame:Show()
	sliderFrame:ClearAllPoints()
	if level.lastDirection == "RIGHT" then
		if level.lastVDirection == "DOWN" then
			sliderFrame:SetPoint("TOPLEFT", parent, "TOPRIGHT", 5, 10)
		else
			sliderFrame:SetPoint("BOTTOMLEFT", parent, "BOTTOMRIGHT", 5, -10)
		end
	else
		if level.lastVDirection == "DOWN" then
			sliderFrame:SetPoint("TOPRIGHT", parent, "TOPLEFT", -5, 10)
		else
			sliderFrame:SetPoint("BOTTOMRIGHT", parent, "BOTTOMLEFT", -5, -10)
		end
	end
	local dirty
	if level.lastDirection == "RIGHT" then
		if sliderFrame:GetRight() > GetScreenWidth() then
			level.lastDirection = "LEFT"
			dirty = true
		end
	elseif sliderFrame:GetLeft() < 0 then
		level.lastDirection = "RIGHT"
		dirty = true
	end
	if level.lastVDirection == "DOWN" then
		if sliderFrame:GetBottom() < 0 then
			level.lastVDirection = "UP"
			dirty = true
		end
	elseif sliderFrame:GetTop() > GetScreenWidth() then
		level.lastVDirection = "DOWN"
		dirty = true
	end
	if dirty then
		sliderFrame:ClearAllPoints()
		if level.lastDirection == "RIGHT" then
			if level.lastVDirection == "DOWN" then
				sliderFrame:SetPoint("TOPLEFT", parent, "TOPRIGHT", 5, 10)
			else
				sliderFrame:SetPoint("BOTTOMLEFT", parent, "BOTTOMRIGHT", 5, -10)
			end
		else
			if level.lastVDirection == "DOWN" then
				sliderFrame:SetPoint("TOPRIGHT", parent, "TOPLEFT", -5, 10)
			else
				sliderFrame:SetPoint("BOTTOMRIGHT", parent, "BOTTOMLEFT", -5, -10)
			end
		end
	end
	local left, bottom = sliderFrame:GetLeft(), sliderFrame:GetBottom()
	sliderFrame:ClearAllPoints()
	sliderFrame:SetPoint("BOTTOMLEFT", UIParent, "BOTTOMLEFT", left, bottom)
	if mod(level.num, 5) == 0 then
		local left, bottom = level:GetLeft(), level:GetBottom()
		level:ClearAllPoints()
		level:SetPoint("BOTTOMLEFT", UIParent, "BOTTOMLEFT", left, bottom)
	end
	sliderFrame:SetClampedToScreen(true)
end

function OpenEditBox(self, parent)
	if not editBoxFrame then
		editBoxFrame = CreateFrame("Frame", nil, nil)
		editBoxFrame:SetWidth(200)
		editBoxFrame:SetHeight(40)
		editBoxFrame:SetScale(UIParent:GetScale())
		editBoxFrame:SetBackdrop(tmp(
			'bgFile', "Interface\\Tooltips\\UI-Tooltip-Background",
			'edgeFile', "Interface\\Tooltips\\UI-Tooltip-Border",
			'tile', true,
			'insets', tmp2(
				'left', 5,
				'right', 5,
				'top', 5,
				'bottom', 5
			),
			'tileSize', 16,
			'edgeSize', 16
		))
		editBoxFrame:SetFrameStrata("FULLSCREEN_DIALOG")
		if editBoxFrame.SetTopLevel then
			editBoxFrame:SetTopLevel(true)
		end
		editBoxFrame:SetBackdropBorderColor(TOOLTIP_DEFAULT_COLOR.r, TOOLTIP_DEFAULT_COLOR.g, TOOLTIP_DEFAULT_COLOR.b)
		editBoxFrame:SetBackdropColor(TOOLTIP_DEFAULT_BACKGROUND_COLOR.r, TOOLTIP_DEFAULT_BACKGROUND_COLOR.g, TOOLTIP_DEFAULT_BACKGROUND_COLOR.b)
		editBoxFrame:EnableMouse(true)
		editBoxFrame:EnableMouseWheel(true)
		editBoxFrame:Hide()
		editBoxFrame:SetPoint("CENTER", UIParent, "CENTER")

		local editBox = CreateFrame("EditBox", nil, editBoxFrame)
		editBoxFrame.editBox = editBox
		editBox:SetFontObject(ChatFontNormal)
		editBox:SetWidth(160)
		editBox:SetHeight(13)
		editBox:SetPoint("CENTER", editBoxFrame, "CENTER", 0, 0)

		local left = editBox:CreateTexture(nil, "BACKGROUND")
		left:SetTexture("Interface\\ChatFrame\\UI-ChatInputBorder-Left")
		left:SetTexCoord(0, 100 / 256, 0, 1)
		left:SetWidth(100)
		left:SetHeight(32)
		left:SetPoint("LEFT", editBox, "LEFT", -10, 0)
		local right = editBox:CreateTexture(nil, "BACKGROUND")
		right:SetTexture("Interface\\ChatFrame\\UI-ChatInputBorder-Right")
		right:SetTexCoord(156/256, 1, 0, 1)
		right:SetWidth(100)
		right:SetHeight(32)
		right:SetPoint("RIGHT", editBox, "RIGHT", 10, 0)
		
		editBox:SetScript("OnEnterPressed", function()
			if editBoxFrame.parent and editBoxFrame.parent.editBoxValidateFunc then
				local t = editBox.realText or editBox:GetText() or ""
				local result = editBoxFrame.parent.editBoxValidateFunc(getArgs(editBoxFrame.parent, 'editBoxValidateArg', 1, t))
				if not result then
					UIErrorsFrame:AddMessage(VALIDATION_ERROR, 1, 0, 0)
					return
				end
			end
			if editBoxFrame.parent and editBoxFrame.parent.editBoxFunc then
				local t
				if editBox.realText ~= "NONE" then
					t = editBox.realText or editBox:GetText() or ""
				end
				editBoxFrame.parent.editBoxFunc(getArgs(editBoxFrame.parent, 'editBoxArg', 1, t))
			end
			self:Close(editBoxFrame.level)
			for i = 1, editBoxFrame.level - 1 do
				Refresh(self, levels[i])
			end
			StartCounting(self, editBoxFrame.level-1)
		end)
		editBox:SetScript("OnEscapePressed", function()
			self:Close(editBoxFrame.level)
			StartCounting(self, editBoxFrame.level-1)
		end)
		editBox:SetScript("OnReceiveDrag", function(this)
			if GetCursorInfo then
				local type, alpha, bravo = GetCursorInfo()
				local text
				if type == "spell" then
					text = GetSpellName(alpha, bravo)
				elseif type == "item" then
					text = bravo
				end
				if not text then
					return
				end
				ClearCursor()
				editBox:SetText(text)
			end
		end)
		local changing = false
		local skipNext = false

		function editBox:SpecialSetText(text)
			local oldText = editBox:GetText() or ""
			if not text then
				text = ""
			end
			if text ~= oldText then
				changing = true
				self:SetText(tostring(text))
				changing = false
				skipNext = true
			end
		end

		editBox:SetScript("OnTextChanged", function()
			if skipNext then
				skipNext = false
			elseif not changing and editBoxFrame.parent and editBoxFrame.parent.editBoxChangeFunc then
				local t
				if editBox.realText ~= "NONE" then
					t = editBox.realText or editBox:GetText() or ""
				end
				local text = editBoxFrame.parent.editBoxChangeFunc(getArgs(editBoxFrame.parent, 'editBoxChangeArg', 1, t))
				if text then
					editBox:SpecialSetText(text)
				end
			end
		end)
		editBoxFrame:SetScript("OnEnter", function()
			StopCounting(self, editBoxFrame.level)
			showGameTooltip(editBoxFrame.parent)
		end)
		editBoxFrame:SetScript("OnLeave", function()
			GameTooltip:Hide()
		end)
		editBox:SetScript("OnEnter", function()
			StopCounting(self, editBoxFrame.level)
			showGameTooltip(editBoxFrame.parent)
		end)
		editBox:SetScript("OnLeave", function()
			GameTooltip:Hide()
		end)
		editBoxFrame:SetScript("OnKeyDown", function(this, a1)
			if not editBox.keybinding then
				return
			end
			local arg1 = a1 or arg1
			local screenshotKey = GetBindingKey("SCREENSHOT")
			if screenshotKey and arg1 == screenshotKey then
				Screenshot()
				return
			end
			
			if arg1 == "LeftButton" then
				arg1 = "BUTTON1"
			elseif arg1 == "RightButton" then
				arg1 = "BUTTON2"
			elseif arg1 == "MiddleButton" then
				arg1 = "BUTTON3"
			elseif arg1 == "Button4" then
				arg1 = "BUTTON4"
			elseif arg1 == "Button5" then
				arg1 = "BUTTON5"
			end
			if arg1 == "UNKNOWN" then
				return
			elseif arg1 == "SHIFT" or arg1 == "CTRL" or arg1 == "ALT" then
				return
			elseif arg1 == "ENTER" then
				if editBox.keybindingOnly and not editBox.keybindingOnly[editBox.realText] then
					return editBox:GetScript("OnEscapePressed")()
				elseif editBox.keybindingExcept and editBox.keybindingExcept[editBox.realText] then
					return editBox:GetScript("OnEscapePressed")()
				else
					return editBox:GetScript("OnEnterPressed")()
				end
			elseif arg1 == "ESCAPE" then
				if editBox.realText == "NONE" then
					return editBox:GetScript("OnEscapePressed")()
				else
					editBox:SpecialSetText(NONE or "NONE")
					editBox.realText = "NONE"
					return
				end
			elseif editBox.keybindingOnly and not editBox.keybindingOnly[arg1] then
				return
			elseif editBox.keybindingExcept and editBox.keybindingExcept[arg1] then
				return
			end
			local s = GetBindingText(arg1, "KEY_")
			if s == "BUTTON1" then
				s = KEY_BUTTON1
			elseif s == "BUTTON2" then
				s = KEY_BUTTON2
			end
			local real = arg1
			if IsShiftKeyDown() then
				s = "Shift-" .. s
				real = "SHIFT-" .. real
			end
			if IsControlKeyDown() then
				s = "Ctrl-" .. s
				real = "CTRL-" .. real
			end
			if IsAltKeyDown() then
				s = "Alt-" .. s
				real = "ALT-" .. real
			end
			if editBox:GetText() ~= s then
				editBox:SpecialSetText("-")
				editBox:SpecialSetText(s)
				editBox.realText = real
				return editBox:GetScript("OnTextChanged")()
			end
		end)
		editBoxFrame:SetScript("OnMouseDown", editBoxFrame:GetScript("OnKeyDown"))
		editBox:SetScript("OnMouseDown", function(this, ...)
			if GetCursorInfo and (CursorHasItem() or CursorHasSpell()) then
				return editBox:GetScript("OnReceiveDrag")(this, ...)
			end
			return editBoxFrame:GetScript("OnKeyDown")(this, ...)
		end)
		editBoxFrame:SetScript("OnMouseWheel", function(t, a1)
			local arg1 = a1 or arg1
			local up = arg1 > 0
			arg1 = up and "MOUSEWHEELUP" or "MOUSEWHEELDOWN"
			return editBoxFrame:GetScript("OnKeyDown")(t or this, arg1)
		end)
		editBox:SetScript("OnMouseWheel", editBoxFrame:GetScript("OnMouseWheel"))
	end
	editBoxFrame.parent = parent
	editBoxFrame.level = parent.level.num + 1
	editBoxFrame.parentValue = parent.level.value
	editBoxFrame:SetFrameLevel(parent.level:GetFrameLevel() + 3)
	editBoxFrame.editBox:SetFrameLevel(editBoxFrame:GetFrameLevel() + 1)
	editBoxFrame.editBox.realText = nil
	editBoxFrame:SetClampedToScreen(false)
	
	editBoxFrame.editBox:SpecialSetText("")
	if parent.editBoxIsKeybinding then
		local s = parent.editBoxText
		if s == "" then
			s =  "NONE"
		end
		editBoxFrame.editBox.realText = s
		if s and s ~= "NONE" then
			local alpha,bravo = s:match("^(.+)%-(.+)$")
			if not bravo then
				alpha = nil
				bravo = s
			end
			bravo = GetBindingText(bravo, "KEY_")
			if alpha then
				editBoxFrame.editBox:SpecialSetText(alpha:upper() .. "-" .. bravo)
			else
				editBoxFrame.editBox:SpecialSetText(bravo)
			end
		else
			editBoxFrame.editBox:SpecialSetText(NONE or "NONE")
		end
	else
		editBoxFrame.editBox:SpecialSetText(parent.editBoxText)
	end
	
	editBoxFrame.editBox.keybinding = parent.editBoxIsKeybinding
	editBoxFrame.editBox.keybindingOnly = parent.editBoxKeybindingOnly
	editBoxFrame.editBox.keybindingExcept = parent.editBoxKeybindingExcept
	editBoxFrame.editBox:EnableKeyboard(not parent.editBoxIsKeybinding)
	editBoxFrame:EnableKeyboard(parent.editBoxIsKeybinding)

	if parent.arrow then
--		parent.arrow:SetVertexColor(0.2, 0.6, 0)
--		parent.arrow:SetHeight(24)
--		parent.arrow:SetWidth(24)
		parent.selected = true
		parent.highlight:Show()
	end

	local level = parent.level
	editBoxFrame:Show()
	editBoxFrame:ClearAllPoints()
	if level.lastDirection == "RIGHT" then
		if level.lastVDirection == "DOWN" then
			editBoxFrame:SetPoint("TOPLEFT", parent, "TOPRIGHT", 5, 10)
		else
			editBoxFrame:SetPoint("BOTTOMLEFT", parent, "BOTTOMRIGHT", 5, -10)
		end
	else
		if level.lastVDirection == "DOWN" then
			editBoxFrame:SetPoint("TOPRIGHT", parent, "TOPLEFT", -5, 10)
		else
			editBoxFrame:SetPoint("BOTTOMRIGHT", parent, "BOTTOMLEFT", -5, -10)
		end
	end
	local dirty
	if level.lastDirection == "RIGHT" then
		if editBoxFrame:GetRight() > GetScreenWidth() then
			level.lastDirection = "LEFT"
			dirty = true
		end
	elseif editBoxFrame:GetLeft() < 0 then
		level.lastDirection = "RIGHT"
		dirty = true
	end
	if level.lastVDirection == "DOWN" then
		if editBoxFrame:GetBottom() < 0 then
			level.lastVDirection = "UP"
			dirty = true
		end
	elseif editBoxFrame:GetTop() > GetScreenWidth() then
		level.lastVDirection = "DOWN"
		dirty = true
	end
	if dirty then
		editBoxFrame:ClearAllPoints()
		if level.lastDirection == "RIGHT" then
			if level.lastVDirection == "DOWN" then
				editBoxFrame:SetPoint("TOPLEFT", parent, "TOPRIGHT", 5, 10)
			else
				editBoxFrame:SetPoint("BOTTOMLEFT", parent, "BOTTOMRIGHT", 5, -10)
			end
		else
			if level.lastVDirection == "DOWN" then
				editBoxFrame:SetPoint("TOPRIGHT", parent, "TOPLEFT", -5, 10)
			else
				editBoxFrame:SetPoint("BOTTOMRIGHT", parent, "BOTTOMLEFT", -5, -10)
			end
		end
	end
	local left, bottom = editBoxFrame:GetLeft(), editBoxFrame:GetBottom()
	editBoxFrame:ClearAllPoints()
	editBoxFrame:SetPoint("BOTTOMLEFT", UIParent, "BOTTOMLEFT", left, bottom)
	if mod(level.num, 5) == 0 then
		local left, bottom = level:GetLeft(), level:GetBottom()
		level:ClearAllPoints()
		level:SetPoint("BOTTOMLEFT", UIParent, "BOTTOMLEFT", left, bottom)
	end
	editBoxFrame:SetClampedToScreen(true)
end

function Dewdrop:EncodeKeybinding(text)
	if text == nil or text == "NONE" then
		return nil
	end
	text = tostring(text):upper()
	local shift, ctrl, alt
	local modifier
	while true do
		if text == "-" then
			break
		end
		modifier, text = strsplit('-', text, 2)
		if text then
			if modifier ~= "SHIFT" and modifier ~= "CTRL" and modifier ~= "ALT" then
				return false
			end
			if modifier == "SHIFT" then
				if shift then
					return false
				end
				shift = true
			end
			if modifier == "CTRL" then
				if ctrl then
					return false
				end
				ctrl = true
			end
			if modifier == "ALT" then
				if alt then
					return false
				end
				alt = true
			end
		else
			text = modifier
			break
		end
	end
	if not text:find("^F%d+$") and text ~= "CAPSLOCK" and text:len() ~= 1 and (text:len() == 0 or text:byte() < 128 or text:len() > 4) and not _G["KEY_" .. text] and text ~= "BUTTON1" and text ~= "BUTTON2" then
		return false
	end
	local s = GetBindingText(text, "KEY_")
	if s == "BUTTON1" then
		s = KEY_BUTTON1
	elseif s == "BUTTON2" then
		s = KEY_BUTTON2
	end
	if shift then
		s = "Shift-" .. s
	end
	if ctrl then
		s = "Ctrl-" .. s
	end
	if alt then
		s = "Alt-" .. s
	end
	return s
end

function Dewdrop:IsOpen(parent)
	self:argCheck(parent, 2, "table", "string", "nil")
	return levels[1] and levels[1]:IsShown() and (not parent or parent == levels[1].parent or parent == levels[1]:GetParent())
end

function Dewdrop:GetOpenedParent()
	return (levels[1] and levels[1]:IsShown()) and (levels[1].parent or levels[1]:GetParent())
end

function Open(self, parent, func, level, value, point, relativePoint, cursorX, cursorY)
	self:Close(level)
	if DewdropLib then
		local d = DewdropLib:GetInstance('1.0')
		local ret, val = pcall(d, IsOpen, d)
		if ret and val then
			DewdropLib:GetInstance('1.0'):Close()
		end
	end
	if type(parent) == "table" then
		parent:GetCenter()
	end
	local frame = AcquireLevel(self, level)
	if level == 1 then
		frame.lastDirection = "RIGHT"
		frame.lastVDirection = "DOWN"
	else
		frame.lastDirection = levels[level - 1].lastDirection
		frame.lastVDirection = levels[level - 1].lastVDirection
	end
	frame:SetFrameStrata("FULLSCREEN_DIALOG")
	frame:ClearAllPoints()
	frame.parent = parent
	frame:SetPoint("LEFT", UIParent, "RIGHT", 10000, 0)
	frame:Show()
	if level == 1 then
		baseFunc = func
	end
	levels[level].value = value
--	levels[level].parentText = parent.text and parent.text:GetText() or nil
--	levels[level].parentTooltipTitle = parent.tooltipTitle
--	levels[level].parentTooltipText = parent.tooltipText
--	levels[level].parentTooltipFunc = parent.tooltipFunc
	if type(parent) == "table" and parent.arrow then
--		parent.arrow:SetVertexColor(0.2, 0.6, 0)
--		parent.arrow:SetHeight(24)
--		parent.arrow:SetWidth(24)
		parent.selected = true
		parent.highlight:Show()
	end
	relativePoint = relativePoint or point
	Refresh(self, levels[level])
	if point or (cursorX and cursorY) then
		frame:ClearAllPoints()
		if cursorX and cursorY then
			local curX, curY = GetScaledCursorPosition()
			if curY < GetScreenHeight() / 2 then
				point, relativePoint = "BOTTOM", "BOTTOM"
			else
				point, relativePoint = "TOP", "TOP"
			end
			if curX < GetScreenWidth() / 2 then
				point, relativePoint = point .. "LEFT", relativePoint .. "RIGHT"
			else
				point, relativePoint = point .. "RIGHT", relativePoint .. "LEFT"
			end
		end
		frame:SetPoint(point, type(parent) == "table" and parent or UIParent, relativePoint)
		if cursorX and cursorY then
			local left = frame:GetLeft()
			local width = frame:GetWidth()
			local bottom = frame:GetBottom()
			local height = frame:GetHeight()
			local curX, curY = GetScaledCursorPosition()
			frame:ClearAllPoints()
			relativePoint = relativePoint or point
			if point == "BOTTOM" or point == "TOP" then
				if curX < GetScreenWidth() / 2 then
					point = point .. "LEFT"
				else
					point = point .. "RIGHT"
				end
			elseif point == "CENTER" then
				if curX < GetScreenWidth() / 2 then
					point = "LEFT"
				else
					point = "RIGHT"
				end
			end
			local xOffset, yOffset = 0, 0
			if curY > GetScreenHeight() / 2 then
				yOffset = -height
			end
			if curX > GetScreenWidth() / 2 then
				xOffset = -width
			end
			frame:SetPoint(point, type(parent) == "table" and parent or UIParent, relativePoint, curX - left + xOffset, curY - bottom + yOffset)
			if level == 1 then
				frame.lastDirection = "RIGHT"
			end
		elseif cursorX then
			local left = frame:GetLeft()
			local width = frame:GetWidth()
			local curX, curY = GetScaledCursorPosition()
			frame:ClearAllPoints()
			relativePoint = relativePoint or point
			if point == "BOTTOM" or point == "TOP" then
				if curX < GetScreenWidth() / 2 then
					point = point .. "LEFT"
				else
					point = point .. "RIGHT"
				end
			elseif point == "CENTER" then
				if curX < GetScreenWidth() / 2 then
					point = "LEFT"
				else
					point = "RIGHT"
				end
			end
			frame:SetPoint(point, type(parent) == "table" and parent or UIParent, relativePoint, curX - left - width / 2, 0)
			if level == 1 then
				frame.lastDirection = "RIGHT"
			end
		elseif cursorY then
			local bottom = frame:GetBottom()
			local height = frame:GetHeight()
			local curX, curY = GetScaledCursorPosition()
			frame:ClearAllPoints()
			relativePoint = relativePoint or point
			if point == "LEFT" or point == "RIGHT" then
				if curX < GetScreenHeight() / 2 then
					point = point .. "BOTTOM"
				else
					point = point .. "TOP"
				end
			elseif point == "CENTER" then
				if curX < GetScreenHeight() / 2 then
					point = "BOTTOM"
				else
					point = "TOP"
				end
			end
			frame:SetPoint(point, type(parent) == "table" and parent or UIParent, relativePoint, 0, curY - bottom - height / 2)
			if level == 1 then
				frame.lastDirection = "DOWN"
			end
		end
		if (strsub(point, 1, 3) ~= strsub(relativePoint, 1, 3)) then
			if frame:GetBottom() < 0 then
				local point, parent, relativePoint, x, y = frame:GetPoint(1)
				local change = GetScreenHeight() - frame:GetTop()
				local otherChange = -frame:GetBottom()
				if otherChange < change then
					change = otherChange
				end
				frame:SetPoint(point, parent, relativePoint, x, y + change)
			elseif frame:GetTop() > GetScreenHeight() then
				local point, parent, relativePoint, x, y = frame:GetPoint(1)
				local change = GetScreenHeight() - frame:GetTop()
				local otherChange = -frame:GetBottom()
				if otherChange < change then
					change = otherChange
				end
				frame:SetPoint(point, parent, relativePoint, x, y + change)
			end
		end
	end
	CheckDualMonitor(self, frame)
	frame:SetClampedToScreen(true)
	frame:SetClampedToScreen(false)
	StartCounting(self, level)
end

function Dewdrop:IsRegistered(parent)
	self:argCheck(parent, 2, "table", "string")
	return not not self.registry[parent]
end

function Dewdrop:Register(parent, ...)
	self:argCheck(parent, 2, "table", "string")
	if self.registry[parent] then
		self:Unregister(parent)
	end
	local info = new(...)
	if type(info.children) == "table" then
		local err, position = validateOptions(info.children)
		
		if err then
			if position then
				Dewdrop:error(position .. ": " .. err)
			else
				Dewdrop:error(err)
			end
		end
	end
	self.registry[parent] = info
	if not info.dontHook and not self.onceRegistered[parent] and type(parent) == "table" then
		if parent:HasScript("OnMouseUp") then
			local script = parent:GetScript("OnMouseUp")
			parent:SetScript("OnMouseUp", function(this, ...)
				if script then
					script(this, ...)
				end
				if arg1 == "RightButton" and self.registry[parent] then
					if self:IsOpen(parent) then
						self:Close()
					else
						self:Open(parent)
					end
				end
			end)
		end
		if parent:HasScript("OnMouseDown") then
			local script = parent:GetScript("OnMouseDown")
			parent:SetScript("OnMouseDown", function(this, ...)
				if script then
					script(this, ...)
				end
				if self.registry[parent] then
					self:Close()
				end
			end)
		end
	end
	self.onceRegistered[parent] = true
end

function Dewdrop:Unregister(parent)
	self:argCheck(parent, 2, "table", "string")
	self.registry[parent] = nil
end

function Dewdrop:Open(parent, ...)
	self:argCheck(parent, 2, "table", "string")
	local info
	local k1 = ...
	if type(k1) == "table" and k1[0] and k1.IsFrameType and self.registry[k1] then
		info = tmp(select(2, ...))
		for k,v in pairs(self.registry[k1]) do
			if info[k] == nil then
				info[k] = v
			end
		end
	else
		info = tmp(...)
		if self.registry[parent] then
			for k,v in pairs(self.registry[parent]) do
				if info[k] == nil then
					info[k] = v
				end
			end
		end
	end
	local point = info.point
	local relativePoint = info.relativePoint
	local cursorX = info.cursorX
	local cursorY = info.cursorY
	if type(point) == "function" then
		local b
		point, b = point(parent)
		if b then
			relativePoint = b
		end
	end
	if type(relativePoint) == "function" then
		relativePoint = relativePoint(parent)
	end
	Open(self, parent, info.children, 1, nil, point, relativePoint, cursorX, cursorY)
end

function Clear(self, level)
	if level then
		if level.buttons then
			for i = #level.buttons, 1, -1 do
				ReleaseButton(self, level, i)
			end
		end
	end
end

function Dewdrop:Close(level)
	if DropDownList1:IsShown() then
		DropDownList1:Hide()
	end
	if DewdropLib then
		local d = DewdropLib:GetInstance('1.0')
		local ret, val = pcall(d, IsOpen, d)
		if ret and val then
			DewdropLib:GetInstance('1.0'):Close()
		end
	end
	self:argCheck(level, 2, "number", "nil")
	if not level then
		level = 1
	end
	if level == 1 and levels[level] then
		levels[level].parented = false
	end
	if level > 1 and levels[level-1].buttons then
		local buttons = levels[level-1].buttons
		for _,button in ipairs(buttons) do
--			button.arrow:SetWidth(16)
--			button.arrow:SetHeight(16)
			button.selected = nil
			button.highlight:Hide()
--			button.arrow:SetVertexColor(1, 1, 1)
		end
	end
	if sliderFrame and sliderFrame.level >= level then
		sliderFrame:Hide()
	end
	if editBoxFrame and editBoxFrame.level >= level then
		editBoxFrame:Hide()
	end
	for i = level, #levels do
		Clear(self, levels[level])
		levels[i]:Hide()
		levels[i]:ClearAllPoints()
		levels[i]:SetPoint("CENTER", UIParent, "CENTER")
		levels[i].value = nil
	end
end

function Dewdrop:AddSeparator(level)
	level = levels[level or currentLevel]
	if not level or not level.buttons then return; end
	
	local prevbutton = level.buttons[#level.buttons]
	if not prevbutton then return; end
	
	if prevbutton.disabled and prevbutton.text:GetText() == "" then
		return
	end
	self:AddLine("text", "", "disabled", true)
end

function Dewdrop:AddLine(...)
	local info = tmp(...)
	local level = info.level or currentLevel
	info.level = nil
	local button = AcquireButton(self, level)
	if not next(info) then
		info.disabled = true
	end
	button.disabled = info.isTitle or info.notClickable or info.disabled or (self.combat and info.secure)
	button.isTitle = info.isTitle
	button.notClickable = info.notClickable
	if button.isTitle then
		button.text:SetFontObject(GameFontNormalSmall)
	elseif button.notClickable then
		button.text:SetFontObject(GameFontHighlightSmall)
	elseif button.disabled then
		button.text:SetFontObject(GameFontDisableSmall)
	else
		button.text:SetFontObject(GameFontHighlightSmall)
	end
	if info.disabled then
		button.arrow:SetDesaturated(true)
		button.check:SetDesaturated(true)
	else
		button.arrow:SetDesaturated(false)
		button.check:SetDesaturated(false)
	end
	if info.textR and info.textG and info.textB then
		button.textR = info.textR
		button.textG = info.textG
		button.textB = info.textB
		button.text:SetTextColor(button.textR, button.textG, button.textB)
	else
		button.text:SetTextColor(button.text:GetFontObject():GetTextColor())
	end
	button.notCheckable = info.notCheckable
	button.text:SetPoint("LEFT", button, "LEFT", button.notCheckable and 0 or 24, 0)
	button.checked = not info.notCheckable and info.checked
	button.mouseoverUnderline = info.mouseoverUnderline
	button.isRadio = not info.notCheckable and info.isRadio
	if info.isRadio then
		button.check:Show()
		button.check:SetTexture(info.checkIcon or "Interface\\Buttons\\UI-RadioButton")
		if button.checked then
			button.check:SetTexCoord(0.25, 0.5, 0, 1)
			button.check:SetVertexColor(1, 1, 1, 1)
		else
			button.check:SetTexCoord(0, 0.25, 0, 1)
			button.check:SetVertexColor(1, 1, 1, 0.5)
		end
		button.radioHighlight:SetTexture(info.checkIcon or "Interface\\Buttons\\UI-RadioButton")
		button.check:SetWidth(16)
		button.check:SetHeight(16)
	elseif info.icon then
		button.check:Show()
		button.check:SetTexture(info.icon)
		if info.iconWidth and info.iconHeight then
			button.check:SetWidth(info.iconWidth)
			button.check:SetHeight(info.iconHeight)
		else
			button.check:SetWidth(16)
			button.check:SetHeight(16)
		end
		if info.iconCoordLeft and info.iconCoordRight and info.iconCoordTop and info.iconCoordBottom then
			button.check:SetTexCoord(info.iconCoordLeft, info.iconCoordRight, info.iconCoordTop, info.iconCoordBottom)
		elseif info.icon:find("^Interface\\Icons\\") then
			button.check:SetTexCoord(0.05, 0.95, 0.05, 0.95)
		else
			button.check:SetTexCoord(0, 1, 0, 1)
		end
		button.check:SetVertexColor(1, 1, 1, 1)
	else
		if button.checked then
			if info.checkIcon then
				button.check:SetWidth(16)
				button.check:SetHeight(16)
				button.check:SetTexture(info.checkIcon)
				if info.checkIcon:find("^Interface\\Icons\\") then
					button.check:SetTexCoord(0.05, 0.95, 0.05, 0.95)
				else
					button.check:SetTexCoord(0, 1, 0, 1)
				end
			else
				button.check:SetWidth(24)
				button.check:SetHeight(24)
				button.check:SetTexture("Interface\\Buttons\\UI-CheckBox-Check")
				button.check:SetTexCoord(0, 1, 0, 1)
			end
			button.check:SetVertexColor(1, 1, 1, 1)
		else
			button.check:SetVertexColor(1, 1, 1, 0)
		end
	end
	if not button.disabled then
		button.func = info.func
		button.secure = info.secure
	end
	button.hasColorSwatch = info.hasColorSwatch
	if button.hasColorSwatch then
		button.colorSwatch:Show()
		button.colorSwatch.texture:Show()
		button.r = info.r or 1
		button.g = info.g or 1
		button.b = info.b or 1
		button.colorSwatch.texture:SetVertexColor(button.r, button.g, button.b)
		button.checked = false
		button.func = nil
		button.colorFunc = info.colorFunc
		local i = 1
		while true do
			local k = "colorArg" .. i
			local x = info[k]
			if x == nil then
				break
			end
			button[k] = x
			i = i + 1
		end
		button.hasOpacity = info.hasOpacity
		button.opacity = info.opacity or 1
	else
		button.colorSwatch:Hide()
		button.colorSwatch.texture:Hide()
	end
	button.hasArrow = not button.hasColorSwatch and (info.value or info.hasSlider or info.hasEditBox) and info.hasArrow
	if button.hasArrow then
		button.arrow:SetAlpha(1)
		if info.hasSlider then
			button.hasSlider = true
			button.sliderMin = info.sliderMin or 0
			button.sliderMax = info.sliderMax or 1
			button.sliderStep = info.sliderStep or 0
			button.sliderBigStep = info.sliderBigStep or button.sliderStep
			if button.sliderBigStep < button.sliderStep then
				button.sliderBigStep = button.sliderStep
			end
			button.sliderIsPercent = info.sliderIsPercent and true or false
			button.sliderMinText = info.sliderMinText or button.sliderIsPercent and string.format("%.0f%%", button.sliderMin * 100) or button.sliderMin
			button.sliderMaxText = info.sliderMaxText or button.sliderIsPercent and string.format("%.0f%%", button.sliderMax * 100) or button.sliderMax
			button.sliderFunc = info.sliderFunc
			button.sliderValue = info.sliderValue
			button.fromAceOptions = info.fromAceOptions
			local i = 1
			while true do
				local k = "sliderArg" .. i
				local x = info[k]
				if x == nil then
					break
				end
				button[k] = x
				i = i + 1
			end
		elseif info.hasEditBox then
			button.hasEditBox = true
			button.editBoxText = info.editBoxText or ""
			button.editBoxFunc = info.editBoxFunc
			local i = 1
			while true do
				local k = "editBoxArg" .. i
				local x = info[k]
				if x == nil then
					break
				end
				button[k] = x
				i = i + 1
			end
			button.editBoxChangeFunc = info.editBoxChangeFunc
			local i = 1
			while true do
				local k = "editBoxChangeArg" .. i
				local x = info[k]
				if x == nil then
					break
				end
				button[k] = x
				i = i + 1
			end
			button.editBoxValidateFunc = info.editBoxValidateFunc
			local i = 1
			while true do
				local k = "editBoxValidateArg" .. i
				local x = info[k]
				if x == nil then
					break
				end
				button[k] = x
				i = i + 1
			end
			button.editBoxIsKeybinding = info.editBoxIsKeybinding
			button.editBoxKeybindingOnly = info.editBoxKeybindingOnly
			button.editBoxKeybindingExcept = info.editBoxKeybindingExcept
		else
			button.value = info.value
			local l = levels[level+1]
			if l and info.value == l.value then
--				button.arrow:SetWidth(24)
--				button.arrow:SetHeight(24)
				button.selected = true
				button.highlight:Show()
			end
		end
	else
		button.arrow:SetAlpha(0)
	end
	local i = 1
	while true do
		local k = "arg" .. i
		local x = info[k]
		if x == nil then
			break
		end
		button[k] = x
		i = i + 1
	end
	button.closeWhenClicked = info.closeWhenClicked
	button.textHeight = info.textHeight or UIDROPDOWNMENU_DEFAULT_TEXT_HEIGHT or 10
	local font,_ = button.text:GetFont()
	button.text:SetFont(STANDARD_TEXT_FONT or "Fonts\\FRIZQT__.TTF", button.textHeight)
	button:SetHeight(button.textHeight + 6)
	button.text:SetPoint("RIGHT", button.arrow, (button.hasColorSwatch or button.hasArrow) and "LEFT" or "RIGHT")
	button.text:SetJustifyH(info.justifyH or "LEFT")
	button.text:SetText(info.text)
	button.tooltipTitle = info.tooltipTitle
	button.tooltipText = info.tooltipText
	button.tooltipFunc = info.tooltipFunc
	local i = 1
	while true do
		local k = "tooltipArg" .. i
		local x = info[k]
		if x == nil then
			break
		end
		button[k] = x
		i = i + 1
	end
	if not button.tooltipTitle and not button.tooltipText and not button.tooltipFunc and not info.isTitle then
		button.tooltipTitle = info.text
	end
	if type(button.func) == "string" then
		if type(button.arg1) ~= "table" then
			self:error("Cannot call method %q on a non-table", button.func)
		end
		if type(button.arg1[button.func]) ~= "function" then
			self:error("Method %q nonexistant.", button.func)
		end
	end
end

function Dewdrop:InjectAceOptionsTable(handler, options)
	self:argCheck(handler, 2, "table")
	self:argCheck(options, 3, "table")
	if tostring(options.type):lower() ~= "group" then
		self:error('Cannot inject into options table argument #3 if its type is not "group"')
	end
	if options.handler ~= nil and options.handler ~= handler then
		self:error("Cannot inject into options table argument #3 if it has a different handler than argument #2")
	end
	options.handler = handler
	local class = handler.class
	if not AceLibrary:HasInstance("AceOO-2.0") or not class then
		self:error("Cannot retrieve AceOptions tables from a non-object argument #2")
	end
	while class and class ~= AceLibrary("AceOO-2.0").Class do
		if type(class.GetAceOptionsDataTable) == "function" then
			local t = class:GetAceOptionsDataTable(handler)
			for k,v in pairs(t) do
				if type(options.args) ~= "table" then
					options.args = {}
				end
				if options.args[k] == nil then
					options.args[k] = v
				end
			end
		end
		local mixins = class.mixins
		if mixins then
			for mixin in pairs(mixins) do
				if type(mixin.GetAceOptionsDataTable) == "function" then
					local t = mixin:GetAceOptionsDataTable(handler)
					for k,v in pairs(t) do
						if type(options.args) ~= "table" then
							options.args = {}
						end
						if options.args[k] == nil then
							options.args[k] = v
						end
					end
				end
			end
		end
		class = class.super
	end
	return options
end

function Dewdrop:OnTooltipHide()
	if lastSetFont then
		if lastSetFont == normalFont then
			lastSetFont = nil
			return
		end
		fillRegionTmp(GameTooltip:GetRegions())
		for i,v in ipairs(regionTmp) do
			if v.GetFont then
				local font,size,outline = v:GetFont()
				if font == lastSetFont then
					v:SetFont(normalFont, size, outline)
				end
			end
			regionTmp[i] = nil
		end
		lastSetFont = nil
	end
end

local function activate(self, oldLib, oldDeactivate)
	Dewdrop = self
	if oldLib and oldLib.registry then
		self.registry = oldLib.registry
		self.onceRegistered = oldLib.onceRegistered
	else
		self.registry = {}
		self.onceRegistered = {}

		local WorldFrame_OnMouseDown = WorldFrame:GetScript("OnMouseDown")
		local WorldFrame_OnMouseUp = WorldFrame:GetScript("OnMouseUp")
		local oldX, oldY, clickTime
		WorldFrame:SetScript("OnMouseDown", function(this, ...)
			oldX,oldY = GetCursorPosition()
			clickTime = GetTime()
			if WorldFrame_OnMouseDown then
				WorldFrame_OnMouseDown(this, ...)
			end
		end)

		WorldFrame:SetScript("OnMouseUp", function(this, ...)
			local x,y = GetCursorPosition()
			if not oldX or not oldY or not x or not y or not clickTime then
				self:Close()
				if WorldFrame_OnMouseUp then
					WorldFrame_OnMouseUp(this, ...)
				end
				return
			end
			local d = math.abs(x - oldX) + math.abs(y - oldY)
			if d <= 5 and GetTime() - clickTime < 0.5 then
				self:Close()
			end
			if WorldFrame_OnMouseUp then
				WorldFrame_OnMouseUp(this, ...)
			end
		end)

		hooksecurefunc(DropDownList1, "Show", function()
			if levels[1] and levels[1]:IsVisible() then
				self:Close()
			end
		end)
		
		hooksecurefunc("HideDropDownMenu", function()
			if levels[1] and levels[1]:IsVisible() then
				self:Close()
			end
		end)
		
		hooksecurefunc("CloseDropDownMenus", function()
			if levels[1] and levels[1]:IsVisible() then
				local stack = debugstack()
				if not stack:find("`TargetFrame_OnHide'") then
					self:Close()
				end
			end
		end)
	end
	self.frame = oldLib and oldLib.frame or CreateFrame("Frame")
	self.frame:UnregisterAllEvents()
	self.frame:RegisterEvent("PLAYER_REGEN_ENABLED")
	self.frame:RegisterEvent("PLAYER_REGEN_DISABLED")
	self.frame:Hide()
	self.frame:SetScript("OnEvent", function(this, event)
		this:Show()
		if event=="PLAYER_REGEN_ENABLED" then			-- track combat state for secure frame operations
			self.combat = false
		elseif event=="PLAYER_REGEN_DISABLED" then
			self.combat = true
		end
	end)
	self.frame:SetScript("OnUpdate", function(this)
		this:Hide()
		self:Refresh(1)
	end)
	self.hookedTooltip = true
	if not oldLib or not oldLib.hookedTooltip then
		local OnTooltipHide = GameTooltip:GetScript("OnHide")
		GameTooltip:SetScript("OnHide", function(this, ...)
			if OnTooltipHide then
				OnTooltipHide(this, ...)
			end
			if type(self.OnTooltipHide) == "function" then
				self:OnTooltipHide()
			end
		end)
	end
	levels = {}
	buttons = {}

	if oldDeactivate then
		oldDeactivate(oldLib)
	end
end

local function external(lib, major, instance)
	if major == "SharedMedia-1.0" then
		SharedMedia = instance
	end
end

AceLibrary:Register(Dewdrop, MAJOR_VERSION, MINOR_VERSION, activate, nil, external)
