--[[- LibExtraTip LibExtraTip is a library of API functions for manipulating additional information into GameTooltips by either adding information to the bottom of existing tooltips (embedded mode) or by adding information to an extra "attached" tooltip construct which is placed to the bottom of the existing tooltip. Copyright (C) 2008, by the respective below authors. This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA @author Matt Richard (Tem) @author Ken Allan @author brykrys @libname LibExtraTip @version 1.(see below) --]] local LIBNAME = "LibExtraTip" local VERSION_MAJOR = 1 local VERSION_MINOR = 328 -- Minor Version cannot be a SVN Revison in case this library is used in multiple repositories -- Should be updated manually with each (non-trivial) change -- A string unique to this version to prevent frame name conflicts. local LIBSTRING = LIBNAME.."_"..VERSION_MAJOR.."_"..VERSION_MINOR local lib = LibStub:NewLibrary(LIBNAME.."-"..VERSION_MAJOR, VERSION_MINOR) if not lib then return end LibStub("LibRevision"):Set("$URL: http://svn.norganna.org/libs/trunk/LibExtraTip/LibExtraTip.lua $","$Rev: 350 $","5.15.DEV.", 'auctioneer', 'libs') -- Call function to deactivate any outdated version of the library. -- (calls the OLD version of this function, NOT the one defined in this -- file's scope) if lib.Deactivate then lib:Deactivate() end -- Forward definition of a few locals that get defined at the bottom of -- the file. local tooltipMethodPrehooks local tooltipMethodPosthooks local ExtraTipClass --[[ The following events are enabled by default unless disabled in the callback options "enabled" table all other events are default disabled. Note that this only applies to events that will lead to calling ProcessCallbacks, currently any method that fires OnTooltipSetItem, OnTooltipSetSpell or OnTooltipSetUnit --]] local defaultEnable = { SetAuctionItem = true, SetAuctionSellItem = true, SetBagItem = true, SetBuybackItem = true, SetGuildBankItem = true, SetInboxItem = true, SetInventoryItem = true, SetLootItem = true, SetLootRollItem = true, SetMerchantItem = true, SetQuestItem = true, SetQuestLogItem = true, SetSendMailItem = true, SetTradePlayerItem = true, SetTradeTargetItem = true, SetTradeSkillItem = true, SetHyperlink = true, SetHyperlinkAndCount = true, -- Creating a tooltip via lib:SetHyperlinkAndCount() SetBattlePet = true, SetBattlePetAndCount = true, } --[[ The following callback types are always enabled regardless of the event ]] local alwaysEnable = { extrashow = true, extrahide = true, } -- Money Icon setup local iconpath = "Interface\\MoneyFrame\\UI-" local goldicon = "%d|T"..iconpath.."GoldIcon:0|t" local silvericon = "%s|T"..iconpath.."SilverIcon:0|t" local coppericon = "%s|T"..iconpath.."CopperIcon:0|t" -- Other constants local MATHHUGE = math.huge -- Function that calls all the interested tooltips local function ProcessCallbacks(reg, tiptype, tooltip, ...) if not reg then return end local event = reg.additional.event or "Unknown" local default = defaultEnable[event] if lib.sortedCallbacks and #lib.sortedCallbacks > 0 then for i,options in ipairs(lib.sortedCallbacks) do if options.type == tiptype then local enable = default if options.allevents or alwaysEnable[tiptype] then enable = true elseif options.enable and options.enable[event] ~= nil then enable = options.enable[event] end if enable then options.callback(tooltip, ...) end end end end end -- Function that gets run when an item is set on a registered tooltip. local function OnTooltipSetItem(tooltip) local self = lib local reg = self.tooltipRegistry[tooltip] if not reg then return end if self.sortedCallbacks and #self.sortedCallbacks > 0 then tooltip:Show() local _,item = tooltip:GetItem() -- For generated tooltips if not item and reg.item then item = reg.item end if item and not reg.hasItem then local name,link,quality,ilvl,minlvl,itype,isubtype,stack,equiploc,texture = GetItemInfo(item) if link then name = name or "unknown" -- WotLK bug reg.hasItem = true local extraTip = self:GetFreeExtraTipObject() reg.extraTip = extraTip extraTip:Attach(tooltip) local r,g,b = GetItemQualityColor(quality) extraTip:AddLine(name,r,g,b) local quantity = reg.quantity or 1 reg.additional.item = item reg.additional.quantity = quantity reg.additional.name = name reg.additional.link = link reg.additional.quality = quality reg.additional.itemLevel = ilvl reg.additional.minLevel = minlvl reg.additional.itemType = itype reg.additional.itemSubtype = isubtype reg.additional.stackSize = stack reg.additional.equipLocation = equiploc reg.additional.texture = texture ProcessCallbacks(reg, "item", tooltip, item,quantity,name,link,quality,ilvl,minlvl,itype,isubtype,stack,equiploc,texture) tooltip:Show() if reg.extraTipUsed then reg.extraTip:Show() ProcessCallbacks(reg, "extrashow", tooltip, reg.extraTip) end end end end end -- Function that gets run when a spell is set on a registered tooltip. local function OnTooltipSetSpell(tooltip) local self = lib local reg = self.tooltipRegistry[tooltip] if not reg then return end if self.sortedCallbacks and #self.sortedCallbacks > 0 then tooltip:Show() local name, category, spellID = tooltip:GetSpell() local link = reg.additional.link if name and not reg.hasItem then reg.hasItem = true local extraTip = self:GetFreeExtraTipObject() reg.extraTip = extraTip extraTip:Attach(tooltip) extraTip:AddLine(name, 1,0.8,0) reg.additional.name = name reg.additional.category = category reg.additional.spellID = spellID ProcessCallbacks(reg, "spell", tooltip, link, name, category, spellID) tooltip:Show() if reg.extraTipUsed then reg.extraTip:Show() ProcessCallbacks(reg, "extrashow", tooltip, reg.extraTip) end end end end -- Function that gets run when a unit is set on a registered tooltip. local function OnTooltipSetUnit(tooltip) local self = lib local reg = self.tooltipRegistry[tooltip] if not reg then return end if self.sortedCallbacks and #self.sortedCallbacks > 0 then tooltip:Show() local name, unitId = tooltip:GetUnit() if name and not reg.hasItem then reg.hasItem = true local extraTip = self:GetFreeExtraTipObject() reg.extraTip = extraTip extraTip:Attach(tooltip) extraTip:AddLine(name, 0.8,0.8,0.8) ProcessCallbacks(reg, "unit", tooltip, name, unitId) tooltip:Show() if reg.extraTipUsed then reg.extraTip:Show() ProcessCallbacks(reg, "extrashow", tooltip, reg.extraTip) end end end end -- Function that gets run when a registered tooltip's item is cleared. local function OnTooltipCleared(tooltip) local self = lib local reg = self.tooltipRegistry[tooltip] if not reg then return end if reg.ignoreOnCleared then return end tooltip:SetFrameLevel(1) reg.extraTipUsed = nil reg.minWidth = 0 reg.quantity = nil reg.hasItem = nil reg.item = nil table.wipe(reg.additional) if reg.extraTip then tinsert(self.extraTippool, reg.extraTip) reg.extraTip:Hide() reg.extraTip:Release() reg.extraTip:SetHeight(0) ProcessCallbacks(reg, "extrahide", tooltip, reg.extraTip) reg.extraTip = nil end end -- Run when a BattlePet is loaded into a BettlePetTooltip -- Requires special handling as BattlePetTooltips aren't real tooltips and lack most of the scripts and methods we normally use -- Hooked directly from BattlePetTooltipTemplate_SetBattlePet local function OnTooltipSetBattlePet(tooltip, data) local reg = lib.tooltipRegistry[tooltip] if not reg then return end -- OnTooltipCleared is normally called via OnHide for BattlePets -- clean up here in case a new BattlePet is loaded into a visible tooltip, in which case OnHide would not have been triggered if reg.hasItem then OnTooltipCleared(tooltip) end if lib.sortedCallbacks and #lib.sortedCallbacks > 0 then -- extract values from data local speciesID = data.speciesID local level = data.level local breedQuality = data.breedQuality local maxHealth = data.maxHealth local power = data.power local speed = data.speed local battlePetID = data.battlePetID or "0x0000000000000000" local name = data.name local customName = data.customName local petType = data.petType local colcode, r, g, b if breedQuality == -1 then colcode = NORMAL_FONT_COLOR_CODE r, g, b = NORMAL_FONT_COLOR.r, NORMAL_FONT_COLOR.g, NORMAL_FONT_COLOR.b else local coltable = ITEM_QUALITY_COLORS[breedQuality] or ITEM_QUALITY_COLORS[0] colcode = coltable.hex r, g, b = coltable.r, coltable.g, coltable.b end -- for certain events there may already be info stored in reg - e.g. SetBattlePetAndCount local quantity = reg.quantity or 1 local link = reg.item if not link then -- it's a bit of a pain that we need to reconstruct a link here, just so it can be chopped up again... link = format("%s|Hbattlepet:%d:%d:%d:%d:%d:%d:%s|h[%s]|h|r", colcode, speciesID, level, breedQuality, maxHealth, power, speed, battlePetID, customName or name) end reg.hasItem = true local extraTip = lib:GetFreeExtraTipObject() reg.extraTip = extraTip extraTip:Attach(tooltip) extraTip:AddLine(name, r, g, b) reg.additional.name = name reg.additional.link = link reg.additional.speciesID = speciesID reg.additional.quality = breedQuality reg.additional.quantity = quantity reg.additional.level = level reg.additional.customName = customName -- nil if no custom name reg.additional.petType = petType reg.additional.maxHealth = maxHealth reg.additional.power = power reg.additional.speed = speed reg.additional.battlePetID = battlePetID -- if not 0 it's a pet in your journal reg.additional.event = reg.additional.event or "SetBattlePet" ProcessCallbacks(reg, "battlepet", tooltip, link, quantity, name, speciesID, breedQuality, level) if reg.extraTipUsed then reg.extraTip:Show() ProcessCallbacks(reg, "extrashow", tooltip, reg.extraTip) end end end -- Function that gets run when a registered tooltip's size changes. local function OnSizeChanged(tooltip,w,h) local self = lib local reg = self.tooltipRegistry[tooltip] if not reg then return end local extraTip = reg.extraTip if extraTip then extraTip:NeedsRefresh(true) end end function lib:GetFreeExtraTipObject() if not self.extraTippool then self.extraTippool = {} end return tremove(self.extraTippool) or ExtraTipClass:new() end --[[ hookStore: @since version 1.1 (see below for hookStore version) stores control information for method and script hooks on tooltips lib.hookStore[tooltip][method] = = {prehook, posthook} is an upvalue to our installed hookstub: insert new values to change the hook, or wipe it to deactivate if we are updating, keep the old hookStore table IF it has the right version, so that we can reuse the hook stubs --]] local HOOKSTORE_VERSION = "C" if not lib.hookStore or lib.hookStore.version ~= HOOKSTORE_VERSION then lib.hookStore = {version = HOOKSTORE_VERSION} end -- Called to install/modify a pre-/post-hook on the given tooltip's method local function hook(tip, method, prehook, posthook) if not lib.hookStore[tip] then lib.hookStore[tip] = {} end local control -- check for existing hook control = lib.hookStore[tip][method] if control then control[1] = prehook or control[1] or false --(avoid nil values by substituting false instead) control[2] = posthook or control[2] or false return end -- prepare upvalues local orig = tip[method] if not orig then -- There should be an original method - abort if it's missing if nLog then nLog.AddMessage("LibExtraTip", "Hooks", N_NOTICE, "Missing method", "LibExtraTip:hook detected missing method: "..tostring(method)) end return end control = {prehook or false, posthook or false} lib.hookStore[tip][method] = control -- install hook stub local stub = function(...) local hook -- prehook hook = control[1] if hook then hook(...) end -- original hook local a,b,c,d,e,f,g,h,i,j,k = orig(...) -- posthook hook = control[2] if hook then hook(...) end -- return values from original return a,b,c,d,e,f,g,h,i,j,k end tip[method] = stub --[[ Note: neither the stub hook nor the original function should be called directly after our hook is installed, because the behaviour of any other third-party hooks to the same method would then be undefined (i.e. they might get called or they might not...) --]] end -- Called to deactivate our stub hook for the given tooltip's method -- The stub is left in place: we assume we are undergoing a version upgrade, and that the stubs will be reused --[[ not used in this version (left in place in case needed for future changes) local function unhook(tip,method) wipe(lib.hookStore[tip][method]) end --]] -- Called to install/modify a pre-hook on the given tooltip's event -- Currently we do not need any posthooks on scripts local function hookscript(tip, script, prehook) if not lib.hookStore[tip] then lib.hookStore[tip] = {} end local control -- check for existing hook control = lib.hookStore[tip][script] if control then control[1] = prehook or control[1] return end -- prepare upvalues local orig = tip:GetScript(script) control = {prehook} lib.hookStore[tip][script] = control -- install hook stub local stub = function(...) local h -- prehook h = control[1] if h then h(...) end -- original hook if orig then orig(...) end end tip:SetScript(script, stub) end -- Called to deactivate all our pre-hooks on the given tooltip's event --local function unhookscript(tip,script) -- not used in this version -- Called to install a post hook on a global function -- func must be the name of a global function local function hookglobal(func, posthook) if not lib.hookStore.global then lib.hookStore.global = {} end local control = lib.hookStore.global[func] if control then control[1] = posthook or control[1] return end control = {posthook} local orig = _G[func] if type(orig) ~= "function" then if nLog then nLog.AddMessage("LibExtraTip", "Hooks", N_WARNING, "Global hook - not a function", "LibExtraTip:hookglobal attempted to hook "..tostring(func).." which is not a global function name") end return end local stub = function(...) local hook -- prehook hook = control[1] if hook then hook(...) end end -- As we only need post-hooks we can use hooksecurefunc -- Using control table protects against multiple hooking and allows us to change or disable the hook hooksecurefunc(func, stub) end --[[- Adds the provided tooltip to the list of tooltips to monitor for items. @param tooltip GameTooltip object @return true if tooltip is registered @since 1.0 ]] function lib:RegisterTooltip(tooltip) local specialTooltip if not tooltip or type(tooltip) ~= "table" or type(tooltip.GetObjectType) ~= "function" then return end if tooltip:GetObjectType() ~= "GameTooltip" then if tooltip:GetObjectType() == "Frame" then -- is it a BattlePetTooltip? check for some of the entries from BattlePetTooltipTemplate if tooltip.BattlePet and tooltip.PetType and tooltip.PetTypeTexture then specialTooltip = "battlepet" else return end else return end end if not self.tooltipRegistry then self.tooltipRegistry = {} self:GenerateTooltipMethodTable() end if not self.tooltipRegistry[tooltip] then local reg = {} self.tooltipRegistry[tooltip] = reg reg.additional = {} if specialTooltip == "battlepet" then reg.NoColumns = true -- This is not a GameTooltip so it has no Text columns. Cannot support certain functions such as embedding hookscript(tooltip,"OnHide",OnTooltipCleared) hookscript(tooltip,"OnSizeChanged",OnSizeChanged) hookglobal("BattlePetTooltipTemplate_SetBattlePet", OnTooltipSetBattlePet) -- yes we hook the same function every time - hookglobal protects against multiple hooks else hookscript(tooltip,"OnTooltipSetItem",OnTooltipSetItem) hookscript(tooltip,"OnTooltipSetUnit",OnTooltipSetUnit) hookscript(tooltip,"OnTooltipSetSpell",OnTooltipSetSpell) hookscript(tooltip,"OnTooltipCleared",OnTooltipCleared) hookscript(tooltip,"OnSizeChanged",OnSizeChanged) for k,v in pairs(tooltipMethodPrehooks) do hook(tooltip,k,v) end for k,v in pairs(tooltipMethodPosthooks) do hook(tooltip,k,nil,v) end end return true end end --[[- Checks to see if the tooltip has been registered with LibExtraTip @param tooltip GameTooltip object @return true if tooltip is registered @since 1.0 ]] function lib:IsRegistered(tooltip) if not self.tooltipRegistry or not self.tooltipRegistry[tooltip] then return end return true end --[[- Returns a reference to the extra tip currently attached to the specified tooltip (if any) Intended for tooltip styling AddOns - should only be used to alter cosmetic elements of the tooltip (Use caution when modifying Text line fonts, as LibExtraTip also modifies the fonts) @param tooltip as registered tooltip @return extratip if any attached to tooltip (may be hidden and/or empty) @since 1.324 ]] function lib:GetExtraTip(tooltip) if not self.tooltipRegistry then return end local reg = self.tooltipRegistry[tooltip] if reg then return reg.extraTip end end --[[- Adds a callback to be informed of any registered tooltip's activity. The parameters passed to callbacks vary depending on the type of callback @param options a table containing entries defining the required callback type (string, required) the callback type, e.g. "item", "spell", and others callback (function, required) the function to be called back when the appropriate event occurs enable (table, optional) a table containing = pairs, specifying which events to respond to callbacks are usually only generated for events enabled either by this table, or by the defaultEnable table allevents (boolean, optional) if true always triggers a callback regardless of the event, overrides defaultEnable and options.event table @param priority the priority of the callback (optional, default 200) @since 1.0 ]] local sortFunc function lib:AddCallback(options,priority) -- Lower priority gets called before higher priority. Default is 200. if not options then return end local otype = type(options) if otype == "function" then options = {type = "item", callback = options} elseif otype == "table" then -- check required keys if type(options.type) ~= "string" or type(options.callback) ~= "function" then return end -- copy into a new table for our internal use local copyoptions = {type = options.type, callback = options.callback} if options.allevents == true then copyoptions.allevents = true elseif type(options.enable) == "table" then copyoptions.enable = options.enable end options = copyoptions else return end if not sortFunc then local callbacks = self.callbacks if not callbacks then callbacks = {} self.callbacks = callbacks self.sortedCallbacks = {} end sortFunc = function(a,b) return callbacks[a] < callbacks[b] end end self.callbacks[options] = priority or 200 tinsert(self.sortedCallbacks,options) sort(self.sortedCallbacks,sortFunc) end --[[- Removes the given callback from the list of callbacks. @param callback the callback to remove from notifications @return true if successfully removed @since 1.0 ]] function lib:RemoveCallback(callback) if not (callback and self.callbacks) then return end if not self.callbacks[callback] then -- backward compatibility for old 'function' style AddCallback and RemoveCallback for options, priority in pairs(self.callbacks) do if options.callback == callback then callback = options break end end if not self.callbacks[callback] then return end end self.callbacks[callback] = nil for index,options in ipairs(self.sortedCallbacks) do if options == callback then tremove(self.sortedCallbacks, index) return true end end end --[[- Sets the default embed mode of the library (default false) A false embedMode causes AddLine, AddDoubleLine and AddMoneyLine to add lines to the attached tooltip rather than embed added lines directly in the item tooltip. This setting only takes effect when embed mode is not specified on individual AddLine, AddDoubleLine and AddMoneyLine commands. @param flag boolean flag if true embeds by default @since 1.0 ]] function lib:SetEmbedMode(flag) self.embedMode = flag and true or false end --[[- Adds a line to a registered tooltip. @param tooltip GameTooltip object @param text the contents of the tooltip line @param r (0-1) red component of the tooltip line color (optional) @param g (0-1) green component of the tooltip line color (optional) @param b (0-1) blue component of the tooltip line color(optional) @param embed (boolean) override the lib's embedMode setting (optional) @param wrap (boolean) specify line-wrapping for long lines (optional) @see SetEmbedMode @since 1.0 ]] function lib:AddLine(tooltip, text, r, g, b, embed, wrap) local reg = self.tooltipRegistry[tooltip] if not reg then return end if reg.NoColumns then embed = false else if r and not g then embed = r r = nil end -- deprecated: (tooltip, text, embed) form if embed == nil then embed = self.embedMode end end if not embed then reg.extraTip:AddLine(text, r, g, b, wrap) reg.extraTipUsed = true else tooltip:AddLine(text, r, g, b, wrap) end end --[[- Adds a two-columned line to the tooltip. @param tooltip GameTooltip object @param textLeft the left column's contents @param textRight the left column's contents @param r red component of the tooltip line color (optional) @param g green component of the tooltip line color (optional) @param b blue component of the tooltip line color (optional) @param embed override the lib's embedMode setting (optional) @see SetEmbedMode @since 1.0 ]] function lib:AddDoubleLine(tooltip,textLeft,textRight,lr,lg,lb,rr,rg,rb,embed) local reg = self.tooltipRegistry[tooltip] if not reg then return end if reg.NoColumns then embed = false else if lr and not lg and not rr then embed = lr lr = nil end if lr and lg and rr and not rg then embed = rr rr = nil end if embed == nil then embed = self.embedMode end end if not embed then reg.extraTip:AddDoubleLine(textLeft,textRight,lr,lg,lb,rr,rg,rb) reg.extraTipUsed = true else tooltip:AddDoubleLine(textLeft,textRight,lr,lg,lb,rr,rg,rb) end end --[[- Creates a string representation of the money value passed using embedded textures for the icons @param money the money value to be converted in copper @param concise when false (default), the representation of 1g is "1g 00s 00c" when true, it is simply "1g" (optional) @since 1.0 ]] function lib:GetMoneyText(money, concise) local g = math.floor(money / 10000) local s = math.floor(money % 10000 / 100) local c = math.floor(money % 100) local moneyText = "" local sep, fmt = "", "%d" if g > 0 then moneyText = goldicon:format(g) sep, fmt = " ", "%02d" end if s > 0 or (money >= 10000 and (concise and c > 0) or not concise) then moneyText = moneyText..sep..silvericon:format(fmt):format(s) sep, fmt = " ", "%02d" end if not concise or c > 0 or money < 100 then moneyText = moneyText..sep..coppericon:format(fmt):format(c) end return moneyText end --[[- Adds a line with text in the left column and a money frame in the right. The money parameter is given in copper coins (i.e. 1g 27s 5c would be 12705) @param tooltip GameTooltip object @param text the contents of the tooltip line @param money the money value to be displayed (in copper) @param r red component of the tooltip line color (optional) @param g green component of the tooltip line color (optional) @param b blue component of the tooltip line color (optional) @param embed override the lib's embedMode setting (optional) @param concise specify if concise money mode is to be used (optional) @see SetEmbedMode @since 1.0 ]] function lib:AddMoneyLine(tooltip,text,money,r,g,b,embed,concise) local reg = self.tooltipRegistry[tooltip] if not reg then return end if reg.NoColumns then embed = false else if r and not g then embed = r r = nil end if embed == nil then embed = self.embedMode end end local moneyText = self:GetMoneyText(money, concise) if not embed then reg.extraTip:AddDoubleLine(text,moneyText,r,g,b,1,1,1) reg.extraTipUsed = true else tooltip:AddDoubleLine(text,moneyText,r,g,b,1,1,1) end end --[[- Sets a tooltip to hyperlink with specified quantity @param tooltip GameTooltip object @param link hyperlink to display in the tooltip @param quantity quantity of the item to display (optional) @param detail additional detail items to set for the callbacks (optional) @return true if successful @since 1.0 ]] function lib:SetHyperlinkAndCount(tooltip, link, quantity, detail) local reg = self.tooltipRegistry[tooltip] if not reg or reg.NoColumns then return end -- NoColumns tooltips can't handle :SetHyperlink OnTooltipCleared(tooltip) reg.quantity = quantity reg.item = link reg.additional.event = "SetHyperlinkAndCount" reg.additional.eventLink = link if detail then for k,v in pairs(detail) do reg.additional[k] = v end end reg.ignoreOnCleared = true reg.ignoreSetHyperlink = true tooltip:SetHyperlink(link) reg.ignoreSetHyperlink = nil reg.ignoreOnCleared = nil return true end --[[- Get the additional information from a tooltip event. Often additional event details are available about the situation under which the tooltip was invoked, such as: * The call that triggered the tooltip. * The slot/inventory/index of the item in question. * Whether the item is usable or not. * Auction price information. * Ownership information. * Any data provided by the Get*Info() functions. If you require access to this information for the current tooltip, call this function to retrieve it. @param tooltip GameTooltip object @return table containing the additional information @since 1.0 ]] function lib:GetTooltipAdditional(tooltip) local reg = self.tooltipRegistry[tooltip] if reg then return reg.additional end return nil end --[[ INTERNAL USE ONLY Deactivates this version of the library, rendering it inert. Needed to run before an upgrade of the library takes place. @since 1.0 ]] function lib:Deactivate() -- deactivate all hook stubs for tooltip, tiptable in pairs(lib.hookStore) do if tooltip ~= "version" then -- skip over the version indicator for method, control in pairs(tiptable) do wipe(control) -- disable the hook stub by removing all hooks from the control table end end end -- deactivate and discard any existing extra tooltips -- (should be extremely rare that any would exist at this point -- therefore minimal code just to prevent errors in those rare instances) if self.tooltipRegistry then for _, reg in pairs(self.tooltipRegistry) do local tip = reg.extraTip if tip then tip:Hide() tip:Release() end reg.extraTip = nil reg.extraTipUsed = nil end end self.extraTippool = nil end --[[ INTERNAL USE ONLY Activates this version of the library. Configures this library for use by setting up its variables and reregistering any previously registered tooltips and callbacks. @since 1.0 ]] function lib:Activate() local oldreg = self.tooltipRegistry if oldreg then self.tooltipRegistry = nil for tooltip in pairs(oldreg) do self:RegisterTooltip(tooltip) end end local oldcallbacks = self.callbacks if oldcallbacks then self.callbacks = nil for options, priority in pairs(oldcallbacks) do self:AddCallback(options, priority) end end end -- Sets all the complex spell details local function SetSpellDetail(reg, link) local name, rank, icon, cost, funnel, power, ctime, min, max = GetSpellInfo(link) reg.additional.name = name reg.additional.link = link reg.additional.rank = rank reg.additional.icon = icon reg.additional.cost = cost reg.additional.powerType = power reg.additional.isFunnel = funnel reg.additional.castTime = ctime reg.additional.minRange = min reg.additional.maxRange = max end --[[ INTERNAL USE ONLY Generates a tooltip method table. The tooltip method table supplies hooking information for the tooltip registration functions, including the methods to hook and a function to run that parses the hooked functions parameters. @since 1.0 Addendum: generates 2 method tables, for prehooks and posthooks. Where the prehook sets a flag, a posthook must be installed to clear it. Specifically: reg.ignoreOnCleared ]] function lib:GenerateTooltipMethodTable() -- Sets up hooks to give the quantity of the item local tooltipRegistry = self.tooltipRegistry self.GenerateTooltipMethodTable = nil -- only run once tooltipMethodPrehooks = { -- Default enabled events SetAuctionItem = function(self,type,index) OnTooltipCleared(self) local reg = tooltipRegistry[self] reg.ignoreOnCleared = true local _,_,q,_,cu,_,_,minb,inc,bo,ba,hb,own = GetAuctionItemInfo(type,index) reg.quantity = q reg.additional.event = "SetAuctionItem" reg.additional.eventType = type reg.additional.eventIndex = index reg.additional.canUse = cu reg.additional.minBid = minb reg.additional.minIncrement = inc reg.additional.buyoutPrice = bo reg.additional.bidAmount = ba reg.additional.highBidder = hb reg.additional.owner = own end, SetAuctionSellItem = function(self) OnTooltipCleared(self) local reg = tooltipRegistry[self] reg.ignoreOnCleared = true local name,texture,quantity,quality,canUse,price = GetAuctionSellItemInfo() reg.quantity = quantity reg.additional.event = "SetAuctionSellItem" reg.additional.canUse = canUse end, SetBagItem = function(self,bag,slot) OnTooltipCleared(self) local reg = tooltipRegistry[self] reg.ignoreOnCleared = true local tex,q,l,_,r,loot = GetContainerItemInfo(bag,slot) if tex then -- texture (only used as a test for occupied bagslot) reg.quantity = q reg.additional.event = "SetBagItem" reg.additional.eventContainer = bag reg.additional.eventIndex = slot reg.additional.readable = r reg.additional.locked = l reg.additional.lootable = loot end end, SetBuybackItem = function(self,index) OnTooltipCleared(self) local reg = tooltipRegistry[self] reg.ignoreOnCleared = true local name,texture,price,quantity = GetBuybackItemInfo(index) reg.quantity = quantity reg.additional.event = "SetBuybackItem" reg.additional.eventIndex = index end, SetGuildBankItem = function(self,tab,index) OnTooltipCleared(self) local reg = tooltipRegistry[self] reg.ignoreOnCleared = true local texture,quantity,locked = GetGuildBankItemInfo(tab,index) reg.quantity = quantity reg.additional.event = "SetGuildBankItem" reg.additional.eventContainer = tab reg.additional.eventIndex = index reg.additional.locked = locked end, SetInboxItem = function(self,index) OnTooltipCleared(self) local reg = tooltipRegistry[self] reg.ignoreOnCleared = true local _,_,q,_,cu = GetInboxItem(index) reg.quantity = q reg.additional.event = "SetInboxItem" reg.additional.eventIndex = index reg.additional.canUse = cu end, SetInventoryItem = function(self,unit,index) OnTooltipCleared(self) local reg = tooltipRegistry[self] reg.ignoreOnCleared = true local q = GetInventoryItemCount(unit,index) reg.quantity = q reg.additional.event = "SetInventoryItem" reg.additional.eventIndex = index reg.additional.eventUnit = unit end, SetLootItem = function(self,index) OnTooltipCleared(self) local reg = tooltipRegistry[self] reg.ignoreOnCleared = true local _,_,q = GetLootSlotInfo(index) reg.quantity = q reg.additional.event = "SetLootItem" reg.additional.eventIndex = index end, SetLootRollItem = function(self,index) OnTooltipCleared(self) local reg = tooltipRegistry[self] reg.ignoreOnCleared = true local texture, name, count, quality = GetLootRollItemInfo(index) reg.quantity = count reg.additional.event = "SetLootRollItem" reg.additional.eventIndex = index end, SetMerchantItem = function(self,index) OnTooltipCleared(self) local reg = tooltipRegistry[self] reg.ignoreOnCleared = true local _,_,p,q,na,cu,ec = GetMerchantItemInfo(index) reg.quantity = q reg.additional.event = "SetLootItem" reg.additional.eventIndex = index reg.additional.price = p reg.additional.numAvailable = na reg.additional.canUse = cu reg.additional.extendedCost = ec end, SetQuestItem = function(self,type,index) OnTooltipCleared(self) local reg = tooltipRegistry[self] reg.ignoreOnCleared = true local _,_,q,_,cu = GetQuestItemInfo(type,index) reg.quantity = q reg.additional.event = "SetQuestItem" reg.additional.eventType = type reg.additional.eventIndex = index reg.additional.canUse = cu end, SetQuestLogItem = function(self,type,index) OnTooltipCleared(self) local reg = tooltipRegistry[self] reg.ignoreOnCleared = true local _,q,cu if type == "choice" then _,_,q,_,cu = GetQuestLogChoiceInfo(index) else _,_,q,_,cu = GetQuestLogRewardInfo(index) end reg.quantity = q reg.additional.event = "SetQuestLogItem" reg.additional.eventType = type reg.additional.eventIndex = index reg.additional.canUse = cu end, SetSendMailItem = function(self,index) OnTooltipCleared(self) local reg = tooltipRegistry[self] reg.ignoreOnCleared = true local name,texture,quantity = GetSendMailItem(index) reg.quantity = quantity reg.additional.event = "SetSendMailItem" reg.additional.eventIndex = index end, SetTradePlayerItem = function(self,index) OnTooltipCleared(self) local reg = tooltipRegistry[self] reg.ignoreOnCleared = true local name, texture, quantity = GetTradePlayerItemInfo(index) reg.quantity = quantity reg.additional.event = "SetTradePlayerItem" reg.additional.eventIndex = index end, SetTradeTargetItem = function(self,index) OnTooltipCleared(self) local reg = tooltipRegistry[self] reg.ignoreOnCleared = true local name, texture, quantity = GetTradeTargetItemInfo(index) reg.quantity = quantity reg.additional.event = "SetTradeTargetItem" reg.additional.eventIndex = index end, SetTradeSkillItem = function(self,index,reagentIndex) OnTooltipCleared(self) local reg = tooltipRegistry[self] reg.ignoreOnCleared = true reg.additional.event = "SetTradeSkillItem" reg.additional.eventIndex = index reg.additional.eventReagentIndex = reagentIndex if reagentIndex then local _,_,q,rc = GetTradeSkillReagentInfo(index,reagentIndex) reg.quantity = q reg.additional.playerReagentCount = rc else local link = GetTradeSkillItemLink(index) reg.additional.link = link reg.quantity = GetTradeSkillNumMade(index) if link and link:match("spell:%d") then SetSpellDetail(reg, link) end end end, SetHyperlink = function(self,link) local reg = tooltipRegistry[self] if reg.ignoreSetHyperlink then return end OnTooltipCleared(self) reg.ignoreOnCleared = true reg.additional.event = "SetHyperlink" reg.additional.eventLink = link reg.additional.link = link end, -- Default disabled events: --[[ disabled due to taint issues SetAction = function(self,actionid) OnTooltipCleared(self) local reg = tooltipRegistry[self] reg.ignoreOnCleared = true local t,id,sub = GetActionInfo(actionid) reg.additional.event = "SetAction" reg.additional.eventIndex = actionid reg.additional.actionType = t reg.additional.actionIndex = id reg.additional.actionSubtype = subtype if t == "item" then reg.quantity = GetActionCount(actionid) elseif t == "spell" then if id and id > 0 then local link = GetSpellLink(id, sub) SetSpellDetail(reg, link) end end end, --]] SetCurrencyToken = function(self, index) OnTooltipCleared(self) local reg = tooltipRegistry[self] reg.ignoreOnCleared = true reg.additional.event = "SetCurrencyToken" reg.additional.eventIndex = index end, SetPetAction = function(self, index) OnTooltipCleared(self) local reg = tooltipRegistry[self] reg.ignoreOnCleared = true reg.additional.event = "SetPetAction" reg.additional.eventIndex = index end, SetQuestLogRewardSpell = function(self) OnTooltipCleared(self) local reg = tooltipRegistry[self] reg.ignoreOnCleared = true reg.additional.event = "SetQuestLogRewardSpell" end, SetQuestRewardSpell = function(self) OnTooltipCleared(self) local reg = tooltipRegistry[self] reg.ignoreOnCleared = true reg.additional.event = "SetQuestRewardSpell" end, SetShapeshift = function(self, index) OnTooltipCleared(self) local reg = tooltipRegistry[self] reg.ignoreOnCleared = true reg.additional.event = "SetShapeshift" reg.additional.eventIndex = index end, SetSpellBookItem = function(self,index,booktype) OnTooltipCleared(self) local reg = tooltipRegistry[self] reg.ignoreOnCleared = true local link = GetSpellLink(index, booktype) if link then reg.additional.event = "SetSpellBookItem" reg.additional.eventIndex = index reg.additional.eventType = booktype SetSpellDetail(reg, link) end end, SetTalent = function(self, index, isInspect, talentGroup, inspectedUnit, classID) OnTooltipCleared(self) local reg = tooltipRegistry[self] reg.ignoreOnCleared = true reg.additional.event = "SetTalent" reg.additional.eventIndex = index end, SetTrainerService = function(self, index) OnTooltipCleared(self) local reg = tooltipRegistry[self] reg.ignoreOnCleared = true reg.additional.event = "SetTrainerService" reg.additional.eventIndex = index end, -- SetUnit = function(self, unit) -- OnTooltipCleared(self) -- local reg = tooltipRegistry[self] -- reg.ignoreOnCleared = true -- reg.additional.event = "SetUnit" -- reg.additional.eventUnit= unit -- end, --[[ disabled due to taint issues SetUnitAura = function(self, unit, index, filter) OnTooltipCleared(self) local reg = tooltipRegistry[self] reg.ignoreOnCleared = true reg.additional.event = "SetUnitAura" reg.additional.eventUnit = unit reg.additional.eventIndex = index reg.additional.eventFilter = filter end, --]] SetUnitBuff = function(self, unit, index, filter) OnTooltipCleared(self) local reg = tooltipRegistry[self] reg.ignoreOnCleared = true reg.additional.event = "SetUnitBuff" reg.additional.eventUnit = unit reg.additional.eventIndex = index reg.additional.eventFilter = filter end, SetUnitDebuff = function(self, unit, index, filter) OnTooltipCleared(self) local reg = tooltipRegistry[self] reg.ignoreOnCleared = true reg.additional.event = "SetUnitDebuff" reg.additional.eventUnit = unit reg.additional.eventIndex = index reg.additional.eventFilter = filter end, } local function posthookClearIgnore(self) tooltipRegistry[self].ignoreOnCleared = nil end tooltipMethodPosthooks = { SetAuctionItem = posthookClearIgnore, SetAuctionSellItem = posthookClearIgnore, SetBagItem = posthookClearIgnore, SetBuybackItem = posthookClearIgnore, SetGuildBankItem = posthookClearIgnore, SetInboxItem = posthookClearIgnore, SetInventoryItem = posthookClearIgnore, SetLootItem = posthookClearIgnore, SetLootRollItem = posthookClearIgnore, SetMerchantItem = posthookClearIgnore, SetQuestItem = posthookClearIgnore, SetQuestLogItem = posthookClearIgnore, SetSendMailItem = posthookClearIgnore, SetTradePlayerItem = posthookClearIgnore, SetTradeTargetItem = posthookClearIgnore, SetTradeSkillItem = posthookClearIgnore, SetHyperlink = function(self) local reg = tooltipRegistry[self] if not reg.ignoreSetHyperlink then reg.ignoreOnCleared = nil end end, --SetAction = posthookClearIgnore, SetCurrencyToken = posthookClearIgnore, SetPetAction = posthookClearIgnore, SetQuestLogRewardSpell = posthookClearIgnore, SetQuestRewardSpell = posthookClearIgnore, SetShapeshift = posthookClearIgnore, SetSpellBookItem = posthookClearIgnore, SetTalent = posthookClearIgnore, SetTrainerService = posthookClearIgnore, -- SetUnit = posthookClearIgnore, --SetUnitAura = posthookClearIgnore, SetUnitBuff = posthookClearIgnore, SetUnitDebuff = posthookClearIgnore, } end do -- ExtraTip "class" definition local methods = {"InitLines","Attach","Show","MatchSize","Release","NeedsRefresh","SetParentClamp"} local scripts = {"OnShow","OnHide","OnSizeChanged"} local numTips = 0 local class = {} ExtraTipClass = class local addLine,addDoubleLine,show = GameTooltip.AddLine,GameTooltip.AddDoubleLine,GameTooltip.Show local line_mt = { __index = function(t,k) local v = _G[t.name..k] rawset(t,k,v) return v end } function class:new() local n = numTips + 1 numTips = n local o = CreateFrame("GameTooltip",LIBSTRING.."Tooltip"..n,UIParent,"GameTooltipTemplate") for _,method in pairs(methods) do o[method] = self[method] end for _,script in pairs(scripts) do o:SetScript(script,self[script]) end o.Left = setmetatable({name = o:GetName().."TextLeft"},line_mt) o.Right = setmetatable({name = o:GetName().."TextRight"},line_mt) return o end function class:Attach(tooltip) if self.parent then self:SetParentClamp(0) end self.parent = tooltip self:SetParent(tooltip) self:SetOwner(tooltip,"ANCHOR_NONE") self:SetPoint("TOP",tooltip,"BOTTOM") end function class:Release() if self.parent then self:SetParentClamp(0) end self.parent = nil self:SetParent(nil) self.minWidth = 0 end function class:InitLines() local nlines = self:NumLines() local changedLines = self.changedLines or 0 if changedLines < nlines then for i = changedLines + 1, nlines do local left,right = self.Left[i],self.Right[i] local font if i == 1 then font = GameFontNormal else font = GameFontNormalSmall end local r,g,b,a r,g,b,a = left:GetTextColor() left:SetFontObject(font) left:SetTextColor(r,g,b,a) r,g,b,a = right:GetTextColor() right:SetFontObject(font) right:SetTextColor(r,g,b,a) end self.changedLines = nlines return true end end local function refresh(self) self:NeedsRefresh(false) self:MatchSize() end function class:NeedsRefresh(flag) if flag then self:SetScript("OnUpdate",refresh) else self:SetScript("OnUpdate",nil) end end function class:SetParentClamp(h) local p = self.parent if not p then return end local l,r,t,b = p:GetClampRectInsets() p:SetClampRectInsets(l,r,t,-h) self:NeedsRefresh(true) end function class:OnShow() self:SetParentClamp(self:GetHeight()) end function class:OnSizeChanged(w,h) self:SetParentClamp(h) end function class:OnHide() self:SetParentClamp(0) end -- The right-side text is statically positioned to the right of the left-side text. -- As a result, manually changing the width of the tooltip causes the right-side text to not be in the right place. local function fixRight(tooltip, shift) local rights, rightname rights = tooltip.Right if not rights then rightname = tooltip:GetName().."TextRight" end for line = 1, tooltip:NumLines() do local right if rights then right = rights[line] else right = _G[rightname..line] end if right and right:IsVisible() then for index = 1, right:GetNumPoints() do local point, relativeTo, relativePoint, xofs, yofs = right:GetPoint(index) if xofs then right:SetPoint(point, relativeTo, relativePoint, xofs + shift, yofs) end end end end end function class:MatchSize() local p = self.parent local pw = p:GetWidth() local w = self:GetWidth() local d = pw - w if d > .005 then self.sizing = true self:SetWidth(pw) fixRight(self, d) elseif d < -.005 then local reg = lib.tooltipRegistry[p] if not reg.NoColumns then self.sizing = true p:SetWidth(w) fixRight(p, -d) end end end function class:Show() show(self) if self:InitLines() then -- sometimes 'show' needs to be called twice to correctly resize the tooltip -- calling it once (before OR after InitLines) doesn't always work {LTT-42} show(self) end end end -- More housekeeping upgrade stuff lib:SetEmbedMode(lib.embedMode) lib:Activate() --[[ Test Code ----------------------------------------------------- local LT = LibStub("LibExtraTip-1") LT:RegisterTooltip(GameTooltip) LT:RegisterTooltip(ItemRefTooltip) --[=[ LT:AddCallback(function(tip,item,quantity,name,link,quality,ilvl) LT:AddDoubleLine(tip,"Item Level:",ilvl,nil,nil,nil,1,1,1,0) LT:AddDoubleLine(tip,"Item Level:",ilvl,1,1,1,0) LT:AddDoubleLine(tip,"Item Level:",ilvl,0) end,0) --]=] LT:AddCallback(function(tip,item,quantity,name,link,quality,ilvl) quantity = quantity or 1 local price = GetSellValue(item) if price then LT:AddMoneyLine(tip,"Sell to vendor"..(quantity > 1 and "("..quantity..")" or "") .. ":",price*quantity,1) end LT:AddDoubleLine(tip,"Item Level:",ilvl,1) end) -- Test Code ]]----------------------------------------------------- --[[ Debugging code local f = {"AddDoubleLine", "AddFontStrings", "AddLine", "AddTexture", "AppendText", "ClearLines", "FadeOut", "GetAnchorType", "GetItem", "GetSpell", "GetOwner", "GetUnit", "IsUnit", "NumLines", "SetAction", "SetAuctionCompareItem", "SetAuctionItem", "SetAuctionSellItem", "SetBagItem", "SetBuybackItem", "SetCraftItem", "SetCraftSpell", "SetCurrencyToken", "SetGuildBankItem", "SetHyperlink", "SetInboxItem", "SetInventoryItem", "SetLootItem", "SetLootRollItem", "SetMerchantCompareItem", "SetMerchantItem", "SetMinimumWidth", "SetOwner", "SetPadding", "SetPetAction", "SetPlayerBuff", "SetQuestItem", "SetQuestLogItem", "SetQuestLogRewardSpell", "SetQuestRewardSpell", "SetSendMailItem", "SetShapeshift", "SetSpell", "SetTalent", "SetText", "SetTracking", "SetTradePlayerItem", "SetTradeSkillItem", "SetTradeTargetItem", "SetTrainerService", "SetUnit", "SetUnitAura", "SetUnitBuff", "SetUnitDebuff"} for _,k in ipairs(f) do print("Hooking ", k) local h = GameTooltip[k] GameTooltip[k] = function(...) local t for i=2,5 do if not t then t = debugstack(i,1,0):gsub("\n[.\s\n]*", ""):gsub(": in function.*", "") if not t:match("Interface\\") then t = nil else t = t:gsub("Interface\\", "") end end end if t then print(t..": "..k.."(", ..., ")") elseif true then print("-------"); print(debugstack()); print("Call to: "..k.."(", ..., ")") print("-------"); end return h(...) end end --]]