changeset 10:67b8537e8432

More work on ML/EQDKP generator and its spawned subprojects.
author Farmbuyer of US-Kilrogg <farmbuyer@gmail.com>
date Tue, 28 Jun 2011 07:36:26 +0000
parents 4fba9c6b5d3d
children 952c3ac0e783
files Ouro_Loot.toc core.lua gui.lua mleqdkp.lua
diffstat 4 files changed, 287 insertions(+), 58 deletions(-) [+]
line wrap: on
line diff
--- a/Ouro_Loot.toc	Fri Jun 17 20:30:46 2011 +0000
+++ b/Ouro_Loot.toc	Tue Jun 28 07:36:26 2011 +0000
@@ -1,4 +1,4 @@
-## Interface: 40000
+## Interface: 40100
 ## Title: Ouro Loot
 ## Version: @project-version@
 ## Notes: Raid loot tracking and text generation
--- a/core.lua	Fri Jun 17 20:30:46 2011 +0000
+++ b/core.lua	Tue Jun 28 07:36:26 2011 +0000
@@ -158,9 +158,8 @@
 local opts				= nil
 
 local pairs, ipairs, tinsert, tremove, tonumber = pairs, ipairs, table.insert, table.remove, tonumber
-
 local pprint, tabledump = addon.pprint, flib.tabledump
-
+local GetNumRaidMembers = GetNumRaidMembers
 -- En masse forward decls of symbols defined inside local blocks
 local _register_bossmod
 local makedate, create_new_cache, _init
@@ -251,10 +250,11 @@
 do
 	local caches = {}
 	local cleanup_group = AnimTimerFrame:CreateAnimationGroup()
+	local time = _G.time
 	cleanup_group:SetLooping("REPEAT")
 	cleanup_group:SetScript("OnLoop", function(cg)
 		addon.dprint('cache',"OnLoop firing")
-		local now = GetTime()
+		local now = time()
 		local alldone = true
 		-- this is ass-ugly
 		for _,c in ipairs(caches) do
@@ -275,7 +275,7 @@
 	end)
 
 	local function _add (cache, x)
-		tinsert(cache, {t=GetTime(),m=x})
+		tinsert(cache, {t=time(),m=x})
 		if not cleanup_group:IsPlaying() then
 			addon.dprint('cache', cache.name, "STARTING animation group")
 			cache.cleanup:SetDuration(2)  -- hmmm
@@ -355,8 +355,8 @@
 end
 
 function addon:OnEnable()
