view gui.lua @ 108:04ccd12c2a41

Tweak 'fix history' output, disable UIDropDownMenu_CreateFrames fix.
author Farmbuyer of US-Kilrogg <farmbuyer@gmail.com>
date Mon, 06 Aug 2012 14:57:14 -0400
parents 35b55c6f5551
children ce45011fab4c
line wrap: on
line source
local addon = select(2,...)
if addon.NOLOAD then return end

--[[
Purely the AceGUI-related routines, and the subroutines needed for support.
------ Constants
------ Globals
------ Behind the scenes routines
------ Main GUI Window
------ Popup dialogs
]]

------ Constants
local eoi_st_rowheight			= 20
local eoi_st_displayed_rows		= math.floor(416/eoi_st_rowheight)
local eoi_st_textured_item_format = "|T%s:"..(eoi_st_rowheight-2).."|t %s[%s]|r%s"
-- This can get indexed by kind/reason/etc, and will default to lib-st's
-- default "blank" background at runtime.
local eoi_st_otherrow_bgcolortable = {
	wipe	= { ["r"] = 0.3, ["g"] = 0.3, ["b"] = 0.3},
	kill	= { ["r"] = 0.2, ["g"] = 0.2, ["b"] = 0.2},
	time	= { ["r"] = 0x0/255, ["g"] = 0x0/255, ["b"] = 1, ["a"] = 0.3},
}
eoi_st_otherrow_bgcolortable[""] = eoi_st_otherrow_bgcolortable["kill"]
local eoi_st_otherrow_bgcolortable_default
local eoi_st_lootrow_col3_colortable = {
	normal	= { text = "",            r = "ff", g = "ff", b = "ff" },
	shard	= { text = "shard",       r = "a3", g = "35", b = "ee" },
	offspec	= { text = "offspec",     r = "c6", g = "9b", b = "6d" },
	gvault	= { text = "guild vault", r = "33", g = "ff", b = "99" },
}
for k,v in pairs(eoi_st_lootrow_col3_colortable) do
	-- for chat output by core code
	v.hex = "|cff" .. v.r .. v.g .. v.b
	-- for lib-st
	v.r = tonumber(v.r,16)/255
	v.g = tonumber(v.g,16)/255
	v.b = tonumber(v.b,16)/255
	v.a = 1
end
addon.disposition_colors = eoi_st_lootrow_col3_colortable
local function eoi_st_lootrow_col3_colortable_func (data, _, realrow)
	local disp = data[realrow].disposition
	return eoi_st_lootrow_col3_colortable[disp or 'normal']
end
local time_column1_used_mt = { __index = {
	[2] = {value=""},
	[3] = {value=""},
} }


------ Globals
local AceGUI = LibStub("AceGUI-3.0")
local flib = LibStub("LibFarmbuyer")

local gui = {
	-- These are used to build the tabgroup_tabs array fed to TabGroup, and
	-- for the official source of mouseover tab titles, etc.  Not completely
	-- hidden because we need to reach in and fiddle too often to be worth it.
	tabtexts = {
		["eoi"] = {title=[[Loot]], desc=[[Observed loot, plus boss kills and other events of interest]]},
		["hist"] = {title=[[History]], desc=[[A short semi-permanent record]]},
	},
	taborder        = { "eoi"                        },
	taborder_APPEND = {        "hist", "help", "opt" },
}
addon.gui_state_pointer = gui    -- only during loading, then cleaned out
if addon.author_debug then
	_G.OLgui = gui
end

local g_loot			= nil
local g_uniques			= nil
local g_generated		= nil
local window_title		= "Ouro Loot"
local dirty_tabs		= nil

local error				= addon.error
local assert			= addon.assert

local pairs, ipairs, tinsert, tremove, tostring, tonumber =
	pairs, ipairs, table.insert, table.remove, tostring, tonumber

local pprint, tabledump = addon.pprint, flib.tabledump
local GetItemInfo, ITEM_QUALITY_COLORS = GetItemInfo, ITEM_QUALITY_COLORS
local GetNumRaidMembers = GetNumGroupMembers or GetNumRaidMembers
local IsInRaid = IsInRaid or (function() return GetNumRaidMembers() > 0 end)

-- En masse forward decls of symbols defined inside local blocks
local _generate_text, _populate_text_specials, _markup
local eoi_dropdownfuncs -- filled out in gui block scope
local _hide_debugging_tooltip, _build_debugging_tooltip
local _new_rebroadcast_hyperlink

--[[
This is a table of callback functions, each responsible for drawing a tab
into the container passed in the first argument.  Special-purpose buttons
can optionally be created (mkbutton) and added to the container in the second
argument.
]]
local tabs_OnGroupSelected = {}
local mkbutton
local tabs_OnGroupSelected_func, tabs_generated_text_OGS
-- Similarly for the popup tips on the right side of the window.
local noob_tips = {}
-- And any special handling for additional CLI arguments.
local tabs_CLI_special = {}

do
	local replacement_colors = {
		["+"]="|cffffffff",   -- white
		["<"]="|cff00ff00",   -- light green
		[">"]="|r" }
	function _markup (t)
		-- wonder if it would be worth memoizing this also
		return t:gsub("[%+<>]",replacement_colors)
		        :gsub("([^\n])\n([^\n])", "%1 %2")
		        :gsub("|r\n\n", "|r\n")
	end
	gui.markup = _markup  -- too useful to keep local
end

-- Working around this bug:
-- http://forums.wowace.com/showpost.php?p=295202&postcount=31
if false then  -- XXX no longer needed?
	local function fix_frame_level (level, ...)
		for i = 1, select("#", ...) do
			local button = select(i, ...)
			button:SetFrameLevel(level)
		end
	end

	local function fix_menu_frame_levels()
		local f = _G.DropDownList1
		local i = 1
		while f do
			fix_frame_level (f:GetFrameLevel() + 2, f:GetChildren())
			i = i + 1
			f = _G["DropDownList"..i]
		end
	end

	-- To fix Blizzard's bug caused by the new "self:SetFrameLevel(2);"
	hooksecurefunc("UIDropDownMenu_CreateFrames", fix_menu_frame_levels)
end


