changeset 84:c87bf3e756f3

Properly handle dropdown menus for the History tab now, initially with delete-item and delete-player entries. Some code cleanup and notes for future work.
author Farmbuyer of US-Kilrogg <farmbuyer@gmail.com>
date Thu, 21 Jun 2012 02:17:26 +0000
parents 940e53dd18c3
children 4771ee8eaa81
files core.lua gui.lua
diffstat 2 files changed, 234 insertions(+), 162 deletions(-) [+]
line wrap: on
line diff
--- a/core.lua	Mon Jun 18 03:02:10 2012 +0000
+++ b/core.lua	Thu Jun 21 02:17:26 2012 +0000
@@ -820,7 +820,7 @@
 						e.unique, e.history_unique = e.history_unique, nil
 					end
 					if e.unique == nil or #e.unique == 0 then
-						e.unique = e.id .. ' ' .. date("%Y/%m/%d %H:%M",e.stamp)
+						e.unique = e.id .. e.person .. date("%Y/%m/%d %H:%M:%S",e.stamp)
 					end
 				end
 			end
@@ -2266,6 +2266,7 @@
 	--    overrides the hour and minute printed; if absent, those values are
 	--    taken from the DAY entry.
 	-- FORMAT_STRING may contain $x (x in Y/M/D/h/m) tokens.
+	-- FIXME this is horribabble
 	local format_timestamp_values, point2dee = {}, "%.2d"
 	function addon:format_timestamp (fmt_opt, day_entry, time_entry_opt)
 		if not time_entry_opt then
@@ -2465,7 +2466,11 @@
 			Detecting A is strictly an optimization.  We should be able to do
 			this code safely in all cases.
 			]]
-			if winning_index ~= 1 then
+			if
+--@debug@
+true or
+--@end-debug@
+				winning_index ~= 1 then
 				--XXX this branch still needs to be tested with live data
 				local cache = g_uniques:SEARCH(exist)
 				local looti,hi,ui = cache.loot, cache.history, cache.history_may