-	self:RegisterEvent "PLAYER_LOGOUT"
-	self:RegisterEvent "RAID_ROSTER_UPDATE"
+	self:RegisterEvent("PLAYER_LOGOUT")
+	self:RegisterEvent("RAID_ROSTER_UPDATE")
 
 	-- Cribbed from Talented.  I like the way jerry thinks: the first argument
 	-- can be a format spec for the remainder of the arguments.  (The new
@@ -401,6 +401,7 @@
 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
@@ -423,26 +424,113 @@
 	OuroLootSV_hist = self.history_all
 end
 
-function addon:RAID_ROSTER_UPDATE (event)
-	if GetNumRaidMembers() > 0 then
+do
+	local IsInInstance, UnitName, UnitIsConnected, UnitClass, UnitRace, UnitSex,
+				UnitLevel, UnitInRaid, UnitIsVisible, GetGuildInfo =
+	      IsInInstance, UnitName, UnitIsConnected, UnitClass, UnitRace, UnitSex,
+		  		UnitLevel, UnitInRaid, UnitIsVisible, GetGuildInfo
+	local time, difftime = time, difftime
+	local R_ACTIVE, R_OFFLINE, R_LEFT = 1, 2, 3
+
+	local lastevent, now = 0, 0
+	local timer_handle
+
+	function addon:CheckRoster (leaving_p, now_a)
+		if not g_loot.raiders then return end -- bad transition
+
+		now = now_a or time()
+
+		if leaving_p then
+			for name,r in pairs(g_loot.raiders) do
+				r.leave = r.leave or now
+			end
+			return
+		end
+
+		for name,r in pairs(g_loot.raiders) do
+			if r.online ~= R_LEFT and not UnitInRaid(name) then
+				r.online = R_LEFT
+				r.leave = now
+			end
+		end
+
+		local redo = false
+		for i = 1, GetNumRaidMembers() do
+			local unit = 'raid'..i
+			local name = UnitName(unit)
+			-- No, that's not my typo, it really is "uknownbeing" in Blizzard's code.
+			if name and name ~= UNKNOWN and name ~= UNKNOWNOBJECT and name ~= UKNOWNBEING then
+				if not g_loot.raiders[name] then
+					g_loot.raiders[name] = { needinfo=true }
+				end
+				local r = g_loot.raiders[name]
+				if r.needinfo and UnitIsVisible(unit) then
+					r.needinfo = nil
+					r.class    = select(2,UnitClass(unit))
+					r.race     = select(2,UnitRace(unit))
+					r.sex      = UnitSex(unit)
+					r.level    = UnitLevel(unit)
+					r.guild    = GetGuildInfo(unit)
+				end
+				local connected = UnitIsConnected(unit)
+				if connected and r.online ~= R_ACTIVE then
+					r.join = r.join or now
+					r.online = R_ACTIVE
+				elseif (not connected) and r.online ~= R_OFFLINE then
+					r.leave = now
+					r.online = R_OFFLINE
+				end
+				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
+		end
+	end
+
+	function addon:RAID_ROSTER_UPDATE (event)
+		if GetNumRaidMembers() == 0 then
+			-- 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.popped = nil
+				self:UnregisterEvent("CHAT_MSG_LOOT")
+				self:CheckRoster(--[[leaving raid]]true)
+			end
+			return
+		end
+
 		local inside,whatkind = IsInInstance()
 		if inside and (whatkind == "pvp" or whatkind == "arena") then
 			return self.dprint('flow', "got RRU event but in pvp zone, bailing")
 		end
+
+		local docheck = self.enabled
 		if event == "Activate" then
 			-- dispatched manually from Activate
-			self:RegisterEvent "CHAT_MSG_LOOT"
+			self:RegisterEvent("CHAT_MSG_LOOT")
 			_register_bossmod(self)
+			docheck = true
 		elseif event == "RAID_ROSTER_UPDATE" then
+			-- hot code path, be careful
+
 			-- event registration from onload, joined a raid, maybe show popup
-			if opts.popup_on_join and not self.popped then
+			if (not self.popped) and opts.popup_on_join then
 				self.popped = StaticPopup_Show "OUROL_REMIND"
 				self.popped.data = self
+				return
 			end
 		end
-	else
-		self:UnregisterEvent "CHAT_MSG_LOOT"
-		self.popped = nil
+		if docheck and not InCombatLockdown() then
+			now = time()
+			if difftime(now,lastevent) > 45 then
+				lastevent = now
+				self:CheckRoster(false,now)
+			end
+		end
 	end
 end
 
@@ -645,12 +733,13 @@
 
 ------ On/off
 function addon:Activate (opt_threshold, opt_bcast_only)
-	self:RegisterEvent "RAID_ROSTER_UPDATE"
+	self:RegisterEvent("RAID_ROSTER_UPDATE")
+	self:RegisterEvent("PLAYER_ENTERING_WORLD","RAID_ROSTER_UPDATE")
 	self.popped = true
 	if GetNumRaidMembers() > 0 then
 		self:RAID_ROSTER_UPDATE("Activate")
 	elseif self.debug.notraid then
-		self:RegisterEvent "CHAT_MSG_LOOT"
+		self:RegisterEvent("CHAT_MSG_LOOT")
 		_register_bossmod(self)
 	elseif g_restore_p then
 		g_restore_p = nil
@@ -677,8 +766,9 @@
 function addon:Deactivate()
 	self.enabled = false
 	self.rebroadcast = false
-	self:UnregisterEvent "RAID_ROSTER_UPDATE"
-	self:UnregisterEvent "CHAT_MSG_LOOT"
+	self:UnregisterEvent("RAID_ROSTER_UPDATE")
+	self:UnregisterEvent("PLAYER_ENTERING_WORLD")
+	self:UnregisterEvent("CHAT_MSG_LOOT")
 	self:Print("Ouro Raid Loot deactivated.")
 end
 
@@ -819,7 +909,7 @@
 		-- FIXME printed could be too large if entries were deleted, how much do we care?
 		self.sharder = g_loot.autoshard
 	else
-		g_loot = { printed = {} }
+		g_loot = { printed = {}, raiders = {} }
 		g_loot.saved = g_saved_tmp; g_saved_tmp = nil	-- potentially restore across a clear
 	end
 
@@ -1093,10 +1183,11 @@
 		if not done_todays_date then do_todays_date() end
 
 		local h, m = GetGameTime()
-		local localuptime = math.floor(GetTime())
+		--local localuptime = math.floor(GetTime())
+		local time_t = time()
 		e.hour = h
 		e.minute = m
-		e.stamp = localuptime
+		e.stamp = time_t --localuptime
 		local index = #g_loot + 1
 		g_loot[index] = e
 		return index
@@ -1126,9 +1217,10 @@
 		name = name,
 		date = makedate(),
 		count = #g_loot,
-		forum = g_loot.forum,
-		attend = g_loot.attend,
 	}