------ Behind the scenes routines
-- Text generation
do
	local text_gen_funcs, specials_gen_funcs = {}, {}
	local accumulator = {}

	local function _reg (tab_code, title, description, --[[unused]]generator,
		opt_specgen, opt_noobtip, opt_cli
	)
		assert(type(tab_code)=='string')
		assert(type(title)=='string')
		assert(type(description)=='string')
		gui.tabtexts[tab_code] = { title=title, desc=description }
		if not gui.suppress_taborder then
			gui:tabposition_insert (tab_code)
		end
		if opt_specgen then
			assert(type(opt_specgen)=='function')
			specials_gen_funcs[tab_code] = opt_specgen
		end
		if opt_noobtip then
			if type(opt_noobtip) == 'string' then
				noob_tips[tab_code] = _markup(opt_noobtip)
			elseif type(opt_noobtip) == 'function' then
				noob_tips[tab_code] = opt_noobtip
			else
				error(("Optional new user tip argument for '%s' must be a string or function!"):format(tab_code))
			end
		end
		if opt_cli then
			assert(type(opt_cli)=='function')
			tabs_CLI_special[tab_code] = opt_cli
		end
		dirty_tabs = true
	end

	-- Can do clever things by passing other halting points as zero
	function addon:zero_printed_fenceposts (zero)
		for t in pairs(text_gen_funcs) do
			g_loot.printed[t] = zero or g_loot.printed[t] or 0
		end
	end

	function addon:registered_textgen_iter()
		return pairs(text_gen_funcs)
	end

	-- This function is called during load, so be careful!
	function addon:register_text_generator (text_type, title, description,
		generator, opt_specgen, opt_noobtip, opt_cli
	)
		if self.NOLOAD then return end
		if type(generator) ~= 'function' then
			error(("Generator for text type '%s' must be a function!"):format(text_type))
		end
		_reg (text_type, title, description, generator, opt_specgen, opt_noobtip, opt_cli)
		text_gen_funcs[text_type] = generator
	end

	-- These two called by tabs_generated_text_OGS
	-- tabs_OnGroupSelected_func will catch propagated errors
	function _generate_text (text_type)
		local f = text_gen_funcs[text_type]
		if not f then
			error(("Generator called for unregistered text type '%s'."):format(text_type))
		end
		g_generated = g_generated or {}
		g_loot[text_type] = g_loot[text_type] or ""

		if g_loot.printed[text_type] >= #g_loot then return false end
		assert (addon.loot_clean == #g_loot,
			tostring(addon.loot_clean) .. " ~= " .. #g_loot)
		-- if glc is nil, #==0 test already returned

		local ok,ret = pcall (f, text_type, g_loot, g_loot.printed[text_type], g_generated, accumulator)
		if not ok then
			error(("ERROR:  text generator '%s' failed:  %s"):format(text_type, ret))
		end
		if ret then
			g_loot.printed[text_type] = #g_loot
			g_generated[text_type] = (g_generated[text_type] or "") .. table.concat(accumulator,'\n') .. '\n'
		end
		wipe(accumulator)
		return ret
	end
	function _populate_text_specials (editbox, specials, mkb, text_type)
		local f = specials_gen_funcs[text_type]
		if not f then return end
		local ok,ret = pcall (f, text_type, editbox, specials, mkb)
		if not ok then
			error(("ERROR:  special widget creation for '%s' failed:  %s"):format(text_type, ret))
		end
	end

	-- LOD tab has been clicked on.
	local function _handle_LOD (tabs_container,specials,tabtitle)
		-- "tabtitle" here is the name in taborder, not the colorized string
		local what = gui.tabtexts[tabtitle]
		local addon_index = what.LOD
		local function LOAD()
			gui.tabtexts[tabtitle] = nil
			gui:tabposition_remove_and_remember (tabtitle)
			local loaded, whynot = LoadAddOn(addon_index)
			local tabdelta = gui:tabposition_restore()
			if loaded then
				addon:Print("%s loaded, %d |4tab:tabs; added.", tabtitle, tabdelta)
			else
				what.disabled = true
				gui.tabtexts[tabtitle] = what -- restore this for mouseovers
				addon:Print("%s could not load (game client reason was '%s').", tabtitle, whynot)
				DisableAddOn(addon_index)
			end
			dirty_tabs = true
			return addon:OpenMainDisplayToTab(tabtitle) or addon:BuildMainDisplay()
		end
		addon.display:Hide()
		if what.LOD_enabled then
			-- totally loadable, go for it
			LOAD()
		else
			-- was disabled at addons menu
			StaticPopupDialogs["OUROL_LOD_DISABLED"] = flib.StaticPopup{
				text = tabtitle.." was disabled at the character selection screen.  Do you want to enable it?",
				button1 = YES,
				button2 = NO,
				OnAccept = function()
					EnableAddOn(addon_index)
					LOAD()
				end,
				OnCancel = function()
					addon:BuildMainDisplay()
				end,
				OnHide = function()
					StaticPopupDialogs["OUROL_LOD_DISABLED"] = nil
				end,
			}
			StaticPopup_Show("OUROL_LOD_DISABLED")
		end
	end

	-- Add a clickable tab that brings the real module in.  Since gui_init has
	-- already been called, we flag the dirty bit and let the main building
	-- routine handle it like any other plugin.
	function addon:_gui_add_LOD_tab (tabtitle, folder, addon_index, enabled_p, why_not)
		gui.tabtexts[tabtitle] = {
			title = ("|cffff0000(%s)|r"):format(tabtitle),
			desc = ("Plugin '|cffff0000%s|r' is not loaded yet.  Click the tab to load it now."):format(folder),
			LOD = addon_index,
			LOD_enabled = enabled_p,
			LOD_why_not = why_not,
		}
		tabs_OnGroupSelected[tabtitle] = _handle_LOD
		gui:tabposition_insert (tabtitle)
		dirty_tabs = true
	end

	-- Registering truly arbitrary tab controls, not just text generators.
	-- (This is slightly out of place, but no more so than the LOD stuff.)
	-- The arguments are nearly the same as those of :register_text_generator
	-- but the "generator" function is the full-blown callback and there is
	-- no "specgen" (since it's handled in the main generator).
	function addon:register_tab_control (tab_code, title, description,
		generator, opt_noobtip, opt_cli
	)
		if self.NOLOAD then return end
		if type(generator) ~= 'function' then
			error(("Generator for tab code '%s' must be a function!"):format(tab_code))
		end
		_reg (tab_code, title, description, generator, --[[opt_specgen=]]nil, opt_noobtip, opt_cli)
		tabs_OnGroupSelected[tab_code] = generator
	end
	function addon:register_tab_control_AT_END (...)
		gui.suppress_taborder = true
		self:register_tab_control(...)
		gui.suppress_taborder = nil
	end
end

--[[
The g_loot table is populated only with "behavior-relevant" data (names,
links, etc).  This function runs through it and fills out the "display-
relevant" bits (icons, user-friendly labels, etc).  Everything from the
loot_clean index to the end of the table is filled out, loot_clean is
updated.  Override the starting point with the argument.

XXX blizzard's scrolling update and lib-st keep finding some way of displaying
the grid without ever calling the hooked refresh, thereby skipping this
function and erroring on missing columnar data.  fuckit.  from now on
this function gets called everywhere, all the time, and loops over the
entire goddamn table each time.  If we can't find blizz's scrollframe bugs,
we'll just work around them.  Sorry for your smoking CPU.

FIXME just move this functionality to a per-entry function and call it once
in _addlootentry.  --actually no, then the columnar data won't be updated once
the backend data is changed on the fly.
]]
do
	function addon:_fill_out_eoi_data (opt_starting_index)
		if #g_loot < 1 then
			--pprint('_f_o_e_d', "#g_loot<1")
			self.loot_clean = nil
			opt_starting_index = nil
		end
		for i = (opt_starting_index or self.loot_clean or 1), #g_loot do
			local e = g_loot[i]
			if e == nil then
				self.loot_clean = nil
				pprint('_f_o_e_d', "index",i,"somehow still in loop past",#g_loot,"bailing")
				return
			end

			local display_bcast_from = self.db.profile.display_bcast_from
			-- XXX FIXME a major weakness here is that we're constantly replacing
			-- what's already been created.  Lots of garbage.  Trying to detect what
			-- actually needs to be replaced is even worse.  We'll live with
			-- garbage for now.
			if e.kind == 'loot' then
				local textured = eoi_st_textured_item_format:format (e.itexture, ITEM_QUALITY_COLORS[e.quality].hex, e.itemname, e.count or "")
				e.cols = {
					{value = textured},
					{value = e.person},
					{}
				}
				-- This is horrible. Must do better.
				if e.extratext then for k,v in pairs(eoi_st_lootrow_col3_colortable) do
					if v.text == e.extratext then
						e.disposition = k ~= 'normal' and k or nil
						--e.extratext = nil, not feasible
						break
					end
				end end
				local ex = eoi_st_lootrow_col3_colortable[e.disposition or 'normal'].text
				if e.bcast_from and display_bcast_from and e.extratext then
					ex = e.extratext .. " (from " .. e.bcast_from .. ")"
				elseif e.bcast_from and display_bcast_from then
					ex = ex .. (e.disposition and " " or "")
					     .. "(from " .. e.bcast_from .. ")"
				elseif e.extratext then
					ex = e.extratext
				end
				e.cols[3].value = ex

			elseif e.kind == 'boss' then
				local v
				e.duration = e.duration or 0 -- can occasionally miss getting set
				if e.reason == 'kill' then
					if e.attempts == 1 then
						v = "one-shot"
					else
						v = ("kill on %d%s attempt"):format(e.attempts or 0,
							e.attempts==2 and "nd" or e.attempts==3 and "rd" or "th")
					end
					v = ("%s (%d:%.2d)"):format(v, math.floor(e.duration/60), math.floor(e.duration%60))
				elseif e.reason == 'wipe' then
					v = ("wipe (%d:%.2d)"):format(math.floor(e.duration/60), math.floor(e.duration%60))
				end
				e.cols = {
					{value = e.bossname},
					{value = e.instance},
					{value = v or ""},
				}

			elseif e.kind == 'time' then
				e.cols = setmetatable({
					{value=e.startday.text},
				}, time_column1_used_mt)
				--[[e.cols = {
					{value=e.startday.text},
					{value=""},
					{value=""},
				}]]

			end
		end
		self.loot_clean = #g_loot
	end
end

do
	function addon:_fill_out_hist_data (opt_starting_index)
		local new, del = flib.new, flib.del

		-- Clearing history finishes this function with #hist==0 and hist_clean==0.
		-- The next call typically detects this (#<1) and handles it.  If loot is
		-- recorded before then, it results in hist_clean==0 and #hist==1, which
		-- breaks the first iteration of the loop.  Thus, the "extra" test here:
		if #self.history < 1 or self.hist_clean == 0 then
			self.hist_clean = nil
			opt_starting_index = nil
		end
		if not self.history.st then
			--print"creating ST!"
			self.history.st = {
				--[[{ kind = "realm",
				  cols = setmetatable({
					{ value = self.history.realm },
				  }, time_column1_used_mt)
			    }]]
			}
		end

		-- for now
		if self.hist_clean == #self.history then return end

		local st = self.history.st
		--print("starting history loop, #st ==", #st, "#history ==", #self.history)
		for i,t in ipairs(st) do
			del(t.cols[1])
			del(t.cols[2])
			del(t.cols[3])
			del(t.cols)
			del(t)
			st[i] = nil
		end

		--for i = (opt_starting_index or self.hist_clean or 1), #self.history do
		local cache_okay = true
		for pi,player in ipairs(self.history) do
			local col1 = new()
			col1.OLi   = pi
			col1.value = player.name   -- may spiffy this up in future

			for li,unique in ipairs(player.unique) do
				local col2 = new()
				col2.OLi   = li
				col2.OLu   = unique
				local col3 = new()
				col3.value = assert(player.when[unique])

				local id = assert(player.id[unique])
				local itexture = GetItemIcon(id)
				local iname, ilink, iquality = GetItemInfo(id)
				local textured
				if itexture and iname then
					textured = eoi_st_textured_item_format:format (itexture,
						ITEM_QUALITY_COLORS[iquality].hex, iname, player.count[unique] or "")
				else
					textured = eoi_st_textured_item_format:format ([[ICONS\INV_Misc_QuestionMark]],
						ITEM_QUALITY_COLORS[ITEM_QUALITY_COMMON].hex, 'UNKNOWN - REDISPLAY LATER', "")
					cache_okay = false
				end
				col2.value = textured

				-- To facilitate sharing lib-st routines between EOI and this
				-- one, we do some of the same fields:  'kind' and 'itemlink'.
				-- These aren't used outside of the GUI.  These become our data
				-- table arguments to the lib-st routines (thus 'e' locals).
				local dotcols = new (col1, col2, col3)
				local st_entry = new()
				st_entry.kind = 'hist'
				st_entry.OLwho = player.name
				st_entry.OLclass = player.person_class
				st_entry.cols = dotcols
				st_entry.itemlink = ilink  -- for onenter and onclick
				tinsert (st, st_entry)
			end
		end

		--print("finished history loop, #st ==", #st)
		self.hist_clean = cache_okay and #self.history or nil
	end
end

-- Debugging tooltip (unfortunately managed by global and semi-global state
-- rather than passing around stack parameters)
do
	local debug_tt

	local _creators, _builders = {}, {}
	local function _create_tooltip()
		local which = assert(tonumber(gui._do_debugging_tooltip))
		if type(_creators[which]) == 'function' then
			_creators[which]()
		end
		debug_tt = _creators[which]
	end
	function _build_debugging_tooltip (parent, index)
		local which = assert(tonumber(gui._do_debugging_tooltip))
		if type(_builders[which]) == 'function' then
			_builders[which](parent,index)
		end
	end
	function _hide_debugging_tooltip()
		if debug_tt then debug_tt:Hide() end
	end

	-- 2 == /dump
	_creators[2] = function()
		local tt = CreateFrame("GameTooltip")
		UIParentLoadAddOn("Blizzard_DebugTools")

		tt:SetBackdrop{
			bgFile = [[Interface\Tooltips\UI-Tooltip-Background]],
			edgeFile = [[Interface\Tooltips\UI-Tooltip-Border]],
			tile = true,
			tileSize = 8,
			edgeSize = 12,
			insets = { left = 2, right = 2, top = 2, bottom = 2 }
		}
		tt:SetBackdropColor(TOOLTIP_DEFAULT_BACKGROUND_COLOR.r,
			TOOLTIP_DEFAULT_BACKGROUND_COLOR.g, TOOLTIP_DEFAULT_BACKGROUND_COLOR.b)
		tt:SetBackdropBorderColor(TOOLTIP_DEFAULT_COLOR.r, TOOLTIP_DEFAULT_COLOR.g,
			TOOLTIP_DEFAULT_COLOR.b)
		tt:SetMovable(false)
		tt:EnableMouse(false)
		tt:SetFrameStrata("TOOLTIP")
		tt:SetToplevel(true)
		tt:SetClampedToScreen(true)

		local font = CreateFont("OuroLootDebugFont")
		font:CopyFontObject(GameTooltipTextSmall)
		if IsAddOnLoaded"tekticles" then    -- maybe check for one of the sharedmedia things?
			font:SetFont([[Interface\AddOns\tekticles\Calibri.ttf]], 9)
		else
			font:SetFont([[Fonts\FRIZQT__.TTF]], 9)
		end

		local left, right, prevleft
		-- Only create as many lines as we might need (the auto growth
		-- by Add*Line does odd things sometimes).
		for i = 1, math.max(DEVTOOLS_MAX_ENTRY_CUTOFF,15)+5 do
			prevleft = left
			left = tt:CreateFontString(nil,"ARTWORK")
			right = tt:CreateFontString(nil,"ARTWORK")
			left:SetFontObject(font)
			right:SetFontObject(font)
			tt:AddFontStrings(left,right)
			if prevleft then
				left:SetPoint("TOPLEFT",prevleft,"BOTTOMLEFT",0,-2)
			else
				left:SetPoint("TOPLEFT",10,-10)  -- top line
			end
			right:SetPoint("RIGHT",left,"LEFT")
		end
		tt.AddMessage = tt.AddLine

		_creators[2] = tt
	end

	_builders[2] = function (parent, index)
		local e = g_loot[index]; assert(type(e)=='table')
		_create_tooltip()
		debug_tt:SetOwner (parent, "ANCHOR_LEFT", -15, -5)
		debug_tt:ClearLines()

		local real = DEFAULT_CHAT_FRAME
		DEFAULT_CHAT_FRAME = debug_tt
		DevTools_Dump{ [index] = e }
		DEFAULT_CHAT_FRAME = real

		debug_tt:Show()
	end

	-- Now here's a thing unheard-of.  A tooltip not inheriting from the big
	-- memory-wasteful template, but also not intended merely for scanning
	-- invisible tooltips.
	-- (If this ever grows beyond a text dump, then replace it with libqtip.)
	--
	-- Fields to put in the fixed-fields tooltip (maybe move these into the
	-- options window if I spend too much time fiddling).
	local loot = {'person', 'id', 'unique', 'disposition', 'count', 'variant'}
	local boss = {'bossname', 'reason', 'instance', 'maxsize', 'duration', 'raidersnap'}

	-- 3 == fixed fields
	_creators[3] = function()
		local tt = CreateFrame("GameTooltip")

		tt:SetBackdrop{
			bgFile = [[Interface\Tooltips\UI-Tooltip-Background]],
			edgeFile = [[Interface\Tooltips\UI-Tooltip-Border]],
			tile = true,
			tileSize = 8,
			edgeSize = 12,
			insets = { left = 2, right = 2, top = 2, bottom = 2 }
		}
		tt:SetBackdropColor(TOOLTIP_DEFAULT_BACKGROUND_COLOR.r,
			TOOLTIP_DEFAULT_BACKGROUND_COLOR.g, TOOLTIP_DEFAULT_BACKGROUND_COLOR.b)
		tt:SetBackdropBorderColor(TOOLTIP_DEFAULT_COLOR.r, TOOLTIP_DEFAULT_COLOR.g,
			TOOLTIP_DEFAULT_COLOR.b)
		tt:SetMovable(false)
		tt:EnableMouse(false)
		tt:SetFrameStrata("TOOLTIP")
		tt:SetToplevel(true)
		tt:SetClampedToScreen(true)

		local font = GameTooltipTextSmall
		local left, right, prevleft
		-- Only create as many lines as we might need (the auto growth
		-- by Add*Line does odd things sometimes).
		for i = 1, math.max(#loot,#boss)+2 do
			prevleft = left
			left = tt:CreateFontString(nil,"ARTWORK")
			right = tt:CreateFontString(nil,"ARTWORK")
			left:SetFontObject(font)
			right:SetFontObject(font)
			tt:AddFontStrings(left,right)
			if prevleft then
				left:SetPoint("TOPLEFT",prevleft,"BOTTOMLEFT",0,-2)
			else
				left:SetPoint("TOPLEFT",10,-10)  -- top line
			end
			right:SetPoint("RIGHT",left,"LEFT")
		end

		_creators[3] = tt
	end

	_builders[3] = function (parent, index)
		local e = g_loot[index]; assert(type(e)=='table')
		_create_tooltip()
		debug_tt:SetOwner (parent, "ANCHOR_LEFT", -15, -5)
		debug_tt:ClearLines()

		-- change these, change the +2 above
		debug_tt:AddDoubleLine (tostring(index), tostring(e), 1,1,1)
		debug_tt:AddDoubleLine ('kind', e.kind, 1,1,1)

		local source = (e.kind == 'loot' and loot) or (e.kind == 'boss' and boss)
		if source then
			for _,field in ipairs(source) do
				debug_tt:AddDoubleLine (field, tostring(e[field]), 1,1,1, 0,156/255,1)
			end
		end
		debug_tt:Show()
	end
end

do
	local rebroadcast_map  -- XXX weaken this somehow?

	local function onclick (self, ident)
		-- Since an arbitrary number of insert/delete ops can happen between
		-- now and (potentially) clicking on the hyperlink, we can't depend on
		-- the row index.  Force the uniques cache to re-search (research?) it.
		local u = assert(rebroadcast_map[ident])
		local cache = g_uniques:SEARCH(u)
		-- A missing history entry isn't necessarily an error here, but we
		-- need a loot entry to go further.
		if cache.loot then
			eoi_dropdownfuncs["Rebroadcast this loot entry"](cache.loot)
		else
			addon:Print("...guh?  Entry was recorded with tag <%s> but cannot now be found!", u)
		end
		-- delete the entry maybe?
	end

	function _new_rebroadcast_hyperlink (u)
		rebroadcast_map = rebroadcast_map or flib.new()
		local clicky, ident = addon.format_hypertext(
			-- same color sequence as what DBM uses to announce pizza timers,
			-- which looks reasonably pleasing
			[[Broadcast this entry]], "|cff3588ff", onclick)
		rebroadcast_map[ident] = u
		return clicky
	end
end

-- UI tips window
local hide_noobtips_frame = flib.nullfunc
local function get_noobtips_frame()
	local f = CreateFrame("Frame")
	f:SetBackdrop{
		bgFile = [[Interface\Tooltips\UI-Tooltip-Background]],
		--bgFile = [[Interface\DialogFrame\UI-DialogBox-Background]],
		edgeFile = [[Interface\Tooltips\UI-Tooltip-Border]],
		--edgeFile = [[Interface\DialogFrame\UI-DialogBox-Border]],
		tile = true,
		tileSize = 8,
		--tileSize = 32,
		edgeSize = 12,
		--edgeSize = 32,
		insets = { left = 2, right = 2, top = 2, bottom = 2 }
		--insets = { left = 11, right = 12, top = 12, bottom = 11 }
	}
	f:SetBackdropColor(TOOLTIP_DEFAULT_BACKGROUND_COLOR.r,
		TOOLTIP_DEFAULT_BACKGROUND_COLOR.g, TOOLTIP_DEFAULT_BACKGROUND_COLOR.b)
	f:SetBackdropBorderColor(TOOLTIP_DEFAULT_COLOR.r, TOOLTIP_DEFAULT_COLOR.g,
		TOOLTIP_DEFAULT_COLOR.b)
	f:SetMovable(false)
	f:EnableMouse(false)
	f:SetFrameStrata("TOOLTIP")
	f:SetToplevel(true)
	f:SetClampedToScreen(true)
	f:SetWidth(220)
	local t = f:CreateFontString (nil, "ARTWORK", "GameFontHighlightSmall")
	--t:SetPoint ("TOPLEFT", f, "TOPLEFT", 14, -15)
	t:SetPoint ("TOPLEFT", f, "TOPLEFT", 10, -10)
	t:SetJustifyH("LEFT")

	f.text = t
	f.DoTextWork = function (self, text)
		self.text:SetText(text)
		self.text:SetWidth (self:GetWidth() - 20)
		self:SetHeight (self.text:GetHeight() + 20)
	end

	get_noobtips_frame = function() return f end
	hide_noobtips_frame = function() f:Hide() end
	return f
end


------ Main GUI Window
local _d   -- display when it's open, nil when it's not
local function setstatus(txt) _d:SetStatusText(txt) end
local function statusy_OnLeave() setstatus("") end
local tabgroup_tabs
gui.setstatus = setstatus

--[[
Controls for the tabs on the left side of the main display.
]]

do
	--local next_insertion_position = 2   -- position in taborder
	local next_insertion_position = #gui.taborder + 1
	local removed, saved_offset

	function gui:tabposition_insert (tabcode)
		tinsert (gui.taborder, next_insertion_position, tabcode)
		next_insertion_position = next_insertion_position + 1
	end

	-- These two functions are push/pop pairs, sort of.  The first removes
	-- a tab and prepares to insert more tab(s) in its place.  The second
	-- returns the "next tab goes here" marker back to the proper end.  (And
	-- doing all 3 adjustments below at once is amazingly hard to read.)
	function gui:tabposition_remove_and_remember (tabcode)
		assert(not removed)   -- enforce stack-ish discipline
		for i = 2, #gui.taborder do
			if gui.taborder[i] == tabcode then
				tremove (gui.taborder, i)
				saved_offset = next_insertion_position - i - 1
				removed, next_insertion_position = i, i
				return
			end
		end
		error(("'%s' not used as a tab-text code"):format(tabcode))
	end
	function gui:tabposition_restore()
		assert(removed)
		local count = next_insertion_position - removed
		next_insertion_position = next_insertion_position + saved_offset
		removed, saved_offset = nil, nil
		return count
	end

	function addon:FINISH_SPECIAL_TABS()
		-- very carefully not touching next_insertion_position
		for i,v in ipairs(gui.taborder_APPEND) do
			gui.taborder[#gui.taborder+1] = v
		end
		gui.taborder_APPEND = nil
		self.register_tab_control_AT_END = nil
		self.FINISH_SPECIAL_TABS = nil
	end
end

-- Done at startup, and whenever we've changed the population of tabs.
function addon:gui_init (loot_pointer, uniques_pointer)
	g_loot = assert(loot_pointer, "something went wrong at startup")
	g_uniques = assert(uniques_pointer, "something went wrong at startup")
	g_generated = nil
	tabgroup_tabs = {}
	window_title = "Ouro Loot " .. self.version
	-- TabGroup stretches out the tabs to fill the row but only if >75% of the
	-- row is already full.  It turns out that not doing this looks like ass.
	-- If we won't have enough tabs to trigger this on its own, pad out the tab
	-- titles (not looking quite as nice, ah well) to force it to trigger.
	local fmtstr = #gui.taborder > 6 and "%s" or "   %s   "
	for i,name in ipairs(gui.taborder) do
		tabgroup_tabs[i] = {
			value = name,
			text = fmtstr:format(gui.tabtexts[name].title),
			disabled = gui.tabtexts[name].disabled,
		}
		-- By default, tabs are editboxes with generated text
		if not tabs_OnGroupSelected[name] then
			tabs_OnGroupSelected[name] = tabs_generated_text_OGS
		end
	end
	dirty_tabs = nil
end

--[[
Dropdown menu handling; this has grown in ungainly directions.
]]
-- forward decls
local eoi_editcell

local dropdownfuncs
do
	local ddf_mt = {
		__index = {
			-- more stuff should be moved into this table
			[CLOSE] = function() CloseDropDownMenus() end,
		}
	}
	dropdownfuncs = function(t)
		return setmetatable(t, ddf_mt)
	end
end

local function dropdownmenu_handler (ddbutton, subfunc, arg)
	local i = _d and _d.GetUserData and _d:GetUserData("DD index")
	if i then
		subfunc(i,arg)
		gui.which_ST:OuroLoot_Refresh(i)
	end
end

local function gen_easymenu_table (initial, list, funcs)
	for _,tag in ipairs(list) do
		local name, arg, tiptext
		name, tiptext = strsplit('|',tag)
		name, arg = strsplit('%',name)
		if name == "--" then
			tinsert (initial, {
				disabled = true, notCheckable = true, text = "",
			})
		else
			if not funcs[name] then
				error(("'%s' not defined as a dropdown function"):format(name))
			end
			tinsert (initial, {
				text = name,
				func = dropdownmenu_handler,
				arg1 = funcs[name],
				arg2 = arg,
				notCheckable = true,
				tooltipOnButton = true,
				tooltipWhileDisabled = true,
				tooltipTitle = tiptext and name or nil,
				tooltipText = tiptext,
			})
		end
	end
	return initial
end

local dropdownmenuframe = CreateFrame("Frame", "OuroLootDropDownMenu", nil, "UIDropDownMenuTemplate")


-- Tab 1:  Events Of Interest
-- This actually takes up quite a bit of the file.
eoi_dropdownfuncs = dropdownfuncs{
	df_INSERT = function(rowi,text)
		local which = (text == 'loot') and "OUROL_EOI_INSERT_LOOT" or "OUROL_EOI_INSERT"
		local dialog = StaticPopup_Show(which,text)
		dialog.editBox:SetScript("OnTextChanged",StaticPopup_EditBoxOnTextChanged)
		dialog.data = {rowindex=rowi, display=_d, kind=text}
	end,

	df_DELETE = function(rowi)
		local gone = tremove (g_loot, rowi)
		addon:Fire ('DelEOIEntry', gone)
		addon:Print("Removed %s.",
			gone.itemlink or gone.bossname or gone.startday.text)
		if gone.kind == 'loot' then
			addon:Fire ('DelLootEntry', gone)
			if IsShiftKeyDown() then
				local okay,err = addon:_delHistoryEntry (gone)
				if okay then
					addon:Print("Removed history entry %s from %s.",
						gone.itemlink, addon:colorize(gone.person,gone.person_class))
				else
					addon:Print(err)
				end
			end
		end
	end,

	["Delete remaining entries for this day"] = function(rowi,kind)
		-- if kind is boss, also need to stop at new timestamp
		local fencepost = addon._find_timeboss_fencepost (kind, rowi)
		local count = fencepost and (fencepost-rowi) or (#g_loot-rowi+1)
		repeat
			eoi_dropdownfuncs.df_DELETE(rowi)
			count = count - 1
		until count < 1
	end,

	["Rebroadcast this loot entry"] = function(rowi)
		local e = g_loot[rowi]
		-- This only works because GetItemInfo accepts multiple argument formats
		addon:vbroadcast('loot', e.person, e.unique, e.itemlink, e.count, e.cols[3].value)
		addon:Print("Rebroadcast entry", rowi, e.itemlink)
	end,

	["Rebroadcast this boss"] = function(rowi,kind)
		-- if kind is boss, also need to stop at new timestamp
		local fencepost = addon._find_timeboss_fencepost (kind, rowi) or #g_loot
		-- this could be a lot of traffic, but frankly it's counterproductive
		-- to try to micromanage when ChatThrottleLib is already doing so
		repeat
			local e = g_loot[rowi]
			if e.kind == 'boss' then
				addon:vbroadcast('boss', e.reason, e.bossname, e.instance)
			elseif e.kind == 'loot' then
				-- This only works because GetItemInfo accepts multiple argument formats
				addon:vbroadcast('loot', e.person, e.unique, e.itemlink, e.count, e.cols[3].value)
			end
			addon:Print("Rebroadcast entry", rowi, e.itemlink or e.bossname or UNKNOWN)
			rowi = rowi + 1
		until rowi >= fencepost
	end,

	["Mark as normal"] = function(rowi,disp)
		addon:loot_mark_disposition ("local", rowi, disp)
	end,

	["Show only this player"] = function(rowi)
		local st = assert(gui.eoiST)
		_d:SetUserData("player filter name", g_loot[rowi].person)
		st:SetFilter(_d:GetUserData("player filter by name"))
		_d:GetUserData("eoi_filter_reset"):SetDisabled(false)
		-- it'd be more futureproof to get the button and call some kind
		-- of :GetText() on it, but no such function is provided by acegui
		setstatus[[Use the "Reset Player Filter" button in the lower-right to return to normal.]]
	end,

	["Change from 'wipe' to 'kill'"] = function(rowi)
		addon:_mark_boss_kill(rowi)
		-- the fillout function called automatically will start too far down the list
		gui.eoiST:OuroLoot_Refresh()
	end,

	["Edit note"] = function(rowi)
		eoi_editcell (rowi, _d:GetUserData("DD cell"))
	end,

	df_REASSIGN = function(rowi,to_whom)
		addon:reassign_loot ("local", rowi, to_whom)
		CloseDropDownMenus()  -- also need to close parent menu
	end,
	["Enter name..."] = function(rowi)
		CloseDropDownMenus()  -- also need to close parent menu
		local dialog = StaticPopup_Show "OUROL_REASSIGN_ENTER"
		dialog.data = {index=rowi, display=_d}
	end,
}
-- Would be better to move the %arg to this list rather than below, but
-- that's a lot of extra effort that doesn't buy much in return.
eoi_dropdownfuncs["Delete this loot event"] = eoi_dropdownfuncs.df_DELETE
eoi_dropdownfuncs["Delete this boss event"] = eoi_dropdownfuncs.df_DELETE
eoi_dropdownfuncs["Insert new loot entry"] = eoi_dropdownfuncs.df_INSERT
eoi_dropdownfuncs["Insert new boss kill event"] = eoi_dropdownfuncs.df_INSERT
eoi_dropdownfuncs["Mark as disenchanted"] = eoi_dropdownfuncs["Mark as normal"]
eoi_dropdownfuncs["Mark as guild vault"] = eoi_dropdownfuncs["Mark as normal"]
eoi_dropdownfuncs["Mark as offspec"] = eoi_dropdownfuncs["Mark as normal"]
eoi_dropdownfuncs["Delete remaining entries for this boss"] =
	eoi_dropdownfuncs["Delete remaining entries for this day"]
eoi_dropdownfuncs["Rebroadcast this day"] = eoi_dropdownfuncs["Rebroadcast this boss"]
local eoi_time_dropdown = gen_easymenu_table(
	{{
		-- this is the dropdown title, text filled in on the fly
		isTitle = true,
		notClickable = true,
		notCheckable = true,
	}},
	{
		"Rebroadcast this day%time|Broadcasts everything from here down until a new day.",
		"Delete remaining entries for this day%time|Erases everything from here down until a new day.\n\nHold down the Shift key to also delete the corresponding entries from player History.",
		"Insert new loot entry%loot|Inserts new loot above this one, prompting you for information.",
		"Insert new boss kill event%boss|Inserts new event above this one, prompting you for information.",
		CLOSE
	}, eoi_dropdownfuncs)
local eoi_loot_dropdown = gen_easymenu_table(
	{{
		-- this is the dropdown title, text filled in on the fly
		notClickable = true,
		notCheckable = true,
	}},
	{
		"Mark as disenchanted%shard",
		"Mark as offspec%offspec",
		"Mark as guild vault%gvault",
		"Mark as normal|This is the default.  Selecting any 'Mark as <x>' action blanks out extra notes about who broadcast this entry, etc.",
		"--",
		"Rebroadcast this loot entry|Sends this loot event, including special notes, as if it just happened.",
		"Delete this loot event|Permanent, no going back!\n\nHold down the Shift key to also delete the corresponding entry from player's History.",
		"Delete remaining entries for this boss%boss|Erases everything from here down until a new boss/day.\n\nHold down the Shift key to also delete the corresponding entries from player History.",
		"Insert new loot entry%loot|Inserts new loot above this one, prompting you for information.",
		"Insert new boss kill event%boss|Inserts new event above this one, prompting you for information.",
		"Edit note|Same as double-clicking in the notes column.",
		"--",
		CLOSE
	}, eoi_dropdownfuncs)
local eoi_player_dropdown = gen_easymenu_table(
	{
		{
			-- this is the dropdown title, text filled in on the fly
			isTitle = true,
			notClickable = true,
			notCheckable = true,
		},
		{
			text = "Reassign to...",
			hasArrow = true,
			--menuList = filled in in the fly,
			tooltipOnButton = true,
			tooltipWhileDisabled = true,
		},
	},
	{
		"Show only this player",
		CLOSE
	}, eoi_dropdownfuncs)
local eoi_boss_dropdown = gen_easymenu_table(
	{{
		-- this is the dropdown title, text filled in on the fly
		isTitle = true,
		notClickable = true,
		notCheckable = true,
	}},
	{
		"Change from 'wipe' to 'kill'|Also collapses previous wipe entries.", -- KILLWIPE
		"Rebroadcast this boss%boss|Broadcasts the kill event and all subsequent loot until next boss.",
		"Delete this boss event|Permanent, no going back!",
		"Delete remaining entries for this boss%boss|Erases everything from here down until a new boss/day.\n\nHold down the Shift key to also delete the corresponding entries from player History.",
		"Insert new loot entry%loot|Inserts new loot above this one, prompting you for information.",
		"Insert new boss kill event%boss|Inserts new event above this one, prompting you for information.",
		"--",
		CLOSE
	}, eoi_dropdownfuncs)

--[[ quoted verbatim from lib-st docs (table->stable for obvious reasons):
rowFrame This is the UI Frame table for the row.
cellFrame This is the UI Frame table for the cell in the row.
data This is the data table supplied to the scrolling table (in case you lost it :) )
cols This is the cols table supplied to the scrolling table (again, in case you lost it :) )
row This is the number of the UI row that the event was triggered for.<br/> ex. If your scrolling table only shows ten rows, this number will be a number between 1 and 10.
realrow This is the exact row index (after sorting and filtering) in the data table of what data is displayed in the row you triggered the event in. (NOT the UI row!)
column This is the index of which column the event was triggered in.
stable This is a reference to the scrollingtable table.
...  Any arguments generated by the '''NORMAL''' Blizzard event triggered by the frame are passed as is.
]]
local function eoi_st_OnEnter (rowFrame, cellFrame, data, cols, row, realrow, column, stable, motion)
	if (row == nil) or (realrow == nil) then return end  -- mouseover column header
	local e = data[realrow]
	if e == nil then return end  -- something horrible has happened
	local kind = e.kind
	local tt = GameTooltip   -- can this be hoisted? does GT ever get securely replaced?

	if gui._do_debugging_tooltip and column == 1 and kind ~= 'hist' then
		_build_debugging_tooltip (cellFrame, realrow)
	end
	if (kind == 'loot' and column == 1) or (kind == 'hist' and column == 2) then
		tt:SetOwner (cellFrame, "ANCHOR_RIGHT", -20, 0)
		if e.cache_miss then
			tt:ClearLines()
			tt:AddLine("Missing Cache Data")
			tt:AddLine([[Wait a few seconds, then type]], 0.8, 0.8, 0.8, 1)
			tt:AddLine([[/ouroloot fix cache]], 0, 1, 64/255, nil)
			tt:AddLine([[and redisplay this window.]], 0.8, 0.8, 0.8, 1)
			tt:Show()
		elseif e.itemlink then
			tt:SetHyperlink (e.itemlink)
		end

	elseif kind == 'loot' and column == 2 then
		tt:SetOwner (cellFrame, "ANCHOR_BOTTOMRIGHT", -50, 5)
		tt:ClearLines()
		tt:AddLine(e.person.." Loot:")
		local counter = 0
		for i,e2 in ipairs(data) do
			if e2.person == e.person then  -- would be awesome to test for alts
				if counter > 10 then
					tt:AddLine("...")
					break
				else
					-- textures screw up too badly, strip them
					local textured = e2.cols[1].value
					local space = textured:find(" ")
					tt:AddLine(textured:sub(space+1))
					counter = counter + 1
				end
			end
		end
		tt:Show()

	elseif kind == 'loot' and column == 3 then
		setstatus(e.cols[column].value)

	end

	return false  -- continue with default highlighting behavior
end
local function eoi_st_OnLeave (rowFrame, cellFrame, data, cols, row, realrow, column, stable, motion)
	GameTooltip:Hide()
	_hide_debugging_tooltip()
	if (row == nil) or (realrow == nil) then return false end

	if stable:GetSelection() ~= realrow then
		if data[realrow].kind ~= 'loot' then
			stable:SetHighLightColor (rowFrame, eoi_st_otherrow_bgcolortable[data[realrow].reason or data[realrow].kind])
			return true   -- do not do anything further
		else
			return false  -- continue with default un-highlighting behavior
		end
	else
		return true   -- do not do anything further
	end
end

local function eoi_st_OnClick (rowFrame, cellFrame, data, cols, row, realrow, column, stable, button, down)
	if (row == nil) or (realrow == nil) then return true end  -- click column header, suppress reordering
	local e = data[realrow]
	local kind = e.kind

	-- Check for shift-clicking a loot line
	if IsModifiedClick("CHATLINK") and kind == 'loot' and column == 1
	then
		ChatEdit_InsertLink (e.itemlink)
		return true  -- do not do anything further
	end

	-- Zap any jump-to-line highlighting
	stable:ClearSelection()

	-- Remaining actions are all right-click
	if button ~= "RightButton" then return true end
	_d:SetUserData("DD index", realrow)

	if kind == 'loot' and (column == 1 or column == 3) then
		_d:SetUserData("DD cell", cellFrame)
		eoi_loot_dropdown[1].text = e.itemlink
		EasyMenu (eoi_loot_dropdown, dropdownmenuframe, cellFrame, 0, 0, "MENU")

	elseif kind == 'loot' and column == 2 then
		eoi_player_dropdown[1].text = e.person
		local raiders = {}
		for i = 1, GetNumRaidMembers() do
			tinsert (raiders, (GetRaidRosterInfo(i)))
		end
		table.sort(raiders)
		for i = 1, #raiders do
			local name = raiders[i]
			raiders[i] = {
				text = name,
				func = dropdownmenu_handler,
				arg1 = eoi_dropdownfuncs.df_REASSIGN,
				arg2 = name,
				notCheckable = true,
			}
		end
		eoi_player_dropdown[2].menuList =
			gen_easymenu_table (raiders, {"Enter name...",CLOSE}, eoi_dropdownfuncs)
		if e.disposition == 'shard' or e.disposition == 'gvault' then
			eoi_player_dropdown[2].disabled = true
			eoi_player_dropdown[2].tooltipTitle = "Cannot Reassign"
			eoi_player_dropdown[2].tooltipText = "You must first mark this item as 'normal' or 'offspec' before reassignment."
		else
			eoi_player_dropdown[2].disabled = nil
			eoi_player_dropdown[2].tooltipTitle = nil
			eoi_player_dropdown[2].tooltipText = nil
		end
		EasyMenu (eoi_player_dropdown, dropdownmenuframe, cellFrame, 0, 0, "MENU")

	elseif kind == 'boss' then
		eoi_boss_dropdown[1].text = e.bossname
		-- KILLWIPE:  update '2' if this is not the 2nd entry in eoi_boss_dropdown
		eoi_boss_dropdown[2].tooltipWhileDisabled = nil
		eoi_boss_dropdown[2].disabled = e.reason ~= 'wipe' and true or nil
		EasyMenu (eoi_boss_dropdown, dropdownmenuframe, cellFrame, 0, 0, "MENU")

	elseif kind == 'time' then
		eoi_time_dropdown[1].text = e.startday.text
		EasyMenu (eoi_time_dropdown, dropdownmenuframe, cellFrame, 0, 0, "MENU")

	end

	return true  -- do not do anything further
end

function eoi_editcell (row_index, cell_frame)
	local e = g_loot[row_index]
	if not e then return end   -- how the hell could we get this far?
	local celldata = e.cols[3]
	local box = AceGUI:Create("EditBox")
	box:SetText(celldata.value)
	box:SetUserData("old show", box.editbox:GetScript("OnShow"))
	box:SetUserData("old escape", box.editbox:GetScript("OnEscapePressed"))
	box.editbox:SetScript("OnShow", box.editbox.SetFocus)
	box.editbox:SetScript("OnEscapePressed", function(_be)
		_be:ClearFocus()
		_be.obj:Release()
	end)
	box:SetCallback("OnEnterPressed", function(_b,event,value)
		e.extratext = value
		celldata.value = value
		e.bcast_from = nil  -- things get screwy if this field is still present. sigh.
		e.extratext_byhand = true
		value = value and value:match("^(x%d+)")
		if value then e.count = value end
		_b:Release()
		return gui.eoiST:OuroLoot_Refresh(row_index)
	end)
	box:SetCallback("OnRelease", function(_b)
		_b.editbox:ClearFocus()
		_b.editbox:SetScript("OnShow", _b:GetUserData("old show"))
		_b.editbox:SetScript("OnEscapePressed", _b:GetUserData("old escape"))
		setstatus("")
	end)
	box.frame:SetAllPoints(cell_frame)
	box.frame:SetParent(cell_frame)
	box.frame:SetFrameLevel(cell_frame:GetFrameLevel()+1)
	box.frame:Show()
	setstatus("Press Enter or click Okay to accept changes, or press Escape to cancel them.")
end

local function eoi_st_OnDoubleClick (rowFrame, cellFrame, data, cols, row, realrow, column, stable, button)
	if (row == nil) or (realrow == nil) then return true end  -- they clicked on column header, suppress reordering
	local e = data[realrow]
	local kind = e.kind

	--_d:SetUserData("DD index", realrow)
	if kind == 'loot' and column == 3 and button == "LeftButton" then
		eoi_editcell (realrow, cellFrame)
	end

	return true  -- do not do anything further
end

-- Used for anything not overridden elsewhere.
local function eoi_st_default_DoCellUpdate (rowFrame, cellFrame, data, cols, row, realrow, column, fShow, stable)
	if not fShow then
		cellFrame.text:SetText("")
		if cellFrame.icontexture then
			cellFrame.icontexture:Hide()
		end
		return
	end

	local e = data[realrow]
	local cell = e.cols[column]

	cellFrame.text:SetText(cell.value)
	-- subset of what the default ST's docellupdate looks for
	local color = cols[column].color and cols[column].color(data,cols,realrow,column,stable)
	if color then
		cellFrame.text:SetTextColor(color.r,color.g,color.b,color.a)
	else
		cellFrame.text:SetTextColor(1,1,1,1)
	end

	if stable:GetSelection() ~= realrow then
		stable:SetHighLightColor (rowFrame, eoi_st_otherrow_bgcolortable[e.reason or e.kind or ""])
	else
		stable:SetHighLightColor (rowFrame, stable:GetDefaultHighlight())
	end
end

-- Used for EOI column 2 and Hist column 1.  Both are player name columns.
local function eoi_st_col2_DoCellUpdate (rowFrame, cellFrame, data, cols, row, realrow, column, fShow, stable)
	if not fShow then
		cellFrame.text:SetText("")
		if cellFrame.icontexture then
			cellFrame.icontexture:Hide()
		end
		return
	end

	local e = data[realrow]
	local cell = e.cols[column]

	cellFrame.text:SetText(cell.value)

	if e.person_class then
		local icon
		if cellFrame.icontexture then
			icon = cellFrame.icontexture
		else
			icon = cellFrame:CreateTexture(nil,"BACKGROUND")
			icon:SetPoint("LEFT", cellFrame, "LEFT")
			icon:SetHeight(eoi_st_rowheight-4)
			icon:SetWidth(eoi_st_rowheight-4)
			icon:SetTexture("Interface\\Glues\\CharacterCreate\\UI-CharacterCreate-Classes")
			cellFrame.icontexture = icon
		end
		icon:SetTexCoord(unpack(CLASS_ICON_TCOORDS[e.person_class]))
		icon:Show()
		cellFrame.text:SetPoint("LEFT", icon, "RIGHT", 1, 0)
		local color = addon.class_colors[e.person_class]
		cellFrame.text:SetTextColor(color.r,color.g,color.b,color.a)
	else
		if cellFrame.icontexture then
			cellFrame.icontexture:Hide()
			cellFrame.text:SetPoint("LEFT", cellFrame, "LEFT")
		end
		cellFrame.text:SetTextColor(1,1,1,1)
	end

	if stable:GetSelection() ~= realrow then
		stable:SetHighLightColor (rowFrame, eoi_st_otherrow_bgcolortable[e.reason or e.kind or ""])
	else
		stable:SetHighLightColor (rowFrame, stable:GetDefaultHighlight())
	end
end

local eoi_st_cols = {
	{  -- col 1
		name	= "Item",
		width	= 250,
	},
	{  -- col 2
		name	= "Player",
		width	= 130,
		DoCellUpdate = eoi_st_col2_DoCellUpdate,
	},
	{  -- col 3
		name	= "Notes",
		width	= 250,
		color	= eoi_st_lootrow_col3_colortable_func,
	},
}

local player_filter_all
local player_filter_by_name = function (st, e)
	if e.kind ~= 'loot' then return true end
	return e.person == _d:GetUserData("player filter name")
end

-- Tab 1:  Events Of Interest (implementation)
tabs_OnGroupSelected["eoi"] = function(ocontainer,specials)
	if (not addon.rebroadcast) and (not addon.enabled) and (#g_loot < 1) then
		addon.dprint('flow', "Nothing to show in first tab, skipping creation")
		return
	end

	-- The first time this function is called, we set up a persistent ST
	-- object and store it.  Any other delayed setup work is done, and then
	-- this function replaces itself with a smaller, sleeker, sexier one.
	-- This function will later be garbage collected.
	local ST = LibStub("ScrollingTable"):CreateST(eoi_st_cols,eoi_st_displayed_rows,eoi_st_rowheight)
	gui.eoiST = assert(ST)
	if addon.author_debug then
		_G.OLST = ST
	end

	ST.DoCellUpdate = eoi_st_default_DoCellUpdate
	if not eoi_st_otherrow_bgcolortable_default then
		eoi_st_otherrow_bgcolortable_default = ST:GetDefaultHighlightBlank()
		setmetatable(eoi_st_otherrow_bgcolortable, {__index = function (bg, key)
			return eoi_st_otherrow_bgcolortable_default
		end})
	end

	-- Calling SetData breaks (trying to call Refresh) if g_loot hasn't gone
	-- through this loop.
	addon:_fill_out_eoi_data(1)
	-- safety check  begin
	for i,e in ipairs(g_loot) do
		if type(e.cols) ~= 'table' then
			addon:Print("ARGH, index",i,"bad in eoi_OGS, type",type(e.cols),
				"entry kind", e.kind, "data", e.itemname or e.bossname or e.startday.text,
				"-- please take a screenshot and send to Farmbuyer@US-Kilrogg.")
			tabledump(e)
		end
	end
	-- safety check  end
	ST:SetData(g_loot)
	ST:EnableSelection(true)
	ST:RegisterEvents{
		OnEnter = eoi_st_OnEnter,
		OnLeave = eoi_st_OnLeave,
		OnClick = eoi_st_OnClick,
		OnDoubleClick = eoi_st_OnDoubleClick,
	}

	-- We want a single "update and redraw" function for the ST.  Also, the
	-- given refresh function is badly named and does nothing; the actual
	-- function is SortData (also badly named when no sorting is being done),
	-- which unconditionally calls the *hooked* Refresh.
	local oldrefresh = ST.Refresh
	ST.Refresh = function (self, opt_index)
		addon:_fill_out_eoi_data(opt_index)
		return oldrefresh(self)
	end
	ST.OuroLoot_Refresh = function (self, opt_index)
		addon:_fill_out_eoi_data(opt_index)
		-- safety check  begin
		for i,e in ipairs(g_loot) do
			if type(e.cols) ~= 'table' then
				addon:Print("ARGH, index",i,"bad in eoi refresh, refreshed at", opt_index, "type",type(e.cols),
					"entry kind", e.kind, "data", e.itemname or e.bossname or e.startday.text,
					"-- please take a screenshot and send to Farmbuyer@US-Kilrogg.")
				tabledump(e)
			end
		end
		-- safety check  end
		self:SortData()  -- calls hooked refresh
	end

	-- No need to keep creating function closures that all just "return true",
	-- instead we grab the one made inside lib-st.  There's no "get filter" API
	-- so we just reach inside.
	player_filter_all = ST.Filter

	-- Now set up the future drawing function...
	tabs_OnGroupSelected["eoi"] = function(container,specials)
		local st_widget = AceGUI:Create("lib-st")
		local st = assert(gui.eoiST)

		gui.which_ST = st

		-- This is actually required each time
		_d:SetUserData ("player filter clear", player_filter_all)
		_d:SetUserData ("player filter by name", player_filter_by_name)

		st:OuroLoot_Refresh()
		st_widget:WrapST(st)
		st_widget.head_offset = 15
		st_widget.tail_offset = 0

		if gui.opts.scroll_to_bottom then
			local scrollbar = _G[st.scrollframe:GetName().."ScrollBar"]
			if scrollbar then
				local _,max = scrollbar:GetMinMaxValues()
				scrollbar:SetValue(max)   -- also calls hooked Refresh
			end
		end

		container:SetLayout("Fill")
		container:AddChild(st_widget)

		local b
		--[===[ b = mkbutton("Generate Header",
			[[]])
		b:SetFullWidth(true)
		b:SetCallback("OnClick", function (_b)
		end)
		specials:AddChild(b) ]===]

		b = mkbutton('eoi_filter_reset', "Reset Player Filter",
			[[Return to showing complete loot information.]])
		b:SetFullWidth(true)
		b:SetCallback("OnClick", function (_b)
			gui.eoiST:SetFilter(player_filter_all)
			_b:SetDisabled(true)
		end)
		b:SetDisabled(st.Filter == player_filter_all)
		specials:AddChild(b)

		-- FIXME iterate over the new raiders table instead
		local people = { "<nobody>" }
		for i = 1, GetNumRaidMembers() do
			tinsert(people,(GetRaidRosterInfo(i)))
		end
		table.sort(people)
		local initial
		for i,n in ipairs(people) do
			if n == addon.sharder then initial = i end
		end
		b = mkbutton("Dropdown", nil, "",
			[[If set, items received by this person will be automatically marked as disenchanted.]])
		b:SetFullWidth(true)
		b:SetLabel("Auto-mark as shard:")
		b:SetList(people)
		b:SetValue(initial or 1)
		b:SetCallback("OnValueChanged", function(_dd,event,choice)
			addon.sharder = (choice ~= 1) and people[choice] or nil
		end)
		specials:AddChild(b)

		b = mkbutton('eoi_bcast_req', "Request B'casters",
			[[Sends out a request for others to enable loot rebroadcasting if they have not already done so.]])
		b:SetFullWidth(true)
		b:SetCallback("OnClick", function ()
			addon:Print("Sending request!")
			addon.requesting = true
			addon:broadcast('bcast_req')
		end)
		b:SetDisabled(not addon.enabled)
		specials:AddChild(b)
	end
	-- ...and call it.
	return tabs_OnGroupSelected["eoi"](ocontainer,specials)
end
noob_tips["eoi"] = _markup[[
<Shift-Left> while over an item link to paste it into chat.

<Right>-click any row to display a dropdown menu.  The menu is different for
the Player column than it is for the Item/Notes columns, and different for
loot entries than it is for other rows.

A normal click on a line will remove any highlighting from opening the
display from a chat link.]]
tabs_CLI_special["eoi"] = function (name_or_lineno)
	if type(name_or_lineno) == 'string' then
		-- uh
	elseif type(name_or_lineno) == 'number' then
		if name_or_lineno < 1 or name_or_lineno > #g_loot then
			return
		end
		local scrollhere = -9
		repeat
			scrollhere = scrollhere + 10
			gui.eoiST.offset = scrollhere
		until gui.eoiST:RowIsVisible(name_or_lineno)
		-- Value must be in pixels, not "how many rows"
		scrollhere = scrollhere * eoi_st_rowheight
		-- But not past the bottom, it looks ugly
		scrollhere = math.min (scrollhere,
			(#gui.eoiST.filtered - eoi_st_displayed_rows) * eoi_st_rowheight)
		gui.eoiST:SetSelection(name_or_lineno)
		local sf = gui.eoiST.scrollframe
		sf:GetScript("OnVerticalScroll")(sf,scrollhere)
	end
end


-- Tab 2/3 (generated text)
function tabs_generated_text_OGS (container, specials, text_kind)
	container:SetLayout("Fill")
	local box = AceGUI:Create("MultiLineEditBox")
	box:SetFullWidth(true)
	box:SetFullHeight(true)
	box:SetLabel("Pressing the Escape key while typing will return keystroke control to the usual chat window.")
	box:DisableButton(true)
	addon:_fill_out_eoi_data(1)

	-- Update the savedvar copy of the text before presenting it for editing,
	-- then save it again when editing finishes.  This way if the user goes
	-- offline while editing, at least the unedited version is saved instead
	-- of all the new text being lost entirely.  (Yes, it's happened.)
	--
	-- No good local-ish place to store the cursor position that will also
	-- survive the entire display being released.  Abuse the generated text
	-- cache for this purpose.
	local pos = text_kind.."_pos"
	if _generate_text(text_kind) then
		g_loot[text_kind] = g_loot[text_kind] .. g_generated[text_kind]
		g_generated[text_kind] = nil
	end
	box:SetText(g_loot[text_kind])
	box.editBox:SetCursorPosition(g_generated[pos] or 0)
	box.editBox:SetScript("OnShow", box.editBox.SetFocus)
	box:SetCallback("OnRelease", function(_box)
		box.editBox:ClearFocus()
		g_loot[text_kind] = _box:GetText()
		g_generated[pos] = _box.editBox:GetCursorPosition()
	end)
	container:AddChild(box)

	local w = mkbutton("Regenerate",
		[[+DISCARD> all text in this tab, and regenerate it from the current loot information.]])
	w:SetFullWidth(true)
	w:SetDisabled ((#g_loot == 0) and (box:GetText() == ""))
	w:SetCallback("OnClick", function(_w)
		box:SetText("")
		g_loot[text_kind] = ""
		g_loot.printed[text_kind] = 0
		g_generated.last_instance = nil
		g_generated[pos] = nil
		addon:Print("'%s' has been regenerated.", gui.tabtexts[text_kind].title)
		return addon:redisplay()
	end)
	specials:AddChild(w)
	_populate_text_specials (box, specials, mkbutton, text_kind)
end


-- Tab 4:  History
-- Much of the implementation here follows a similar desgin for the first
-- tab's handling of ST objects.  We will even reuse its controlling tables
-- when feasible.
local histST, hist_dropdownfuncs
local hist_normal_status =
	[[Click on a row to view all history for that player only.  (Click column headers to re-sort.)]]
local hist_name_status =
	[[Right-click on any row to return to normal history display.]]

local history_filter_by_recent = function (st, e)
	if e.kind ~= 'hist' then return true end
	return e.cols[2].OLi == 1
end

local history_filter_who
local history_filter_by_name = function (st, e)
	if e.kind ~= 'hist' then return true end
	return e.OLwho == history_filter_who
end

hist_dropdownfuncs = dropdownfuncs{
	["Delete this loot event from history"] = function()--rowi
		local h = _d:GetUserData("DD history entry")
		local numleft,err = addon:_delHistoryEntry (h.cols[2].OLu, h.itemlink)
		if numleft then
			addon:Print("Removed history entry %s from %s.",
				h.itemlink, addon:colorize(h.OLwho,h.OLclass))
			if numleft < 1 then
				history_filter_who = nil
				histST:SetFilter(history_filter_by_recent)
				setstatus(hist_normal_status)
			end
		else
			addon:Print(err)
		end
	end,

	["Delete this player's entire loot history"] = function()--rowi
		local h = _d:GetUserData("DD history entry")
		local name = h.OLwho
		local player_i = addon.history.byname[name]
		local gone = tremove (addon.history, player_i)
		assert(gone.name == name)
		addon:_build_history_names()
		addon:Print("Removed player %s from history (%d total entries).",
			addon:colorize(name,gone.person_class), #gone.unique)
	end,
}
local hist_general_dropdown = gen_easymenu_table(
	{{
		-- this is the dropdown title, text filled in on the fly
		isTitle = true,
		notClickable = true,
		notCheckable = true,
	}},
	{
		"Delete this player's entire loot history|Permanent, no going back!",
		"--",
		CLOSE
	}, hist_dropdownfuncs)
local hist_specific_dropdown = gen_easymenu_table(
	{{
		-- this is the dropdown title, text filled in on the fly
		notClickable = true,
		notCheckable = true,
	}},
	{
		"Delete this loot event from history|Permanent, no going back!",
		"--",
		CLOSE
	}, hist_dropdownfuncs)

-- Loot column
--[[
local function hist_st_col2_DoCellUpdate (rowFrame, cellFrame, data, cols, row, realrow, column, fShow, stable)
end]]

-- Formatted timestamp column
local function hist_st_col3_DoCellUpdate (rowFrame, cellFrame, data, cols, row, realrow, column, fShow, stable)
	if not fShow then
		cellFrame.text:SetText("")
		return
	end

	local h = data[realrow]
	local cell = h.cols[column]

	cellFrame.text:SetText(cell.value)
	cellFrame.text:SetTextColor(1,1,1,1)

	--stable:SetHighLightColor (rowFrame, eoi_st_otherrow_bgcolortable[h.kind])
	stable:SetHighLightColor (rowFrame, eoi_st_otherrow_bgcolortable_default)
end

local function hist_st_OnClick (rowFrame, cellFrame, data, cols, row, realrow, column, stable, button, down)
	if (row == nil) or (realrow == nil) then return false end  -- click column header, do default resorting
	local h = data[realrow]
	assert(h.kind=='hist')

	-- Four button combinations we need to care about:

	-- Shift-left pastes loot
	if IsModifiedClick("CHATLINK") and column == 2 then
		ChatEdit_InsertLink (h.itemlink)
		return true  -- do not do anything further
	end

	_d:SetUserData("DD index", realrow)
	_d:SetUserData("DD history entry", h)

	-- The rest depends on whether we're filtering (focused in on a specific
	-- player) or not.
	if history_filter_who then
		-- Shift-right opens a menu
		if IsShiftKeyDown() and button == "RightButton" then
			hist_specific_dropdown[1].text = h.itemlink
			EasyMenu (hist_specific_dropdown, dropdownmenuframe, cellFrame, 0, 0, "MENU")

		-- Right goes back to normal mode
		elseif button == "RightButton" then
			history_filter_who = nil
			stable:SetFilter(history_filter_by_recent)
			setstatus(hist_normal_status)
		end

	else  -- not focused
		-- Shift-right opens a menu
		if IsShiftKeyDown() and button == "RightButton" then
			hist_general_dropdown[1].text = h.OLwho
			EasyMenu (hist_general_dropdown, dropdownmenuframe, cellFrame, 0, 0, "MENU")

		-- Left focuses on a specific player
		elseif button == "LeftButton" then
			history_filter_who = h.OLwho
			stable:SetFilter(history_filter_by_name)
			setstatus(hist_name_status)
		end
	end

	return true  -- do not do anything further
end

--[[
local function hist_st_OnDoubleClick (rowFrame, cellFrame, data, cols, row, realrow, column, stable, button)
	if (row == nil) or (realrow == nil) then return true end  -- they clicked on column header, suppress reordering
	local h = data[realrow]
	assert(h.kind=='hist')

	return true  -- do not do anything further
end]]

local hist_st_cols = {
	{  -- col 1
		name	= "Player",
		width	= 130,
		DoCellUpdate = eoi_st_col2_DoCellUpdate,
	},
	{  -- col 2
		name	= "Most Recent Loot",
		width	= 250,
		--DoCellUpdate = hist_st_col2_DoCellUpdate,
	},
	{  -- col 3
		name	= "When",
		width	= 250,
		DoCellUpdate = hist_st_col3_DoCellUpdate,
		defaultsort = "asc",
		sort = "asc",
		sortnext = 1,
	},
}

-- Tab 4:  History (implementation)
tabs_OnGroupSelected["hist"] = function(container,specials)
	histST = LibStub("ScrollingTable"):CreateST(hist_st_cols,eoi_st_displayed_rows,eoi_st_rowheight)
	gui.histST = histST
	if addon.author_debug then
		_G.OLHST = histST
	end

	if not eoi_st_otherrow_bgcolortable_default then
		eoi_st_otherrow_bgcolortable_default = histST:GetDefaultHighlightBlank()
		setmetatable(eoi_st_otherrow_bgcolortable, {__index = function (bg, key)
			return eoi_st_otherrow_bgcolortable_default
		end})
	end

	addon:_build_history_names()
	addon:_fill_out_hist_data(1)
	histST:SetData(addon.history.st)
	histST:RegisterEvents{
		OnEnter = eoi_st_OnEnter,
		OnLeave = eoi_st_OnLeave,
		OnClick = hist_st_OnClick,
		--OnDoubleClick = hist_st_OnDoubleClick,
	}
	local oldrefresh = histST.Refresh
	histST.Refresh = function (self, opt_index)
		addon:_fill_out_hist_data(opt_index)
		return oldrefresh(self)
	end
	histST.OuroLoot_Refresh = function (self, opt_index)
		addon:_fill_out_hist_data(opt_index)
		self:SortData()  -- calls hooked refresh
	end

	histST:SetFilter(history_filter_by_recent)

	-- Zaps history for the given realm, or the current (current-playing
	-- realm, not currently-displayed realm) one if not specified.
	local function reset_current_realm (opt_realmname)
		local r = assert(opt_realmname or GetRealmName())
		-- new .history table:
		addon.history_all[r] = addon:_prep_new_history_category (nil, r)
		addon.history = addon.history_all[r]
		addon.hist_clean = nil
		-- new .history.st table:
		histST:OuroLoot_Refresh()
		histST:SetData(addon.history.st)
	end

	tabs_OnGroupSelected["hist"] = function(container,specials)
		local st_widget = AceGUI:Create("lib-st")
		gui.which_ST = histST
		histST:OuroLoot_Refresh()
		st_widget:WrapST(histST)
		st_widget.head_offset = 15
		st_widget.tail_offset = 0
		container:SetLayout("Fill")
		container:AddChild(st_widget)
		setstatus(hist_normal_status)

		local b
		do
			local realms,current = {},1
			for realmname,histtable in pairs(addon.history_all) do
				if type(histtable) == 'table' then
					tinsert(realms,realmname)
					if addon.history == histtable then current = #realms end
				end
			end
			b = mkbutton("Dropdown", nil, "", [[Which realm to display]])
			b:SetFullWidth(true)
			b:SetLabel()  -- required even when empty, see ace3 ticket #234
			b:SetList(realms)
			b:SetValue(current)
			b:SetCallback("OnValueChanged", function(_dd,event,choice)
				local r = realms[choice]
				addon.history = addon:_prep_new_history_category (addon.history_all[r], r)
				addon.hist_clean = nil
				histST:OuroLoot_Refresh()
				histST:SetData(addon.history.st)
				-- Reset filters to normal
				history_filter_who = nil
				histST:SetFilter(history_filter_by_recent)
				setstatus(hist_normal_status)
				return addon:redisplay()
			end)
			specials:AddChild(b)
		end

		--[[ b = AceGUI:Create("Spacer") b:SetFullWidth(true) b:SetHeight(10) specials:AddChild(b) ]]

		b = mkbutton("Regenerate",
			[[Erases all history entries from the displayed realm, and regenerates it from current loot information.]])
		b:SetFullWidth(true)
		b:SetDisabled (#addon.history == 0)
		b:SetCallback("OnClick", function(_b)
			local dialog = StaticPopup_Show("OUROL_HIST_REGEN", addon.history.realm)
			dialog.data = addon
			dialog.data2 = function(_addon)
				_addon:rewrite_history (_addon.history.realm)
				histST:OuroLoot_Refresh()
				histST:SetData(_addon.history.st)
			end
		end)
		specials:AddChild(b)

		b = mkbutton('hist_clear', "Clear Realm History",
			[[|cffff1010Erases absolutely all> history entries from the displayed realm.]])
		b:SetFullWidth(true)
		b:SetCallback("OnClick", function (_b)
			local dialog = StaticPopup_Show("OUROL_HIST_CLEAR", addon.history.realm)
			dialog.data = addon
			dialog.data2 = function(_addon)
				reset_current_realm(_addon.history.realm)
			end
		end)
		specials:AddChild(b)

		b = mkbutton('hist_clear_all', "Clear All History",
			[[|cffff1010Erases absolutely all> history entries from ALL realms.]])
		b:SetFullWidth(true)
		b:SetCallback("OnClick", function (_b)
			local dialog = StaticPopup_Show("OUROL_HIST_CLEAR", "ALL realms")
			dialog.data = addon
			dialog.data2 = function(_addon)
				_addon.history_all = {}
				reset_current_realm()
			end
		end)
		specials:AddChild(b)

		b = mkbutton('hist_clear_old', "Clear Older",
			[[Preserves only the latest loot entries for players on the displayed realm, removing all earlier ones.]])
		b:SetFullWidth(true)
		b:SetCallback("OnClick", function (_b)
			local dialog = StaticPopup_Show("OUROL_HIST_PREEN", '', addon.history.realm, addon)
			dialog.data = addon
			dialog.data2 = function (_addon, howmany)
				_addon:preen_history (_addon.history.realm, howmany)
				_addon.hist_clean = nil
				histST:OuroLoot_Refresh()
			end
		end)
		specials:AddChild(b)
	end
	return tabs_OnGroupSelected["hist"](container,specials)
end
noob_tips["hist"] = _markup[[
<Left>-click a row to see all history for that player.  <Right>-click any row
to return to showing all players.

<Shift-Left> while over an item link to paste it into chat.  <Shift-Right>
any row to display a dropdown menu.]]
-- '/ol hi pla' -> set filter on Playername
tabs_CLI_special["hist"] = function (name)
	name = '^'..name   -- already tolower'd by onslash
	for _,player in ipairs(addon.history) do
		if player.name:lower():find(name) then
			history_filter_who = player.name
			histST:SetFilter(history_filter_by_name)
			setstatus(hist_name_status)
			break
		end
	end
	-- If nothing found, reset to normal or just leave alone?
end


-- Tab 5:  Help (content in verbage.lua)


-- Tab 6:  Options (content in options.lua)


-- Simply to avoid recreating the same function over and over
local tabs_OnGroupSelected_func_args = { [2] = "OnGroupSelected" }
tabs_OnGroupSelected_func = function (tabs,event,group)
	tabs_OnGroupSelected_func_args[1] = tabs
	tabs_OnGroupSelected_func_args[3] = group
	gui.opts = addon.db.profile
	hide_noobtips_frame()
	tabs:ReleaseChildren()
	local spec = tabs:GetUserData("special buttons group")
	spec:ReleaseChildren()
	local h = AceGUI:Create("Heading")
	h:SetFullWidth(true)
	h:SetText(gui.tabtexts[group].title)
	spec:AddChild(h)
	do
		addon.sender_list.sort()
		local fmt = "Received broadcast data from %d |4player:players;."
		if addon.history_suppress then
			-- this is the druid class color reworked into hex
			fmt = fmt .. "  |cffff7d0aHistory recording suppressed.|r"
		end
		tabs.titletext:SetFormattedText (fmt, addon.sender_list.activeI)
	end
	local status,err = pcall (tabs_OnGroupSelected[group], tabs, spec, group)
	if not status then
		addon:horrible_horrible_error(err)
	end
	if gui.opts.gui_noob then
		local tip = noob_tips[group]
		if type(tip) == 'function' then
			tip = tip()
		end
		if type(tip) == 'string' and tip ~= "" then
			local w = get_noobtips_frame()
			w:SetParent (_d.content)
			w:ClearAllPoints()
			w:SetPoint("BOTTOMLEFT", _d.frame, "BOTTOMRIGHT", 3, 3)
			w:Show()
			w:DoTextWork(tip)
		end
	end
	--[====[
	Unfortunately, :GetHeight() called on anything useful out of a TabGroup
	returns the static default size (about 50 pixels) until the refresh
	cycle *after* all the frames are shown.  Trying to fix it up after a
	single OnUpdate doesn't work either.  So for now it's all hardcoded.
	
	Using this to determine the actual height of the usable area.  (Will
	error until an ST is shown, which only happens if it's tracking, etc.)
	416 pixels
	if group == "eoi" then
		local stframe = tabs.children[1].frame
		print(stframe:GetTop(),"-",stframe:GetBottom(),"=",
		      stframe:GetTop()-stframe:GetBottom())
		print(stframe:GetRight(),"-",stframe:GetLeft(),"=",
		      stframe:GetRight()-stframe:GetLeft())
	end
	]====]
end

--[[
mkbutton ("WidgetType", 'display key', "Text On Widget", "the mouseover display text")
mkbutton ( [Button]     'display key', "Text On Widget", "the mouseover display text")
mkbutton ( [Button]      [text]        "Text On Widget", "the mouseover display text")
]]
function mkbutton (opt_widget_type, opt_key, label, status)
	if not label then
		opt_widget_type, opt_key, label, status = "Button", opt_widget_type, opt_widget_type, opt_key
	elseif not status then
		opt_widget_type, opt_key, label, status = "Button", opt_widget_type, opt_key, label
	end
	local button = assert(AceGUI:Create(opt_widget_type))
	if button.SetText then button:SetText(tostring(label)) end
	status = _markup(status)
	button:SetCallback("OnEnter", function() setstatus(status) end) -- maybe factor that closure out
	button:SetCallback("OnLeave", statusy_OnLeave)
	-- retrieval key may be specified as nil if all the parameters are given
	if opt_key then _d:SetUserData (opt_key, button) end
	return button
end
gui.mkbutton = mkbutton

--[[
Creates the main window.
]]
function addon:BuildMainDisplay (opt_tabselect)
	if self.display then
		-- try to get everything to update, rebuild, refresh... ugh, no
		self.display:Hide()
	end
	if self.NOLOAD then
		-- don't even try
		return
	end

	-- This probably causes taint... hm.
	local prev_fade_time = UIDROPDOWNMENU_SHOW_TIME
	UIDROPDOWNMENU_SHOW_TIME = 4

	if dirty_tabs then
		-- pointers known to be good by now, pass them back in
		self:gui_init (g_loot, g_uniques)
		self:zero_printed_fenceposts()
	end
	gui.opts = self.db.profile

	local display = AceGUI:Create("Frame")
	_d = display
	self.display = display
	display:SetTitle(window_title)
	display:SetStatusText(self.status_text)
	display:SetLayout("Flow")
	display:SetStatusTable{width=900,height=550}   -- default height is 500
	display:EnableResize(false)
	display:SetUserData("GUI state",gui)
	display:SetCallback("OnClose", function(_display)
		UIDROPDOWNMENU_SHOW_TIME = prev_fade_time 
		hide_noobtips_frame()
		_d = nil
		self.display = nil
		AceGUI:Release(_display)
		flib.clear()
		collectgarbage()
	end)

	----- Right-hand panel
	local rhs_width = 0.20
	local control = AceGUI:Create("SimpleGroup")
	control:SetLayout("Flow")
	control:SetRelativeWidth(rhs_width)
	control.alignoffset = 25
	control:PauseLayout()
	local h,b

	--- Main ---
	h = AceGUI:Create("Heading")
	h:SetFullWidth(true)
	h:SetText("Main")
	control:AddChild(h)

	do
		b = mkbutton("Dropdown", nil, "",
			[[Enable full tracking, only rebroadcasting, or disable activity altogether.]])
		b:SetFullWidth(true)
		b:SetLabel("On/Off:")
		b:SetList{"Full Tracking", "Broadcasting", "Disabled"}
		b:SetValue(self.enabled and 1 or (self.rebroadcast and 2 or 3))
		b:SetCallback("OnValueChanged", function(_w,event,choice)
			if choice == 1 then       self:Activate()
			elseif choice == 2 then   self:Activate(nil,true)
			else                      self:Deactivate()
			end
			_w = display:GetUserData('comm_ident')
			if _w and _w:IsVisible() then
				_w:SetDisabled(self.enabled or self.rebroadcast)
			end
			_w = display:GetUserData('eoi_bcast_req')
			if _w and _w:IsVisible() then
				_w:SetDisabled(not self.enabled)
			end
		end)
		control:AddChild(b)
	end

	b = mkbutton("Dropdown", 'threshold', "",
		[[Items greater than or equal to this quality will be tracked/rebroadcast.]])
	b:SetFullWidth(true)
	b:SetLabel("Threshold:")
	b:SetList(self.thresholds)
	b:SetValue(self.threshold)
	b:SetCallback("OnValueChanged", function(_dd,event,choice)
		self:SetThreshold(choice)
	end)
	control:AddChild(b)

	b = mkbutton("Clear Loot",
		[[+Erases> all current loot information and generated text (but not saved texts).]])
	b:SetFullWidth(true)
	b:SetCallback("OnClick", function()
		StaticPopup_Show("OUROL_CLEAR").data = self
	end)
	control:AddChild(b)

	b = AceGUI:Create("Spacer")
	b:SetFullWidth(true)
	b:SetHeight(10)
	control:AddChild(b)

	--[[
	--- Saved Texts ---
	 [ Save Current As... ]
	   saved1
	   saved2
	   ...
	 [ Load ]  [ Delete ]
	]]
	h = AceGUI:Create("Heading")
	h:SetFullWidth(true)
	h:SetText("Saved Texts")
	control:AddChild(h)
	b = mkbutton("Save Current As...",
		[[Save forum/attendance/etc texts for later retrieval.  Main loot information not included.]])
	b:SetFullWidth(true)
	b:SetCallback("OnClick", function()
		StaticPopup_Show "OUROL_SAVE_SAVEAS"
		_d:Hide()
	end)
	control:AddChild(b)

	do
		local scontainer = AceGUI:Create("SimpleGroup")
		scontainer:SetFullWidth(true)
		scontainer:SetFullHeight(false)
		scontainer:SetAutoAdjustHeight(false)
		scontainer:SetHeight(40)  -- no relative height available anymore
		scontainer:SetLayout("Fill")
		local scroll = AceGUI:Create("ScrollFrame")
		scroll:SetLayout("List")
		local saved = self:check_saved_table(--[[silent_on_empty=]]true)
		if saved then for i,s in ipairs(saved) do
			local il = AceGUI:Create("InteractiveLabel")
			il:SetFullWidth(true)
			il:SetText(s.name)
			il:SetUserData("num",i)
			il:SetHighlight(1,1,1,0.4)
			local str = ("%s    %d entries     %s"):format(s.date,s.count,s.name)
			il:SetCallback("OnEnter", function() setstatus(str) end)
			il:SetCallback("OnLeave", statusy_OnLeave)
			il:SetCallback("OnClick", function(_il)
				local prev = _d:GetUserData("saved selection")
				if prev then
					prev.highlight:Hide()
					prev:SetColor()
				end
				_il:SetColor(0,1,0)
				_il.highlight:Show()
				_d:SetUserData("saved selection",_il)
				_d:GetUserData("Load"):SetDisabled(false)
				_d:GetUserData("Delete"):SetDisabled(false)
			end)
			scroll:AddChild(il)
		end end
		scontainer:AddChild(scroll)
		control:AddChild(scontainer)
	end

	b = mkbutton("Load",
		[[Load previously saved text.  +REPLACES> all current loot information!]])
	b:SetRelativeWidth(0.5)
	b:SetCallback("OnClick", function()
		local num = _d:GetUserData("saved selection"):GetUserData("num")
		self:save_restore(num)
		self:BuildMainDisplay()
	end)
	b:SetDisabled(true)
	control:AddChild(b)
	b = mkbutton("Delete",
		[[Delete previously saved text.]])
	b:SetRelativeWidth(0.5)
	b:SetCallback("OnClick", function()
		local num = _d:GetUserData("saved selection"):GetUserData("num")
		self:save_delete(num)
		self:BuildMainDisplay()
	end)
	b:SetDisabled(true)
	control:AddChild(b)

	b = AceGUI:Create("Spacer")
	b:SetFullWidth(true)
	b:SetHeight(10)
	control:AddChild(b)

	-- Other stuff on right-hand side
	local tab_specials = AceGUI:Create("SimpleGroup")
	tab_specials:SetLayout("Flow")
	tab_specials:SetFullWidth(true)
	control:AddChild(tab_specials)
	control:ResumeLayout()

	----- Left-hand group
	local tabs = AceGUI:Create("TabGroup")
	tabs:SetLayout("Flow")
	tabs.alignoffset = 25
	local titletext_orig_fo = tabs.titletext:GetFontObject()
	tabs.titletext:SetFontObject(GameFontNormalSmall)
	tabs:SetCallback("OnRelease", function(_tabs)
		tabs.titletext:SetFontObject(titletext_orig_fo)
	end)
	tabs:SetRelativeWidth(0.99-rhs_width)
	tabs:SetFullHeight(true)
	tabs:SetTabs(tabgroup_tabs)
	tabs:SetCallback("OnGroupSelected", tabs_OnGroupSelected_func)
	tabs:SetCallback("OnTabEnter", function(_tabs,event,value,tab)
		setstatus(gui.tabtexts[value].desc)
	end)
	tabs:SetCallback("OnTabLeave", statusy_OnLeave)
	tabs:SetUserData("special buttons group",tab_specials)
	tabs:SelectTab((opt_tabselect and #opt_tabselect>0)
		and opt_tabselect or "eoi")

	display:AddChildren (tabs, control)
	display:ApplyStatus()

	display:Show() -- without this, only appears every *other* function call
	return display
end

-- Searches tab titles from left to right.
function addon:OpenMainDisplayToTab (text, opt_arg)
	text = '^'..text:lower()
	for _,tab in ipairs(gui.taborder) do
		local v = gui.tabtexts[tab]
		if v and v.title:lower():find(text) then
			self:BuildMainDisplay(tab)
			if opt_arg and tabs_CLI_special[tab] then
				tabs_CLI_special[tab](opt_arg)
			end
			return true
		end
	end
end

-- Essentially a re-click on the current tab (if the current tab were clickable).
function addon:redisplay ()
	tabs_OnGroupSelected_func (unpack(tabs_OnGroupSelected_func_args))
end

function addon:GoToLootLine (line)
	local lineno = tonumber(self.lootjumps[line])
	self:OpenMainDisplayToTab ("Loot", lineno)
end


------ Popup dialogs
local function build_my_slider_widget()
	local s = CreateFrame("Slider", "OuroLootSlider", nil, "OptionsSliderTemplate")
	s.text = OuroLootSliderText
	s.low = OuroLootSliderLow
	s.high = OuroLootSliderHigh
	s:SetScript("OnValueChanged", function (_s, value)
		_s.value = value  -- conveniently, this is already of numeric type
		--_s.text:SetText(tostring(value))
		if _s.DoOnValueChanged then
			_s:DoOnValueChanged()
		end
	end)
	build_my_slider_widget = nil
	return s
end

StaticPopupDialogs["OUROL_CLEAR"] = flib.StaticPopup{
	text = "Clear current loot information and text?",
	button1 = YES,
	button2 = NO,
	OnAccept = function (dialog, addon)
		addon:Clear(--[[verbose_p=]]true)
	end,
}

StaticPopupDialogs["OUROL_HIST_REGEN"] = flib.StaticPopup{
	-- Concatenate this once at load time.  There is no ITEM_QUALITY_LEGENDARY constant.
	text = "Erase all history entries from " .. ITEM_QUALITY_COLORS[5].hex
		.. "%s|r, and generate it anew from current loot?",
	button1 = YES,
	button2 = NO,
	OnAccept = function (dialog, addon, data2)
		data2(addon)
		addon:Print("%s history has been regenerated.", addon.history.realm)
		addon:redisplay()
	end,
}

StaticPopupDialogs["OUROL_HIST_CLEAR"] = flib.StaticPopup{
	-- Concatenate this once at load time.  There is no ITEM_QUALITY_LEGENDARY constant.
	text = "Erase all history entries from " .. ITEM_QUALITY_COLORS[5].hex .. "%s|r?",
	button1 = YES,
	button2 = NO,
	OnAccept = function (dialog, addon, data2)
		data2(addon)
		addon:Print("Stimpy, you eeediot, you've pushed the history erase button!")
		addon:redisplay()
	end,
}

StaticPopupDialogs["OUROL_HIST_PREEN"] = flib.StaticPopup{
	-- Concatenate this once at load time.  There is no ITEM_QUALITY_LEGENDARY constant.
	text = "This will erase all but the latest "
		.. ITEM_QUALITY_COLORS[ITEM_QUALITY_UNCOMMON].hex
		.. "%s|r for each player on "
		.. ITEM_QUALITY_COLORS[5].hex .. "%s|r.  " .. CONTINUE .. "?",
	button1 = YES,
	button2 = NO,
	OnShow = function (dialog, addon)
		local thistable = StaticPopupDialogs[dialog.which]
		-- StaticPopup_Resize does not take extraFrame into account, so we
		-- monkeypatch the sizing method that _Resize calls at the end.
		dialog.saved_setheight = dialog.SetHeight
		dialog.SetHeight = function (d, h)
			return d.saved_setheight(d,h+35)
		end
		dialog.extraFrame:ClearAllPoints()
		dialog.extraFrame:SetPoint("TOP", dialog.text, "BOTTOM")
		dialog.extraFrame:SetWidth(150)
		dialog.extraFrame:SetHeight(35)
		dialog.extraFrame:Show()
		local slider = _G.OuroLootSlider or build_my_slider_widget()
		slider.DoOnValueChanged = function(s)
			dialog.text:SetFormattedText (thistable.text,
				s.value == 1 and "single entry" or (s.value .. " entries"),
				addon.history.realm)
			StaticPopup_Resize (dialog, "OUROL_HIST_PREEN")
		end
		slider:SetOrientation('HORIZONTAL')
		slider:SetMinMaxValues(1,30)
		slider:SetValueStep(1)
		slider.low:SetText("1")
		slider.high:SetText("30")
		--slider.tooltipText = ???
		slider:SetParent(dialog.extraFrame)
		slider:ClearAllPoints()
		slider:SetPoint("TOPLEFT",dialog.extraFrame,"TOPLEFT",0, -15)
		slider:SetPoint("BOTTOMRIGHT",dialog.extraFrame,"BOTTOMRIGHT",0, 0)
		slider:Show()
		-- This causes OnValueChanged to fire, reformatting the text.  Except
		-- IF the slider has already been shown, and IF at the time it was hidden
		-- it had the same value here, THEN there is technically no "change"
		-- and no event is fired.  We work around this clever optimization by
		-- doing a pair of set's, forcing the last one to fire OVC.
		slider:SetValue(1)
		slider:SetValue(5)
	end,
	OnAccept = function (dialog, addon, callback)
		local howmany = assert(tonumber(_G.OuroLootSlider.value))
		callback (addon, howmany)
		addon:Print("All loot prior to the most recent %d |4entry:entries; has been erased.", howmany)
		addon:redisplay()
	end,
	OnHide = function (dialog, addon)
		dialog.SetHeight = nil
		dialog.saved_setheight = nil
		dialog.extraFrame:ClearAllPoints()
		_G.OuroLootSlider:Hide()  -- parent is hidden, why is this required?
		_G.OuroLootSlider:ClearAllPoints()
		_G.OuroLootSlider:SetParent(nil)
	end,
}

StaticPopupDialogs["OUROL_URL"] = { --flib.StaticPopup{
	text = "Use Control-C or equivalent to copy this URL to your system clipboard:",
	button1 = OKAY,
	timeout = 0,
	whileDead = true,
	hideOnEscape = true,
	enterClicksFirstButton = true,
	hasEditBox = true,
	editBoxWidth = 350,
	preferredIndex = 3,
	OnShow = function (dialog, url)
		dialog.editBox:SetText(url)
		dialog.editBox:SetFocus()
		dialog.editBox:HighlightText()
	end,
}

StaticPopupDialogs["OUROL_REMIND"] = flib.StaticPopup{
	text = "Do you wish to activate Ouro Loot?|n|n(Hit the Escape key to close this window without clicking; Enter is the same as Activate)",
	button1 = "Activate recording",  -- "accept", left
	button2 = "Broadcast Only",      -- "cancel", middle
	button3 = HELP_LABEL,            -- "alt", right
	OnAccept = function (dialog, addon)
		addon:Activate()
	end,
	noCancelOnEscape = true,
	OnCancel = function (dialog, addon)
		addon:Activate(nil,true)
	end,
	OnAlt = function (dialog, addon)
		-- hitting escape also calls this, but the 3rd arg would be "clicked"
		-- in both cases, not useful here.
		if MouseIsOver(dialog.button3) then
			-- they actually clicked the button (or at least the mouse was over "Help"
			-- when they hit escape... sigh)
			addon:BuildMainDisplay('help')
		else
			addon.popped = true
		end
	end,
}

-- Callback for each Next/Accept stage of inserting a new loot or boss row via
-- dropdown.  Thanks to noCancelOnReuse, each Show done here will technically
-- Hide and redisplay the same dialog, passing along the same 'data' structure
-- each time.  The topmost call to our OnAccept will then finish by hiding the
-- (very last) dialog.
--
-- This is really, really hideous to read.
local function eoi_st_insert_OnAccept_boss (dialog, data, data2)
	if data.all_done then
		-- It'll probably be the final entry in the table, but there might have
		-- been real loot happening while the user was clicking and typing.
		local boss_index = addon._addBossEntry{
			kind		= 'boss',
			bossname	= (gui.opts.snarky_boss and addon.boss_abbrev[data.name] or data.name) or data.name,
			reason		= 'kill',
			instance	= data.instance,
			duration	= 0,
			maxsize		= data.max_raid_size,
			raidersnap	= data.yes_snap or {},
		}
		local entry = tremove(g_loot,boss_index)
		tinsert(g_loot,data.rowindex,entry)
		addon:_mark_boss_kill(data.rowindex)
		gui.eoiST:OuroLoot_Refresh(data.rowindex)
		local jumpprefix = addon.chatprefix ("GoToLootLine", data.rowindex)
		dialog.data = nil   -- free up memory
		addon:PCFPrint (_G.DEFAULT_CHAT_FRAME, jumpprefix,
			"Inserted %s %s at entry %d.",
			data.kind, data.name, data.rowindex)
		return
	end

	-- third click
	if data.name and data.instance then
		data.all_done = true
		-- this is how we distinguish OnAccept from OnCancel ("clicked"); the
		-- 3rd param is handled all in StaticPopup_OnClick
		if data2 ~= 'clicked' then
			data.yes_snap = data.maybe_snap
		end
		return eoi_st_insert_OnAccept_boss (dialog, data)
	end

	local text = dialog.editBox:GetText():trim()

	-- second click
	if data.name and text then
		data.instance = text
		-- not "reusing" this dialog in the same sense as with loot
		dialog.data = nil
		dialog:Hide()
		local getsnap = StaticPopup_Show("OUROL_EOI_INSERT_INCLUDE_RAIDERSNAP")
		getsnap.data = data
		return true
	end

	-- first click
	if text then
		data.name = text
		local maybe_instance
		data.maybe_snap, data.max_raid_size, maybe_instance = addon:snapshot_raid()
		local getinstance = StaticPopup_Show("OUROL_EOI_INSERT","instance")
		getinstance.data = data
		getinstance.editBox:SetText(maybe_instance)
		-- This suppresses auto-hide (which would cause the getinstance dialog
		-- to go away), but only when mouse clicking.  OnEnter is on its own.
		return true
	end
end

local function eoi_st_insert_OnAccept_loot (dialog, data)
	if data.all_done then
		data.display:Hide()
		local loot_index = assert(addon:CHAT_MSG_LOOT ("manual", data.recipient, data.name, data.notes))
		local entry = tremove(g_loot,loot_index)
		tinsert(g_loot,data.rowindex,entry)
		addon:_fill_out_eoi_data(data.rowindex)
		addon:BuildMainDisplay()
		local clicky = _new_rebroadcast_hyperlink (entry.unique)
		local jumpprefix = addon.chatprefix ("GoToLootLine", data.rowindex)
		dialog.data = nil
		addon:PCFPrint (_G.DEFAULT_CHAT_FRAME, jumpprefix,
			"Inserted %s %s at entry %d.  %s",
			data.kind, data.name, data.rowindex, tostring(clicky))
		return
	end

	local text = dialog.editBox:GetText():trim()

	-- third click
	if data.name and data.recipient and text then
		data.notes = (text ~= "<none>") and text or nil
		data.all_done = true
		return eoi_st_insert_OnAccept_loot (dialog, data)
	end

	-- second click
	if data.name and text then
		data.recipient = text
		local getnotes = StaticPopup_Show("OUROL_EOI_INSERT","notes")
		getnotes.data = data
		getnotes.editBox:SetText("<none>")
		getnotes.editBox:HighlightText()
		return true
	end

	-- first click
	if text then
		data.name = text
		dialog:Hide()  -- technically a "different" one about to be shown
		StaticPopupDialogs["OUROL_EOI_INSERT"].autoCompleteParams =
			AUTOCOMPLETE_LIST_TEMPLATES[IsInRaid() and "IN_GROUP" or "IN_GUILD"]
		local getrecipient = StaticPopup_Show("OUROL_EOI_INSERT","recipient")
		StaticPopupDialogs["OUROL_EOI_INSERT"].autoCompleteParams = nil
		getrecipient.data = data
		getrecipient.editBox:SetText("")
		return true
	end
end

local function eoi_st_insert_OnAccept (dialog, data)
	if data.kind == 'boss' then
		return eoi_st_insert_OnAccept_boss (dialog, data)
	elseif data.kind == 'loot' then
		return eoi_st_insert_OnAccept_loot (dialog, data)
	end
end

-- The data member here is a table built with:
-- {rowindex=<GUI row receiving click>, display=_d, kind=<loot/boss>}
do
	local t = flib.StaticPopup{
		text = "Enter name of new %s, then click "..CONTINUE.." or press Enter:",
		button1 = CONTINUE.." ->",
		button2 = CANCEL,
		hasEditBox = true,
		editBoxWidth = 350,
		maxLetters = 50,
		noCancelOnReuse = true,
	}
	t.EditBoxOnEnterPressed = function(editbox)
		if editbox:GetText() == "" then return end
		local dialog = editbox:GetParent()
		if not eoi_st_insert_OnAccept (dialog, dialog.data) then
			dialog:Hide()  -- replicate OnAccept click behavior
		end
	end
	t.enterClicksFirstButton = nil  -- no effect with editbox focused
	t.OnAccept = eoi_st_insert_OnAccept
	StaticPopupDialogs["OUROL_EOI_INSERT"] = t

	-- This seems to be gratuitous use of metatables, really.
	local OEIL = {
		text = "Paste the new item into here, then click "..CONTINUE.." or press Enter:",
		__index = StaticPopupDialogs["OUROL_EOI_INSERT"]
	}
	StaticPopupDialogs["OUROL_EOI_INSERT_LOOT"] = setmetatable(OEIL,OEIL)

	hooksecurefunc("ChatEdit_InsertLink", function (link,...)
		local dialogname = StaticPopup_Visible "OUROL_EOI_INSERT_LOOT"
		if dialogname then
			_G[dialogname.."EditBox"]:SetText(link)
			return true
		end
	end)

	t = flib.StaticPopup{
	-- Concatenate this once at load time.  There is no ITEM_QUALITY_LEGENDARY constant.
		text = "Include a snapshot of the " .. ITEM_QUALITY_COLORS[5].hex
			.. "CURRENT|r raid?|n|nClicking '" .. YES .. "' will allow this entry to "
			.. "appear in attendance lists, but with the roster as it is NOW, not as it "
			.. "was THEN.  Clicking '" .. NO .."' means this kill cannot be included in "
			.. "attendance.|n|n(Enter = '" .. YES .."', Escape = '" .. CANCEL .. "')",
		button1 = YES,     -- "accept", left
		button2 = NO,      -- "cancel", middle
		button3 = CANCEL,  -- "alt", right
	}
	-- Hitting Escape still hides the frame, but doesn't run OnCancel (which
	-- is for the "No" button, not the "Cancel"/OnAlt button).  Dizzy yet?
	t.noCancelOnEscape = true
	t.OnAccept = eoi_st_insert_OnAccept_boss
	t.OnCancel = eoi_st_insert_OnAccept_boss
	StaticPopupDialogs["OUROL_EOI_INSERT_INCLUDE_RAIDERSNAP"] = t
end

StaticPopupDialogs["OUROL_REASSIGN_ENTER"] = flib.StaticPopup{
	text = "Enter the player name:",
	button1 = ACCEPT,
	button2 = CANCEL,
	hasEditBox = true,
	OnAccept = function(dialog, data)
		local name = dialog.usertext --editBox:GetText()
		addon:reassign_loot ("local", data.index, name)
		gui.eoiST:OuroLoot_Refresh(data.index)
	end,
}

StaticPopupDialogs["OUROL_SAVE_SAVEAS"] = flib.StaticPopup{
	text = "Enter a name for the loot collection:",
	button1 = ACCEPT,
	button2 = CANCEL,
	hasEditBox = true,
	maxLetters = 30,
	OnAccept = function(dialog)--, data)
		local name = dialog.usertext --editBox:GetText()
		addon:save_saveas(name)
		addon:BuildMainDisplay()
	end,
	OnCancel = function(dialog)--, data, reason)
		addon:BuildMainDisplay()
	end,
}


-- Workaround this bug:  http://us.battle.net/wow/en/forum/topic/3278901991
if true then
	-- Verbatim copy of UIDropDownMenuTemplates.xml:155 or so, except as
	-- tagged with CHANGE.
	local function onenter (self, motion)
		if ( self.hasArrow ) then
			local level =  self:GetParent():GetID() + 1;
			local listFrame = _G["DropDownList"..level];
			if ( not listFrame or not listFrame:IsShown() or select(2, listFrame:GetPoint()) ~= self ) then
				ToggleDropDownMenu(self:GetParent():GetID() + 1, self.value, nil, nil, nil, nil, self.menuList, self);
			end
		else
			CloseDropDownMenus(self:GetParent():GetID() + 1);
		end
		_G[self:GetName().."Highlight"]:Show();
		UIDropDownMenu_StopCounting(self:GetParent());
		if ( self.tooltipTitle ) then
			if ( self.tooltipOnButton ) then
				GameTooltip:SetOwner(self, "ANCHOR_RIGHT");
				GameTooltip:AddLine(self.tooltipTitle, 1.0, 1.0, 1.0);
				GameTooltip:AddLine(self.tooltipText, nil,nil,nil,1);  -- CHANGE added nil->1 arguments
				GameTooltip:Show();
			else
				GameTooltip_AddNewbieTip(self, self.tooltipTitle, 1.0, 1.0, 1.0, self.tooltipText, 1);
			end
		end
	end
	-- end verbatime copy

	for i = 1, UIDROPDOWNMENU_MAXLEVELS do
		local list = _G["DropDownList"..i]
		if list then
			for j = 1, UIDROPDOWNMENU_MAXBUTTONS do
				local button = _G["DropDownList"..i.."Button"..j]
				if button then
					--print("button fixup",i,j)
					button:SetScript("OnEnter",onenter)
				end
			end
		end
	end
end

-- vim:noet