@@ -2872,7 +2877,7 @@
 
 		-- If any of these change, update the end of history_handle_disposition.
 		if (not e.unique) or (#e.unique==0) then
-			e.unique = e.id .. ' ' .. when
+			e.unique = e.id .. e.person .. when
 		end
 		local U = e.unique
 		tinsert (h.unique, 1, U)
@@ -3100,28 +3105,50 @@
 		return index
 	end
 
-	-- Similar to _addHistoryEntry.  The second arg may be a loot entry
-	-- (which used to be at LOOTINDEX), or nil (and the loot entry will
-	-- be pulled from LOOTINDEX instead).
-	function addon:_delHistoryEntry (lootindex, opt_e)
-		local e = opt_e or g_loot[lootindex]
-		if e.kind ~= 'loot' then return end
-
-		local from_i, from_h, hist_i = _history_by_loot_id (e, "delete")
+	local function expunge (player, index_or_unique)
+		local i,u
+		if type(index_or_unique) == 'string' then
+			for u = 1, #player.unique do
+				if player.unique[u] == index_or_unique then
+					i = u
+					break
+				end
+			end
+		elseif type(index_or_unique) == 'number' then
+			i = index_or_unique
+		end
+		if not i then
+			return -- error here?
+		end
+		u = player.unique[i]
+		assert(#u>0)
+		tremove (player.unique, i)
+		player.when[u], player.id[u], player.count[u] = nil, nil, nil
+		g_uniques[u] = nil
+		addon.hist_clean = nil
+	end
+
+	-- Mirror of _addHistoryEntry.  Arguments are either:
+	--   E          - loot entry
+	--   U,ITEM     - unique tag, and a name/itemlink for errors
+	-- Returns true on success.
+	function addon:_delHistoryEntry (first, second)
+		if type(first) == 'table' then
+			second = first.itemlink or second
+		--elseif type(first) == 'string' then
+		end
+
+		local from_i, from_h, hist_i = _history_by_loot_id (first, "delete")
+
 		if not from_i then
 			-- from_h is the formatted error text
-			self:Print(from_h .. "  Loot will be deleted, but history will NOT be updated.", e.itemlink)
-			return
+			return nil, (from_h
+			        .."  Loot will be deleted, but history will NOT be updated."
+			       ):format(second)
 		end
 
-		local hist_u = tremove (from_h.unique, hist_i)
-		from_h.when[hist_u] = nil
-		from_h.id[hist_u] = nil
-		from_h.count[hist_u] = nil
-		g_uniques[hist_u] = nil
-		self.hist_clean = nil
-
-		self:Print("Removed history entry %d/%s from '%s'.", lootindex, e.itemlink, e.person)
+		expunge (from_h, hist_i)
+		return true
 	end
 
 	-- Any extra work for the "Mark as <x>" dropdown actions.  The
@@ -3141,6 +3168,7 @@
 		if (newdisp == 'shard' or newdisp == 'gvault') then
 			local name_i, name_h, hist_i = _history_by_loot_id (e, "mark")
 			-- remove history entry if it exists
+			-- FIXME revist this and use expunge
 			if hist_i then
 				local c = flib.new()
 				local hist_u = tremove (name_h.unique, hist_i)
@@ -3174,14 +3202,14 @@
 			-- FIXME The deleted cache isn't nearly as useful now with the new data structures.
 			local when
 			if (not e.unique) or (#e.unique==0) then
-				when = g_today and self:format_timestamp (g_today, e) or date("%Y/%m/%d %H:%M",e.stamp)
-				e.unique = e.id .. ' ' .. when
+				when = g_today and self:format_timestamp (g_today, e) or date("%Y/%m/%d %H:%M:%S",e.stamp)
+				e.unique = e.id .. name .. when
 			end
 			local U = e.unique
 			local c = deleted_cache[U]
 			deleted_cache[U] = nil
 			name_h.unique[#name_h.unique+1] = U
-			name_h.when[U] = c and c.when or when or date("%Y/%m/%d %H:%M",e.stamp)
+			name_h.when[U] = c and c.when or when or date("%Y/%m/%d %H:%M:%S",e.stamp)
 			name_h.id[U] = e.id -- c.id
 			name_h.count[U] = c and c.count or e.count
 			sort_player(name_h)
--- a/gui.lua	Mon Jun 18 03:02:10 2012 +0000
+++ b/gui.lua	Thu Jun 21 02:17:26 2012 +0000
@@ -14,13 +14,14 @@
 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"]
---eoi_st_otherrow_bgcolortable["realm"] = eoi_st_otherrow_bgcolortable["time"]
 local eoi_st_otherrow_bgcolortable_default
 local eoi_st_lootrow_col3_colortable = {
 	normal	= { text = "",            r = "ff", g = "ff", b = "ff" },
@@ -153,7 +154,7 @@
 	local accumulator = {}
 
 	-- Can do clever things by passing other halting points as zero
-	function addon:zero_printed_fenceposts(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
@@ -175,7 +176,8 @@
 		dirty_tabs = true
 	end
 
-	-- Called by tabs_generated_text_OGS
+	-- 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
@@ -192,7 +194,6 @@
 		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))
-			return false  -- why is this here again?
 		end
 		if ret then
 			g_loot.printed[text_type] = #g_loot
@@ -204,7 +205,10 @@
 	function _populate_text_specials (editbox, specials, mkb, text_type)
 		local f = specials_gen_funcs[text_type]
 		if not f then return end
-		pcall (f, text_type, editbox, specials, mkb)
+		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.
@@ -289,11 +293,6 @@
 the backend data is changed on the fly.
 ]]
 do
-	local grammar = { -- not worth making a mt for this
-		[2] = "nd",
-		[3] = "rd",
-	}
-
 	function addon:_fill_out_eoi_data (opt_starting_index)
 		if #g_loot < 1 then
 			--pprint('_f_o_e_d', "#g_loot<1")
@@ -346,7 +345,8 @@
 					if e.attempts == 1 then
 						v = "one-shot"
 					else
-						v = ("kill on %d%s attempt"):format(e.attempts or 0, grammar[e.attempts] or "th")
+						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
@@ -422,6 +422,7 @@
 			for li,unique in ipairs(player.unique) do
 				local col2 = new()
 				col2.OLi   = li
+				col2.OLu   = unique
 				local col3 = new()
 				col3.value = player.when[unique]
 
@@ -439,9 +440,13 @@
 				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 = 'history'
+				st_entry.kind = 'hist'
 				st_entry.OLwho = player.name
 				st_entry.cols = dotcols
 				st_entry.itemlink = ilink  -- for onenter and onclick
@@ -712,11 +717,8 @@
 	["hist"] = {title=[[History]], desc=[[A short semi-permanent record]]},
 	["help"] = {title=[[Help]], desc=[[Instructions, reminders, and tips for non-obvious features]]},
 	["opt"] = {title=[[Options]], desc=[[Options for fine-tuning behavior]]},
-	--["adv"] = {title=[[Advanced]], desc=[[Debugging and testing]]},
 }
---if addon.author_debug then
 _taborder = { "eoi", "hist", "help", "opt" }
---else _taborder = { "eoi", "help", "opt" } end
 
 do
 	local next_insertion_position = 2   -- position in _taborder
@@ -779,7 +781,7 @@
 end
 
 --[[
-Dropdown menu handling
+Dropdown menu handling; this has grown in ungainly directions.
 ]]
 -- forward decls
 local eoi_editcell
@@ -798,10 +800,10 @@
 end
 
 local function dropdownmenu_handler (ddbutton, subfunc, arg)
-	local i = _d and _d.GetUserData and _d:GetUserData("DD loot index")
+	local i = _d and _d.GetUserData and _d:GetUserData("DD index")
 	if i then
 		subfunc(i,arg)
-		_d:GetUserData("eoiST"):OuroLoot_Refresh(i)
+		_d:GetUserData("which ST"):OuroLoot_Refresh(i)
 	end
 end
 
@@ -848,11 +850,17 @@
 	end,
 
 	df_DELETE = function(rowi)
-		local gone = tremove(g_loot,rowi)
+		local gone = tremove (g_loot, rowi)
 		addon:Print("Removed %s.",
 			gone.itemlink or gone.bossname or gone.startday.text)
 		if gone.kind == 'loot' and IsShiftKeyDown() then
-			addon:_delHistoryEntry (rowi, gone)
+			local okay,err = addon:_delHistoryEntry (gone)
+			if okay then
+				addon:Print("Removed history entry %s from '%s'.",
+					gone.itemlink, gone.person)
+			else
+				addon:Print(err)
+			end
 		end
 	end,
 
@@ -912,7 +920,7 @@
 	end,
 
 	["Edit note"] = function(rowi)
-		eoi_editcell (rowi, _d:GetUserData("DD loot cell"))
+		eoi_editcell (rowi, _d:GetUserData("DD cell"))
 	end,
 
 	df_REASSIGN = function(rowi,to_whom)
@@ -946,7 +954,7 @@
 	}},
 	{
 		"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 players' corresponding History entry.",
+		"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
@@ -964,8 +972,8 @@
 		"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 player's corresponding History entry.",
-		"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 players' corresponding History entry.",
+		"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.",
@@ -1003,14 +1011,14 @@
 		"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 player's corresponding History entry.",
+		"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:
+--[[ 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 :) )
@@ -1018,19 +1026,19 @@
 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.
-table This is a reference to the scrollingtable table.
+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, table, button, ...)
+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]
 	local kind = e.kind
 	local tt = GameTooltip   -- can this be hoisted? does GT ever get securely replaced?
 