+	for text in self:registered_textgen_iter() do
+		save[text] = g_loot[text]
+	end
 	self:Print("Saving current loot texts to #%d '%s'", n, name)
 	g_loot.saved[n] = save
 	return self:save_list()
@@ -1144,8 +1236,9 @@
 	self:Clear(--[[verbose_p=]]false)
 	-- Clear will already have displayed the window, and re-selected the first
 	-- tab.  Set these up for when the text tabs are clicked.
-	g_loot.forum = save.forum
-	g_loot.attend = save.attend
+	for text in self:registered_textgen_iter() do
+		g_loot[text] = save[text]
+	end
 end
 
 function addon:save_delete(num)
--- a/gui.lua	Fri Jun 17 20:30:46 2011 +0000
+++ b/gui.lua	Tue Jun 28 07:36:26 2011 +0000
@@ -93,6 +93,10 @@
 		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)
 		if type(generator) ~= 'function' then
@@ -1400,8 +1404,25 @@
 			simple:AddChild(w)
 		end
 		grp:AddChild(simple)
+
+		simple = GUI:Create("SimpleGroup")
+		simple:SetLayout("Flow")
+		simple:SetRelativeWidth(0.85)
+		w = mkbutton("MidS-H", [[not exactly an Easter egg, with sound]])
+		w:SetRelativeWidth(0.15)
+		w:SetCallback("OnClick", function() 
+			PlaySoundFile[[Sound\Music\WorldEvents\HordeFirepole.mp3]]
+		end)
+		simple:AddChild(w)
+		w = mkbutton("MidS-A", [[not exactly an Easter egg, with sound]])
+		w:SetRelativeWidth(0.15)
+		w:SetCallback("OnClick", function() 
+			PlaySoundFile[[Sound\Music\WorldEvents\AllianceFirepole.mp3]]
+		end)
+		simple:AddChild(w)
+		grp:AddChild(simple)
+
 		grp:ResumeLayout()
-
 		container:AddChild(grp)
 		GUI:ClearFocus()
 	end
@@ -1437,33 +1458,36 @@
 		grp:SetFullWidth(true)
 		grp:SetTitle("User Options     [these are saved across sessions]")
 
