changeset 16:5ee4edd24c13

- new blizz methods for editboxes in dialog popups - initial code for dropdowns in history (not active yet) - hovering and shift-clicking to link out of history - proper confirmations for history rewriting - options checkboxes more grid-like - saved texts get a scrollbar instead of expanding indefinitely (duh) - rearranged savedvars a bit (and added transition code) - stores raider join/leave times and "demographic" info, all for MLEQDKP - minor bugfixes and tweaks
author Farmbuyer of US-Kilrogg <farmbuyer@gmail.com>
date Thu, 25 Aug 2011 00:45:31 +0000
parents d8fee518ce5d
children d929c40cdb09
files LibFarmbuyer.lua Ouro_Loot.toc abbreviations.lua core.lua gui.lua text_tabs.lua verbage.lua
diffstat 7 files changed, 298 insertions(+), 136 deletions(-) [+]
line wrap: on
line diff
--- a/LibFarmbuyer.lua	Sun Jul 17 17:40:00 2011 +0000
+++ b/LibFarmbuyer.lua	Thu Aug 25 00:45:31 2011 +0000
@@ -53,7 +53,7 @@
   Ditto for table recycling.
 ]]
 
-local MAJOR, MINOR = "LibFarmbuyer", 9
+local MAJOR, MINOR = "LibFarmbuyer", 11
 assert(LibStub,MAJOR.." requires LibStub")
 local lib = LibStub:NewLibrary(MAJOR, MINOR)
 if not lib then return end
@@ -157,18 +157,24 @@
 end
 local function OnShow_witheditbox (dialog, data)
 	local info = StaticPopupDialogs[dialog.which]
-	dialog[info.hasWideEditBox and "wideEditBox" or "editBox"]:SetFocus()
+	--dialog[info.hasWideEditBox and "wideEditBox" or "editBox"]:SetFocus()
+	dialog.editBox:SetFocus()
     if info.farm_OnShow then
         return info.farm_OnShow (dialog, data)
     end
 end
 local function OnAccept_witheditbox (dialog, data, data2)
 	local info = StaticPopupDialogs[dialog.which]
-	dialog.usertext = dialog[info.hasWideEditBox and "wideEditBox" or "editBox"]:GetText():trim()
+	--dialog.usertext = dialog[info.hasWideEditBox and "wideEditBox" or "editBox"]:GetText():trim()
+	dialog.usertext = dialog.editBox:GetText():trim()
     if info.farm_OnAccept then
         return info.farm_OnAccept (dialog, data, data2)
     end
 end
+local function OnHide_cleanup (dialog)
+	dialog.data = nil
+	dialog.data2 = nil
+end
 
 --[[
 	StaticPopup
@@ -189,6 +195,10 @@
         t.EditBoxOnEscapePressed = StaticPopup_EscapePressed
     end
 
+	if not t.OnHide then
+		t.OnHide = OnHide_cleanup
+	end
+
     t.timeout = 0
     t.whileDead = true
     t.hideOnEscape = true
@@ -208,6 +218,7 @@
 	["Bandwagon"] = true, ["Kilvin"] = true, ["Waterfaucet"] = true,
 	["Farmbuyer"] = true, ["Oxdeadbeef"] = true, ["Pointystick"] = true,
 	["Angryhobbit"] = true, ["Malrubius"] = true, ["Hemogoblin"] = true,
+	["Ossipago"] = true,
 })[UnitName("player")] then
 	lib.author_debug = true
 	_G.safeprint = lib.safeprint
--- a/Ouro_Loot.toc	Sun Jul 17 17:40:00 2011 +0000
+++ b/Ouro_Loot.toc	Thu Aug 25 00:45:31 2011 +0000
@@ -3,7 +3,7 @@
 ## Version: @project-version@
 ## Notes: Raid loot tracking and text generation
 ## Author: Farmbuyer of Kilrogg
-## SavedVariables: OuroLootSV, OuroLootSV_opts, OuroLootSV_hist
+## SavedVariables: OuroLootSV, OuroLootSV_saved, OuroLootSV_opts, OuroLootSV_hist
 ## OptionalDeps: Ace3, DBM-Core, lib-st, LibFarmbuyer
 
 #@no-lib-strip@
--- a/abbreviations.lua	Sun Jul 17 17:40:00 2011 +0000
+++ b/abbreviations.lua	Thu Aug 25 00:45:31 2011 +0000
@@ -33,6 +33,7 @@
 	["Shannox"] = "Shnox",
 	["Lord Rhyolith"] = "LEFT, LEFT, LEFT RIGHT LEFT",  -- left my wife and 49 kids, an old grey mare and a peanut stand
 	["Alysrazor"] = "Steppin' Razor",  -- how many people have read Neuromancer, hmmmm
+	["Majordomo Staghelm"] = "Mojododo",
 
 	-------- WotLK
 	-- ToC
--- a/core.lua	Sun Jul 17 17:40:00 2011 +0000
+++ b/core.lua	Thu Aug 25 00:45:31 2011 +0000
@@ -5,11 +5,7 @@
 etc); its named indices are:
 - forum:		saved text from forum markup window, default nil
 - attend:		saved text from raid attendence window, default nil
