Mercurial > wow > ouroloot
view gui.lua @ 115:289c7667adab
When fixing up missing item cache during load, make sure GUI displays from scratch. Put the bang back on UI tips checkbox (widget fixed). Revent r112, widgets both fixed now.
author | Farmbuyer of US-Kilrogg <farmbuyer@gmail.com> |
---|---|
date | Tue, 14 Aug 2012 20:37:12 -0400 |
parents | 67bf97136273 |
children | fc2ff128835a |
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 = addon.disposition_colors local function eoi_st_lootrow_col3_colortable_func (data, _, realrow) return eoi_st_lootrow_col3_colortable[data[realrow].disposition] 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" }, -- Tables for feeding to EasyMenu dropdown = {}, } 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? test on mop beta 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") -- hmm. used to bail here. does restarting cause problems? return self:_fill_out_eoi_data(1) 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 "") local pdisplay = e.person_realm and (e.person .. FOREIGN_SERVER_LABEL) or e.person e.cols = { {value = textured}, {value = pdisplay}, {} } -- This is horrible. Must do better. if e.extratext then for disp,text in self:_iter_dispositions('from_notes_text') do if text == e.extratext then e.disposition = disp break end end end local ex = eoi_st_lootrow_col3_colortable[e.disposition].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 = player.when[unique] if not col3.value then col3.hist_miss = true col3.value = '??' end 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 dropdownmenuframe = CreateFrame("Frame", "OuroLootDropDownMenu", nil, "UIDropDownMenuTemplate") 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_dd_entry (name, functbl, funci, arg, ttt) local entry if name == '--' then entry = { text = "", disabled = true, notCheckable = true, } else local a1 = functbl[name] if type(funci) == 'string' then a1 = functbl[funci] end entry = { text = name, func = dropdownmenu_handler, arg1 = a1, arg2 = arg, notCheckable = true, tooltipOnButton = true, tooltipWhileDisabled = true, tooltipTitle = ttt and name or nil, tooltipText = ttt, } end return entry end -- 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, df_DISPOSITION = function(rowi,disp) -- all "mark as <x>" entries start here addon:loot_mark_disposition ("local", rowi, disp) 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, ["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, } do local function E (name, funci, arg, ttt) return gen_dd_entry (name, eoi_dropdownfuncs, funci, arg, ttt) end gui.dropdown.eoi_time = { { -- this is the dropdown title, text filled in on the fly isTitle = true, notClickable = true, notCheckable = true, }, E("Rebroadcast this day", "Rebroadcast this boss", 'time', "Broadcasts everything from here down until a new day."), E("Delete remaining entries for this day", nil, '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."), E("Insert new loot entry", 'df_INSERT', 'loot', "Inserts new loot above this one, prompting you for information."), E("Insert new boss kill event", 'df_INSERT', 'boss', "Inserts new event above this one, prompting you for information."), E(CLOSE), } gui.dropdown.eoi_loot = { { -- this is the dropdown title, text filled in on the fly notClickable = true, notCheckable = true, }, E("--"), E("Rebroadcast this loot entry", nil, nil, "Sends this loot event, including special notes, as if it just happened."), E("Delete this loot event", 'df_DELETE', nil, "Permanent, no going back!\n\nHold down the Shift key to also delete the corresponding entry from player's History."), E("Delete remaining entries for this boss", "Delete remaining entries for this day", '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."), E("Insert new loot entry", 'df_INSERT', 'loot', "Inserts new loot above this one, prompting you for information."), E("Insert new boss kill event", 'df_INSERT', 'boss', "Inserts new event above this one, prompting you for information."), E("Edit note", nil, nil, "Same as double-clicking in the Notes column."), E("--"), E(CLOSE), } gui.dropdown.eoi_player = { { -- 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, }, E("Show only this player"), E(CLOSE), } gui.dropdown.eoi_boss = { { -- this is the dropdown title, text filled in on the fly isTitle = true, notClickable = true, notCheckable = true, }, E("Change from 'wipe' to 'kill'", nil, nil, "Also collapses previous wipe entries."), -- KILLWIPE E("Rebroadcast this boss", nil, 'boss', "Broadcasts the kill event and all subsequent loot until next boss."), E("Delete this boss event", 'df_DELETE', nil, "Permanent, no going back!"), E("Delete remaining entries for this boss", "Delete remaining entries for this day", '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."), E("Insert new loot entry", 'df_INSERT', 'loot', "Inserts new loot above this one, prompting you for information."), E("Insert new boss kill event", 'df_INSERT', 'boss', "Inserts new event above this one, prompting you for information."), E("--"), E(CLOSE), } end --[[ 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 cell = e.cols[column] 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(("%s Loot:"):format(e.person_realm and (e.person .. "-" .. e.person_realm) or e.person)) 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(cell.value) elseif kind == 'hist' and column == 3 and cell.hist_miss then tt:SetOwner (cellFrame, "ANCHOR_RIGHT", -20, 0) tt:ClearLines() tt:AddLine("Corrupted History Data") tt:AddLine([[Close this window, then type]], 0.8, 0.8, 0.8, 1) tt:AddLine([[/ouroloot fix history]], 0, 1, 64/255, nil) tt:AddLine([[and redisplay this window.]], 0.8, 0.8, 0.8, 1) tt:Show() 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) gui.dropdown.eoi_loot[1].text = e.itemlink EasyMenu (gui.dropdown.eoi_loot, dropdownmenuframe, cellFrame, 0, 0, "MENU") elseif kind == 'loot' and column == 2 then local ddep = gui.dropdown.eoi_player ddep[1].text = e.person -- FIXME realm this too 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] = gen_dd_entry (name, eoi_dropdownfuncs, 'df_REASSIGN', name) end tinsert (raiders, gen_dd_entry ("Enter name...", eoi_dropdownfuncs)) tinsert (raiders, gen_dd_entry (CLOSE, eoi_dropdownfuncs)) ddep[2].menuList = raiders if not addon:_test_disposition (e.disposition, 'can_reassign') then ddep[2].disabled = true ddep[2].tooltipTitle = "Cannot Reassign" ddep[2].tooltipText = "You must first mark this item as 'normal' or 'offspec' before reassignment." else ddep[2].disabled = nil ddep[2].tooltipTitle = nil ddep[2].tooltipText = nil end EasyMenu (ddep, dropdownmenuframe, cellFrame, 0, 0, "MENU") elseif kind == 'boss' then gui.dropdown.eoi_boss[1].text = e.bossname -- KILLWIPE: update '2' if this is not the 2nd entry in gui.dropdown.eoi_boss gui.dropdown.eoi_boss[2].tooltipWhileDisabled = nil gui.dropdown.eoi_boss[2].disabled = e.reason ~= 'wipe' and true or nil EasyMenu (gui.dropdown.eoi_boss, dropdownmenuframe, cellFrame, 0, 0, "MENU") elseif kind == 'time' then gui.dropdown.eoi_time[1].text = e.startday.text EasyMenu (gui.dropdown.eoi_time, 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) if name_or_lineno > eoi_st_displayed_rows then -- don't try to scroll if there's not enough lines local sf = gui.eoiST.scrollframe sf:GetScript("OnVerticalScroll")(sf,scrollhere) end 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, } do local function E (name, funci, arg, ttt) return gen_dd_entry (name, hist_dropdownfuncs, funci, arg, ttt) end gui.dropdown.hist_general = { { -- this is the dropdown title, text filled in on the fly isTitle = true, notClickable = true, notCheckable = true, }, E("Delete this player's entire loot history", nil, nil, "Permanent, no going back!"), E("--"), E(CLOSE), } gui.dropdown.hist_specific = { { -- this is the dropdown title, text filled in on the fly notClickable = true, notCheckable = true, }, E("Delete this loot event from history", nil, nil, "Permanent, no going back!"), E("--"), E(CLOSE), } end -- 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 gui.dropdown.hist_specific[1].text = h.itemlink EasyMenu (gui.dropdown.hist_specific, 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 gui.dropdown.hist_general[1].text = h.OLwho EasyMenu (gui.dropdown.hist_general, 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) -- If we're focused on one player, but have deleted all entries for -- that player, don't sit there stuck on a blank grid. if history_filter_who and #histST.filtered < 1 then history_filter_who = nil histST:SetFilter(history_filter_by_recent) setstatus(hist_normal_status) else setstatus(hist_name_status) end 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 -- We need to be able to reference the dropdownmenu locals, and I didn't want to -- bubble them up any higher. function gui.add_dropdown_entry (menutag, name, func_tbl, func_or_othername, arg, tooltiptext) local emtbl = assert(gui.dropdown[menutag]) if type(func_tbl) == 'table' then -- use it directly elseif func_tbl == nil then -- determine it from the menu tag func_tbl = (menutag:sub(1,3) == 'eoi' and eoi_dropdownfuncs) or (menutag:sub(1,4) == 'hist' and hist_dropdownfuncs) or error("Cannot figure out function table from menu tag name") end if type(func_or_othername) == 'string' then -- gen_dd_entry handles this elseif type(func_or_othername) == 'function' then error"bah" end local index if menutag == 'eoi_loot' then index = 2 elseif menutag == 'eoi_player' then index = 3 else index = 2 end local ent = gen_dd_entry (name, func_tbl, func_or_othername, arg, tooltiptext) tinsert (emtbl, index, ent) return ent 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 addon.FILES_LOADED = addon.FILES_LOADED + 1 -- vim:noet