+		-- The relative width fields used to be done to take up less vertical space, but
+		-- 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.35,
+		w = mkoption ('popup_on_join', "Show reminder popup", 0.95,
 			[[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.60,
+		w = mkoption('scroll_to_bottom', "Scroll to bottom when opening display", 0.95,
 			[[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.45,
+		w = mkoption('register_slashloot', "Register /loot slash command on login", 0.95,
 			[[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.30,
+		w = mkoption('chatty_on_kill', "Be chatty on boss kill", 0.95,
 			[[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.25,
+		w = mkoption('no_tracking_wipes', "Do not track wipes", 0.95,
 			[[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.35,
+		w = mkoption('snarky_boss', "Use snarky boss names", 0.95,
 			[[Irreverent replacement names for boss events.]])
 		grp:AddChild(w)
 
@@ -1471,7 +1495,7 @@
 		do
 			local pair = GUI:Create("SimpleGroup")
 			pair:SetLayout("Flow")
-			pair:SetRelativeWidth(0.6)
+			pair:SetRelativeWidth(0.95)
 			local editbox, checkbox
 			editbox = mkbutton("EditBox", nil, OuroLootSV_opts.keybinding_text,
 				[[Keybinding text format is fragile!  Relog to take effect.]])
--- a/mleqdkp.lua	Fri Jun 17 20:30:46 2011 +0000
+++ b/mleqdkp.lua	Tue Jun 28 07:36:26 2011 +0000
@@ -1,5 +1,14 @@
-if UnitName"player" ~= "Farmbuyer" then return end
+-- This file is one gigantic exercise in abusing the garbage collector via
+-- string manipulation.  A real XML-handling library would be cleaner, alas,
+-- we can't load third-party .so/.dll inside the WoW client.  Life is hard.
+
 local addon = select(2,...)
+local pairs, ipairs, tinsert, tremove, tconcat = pairs, ipairs, table.insert, table.remove, table.concat
+local tostring, tonumber = tostring, tonumber
+
+local banner_formatted = "Formatted version (scroll down for unformatted):"
+local banner_unformatted = "Unformatted version:"
+local banner_sep = "==========================="
 
 -- We keep some local state bundled up rather than trying to pass it around
 -- as paramters (which would have entailed creating a ton of closures).
@@ -9,7 +18,7 @@
 
 
 --[[
-This is taken from CT_RaidTracker 1.7.32, reconstructing the output from
+This is based on CT_RaidTracker 1.7.32, reconstructing the output from
 code inspection.  No official format documents are available on the web
 without downloading and installing the EQDKP webserver package.  Bah.
 
@@ -31,22 +40,32 @@
 <key>$TIMESTAMP</key>
 <realm>$REALM</realm>
 <start>$TIMESTAMP</start>   {Same as the key, apparently?}
-<end>$ENDTIME</end>    {Set by the "end the raid" command in the raid tracker, here just the final entry time}
+<end>$ENDTIME</end>    {Set by the "end the raid" command in CTRT, here it is just the final entry time}
 <zone>$ZONE</zone>   {may be optional.  first one on the list in case of multiple zones?}
 {<difficulty>$DIFFICULTY</difficulty>   {this scales badly in places like ICC.  may be optional?}}
 
-{<PlayerInfos>... {guh.}}
+<PlayerInfos>
+  $PLAYER_INFOS
+</PlayerInfos>
 
 <BossKills>
   $BOSS_KILLS
 </BossKills>
 
-{<Wipes>  bleh}
-{<NextBoss>Baron Steamroller</NextBoss>  {only one "next boss" for the whole raid event?  huh?}}
+<Wipes>
+  $WIPES
+</Wipes>
+{<NextBoss>Baron Steamroller</NextBoss>  {only one "next boss" for the whole
+                          raid event? presumably where to pick up next time?}}
 
 <note><![CDATA[$RAIDNOTE - Zone: $ZONE]]></note>
 
-{<Join>...</Join><Leave>...</Leave>   {specific timestamps per player. meh.}}
+<Join>
+  $JOINS
+</Join>
+<Leave>
+  $LEAVES
+</Leave>
 
 <Loot>
   $PHAT_LEWTS
@@ -54,7 +73,7 @@
 </RaidInfo>]====]):gsub('%b{}', "")
 
 --[[
-See the loot markup, next block.
+See the loot markup below.
 ]]
 local boss_kills_xml = ([====[
   <key$N>
@@ -78,9 +97,52 @@
 end
 
 --[[
+Handles the PlayerInfo, Join, and Leave tags.
+]]
+local joinleave_xml
+local player_info_xml = ([====[
+  <key$N>
+$PLAYER_GRUNTWORK
+  </key$N>
+]====]):gsub('%b{}', "")
+
+local function player_info_tag_lookup (tag)
+	if tag == 'N' then
+		return tostring(state.key)
+	elseif tag == 'NAME' then
+		return state.index
+	elseif tag == 'TIME' then
+		return state.time
+	end
+	local ltag = tag:lower()
+	if state.entry[ltag] then
+		-- handles race, guild, sex, class, level
+		return state.entry[ltag]
+	end
+	return "?"
+end
+
+do
+	local pi_xml_save = player_info_xml
+	local gruntwork_tags = {
+		"name", "race", "guild", "sex", "class", "level"
+	}
+	for i,tag in ipairs(gruntwork_tags) do
+		gruntwork_tags[i] = ("    <%s>$%s</%s>"):format(tag,tag:upper(),tag)
+	end
+	player_info_xml = player_info_xml:gsub('$PLAYER_GRUNTWORK', table.concat(gruntwork_tags,'\n'))
+
+	-- The join/leave blocks use "player" instead of "name".  They don't have a
+	-- guild tag, but they do have a time tag.
+	gruntwork_tags[1] = "    <player>$NAME</player>"
+	gruntwork_tags[3] = "    <time>$TIME</time>"
+	joinleave_xml = pi_xml_save:gsub('$PLAYER_GRUNTWORK', table.concat(gruntwork_tags,'\n'))
+end
+
+--[[
 $N                  1-based loop variable for key element
-$ITEMNAME           Without The Brackets
-$ITEMID             Not the ID, actually a full itemstring without the leading "item:"
+$ITEMNAME           Without The Square Brackets of the Whale
+$ITEMID             Not the numeric ID, actually a full itemstring without the leading "item:"
 $ICON               Last component of texture path?
 $CLASS,$SUBCLASS    ItemType
 $COLOR              GetItemQualityColor, full 8-digit string
@@ -169,7 +231,7 @@
 		return e.count and e.count:sub(2) or "1"   -- skip the leading "x"
 	end
 
--- maybe combine these next two
+-- should combine these next two
 tag_lookup_handlers.BOSS =
 	function (i, e)
 		while i > 0 and state.loot[i].kind ~= 'boss' do
@@ -224,6 +286,7 @@
 	end
 end
 
+
 local function generator (ttype, loot, last_printed, generated, cache)
 	-- Because it's XML, generated text is "grown" by shoving more crap into
 	-- the middle instead of appending to the end.  Only easy way of doing that
@@ -280,25 +343,49 @@
 			state.key = state.key + 1
 		end
 
-		text = text:gsub('$PHAT_LEWTS', table.concat(all_lewts, '\n'))
+		text = text:gsub('$PHAT_LEWTS', tconcat(all_lewts, '\n'))
 	end
 
-	-- Bosses
+	-- Player info, join times, leave times
 	do
-		local all_bosses = {}
+		local all_players, all_joins, all_leaves = {}, {}, {}
+		local player_template, joinleave_template = player_info_xml, joinleave_xml
+		local date = date
+
+		state.key = 1
+		if type(loot.raiders) == 'table' then for name,r in pairs(loot.raiders) do
+			state.index, state.entry = name, r
+			all_players[#all_players+1] = player_template:gsub('%$([%w_]+)', player_info_tag_lookup)
+			state.time = date ("%m/%d/%y %H:%M:00", r.join)
+			all_joins[#all_joins+1] = joinleave_template:gsub('%$([%w_]+)', player_info_tag_lookup)
+			state.time = date ("%m/%d/%y %H:%M:00", r.leave)
+			all_leaves[#all_leaves+1] = joinleave_template:gsub('%$([%w_]+)', player_info_tag_lookup)
+			state.key = state.key + 1
+		end end
+		text = text:gsub('$PLAYER_INFOS', tconcat(all_players, '\n'))
+		           :gsub('$JOINS', tconcat(all_joins, '\n'))
+		           :gsub('$LEAVES', tconcat(all_leaves, '\n'))
+	end
+
+	-- Bosses and wipes (does anybody really use the latter?)
+	do
+		local all_bosses, all_wipes = {}, {}
 		local boss_template = boss_kills_xml
 
 		state.key = 1
 		for i,e in addon:filtered_loot_iter('boss') do
-			if e.reason == 'kill' then  -- oh, for a 'continue' statement...
+			if e.reason == 'kill' then
 				state.index, state.entry = i, e
 				all_bosses[#all_bosses+1] = boss_template:gsub('%$([%w_]+)',
 					boss_kills_tag_lookup)
 				state.key = state.key + 1
+			elseif e.reason == 'wipe' then
+				all_wipes[#all_wipes+1] = ('<Wipe>%d</Wipe>'):format(e.stamp)
 			end
 		end
 
-		text = text:gsub('$BOSS_KILLS', table.concat(all_bosses, '\n'))
+		text = text:gsub('$BOSS_KILLS', tconcat(all_bosses, '\n'))
+		           :gsub('$WIPES', tconcat(all_wipes, '\n'))
 	end
 
 	-- In addition to doing the top-level zone, this will also catch any
@@ -316,13 +403,13 @@
 	--text = text:gsub('$DIFFICULTY', )
 	text = text:gsub('$RAIDNOTE', "")
 
-	cache[#cache+1] = "Formatted version (scroll down for unformatted):"
-	cache[#cache+1] = "==========================="
+	cache[#cache+1] = banner_formatted
+	cache[#cache+1] = banner_sep
 	cache[#cache+1] = text
 	cache[#cache+1] = '\n'
 
-	cache[#cache+1] = "Unformatted version:"
-	cache[#cache+1] = "==========================="
+	cache[#cache+1] = banner_unformatted
+	cache[#cache+1] = banner_sep
 	text = text:gsub('>%s+<', "><")
 	cache[#cache+1] = text
 	cache[#cache+1] = '\n'
@@ -332,17 +419,41 @@
 end
 
 local function specials (_, editbox, container, mkbutton)
-	local hl = mkbutton("Highlight",
+	local b = mkbutton("Highlight",
 		[[Highlight the unformatted copy for copy-and-pasting.]])
-	hl:SetFullWidth(true)
-	hl:SetCallback("OnClick", function(_hl)
+	b:SetFullWidth(true)
+	b:SetCallback("OnClick", function(_b)
+		local _,start,finish
 		local txt = editbox:GetText()
-		local _,start = txt:find("Unformatted version:\n=+\n")
-		local _,finish = txt:find("</RaidInfo>", start)
+		_,start = txt:find(banner_unformatted..'\n'..banner_sep..'\n')
+		_,finish = txt:find("</RaidInfo>", start)
 		editbox.editBox:HighlightText(start,finish)
 		editbox.editBox:SetCursorPosition(start)
 	end)
-	container:AddChild(hl)
+	container:AddChild(b)
+
+	local b = mkbutton("Re-Unformat",
+		[[Regenerate only the unformatted copy at the bottom <*from*> the formatted copy at the top.]])
+	b:SetFullWidth(true)
+	b:SetCallback("OnClick", function(_b)
+		local _,start,finish
+		local txt = editbox:GetText()
+		_,start = txt:find(banner_formatted..'\n'..banner_sep..'\n', --[[init=]]1, --[[plain=]]true)
+		_,finish = txt:find("</RaidInfo>", start, true)
+		txt = txt:sub(start+1,finish)
+		txt = banner_formatted .. '\n'
+			.. banner_sep .. '\n'
+			.. txt .. '\n\n\n'
+			.. banner_unformatted .. '\n'
+			.. banner_sep .. '\n'
+			.. txt:gsub('>%s+<', "><") .. '\n'
+		-- This would normally screw up the cached version, but we're regenerating
+		-- everything on each new display for this tab anyhow.
+		editbox.editBox:SetText(txt)
+		_,start = txt:find(banner_unformatted..'\n'..banner_sep..'\n', --[[init=]]1, --[[plain=]]true)
+		editbox.editBox:SetCursorPosition(start)
+	end)
+	container:AddChild(b)
 end
 
 addon:register_text_generator ("mleqdkp", [[ML/EQ-DKP]], [[MLdkp 1.1 EQDKP format]], generator, specials)