-- printed.FOO:	last index formatted into text window FOO, default 0
-- saved:		table of copies of saved texts, default nil; keys are numeric
-				indices of tables, subkeys of those are name/forum/attend/date
-- autoshard:	optional name of disenchanting player, default nil
-- threshold:	optional loot threshold, default nil
+- printed.FOO:	last loot index formatted into text window FOO, default 0
 
 Functions arranged like this, with these lables (for jumping to).  As a
 rule, member functions with UpperCamelCase names are called directly by
@@ -33,14 +29,21 @@
 After he retired, I began modifying the code.  Eventually I set aside the
 entire package and rewrote the loot tracker module from scratch.  Many of the
 variable/function naming conventions (sv_*, g_*, and family) stayed across the
-rewrite.  Some variables are needlessly initialized to nil just to look uniform.
+rewrite.
+
+Some variables are needlessly initialized to nil just to look uniform.
 
 ]==]
 
 ------ Saved variables
-OuroLootSV		= nil   -- possible copy of g_loot
-OuroLootSV_opts	= nil   -- same as option_defaults until changed
-OuroLootSV_hist	= nil
+OuroLootSV       = nil   -- possible copy of g_loot
+OuroLootSV_saved = nil   -- table of copies of saved texts, default nil; keys
+                         -- are numeric indices of tables, subkeys of those
+						 -- are name/forum/attend/date
+OuroLootSV_opts  = nil   -- same as option_defaults until changed
+                         -- autoshard:  optional name of disenchanting player, default nil
+                         -- threshold:  optional loot threshold, default nil
+OuroLootSV_hist  = nil
 
 
 ------ Constants
@@ -149,9 +152,9 @@
 ------ Globals
 local g_loot			= nil
 local g_restore_p		= nil
-local g_saved_tmp		= nil   -- restoring across a clear
 local g_wafer_thin		= nil   -- for prompting for additional rebroadcasters
 local g_today			= nil   -- "today" entry in g_loot
+local g_boss_signpost	= nil
 local opts				= nil
 
 local pairs, ipairs, tinsert, tremove, tonumber = pairs, ipairs, table.insert, table.remove, tonumber
@@ -329,6 +332,17 @@
 		opts.forum["Custom..."] = opts["forum_format"]
 		opts["forum_format"] = nil
 	end
+	if OuroLootSV then  -- may not be the same as testing g_restore_p soon
+		if OuroLootSV.saved then
+			OuroLootSV_saved = OuroLootSV.saved; OuroLootSV.saved = nil
+		end
+		if OuroLootSV.threshold then
+			opts.threshold = OuroLootSV.threshold; OuroLootSV.threshold = nil
+		end
+		if OuroLootSV.autoshard then
+			opts.autoshard = OuroLootSV.autoshard; OuroLootSV.autoshard = nil
+		end
+	end
 	-- get item filter table if needed
 	if opts.itemfilter == nil then
 		opts.itemfilter = addon.default_itemfilter
@@ -398,23 +412,30 @@
 function addon:_clear_SVs()
 	g_loot = {}  -- not saved, just fooling PLAYER_LOGOUT tests
 	OuroLootSV = nil
+	OuroLootSV_saved = nil
 	OuroLootSV_opts = nil
 	OuroLootSV_hist = nil
 	ReloadUI()
 end
 function addon:PLAYER_LOGOUT()