-	if _do_debugging_tooltip and column == 1 and kind~='history' then -- FIXME history
+	if _do_debugging_tooltip and column == 1 and kind ~= 'hist' then
 		_build_debugging_tooltip (cellFrame, realrow)
 	end
-	if (kind == 'loot' and column == 1) or (kind == 'history' and column == 2) then
+	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()
@@ -1071,11 +1079,11 @@
 
 	return false  -- continue with default highlighting behavior
 end
-local function eoi_st_OnLeave (rowFrame, cellFrame, data, cols, row, realrow, column, table, button, ...)
+local function eoi_st_OnLeave (rowFrame, cellFrame, data, cols, row, realrow, column, stable, motion)
 	GameTooltip:Hide()
 	_hide_debugging_tooltip()
 	if row and realrow and data[realrow].kind ~= 'loot' then
-		table:SetHighLightColor (rowFrame, eoi_st_otherrow_bgcolortable[data[realrow].reason or data[realrow].kind])
+		stable:SetHighLightColor (rowFrame, eoi_st_otherrow_bgcolortable[data[realrow].reason or data[realrow].kind])
 		return true   -- do not do anything further
 	else
 		--setstatus("")
@@ -1083,14 +1091,13 @@
 	end
 end
 
-local function eoi_st_OnClick (rowFrame, cellFrame, data, cols, row, realrow, column, stable, button, ...)
+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) or (kind == 'history' and column == 2))
+	if IsModifiedClick("CHATLINK") and kind == 'loot' and column == 1
 	then
 		ChatEdit_InsertLink (e.itemlink)
 		return true  -- do not do anything further
@@ -1098,10 +1105,10 @@
 
 	-- Remaining actions are all right-click
 	if button ~= "RightButton" then return true end
-	_d:SetUserData("DD loot index", realrow)
+	_d:SetUserData("DD index", realrow)
 
 	if kind == 'loot' and (column == 1 or column == 3) then
-		_d:SetUserData("DD loot cell", cellFrame)
+		_d:SetUserData("DD cell", cellFrame)
 		eoi_loot_dropdown[1].text = e.itemlink
 		EasyMenu (eoi_loot_dropdown, dropdownmenuframe, cellFrame, 0, 0, "MENU")
 
@@ -1146,10 +1153,6 @@
 		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
@@ -1191,12 +1194,12 @@
 	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, ...)
+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 loot index", realrow)
+	--_d:SetUserData("DD index", realrow)
 	if kind == 'loot' and column == 3 and button == "LeftButton" then
 		eoi_editcell (realrow, cellFrame)
 	end
@@ -1205,7 +1208,7 @@
 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, ...)
+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
@@ -1247,7 +1250,7 @@
 	--if e.kind ~= 'loot' then
 		stable:SetHighLightColor (rowFrame, eoi_st_otherrow_bgcolortable[e.reason or e.kind or ""])
 	--else
-	--	table:SetHighLightColor (rowFrame, table:GetDefaultHighlightBlank())
+	--	stable:SetHighLightColor (rowFrame, table:GetDefaultHighlightBlank())
 	--end
 end
 
@@ -1353,6 +1356,8 @@
 		local st_widget = GUI:Create("lib-st")
 		local st = _d:GetUserData("eoiST")
 
+		_d:SetUserData("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)
@@ -1501,27 +1506,148 @@
 -- 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 history"] = function(rowi)
-		--local gone = tremove(g_loot,rowi)
-		--addon:Print("Removed %s.",
-			--gone.itemlink or gone.bossname or gone.startday.text)
+	["Delete this loot event from history"] = function(rowi)
+		local h = _d:GetUserData("DD history entry")
+		local okay,err = addon:_delHistoryEntry (h.cols[2].OLu, h.itemlink)
+		if okay then
+			addon:Print("Removed history entry %s from '%s'.",
+				h.itemlink, h.OLwho)
+		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).",
+			name, #gone.unique)
 	end,
 }
-local hist_dropdown = gen_easymenu_table(
+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 history|Permanent, no going back!",
-		--"Delete remaining entries for this boss%boss|Erases everything from here down until a new boss/day",
+		"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",
@@ -1531,7 +1657,7 @@
 	{  -- col 2
 		name	= "Most Recent Loot",
 		width	= 250,
-		DoCellUpdate = hist_st_col2_DoCellUpdate,
+		--DoCellUpdate = hist_st_col2_DoCellUpdate,
 	},
 	{  -- col 3
 		name	= "When",
@@ -1543,75 +1669,6 @@
 	},
 }
 