-	if (#g_loot > 0) or g_loot.saved
-		-- someday make this smarter
-	   or (g_loot.forum and g_loot.forum ~= "")
-	   or (g_loot.attend and g_loot.attend ~= "")
-	then
-		g_loot.autoshard = self.sharder
-		g_loot.threshold = self.threshold
+	self:UnregisterEvent("RAID_ROSTER_UPDATE")
+	self:UnregisterEvent("PLAYER_ENTERING_WORLD")
+
+	local worth_saving = #g_loot > 0 or next(g_loot.raiders)
+	if not worth_saving then for text in self:registered_textgen_iter() do
+		worth_saving = worth_saving or g_loot.printed[text] > 0
+	end end
+	if worth_saving then
+		opts.autoshard = self.sharder
+		opts.threshold = self.threshold
 		for i,e in ipairs(g_loot) do
 			e.cols = nil
 		end
 		OuroLootSV = g_loot
+	else
+		OuroLootSV = nil
 	end
+
 	for r,t in pairs(self.history_all) do if type(t) == 'table' then
 		if #t == 0 then
 			self.history_all[r] = nil
@@ -436,7 +457,8 @@
 	local R_ACTIVE, R_OFFLINE, R_LEFT = 1, 2, 3
 
 	local lastevent, now = 0, 0
-	local timer_handle
+	local redo_count = 0
+	local redo, timer_handle
 
 	function addon:CheckRoster (leaving_p, now_a)
 		if not g_loot.raiders then return end -- bad transition
@@ -444,6 +466,10 @@
 		now = now_a or time()
 
 		if leaving_p then
+			if timer_handle then
+				self:CancelTimer(timer_handle)
+				timer_handle = nil
+			end
 			for name,r in pairs(g_loot.raiders) do
 				r.leave = r.leave or now
 			end
@@ -457,7 +483,10 @@
 			end
 		end
 
-		local redo = false
+		if redo then
+			redo_count = redo_count + 1
+		end
+		redo = false
 		for i = 1, GetNumRaidMembers() do
 			local unit = 'raid'..i
 			local name = UnitName(unit)
@@ -486,21 +515,29 @@
 				redo = redo or r.needinfo
 			end
 		end
-		if redo then
-			timer_handle = self:ScheduleRepeatingTimer("RAID_ROSTER_UPDATE", 60)
-		elseif timer_handle then
-			self:CancelTimer(timer_handle)
-			timer_handle = nil
+		if redo then  -- XXX test redo_count here and eventually give up?
+			if not timer_handle then
+				timer_handle = self:ScheduleRepeatingTimer("RAID_ROSTER_UPDATE", 60)
+			end
+		else
+			redo_count = 0
+			if timer_handle then
+				self:CancelTimer(timer_handle)
+				timer_handle = nil
+			end
 		end
 	end
 
 	function addon:RAID_ROSTER_UPDATE (event)
 		if GetNumRaidMembers() == 0 then
+			-- Leaving a raid group.
 			-- Because of PLAYER_ENTERING_WORLD, this code also executes on load
 			-- screens while soloing and in regular groups.  Take care.
-			if self.enabled then
+			self.dprint('flow', "GetNumRaidMembers == 0")
+			if self.enabled and not self.debug.notraid then
+				self.dprint('flow', "enabled, leaving raid")
 				self.popped = nil
-				self:UnregisterEvent("CHAT_MSG_LOOT")
+				self:Deactivate()  -- self:UnregisterEvent("CHAT_MSG_LOOT")
 				self:CheckRoster(--[[leaving raid]]true)
 			end
 			return
@@ -521,6 +558,7 @@
 			-- hot code path, be careful
 
 			-- event registration from onload, joined a raid, maybe show popup
+			self.dprint('flow', "RRU check:", self.popped, opts.popup_on_join)
 			if (not self.popped) and opts.popup_on_join then
 				self.popped = StaticPopup_Show "OUROL_REMIND"
 				self.popped.data = self
@@ -737,22 +775,30 @@
 
 ------ On/off
 function addon:Activate (opt_threshold, opt_bcast_only)
+	self.dprint('flow', ":Activate is running")
 	self:RegisterEvent("RAID_ROSTER_UPDATE")
-	self:RegisterEvent("PLAYER_ENTERING_WORLD","RAID_ROSTER_UPDATE")
+	self:RegisterEvent("PLAYER_ENTERING_WORLD",
+		function() self:ScheduleTimer("RAID_ROSTER_UPDATE", 5, "PLAYER_ENTERING_WORLD") end)
 	self.popped = true
 	if GetNumRaidMembers() > 0 then
+		self.dprint('flow', ">:Activate calling RRU")
 		self:RAID_ROSTER_UPDATE("Activate")
 	elseif self.debug.notraid then
+		self.dprint('flow', ">:Activate registering loot and bossmods")
 		self:RegisterEvent("CHAT_MSG_LOOT")
 		_register_bossmod(self)
 	elseif g_restore_p then
 		g_restore_p = nil
-		if #g_loot == 0 then return end -- only saved texts, not worth verbage
+		self.popped = nil  -- get the reminder if later joining a raid
+		if #g_loot == 0 then
+			-- only generated text and raider join/leave data, not worth verbage
+			self.dprint('flow', ">:Activate restored generated texts, un-popping")
+			return
+		end
 		self:Print("Ouro Raid Loot restored previous data, but not in a raid",
 				"and 5-person mode not active.  |cffff0505NOT tracking loot|r;",
 				"use 'enable' to activate loot tracking, or 'clear' to erase",
 				"previous data, or 'help' to read about saved-texts commands.")
-		self.popped = nil  -- get the reminder if later joining a raid
 		return
 	end
 	self.rebroadcast = true  -- hardcode to true; this used to be more complicated
@@ -790,10 +836,9 @@
 	g_restore_p = nil
 	OuroLootSV = nil
 	self:_reset_timestamps()
-	g_saved_tmp = g_loot.saved
 	if verbose_p then
-		if (g_saved_tmp and #g_saved_tmp>0) then
-			self:Print("Current loot data cleared, %d saved sets remaining.", #g_saved_tmp)
+		if (OuroLootSV_saved and #OuroLootSV_saved>0) then
+			self:Print("Current loot data cleared, %d saved sets remaining.", #OuroLootSV_saved)
 		else
 			self:Print("Current loot data cleared.")
 		end
@@ -900,25 +945,26 @@
 end
 
 -- Called when first loading up, and then also when a 'clear' is being
--- performed.  If SV's are present then restore_p will be true.
+-- performed.  If SV's are present then g_restore_p will be true.
 function _init (self, possible_st)
 	self.dprint('flow',"_init running")
 	self.loot_clean = nil
 	self.hist_clean = nil
 	if g_restore_p then
 		g_loot = OuroLootSV
-		self.popped = true
+		self.popped = #g_loot > 0
 		self.dprint('flow', "restoring", #g_loot, "entries")
-		self:ScheduleTimer("Activate", 12, g_loot.threshold)
+		self:ScheduleTimer("Activate", 12, opts.threshold)
 		-- FIXME printed could be too large if entries were deleted, how much do we care?
-		self.sharder = g_loot.autoshard
+		self.sharder = opts.autoshard
 	else
 		g_loot = { printed = {}, raiders = {} }
-		g_loot.saved = g_saved_tmp; g_saved_tmp = nil	-- potentially restore across a clear
 	end
 
-	self.threshold = g_loot.threshold or self.threshold -- in the case of restoring but not tracking
+	self.threshold = opts.threshold or self.threshold -- in the case of restoring but not tracking
 	self:gui_init(g_loot)
+	opts.autoshard = nil
+	opts.threshold = nil
 
 	if g_restore_p then
 		self:zero_printed_fenceposts()                  -- g_loot.printed.* = previous/safe values
@@ -970,6 +1016,9 @@
 			end
 		end
 		bossi = addon._addLootEntry(boss)
+		--
+		bossi = addon._adjustBossOrder (bossi, g_boss_signpost)
+		g_boss_signpost = nil
 		addon.dprint('loot', "added entry", bossi)
 		if boss.reason == 'kill' then
 			addon:_mark_boss_kill (bossi)
@@ -998,6 +1047,7 @@
 				self.dprint('cache', "boss <",signature,"> already in cache, skipping")
 			else
 				self.recent_boss:add(signature)
+				g_boss_signpost = #g_loot + 1
 				-- Possible scenarios:  (1) we don't see a boss event at all (e.g., we're
 				-- outside the instance) and so this only happens once as a non-local event,
 				-- (2) we see a local event first and all non-local events are filtered
@@ -1025,7 +1075,8 @@
 	function addon:_mark_boss_kill (index)
 		local e = g_loot[index]
 		if not e.bosskill then
-			return self:Print("Something horribly wrong;", index, "is not a boss entry!")
+			self:Print("Something horribly wrong;", index, "is not a boss entry!")
+			return
 		end
 		if e.reason ~= 'wipe' then
 			-- enh, bail
@@ -1196,14 +1247,41 @@
 		g_loot[index] = e
 		return index
 	end
+
+	-- Problem:  (1) boss kill happens, (2) fast looting happens, (3) boss
+	-- cache cleanup fires.  Result:  loot shows up before boss kill entry.
+	-- Solution:  We need to shuffle the boss entry above any of the loot
+	-- from that boss.
+	function addon._adjustBossOrder (is, should_be)
+		--pprint('loot', is, should_be)
+		if is == should_be then --pprint('loot', "equal, yay")
+			return
+		end
+		if is < should_be then --pprint('loot', "...the hell? bailing")
+			return
+		end
+		if g_loot[should_be].kind == 'time' then
+			should_be = should_be + 1
+			if is == should_be then
+				--pprint('loot', "needed to mark day entry, otherwise equal, yay")
+				return
+			end
+		end
+
+		assert(g_loot[is].kind == 'boss')
+		local boss = tremove (g_loot, is)
+		--pprint('loot', "MOVING", boss.bosskill)
+		tinsert (g_loot, should_be, boss)
+		return should_be
+	end
 end
 
 
 ------ Saved texts
 function addon:check_saved_table(silent_p)
-	local s = g_loot.saved
+	local s = OuroLootSV_saved
 	if s and (#s > 0) then return s end
-	g_loot.saved = nil
+	OuroLootSV_saved = nil
 	if not silent_p then self:Print("There are no saved loot texts.") end
 end
 
@@ -1215,8 +1293,9 @@
 end
 
 function addon:save_saveas(name)
-	g_loot.saved = g_loot.saved or {}
-	local n = #(g_loot.saved) + 1
+	OuroLootSV_saved = OuroLootSV_saved or {}
+	local SV = OuroLootSV_saved
+	local n = #SV + 1
 	local save = {
 		name = name,
 		date = makedate(),
@@ -1226,7 +1305,7 @@
 		save[text] = g_loot[text]
 	end
 	self:Print("Saving current loot texts to #%d '%s'", n, name)
-	g_loot.saved[n] = save
+	SV[n] = save
 	return self:save_list()
 end
 
--- a/gui.lua	Sun Jul 17 17:40:00 2011 +0000
+++ b/gui.lua	Thu Aug 25 00:45:31 2011 +0000
@@ -11,7 +11,7 @@
 
 ------ Constants
 local eoi_st_rowheight			= 20
-local eoi_st_displayed_rows		= math.floor(366/eoi_st_rowheight)
+local eoi_st_displayed_rows		= math.floor(416/eoi_st_rowheight) --math.floor(366/eoi_st_rowheight)
 local eoi_st_textured_item_format = "|T%s:"..(eoi_st_rowheight-2).."|t %s[%s]|r%s"
 local eoi_st_otherrow_bgcolortable = {
 	wipe	= { ["r"] = 0.3, ["g"] = 0.3, ["b"] = 0.3},
@@ -214,7 +214,7 @@
 					if e.attempts == 1 then
 						v = "one-shot"
 					else
-						v = ("kill on %d%s attempt"):format(e.attempts, grammar[e.attempts] or "th")
+						v = ("kill on %d%s attempt"):format(e.attempts or 0, grammar[e.attempts] or "th")
 					end
 					v = ("%s (%d:%.2d)"):format(v, math.floor(e.duration/60), math.floor(e.duration%60))
 				elseif e.reason == 'wipe' then
@@ -311,6 +311,7 @@
 				st_entry.kind = 'history'
 				st_entry.OLwho = player.name
 				st_entry.cols = dotcols
+				st_entry.itemlink = ilink  -- for onenter and onclick
 				tinsert (st, st_entry)
 			end
 		end
@@ -414,7 +415,7 @@
 	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.wideEditBox:SetScript("OnTextChanged",StaticPopup_EditBoxOnTextChanged)
+		dialog.editBox:SetScript("OnTextChanged",StaticPopup_EditBoxOnTextChanged)
 		dialog.data = {rowindex=rowi, display=_d, kind=text}
 	end,
 
@@ -424,6 +425,12 @@
 			gone.itemlink or gone.bosskill or gone.startday.text)
 	end,
 
+	["Delete this history event"] = function(rowi)
+		--local gone = tremove(g_loot,rowi)
+		--addon:Print("Removed %s.",
+			--gone.itemlink or gone.bosskill or gone.startday.text)
+	end,
+
 	-- if kind is boss, also need to stop at new timestamp
 	["Delete remaining entries for this day"] = function(rowi,kind)
 		local fencepost
@@ -484,6 +491,7 @@
 	df_REASSIGN = function(rowi,to_whom)
 		g_loot[rowi].person = to_whom
 		g_loot[rowi].person_class = select(2,UnitClass(to_whom))
+		-- FIXME also update history
 		CloseDropDownMenus()  -- also need to close parent menu
 	end,
 	["Enter name..."] = function(rowi)
@@ -572,6 +580,18 @@
 		"--",
 		CLOSE
 	}, dropdownfuncs)
+local hist_dropdown = gen_easymenu_table(
+	{{
+		-- this is the dropdown title, text filled in on the fly
+		notClickable = true,
+		notCheckable = true,
+	}},
+	{
+		"Delete this history event|Permanent, no going back!",
+		--"Delete remaining entries for this boss%boss|Erases everything from here down until a new boss/day",
+		"--",
+		CLOSE
+	}, dropdownfuncs)
 
 --[[ quoted verbatim from lib-st docs:
 rowFrame This is the UI Frame table for the row.
@@ -589,9 +609,11 @@
 	local e = data[realrow]
 	local kind = e.kind
 
-	if kind == 'loot' and column == 1 then
-		GameTooltip:SetOwner (cellFrame, "ANCHOR_RIGHT", -20, 0)
-		GameTooltip:SetHyperlink (e.itemlink)
+	if (kind == 'loot' and column == 1) or (kind == 'history' and column == 2) then
+		if e.itemlink then
+			GameTooltip:SetOwner (cellFrame, "ANCHOR_RIGHT", -20, 0)
+			GameTooltip:SetHyperlink (e.itemlink)
+		end
 
 	elseif kind == 'loot' and column == 2 then
 		GameTooltip:SetOwner (cellFrame, "ANCHOR_BOTTOMRIGHT", -50, 5)
@@ -638,7 +660,9 @@
 	local kind = e.kind
 
 	-- Check for shift-clicking a loot line
-	if IsModifiedClick("CHATLINK") and kind == 'loot' and column == 1 then
+	if IsModifiedClick("CHATLINK") and
+	   ((kind == 'loot' and column == 1) or (kind == 'history' and column == 2))
+	then
 		ChatEdit_InsertLink (e.itemlink)
 		return true  -- do not do anything further
 	end
@@ -682,6 +706,10 @@
 		eoi_time_dropdown[1].text = e.startday.text
 		EasyMenu (eoi_time_dropdown, dropdownmenuframe, cellFrame, 0, 0, "MENU")
 
+	elseif kind == 'history' then  -- XXX need to move this into new onclick handler
+		_d:SetUserData("DD loot cell", cellFrame)
+		hist_dropdown[1].text = e.itemlink
+		EasyMenu (hist_dropdown, dropdownmenuframe, cellFrame, 0, 0, "MENU")
 	end
 
 	return true  -- do not do anything further
@@ -1194,23 +1222,27 @@
 			specials:AddChild(b)
 
 			b = mkbutton('hist_clear', "Clear Realm History",
-				[[No confirmation!  |cffff1010Erases absolutely all> history entries from the displayed realm.]])
+				[[|cffff1010Erases absolutely all> history entries from the displayed realm.]])
 			b:SetFullWidth(true)
 			b:SetCallback("OnClick", function (_b)
-				reset_current_realm(addon.history.realm)
-				addon:Print("Stimpy, you eeediot, you've pushed the history erase button!")
-				return addon:redisplay()
+				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",
-				[[No confirmation!  |cffff1010Erases absolutely all> history entries from ALL realms.]])
+				[[|cffff1010Erases absolutely all> history entries from ALL realms.]])
 			b:SetFullWidth(true)
 			b:SetCallback("OnClick", function (_b)
-				addon.history_all = {}
-				reset_current_realm()
-				addon:Print("Stimpy, you eeediot, you've pushed the history erase button!")
-				return addon:redisplay()
+				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)
 
@@ -1218,11 +1250,13 @@
 				[[Preserves only the latest loot entry for each player on the displayed realm, removing all earlier ones.]])
 			b:SetFullWidth(true)
 			b:SetCallback("OnClick", function (_b)
-				addon:preen_history(addon.history.realm)
-				addon:Print("All loot prior to the most recent entries has been erased.")
-				addon.hist_clean = nil
-				histST:OuroLoot_Refresh()
-				return addon:redisplay()
+				local dialog = StaticPopup_Show("OUROL_HIST_PREEN", addon.history.realm)
+				dialog.data = addon
+				dialog.data2 = function(_addon)
+					_addon:preen_history(_addon.history.realm)
+					_addon.hist_clean = nil
+					histST:OuroLoot_Refresh()
+				end
 			end)
 			specials:AddChild(b)
 		end
@@ -1462,32 +1496,32 @@
 		-- that turned out to look messy.  Now they're just a straight line for the most part.
 
 		-- reminder popup
-		w = mkoption ('popup_on_join', "Show reminder popup", 0.95,
+		w = mkoption ('popup_on_join', "Show reminder popup", 0.49,
 			[[When joining a raid and not already tracking, display a dialog asking for instructions.]])
 		grp:AddChild(w)
 
 		-- toggle scroll-to-bottom on first tab
-		w = mkoption('scroll_to_bottom', "Scroll to bottom when opening display", 0.95,
+		w = mkoption('scroll_to_bottom', "Scroll to bottom when opening display", 0.49,
 			[[Scroll to the bottom of the loot window (most recent entries) when displaying the GUI.]])
 		grp:AddChild(w)
 
 		-- /loot option
-		w = mkoption('register_slashloot', "Register /loot slash command on login", 0.95,
+		w = mkoption('register_slashloot', "Register /loot slash command on login", 0.49,
 			[[Register "/loot" as a slash command in addition to the normal "/ouroloot".  Relog to take effect.]])
 		grp:AddChild(w)
 
 		-- chatty mode
-		w = mkoption('chatty_on_kill', "Be chatty on boss kill", 0.95,
+		w = mkoption('chatty_on_kill', "Be chatty on boss kill", 0.49,
 			[[Print something to chat output when DBM tells Ouro Loot about a successful boss kill.]])
 		grp:AddChild(w)
 
 		-- less noise in main panel
-		w = mkoption('no_tracking_wipes', "Do not track wipes", 0.95,
+		w = mkoption('no_tracking_wipes', "Do not track wipes", 0.49,
 			[[Do not add 'wipe' entries on the main loot grid, or generate any text for them.]])
 		grp:AddChild(w)
 
 		-- cutesy abbrevs
-		w = mkoption('snarky_boss', "Use snarky boss names", 0.95,
+		w = mkoption('snarky_boss', "Use snarky boss names", 0.49,
 			[[Irreverent replacement names for boss events.]])
 		grp:AddChild(w)
 
@@ -1701,7 +1735,7 @@
 	display:SetTitle("Ouro Loot")
 	display:SetStatusText(self.status_text)
 	display:SetLayout("Flow")
-	display:SetStatusTable{width=900}
+	display:SetStatusTable{width=900,height=550}   -- default height is 500
 	-- prevent resizing, also see ace3 tickets 80 and 214
 	-- grrrr, no longer works after frame rewrite
 	--[[
@@ -1780,7 +1814,7 @@
 
 	b = GUI:Create("Spacer")
 	b:SetFullWidth(true)
-	b:SetHeight(15)
+	b:SetHeight(10)
 	control:AddChild(b)
 
 	--[[
@@ -1804,30 +1838,42 @@
 	end)
 	control:AddChild(b)
 
-	local saved = self:check_saved_table(--[[silent_on_empty=]]true)
-	if saved then for i,s in ipairs(saved) do
-		local il = GUI: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)
-		control:AddChild(il)
-	end end
+	do
+		local scontainer = GUI:Create("SimpleGroup")
+		scontainer:SetFullWidth(true)
+		scontainer:SetFullHeight(false)
+		scontainer:SetAutoAdjustHeight(false)
+		scontainer:SetHeight(40)  -- no relative height available anymore
+		scontainer:SetLayout("Fill")
+		local scroll = GUI: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 = GUI: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!]])
@@ -1852,7 +1898,7 @@
 
 	b = GUI:Create("Spacer")
 	b:SetFullWidth(true)
-	b:SetHeight(15)
+	b:SetHeight(10)
 	control:AddChild(b)
 
 	-- Other stuff on right-hand side
@@ -1930,7 +1976,7 @@
 		return
 	end
 
-	local text = dialog.wideEditBox:GetText()
+	local text = dialog.editBox:GetText()
 
 	-- second click
 	if data.name and text then
@@ -1945,7 +1991,7 @@
 		data.name = text
 		local getinstance = StaticPopup_Show("OUROL_EOI_INSERT","instance")
 		getinstance.data = data
-		getinstance.wideEditBox:SetText(addon.instance_tag())
+		getinstance.editBox:SetText(addon.instance_tag())
 		-- This suppresses auto-hide (which would case the getinstance dialog
 		-- to go away), but only when mouse clicking.  OnEnter is on its own.
 		return true
@@ -1969,7 +2015,7 @@
 		return
 	end
 
-	local text = dialog.wideEditBox:GetText():trim()
+	local text = dialog.editBox:GetText():trim()
 
 	-- third click
 	if data.name and data.recipient and text then
@@ -1983,8 +2029,8 @@
 		data.recipient = text
 		local getnotes = StaticPopup_Show("OUROL_EOI_INSERT","notes")
 		getnotes.data = data
-		getnotes.wideEditBox:SetText("<none>")
-		getnotes.wideEditBox:HighlightText()
+		getnotes.editBox:SetText("<none>")
+		getnotes.editBox:HighlightText()
 		return true
 	end
 
@@ -1994,7 +2040,7 @@
 		dialog:Hide()  -- technically a "different" one about to be shown
 		local getrecipient = StaticPopup_Show("OUROL_EOI_INSERT","recipient")
 		getrecipient.data = data
-		getrecipient.wideEditBox:SetText("")
+		getrecipient.editBox:SetText("")
 		return true
 	end
 end
@@ -2009,13 +2055,35 @@
 
 StaticPopupDialogs["OUROL_CLEAR"] = flib.StaticPopup{
 	text = "Clear current loot information and text?",
-	button1 = ACCEPT,
-	button2 = CANCEL,
+	button1 = YES,
+	button2 = NO,
 	OnAccept = function (dialog, addon)
 		addon:Clear(--[[verbose_p=]]true)
 	end,
 }
 
+StaticPopupDialogs["OUROL_HIST_CLEAR"] = flib.StaticPopup{
+	text = "Erase all history entries from %s?",
+	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{
+	text = "Erase all but the latest entry for players on %s?",
+	button1 = YES,
+	button2 = NO,
+	OnAccept = function (dialog, addon, data2)
+		data2(addon)
+		addon:Print("All loot prior to the most recent entries has been erased.")
+		addon:redisplay()
+	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)",
 	button1 = "Activate recording",  -- "accept", left
@@ -2050,7 +2118,7 @@
 		button1 = "Next >",
 		button2 = CANCEL,
 		hasEditBox = true,
-		hasWideEditBox = true,
+		editBoxWidth = 350,
 		maxLetters = 50,
 		noCancelOnReuse = true,
 		--[[ XXX still needed?
@@ -2081,7 +2149,7 @@
 	hooksecurefunc("ChatEdit_InsertLink", function (link,...)
 		local dialogname = StaticPopup_Visible "OUROL_EOI_INSERT_LOOT"
 		if dialogname then
-			_G[dialogname.."WideEditBox"]:SetText(link)
+			_G[dialogname.."EditBox"]:SetText(link)
 			return true
 		end
 	end)
@@ -2101,6 +2169,7 @@
 		local name = dialog.usertext --editBox:GetText()
 		g_loot[data.index].person = name
 		g_loot[data.index].person_class = select(2,UnitClass(name))
+		-- FIXME also update history
 		addon:Print("Reassigned entry %d to '%s'.", data.index, name)
 		data.display:GetUserData("eoiST"):OuroLoot_Refresh(data.index)
 	end,
--- a/text_tabs.lua	Sun Jul 17 17:40:00 2011 +0000
+++ b/text_tabs.lua	Thu Aug 25 00:45:31 2011 +0000
@@ -163,7 +163,7 @@
 		end
 		table.sort(raiders)
 		local h, m = GetGameTime()
-		local additional = ("Attendance at %s:%s:\n%s"):format(h,m,table.concat(raiders, ", "))
+		local additional = ("Attendance at %.2d:%.2d:\n%s"):format(h,m,table.concat(raiders, ", "))
 		editbox:SetText(editbox:GetText() .. '\n' .. additional)
 	end)
 	container:AddChild(w)
--- a/verbage.lua	Sun Jul 17 17:40:00 2011 +0000
+++ b/verbage.lua	Thu Aug 25 00:45:31 2011 +0000
@@ -11,7 +11,7 @@
 - rebroadcasting entire boss sections, entire days.  maybe only whisper
 to specific people rather than broadcast.
 
-- signpost a potential boss kill, pipeline loot until the cache clears
+- [DONE] signpost a potential boss kill, pipeline loot until the cache clears
 
 - Being able to drag rows up and down the main loot grid would be awesome.  Coding
 that would be likely to drive me batshiat insane.
@@ -123,6 +123,10 @@
 The first tab on the left side, <Loot>, is where everything goes to and comes
 from.  Looting events and Deadly Boss Mods notifications go to the <Loot> tab; the
 other tabs are all generated from the information in the <Loot> tab.
+
+|cffff335dNote about these Help pages:|r  The category "titles" on the left side
+have their own help text, in addition to the more specific entries in the expanded
+list.  Read those first before expanding the categories or you'll miss stuff.
 ]]
 
 T.basic_loot = [[
@@ -206,8 +210,7 @@
 
 The addon tries to be smart about logging on during a raid (due to a disconnect or
 relog).  If you log in, are already in a raid group, and loot has already been
-stored from tracking, it will re-enable itself automatically.  It will not (as of
-this writing) restore ancillary settings such as the tracking threshold.
+stored from tracking, it will re-enable itself automatically.
 
 The intent of the addon design is that, after the end of a raid, all the generated
 markup text is done, optionally saved (see "Generated Texts - Saved Texts"), and
@@ -298,8 +301,8 @@
 
 The [url] choice defaults to using Wowhead.  If you have the [item] extension
 for your BBcode installed, you can use either of those choices too.  The "by ID"
-variant is good for heroic ToC/ICC items that share names with nonheroic items,
-but is harder to read in the text tab.
+variant is good for heroic raid items that share names with nonheroic items,
+but the raw output is harder to read in the text tab.
 
 You can also specify a custom string.  Formatting is done with these replacements:
 
@@ -334,28 +337,25 @@
 ]]
 
 T.texts_saved = [[
-The contents of the <Forum Markup> and <Attendance> tabs can be saved, so that they
-will not be lost when you use the +Clear> button.
+The contents of the <Forum Markup>, <Attendance>, and other such tabs can be saved,
+so that they will not be lost when you use the +Clear> button.
 
 Do any edits you want to the generated text tabs, then click the +Save Current As...>
 button on the right-hand side.  Enter a short descriptive reminder (for example,
 "thursday hardmodes") in the popup dialog.  The texts will remain in their tabs,
 but clearing loot information will not lose them now.
 
-All saved texts are listed on the right-hand side.  There is no technical limit to
-the number of saved texts, but the graphical display will begin to overflow after
-about half a dozen saved sets.  (And I don't care.)
-
-Clicking a saved text name lets you +Load> or +Delete> that saved set.  The primary
-<Loot> tab is not saved and restored by this process, only the generated texts.
-This also means you cannot +Regenerate> the texts.
+All saved texts are listed on the right-hand side.  Clicking a saved text name
+lets you +Load> or +Delete> that saved set.  The primary <Loot> tab is not saved
+and restored by this process, only the generated texts.  This also means you cannot
++Regenerate> the texts.
 ]]
 
 T.history = [[
 The <History> tab maintains a list of all loot.  It is intended to help answer
 questions such as "When was the last time PlayerX won something?" and "How much stuff
 has PlayerY gotten lately?"  The history tab is, by design, not as configurable
-as the main <Loot> tab; entries cannot be edited, individually deleted, and so forth.
+as the main <Loot> tab; entries cannot be edited and so forth.
 
 Loot history is the only "live" data tab which persists across the +Clear Loot> command.
 For this reason, very little information is stored:  only the recipient, the item,
@@ -398,9 +398,10 @@
 ]]
 
 T.tips = [[
-Shift-clicking an item in the <Loot> display will paste it into an open chat editbox.
+Shift-clicking an item in the <Loot> or <History> display will paste it into an
+open chat editbox.
 
-The |cffff8000[Ouro Loot]|r "legendary item" link displayed at the start of all
+The |cffff8000[Ouro Loot]|r "legendary item" displayed at the start of all
 chat messages is a clickable link.  Clicking opens the main display.  An option
 on the <Options> tab will cause a message to be printed after a boss kill,
 mostly for lazy loot trackers who don't like typing slash commands to open windows.
@@ -421,6 +422,13 @@
 ]]
 
 T.tips_slashies = [[
+If you give an unrecognized argument to the </ouroloot> slash command, it will
+search the tab titles left to right for a title beginning with the same letters as
+the argument, and open the display to that tab.  For example, <"/loot a"> would
+open the <Attendance> tab, and <"/loot ad"> would open the <Advanced> tab.  If
+you had added a theoretical <EQDKP> tab, then <"/loot eq"> would be the fastest
+way to see it.
+
 The </ouroloot> command can take arguments to do things without going through
 the UI.  Parts given in *(angle brackets)* are required, parts in [square brackets]
 are optional:
@@ -449,13 +457,6 @@
 If you use the slash commands to enable tracking or set loot thresholds, you can
 give numbers or common names for the threshold.  For example, "0", "poor", "trash",
 "gray"/"grey" are all the same, "4", "epic", "purple" are the same, and so on.
-
-If you give an unrecognized argument to the </ouroloot> slash command, it will
-search the tab titles left to right for a title beginning with the same letters as
-the argument, and open the display to that tab.  For example, <"/loot a"> would
-open the <Attendance> tab, and <"/loot ad"> would open the <Advanced> tab.  If
-you had added a theoretical <EQDKP> tab, then <"/loot eq"> would be the fastest
-way to see it.
 ]]
 
 T.todo = [[