-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 ~= 'history' 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 ~= 'history' then return true end
-	return e.OLwho == history_filter_who
-end
-
--- Loot column
-local function hist_st_col2_DoCellUpdate (rowFrame, cellFrame, data, cols, row, realrow, column, fShow, stable, ...)
-	print("col2 DCU", realrow)
-end
-
--- Formatted timestamp column
-local function hist_st_col3_DoCellUpdate (rowFrame, cellFrame, data, cols, row, realrow, column, fShow, stable, ...)
-	print("col3 DCU", realrow)
-	if not fShow then
-		cellFrame.text:SetText("")
-		return
-	end
-
-	local d = data[realrow]
-	local cell = d.cols[column]
-
-	cellFrame.text:SetText(cell.value)
-	cellFrame.text:SetTextColor(1,1,1,1)
-
-	--if d.kind ~= 'loot' then
-		stable:SetHighLightColor (rowFrame, eoi_st_otherrow_bgcolortable[d.kind])
-	--else
-	--	table:SetHighLightColor (rowFrame, table:GetDefaultHighlightBlank())
-	--end
-end
-
-local function hist_st_OnClick (rowFrame, cellFrame, data, cols, row, realrow, column, stable, button, ...)
-	if (row == nil) or (realrow == nil) then return false end  -- click column header, do default resorting
-	local h = data[realrow]
-	local kind = h.kind
-
-	if history_filter_who and button == "RightButton" then  -- now filtering and wanting not to
-		history_filter_who = nil
-		stable:SetFilter(history_filter_by_recent)
-		setstatus(hist_normal_status)
-	elseif (not history_filter_who) and button == "LeftButton" then  -- not filtering and wanting to
-		history_filter_who = h.OLwho
-		stable:SetFilter(history_filter_by_name)
-		setstatus(hist_name_status)
-	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]
-	local kind = h.kind
-
-	return true  -- do not do anything further
-end]]
-
 -- Tab 4:  History (implementation)
 tabs_OnGroupSelected["hist"] = function(container,specials)
 	histST = LibStub("ScrollingTable"):CreateST(hist_st_cols,eoi_st_displayed_rows,eoi_st_rowheight)
@@ -1664,6 +1721,7 @@
 	tabs_OnGroupSelected["hist"] = function(container,specials)
 		local st_widget = GUI:Create("lib-st")
 		-- don't need _d:GetUserData("histST") here, as it's already a local
+		_d:SetUserData("which ST",histST)
 		histST:OuroLoot_Refresh()
 		st_widget:WrapST(histST)
 		st_widget.head_offset = 15
@@ -2180,7 +2238,6 @@
 			w = mkbutton("Chat Frame Numbers",
 				[[Print each chat window number in its own frame, for easy reference in the editing field.]])
 			w:SetFullWidth(true)
-			--w:SetDisabled(not opts.chatty_on_remote_changes)
 			w:SetCallback("OnClick", function()
 				for i = 1, NUM_CHAT_WINDOWS do
 					local cf = _G['ChatFrame'..i]
@@ -2445,7 +2502,8 @@
 	UIDROPDOWNMENU_SHOW_TIME = 4
 
 	if dirty_tabs then
-		self:gui_init (g_loot, g_uniques)  -- pointers known to be good by now
+		-- pointers known to be good by now, pass them back in
+		self:gui_init (g_loot, g_uniques)
 		self:zero_printed_fenceposts()
 	end
 
@@ -3011,11 +3069,6 @@
 	button1 = ACCEPT,
 	button2 = CANCEL,
 	hasEditBox = true,
-	--[[ XXX needed?
-	OnShow = function(dialog)
-		dialog.editBox:SetText("")
-		dialog.editBox:SetFocus()
-	end,]]
 	OnAccept = function(dialog, data)
 		local name = dialog.usertext --editBox:GetText()
 		addon:reassign_loot ("local", data.index, name)
@@ -3029,11 +3082,6 @@
 	button2 = CANCEL,
 	hasEditBox = true,
 	maxLetters = 30,
-	--[[ XXX
-	OnShow = function(dialog)
-		dialog.editBox:SetText("")
-		dialog.editBox:SetFocus()
-	end,]]
 	OnAccept = function(dialog)--, data)
 		local name = dialog.usertext --editBox:GetText()
 		addon:save_saveas(name)
@@ -3042,12 +3090,6 @@
 	OnCancel = function(dialog)--, data, reason)
 		addon:BuildMainDisplay()
 	end,
-	--[[XXX
-	EditBoxOnEnterPressed = function(editbox)
-		local dialog = editbox:GetParent()
-		StaticPopupDialogs["OUROL_SAVE_SAVEAS"].OnAccept (dialog, dialog.data)
-		dialog:Hide()
-	end,]]
 }