Files
Bagshui/Components/Bagshui.Tooltips.lua
T
OEVG 896f46f0d1
enUS Localization Update Announcement / localization-update-announcement (push) Canceled after 0s
Bagshui 1.6.4
2026-10-30 15:55:43 +00:00

747 lines
31 KiB
Lua

-- Bagshui Core: Tooltips
-- General tooltip stuff along with sneaky tooltip hooking to globally display
-- the Bagshui info tooltip when Alt is held.
-- There's some slight redundancy here and in Inventory.Ui.ItemButton.lua, but it
-- doesn't feel like enough to warrant refactoring right now.
--
-- Exposes:
-- - BsTooltips (table of { name = tooltip }).
-- - BsInfoTooltip, BsCursorTooltip, BsIconButtonTooltip, BsHiddenTooltip (pointers to tooltips within BsTooltips).
Bagshui:AddComponent(function()
--- Initialize tooltips that are shared across all of Bagshui.
function Bagshui:InitTooltips()
-- Info Tooltip -- because sometimes GameTooltip runs out of space.
self.tooltips.info = self:CreateTooltip("InfoTooltip", nil, true)
-- Cursor Tooltip for "carrying" items on the cursor in edit mode.
self.tooltips.cursor = self:CreateTooltip("CursorTooltip")
local cursorTooltipName = self.tooltips.cursor.bagshuiData.name
-- Move texture to top left.
local cursorTooltipTexture1 = _G[cursorTooltipName .. "Texture1"]
cursorTooltipTexture1:ClearAllPoints()
cursorTooltipTexture1:SetPoint("TOPLEFT", self.tooltips.cursor, "TOPLEFT", 10, -9)
cursorTooltipTexture1:SetVertexColor(unpack(BS_COLOR.YELLOW))
cursorTooltipTexture1:Show()
-- Shift text left so it doesn't overlap with texture.
local textLeft1 = _G[cursorTooltipName .. "TextLeft1"]
local textLeft2 = _G[cursorTooltipName .. "TextLeft2"]
textLeft1:SetPoint("TOPLEFT", self.tooltips.cursor, "TOPLEFT", 26, -10)
textLeft2:ClearAllPoints()
textLeft2:SetPoint("LEFT", cursorTooltipTexture1, "LEFT", 0, 0)
textLeft2:SetPoint("TOP", textLeft1, "BOTTOM", 0, -2)
self.tooltips.cursor.bagshuiData.textLeft1 = textLeft1
self.tooltips.cursor.bagshuiData.textLeft2 = textLeft2
self.tooltips.cursor.bagshuiData.texture1 = cursorTooltipTexture1
-- Icon button tooltip for toolbar buttons, etc.
self.tooltips.iconButton = self:CreateTooltip("IconButtonTooltip")
local iconButtonTooltipName = self.tooltips.iconButton.bagshuiData.name
-- Set all text the same size as the title so that when we scale down the tooltip
-- the non-title text doesn't become unreadable.
local _, titleFontSize = _G[iconButtonTooltipName .. "TextLeft1"]:GetFont()
for i = 2, 10 do
_G[iconButtonTooltipName .. "TextLeft" .. i]:SetFont(
_G[iconButtonTooltipName .. "TextLeft" .. i]:GetFont(),
titleFontSize
)
end
self.tooltips.iconButton:SetScale(0.65)
-- Hidden tooltip for retrieving item tooltips and forcing items to load into the local game cache.
self.tooltips.hidden = self:CreateTooltip("HiddenTooltip")
-- This makes looping through the tooltip easier.
self.tooltips.hidden.bagshuiData.textFieldsPerLine = { "Left", "Right" }
-- Must have an owner to function.
self.tooltips.hidden:SetOwner(_G.WorldFrame, "ANCHOR_NONE")
-- These font strings are required to allow tooltip the SetBagItem(), etc. methods to dynamically add new lines.
self.tooltips.hidden:AddFontStrings(
self.tooltips.hidden:CreateFontString("$parentTextLeft1", nil, "GameTooltipText"),
self.tooltips.hidden:CreateFontString("$parentTextRight1", nil, "GameTooltipText")
)
-- Avoid `SetTooltipMoney()` errors when very long tooltips are loaded.
-- There are probably other ways around this, but we don't (currently) need money info
-- in the hidden tooltip.
-- https://github.com/veechs/Bagshui/issues/102
self.tooltips.hidden:SetScript("OnTooltipAddMoney", nil)
-- Expose tooltips.
Bagshui.environment.BsTooltips = self.tooltips
for _, tooltip in pairs(self.tooltips) do
self.environment["Bs" .. tooltip.bagshuiData.shortName] = tooltip
end
end
--- Create a new tooltip.
---@param name string Unique name for the tooltip (will be passed to `Ui:CreateElementName()`).
---@param inherits string? Widget template.
---@param smallFont boolean? When true, change all text to `GameFontNormalSmall`.
---@return table tooltip
function Bagshui:CreateTooltip(name, inherits, smallFont)
local tooltipName = Bagshui.prototypes.Ui:CreateElementName(name)
local tooltip = _G.CreateFrame(
"GameTooltip",
tooltipName,
_G.UIParent, -- Required for tooltips to respect UI scale.
inherits or "GameTooltipTemplate"
)
tooltip.bagshuiData = {
name = tooltipName,
shortName = name,
}
-- Apply per-skin tooltip styling.
if BsSkin.tooltipSkinFunc then
BsSkin.tooltipSkinFunc(tooltip)
end
-- Shrink all the tooltip lines
if smallFont then
local i = 1
while true do
local fontString = _G[tooltip.bagshuiData.name .. "TextLeft" .. i]
if not fontString then
break
end
fontString:SetFontObject("GameFontNormalSmall")
_G[tooltip.bagshuiData.name .. "TextRight" .. i]:SetFontObject("GameFontNormalSmall")
i = i + 1
end
end
return tooltip
end
--- Wrapper for `Bagshui:FormatTooltipLine()` that automatically adds to the Bagshui info tooltip.
---@param tooltip table WoW UI tooltip.
---@param text string? Primary text value.
---@param label string? Labels to display in front of text, if any.
---@param title boolean? Use highlight color instead of normal color for `text`.
---@param indent boolean? Indent the entire line.
function Bagshui:AddTooltipLine(tooltip, text, label, title, indent)
tooltip:AddLine(self:FormatTooltipLine(text, label, title, indent))
end
--- Prepare text to be added to a tooltip. Will colorize, label, and indent.
---@param text string? Primary text value.
---@param label string? Labels to display in front of text, if any.
---@param title boolean? Use highlight color instead of normal color for `text`.
---@param indent boolean? Indent the entire line.
---@return string tooltipText
function Bagshui:FormatTooltipLine(text, label, title, indent)
local tooltipText = (title and HIGHLIGHT_FONT_COLOR_CODE or NORMAL_FONT_COLOR_CODE)
.. (text or "")
.. FONT_COLOR_CODE_CLOSE
if label then
tooltipText = BsUtil.Trim(
(title and BS_FONT_COLOR.BAGSHUI or HIGHLIGHT_FONT_COLOR_CODE)
.. label
.. FONT_COLOR_CODE_CLOSE
.. " "
.. tooltipText
)
end
if indent then
tooltipText = BS_INDENT .. tooltipText
end
return tooltipText
end
--- Display the given tooltip after a delay if it's still owned by the same element.
--- To utilize this, use `<tooltip>:AddLine()`, not `SetText()` since the latter instantly makes the tooltip visible:
--- ```
--- <tooltip>:SetOwner(<owner>, "ANCHOR_TOPLEFT")
--- <tooltip>:ClearLines()
--- <tooltip>:AddLine("Text")
--- Bagshui:ShowTooltipAfterDelay(<tooltip>, <owner>)
--- ```
---@param tooltip table Tooltip frame to show.
---@param expectedOwner table Frame that should own the tooltip.
---@param tooltipGroupElement table? UI element that conceptually groups multiple items together and will be used to determine when the delay to display tooltips should be shortened.
---@param delayOverride number? Delay after which the tooltip should be shown. `BS_TOOLTIP_DELAY_SECONDS.DEFAULT` will be used if not provided.
---@param noTooltipDelayShorting boolean? When true, do not shorten subsequent tooltip display delays.
---@param postDisplayCallback function? Callback to trigger after the tooltip is shown.
function Bagshui:ShowTooltipAfterDelay(
tooltip,
expectedOwner,
tooltipGroupElement,
delayOverride,
noTooltipDelayShorting,
postDisplayCallback
)
tooltipGroupElement = tooltipGroupElement or expectedOwner:GetParent()
if not tooltipGroupElement.bagshuiData then
tooltipGroupElement.bagshuiData = {}
end
-- Set the tooltip group tracking property on the tooltip owner.
if not expectedOwner.bagshuiData then
expectedOwner.bagshuiData = {}
end
expectedOwner.bagshuiData._showTooltipAfterDelay_TooltipGroupElement = tooltipGroupElement
-- Default delay
local delaySeconds = delayOverride or BS_TOOLTIP_DELAY_SECONDS.DEFAULT
-- Shorten delay if a tooltip has been displayed for this group in the last X seconds.
if
not noTooltipDelayShorting
and type(tooltipGroupElement.bagshuiData._showTooltipAfterDelay_LastShown) == "number"
and _G.GetTime() - tooltipGroupElement.bagshuiData._showTooltipAfterDelay_LastShown
< BS_TOOLTIP_DELAY_SECONDS.USE_SHORTENED_AFTER_LAST_SHOW_FOR
then
delaySeconds = BS_TOOLTIP_DELAY_SECONDS.SHORTENED
end
-- Show the tooltip after the delay.
if delayOverride == 0 then
self:ShowTooltipIfStillOwned(tooltip, expectedOwner, postDisplayCallback)
else
Bagshui:QueueClassCallback(
self,
self.ShowTooltipIfStillOwned,
delaySeconds,
false,
tooltip,
expectedOwner,
postDisplayCallback
)
end
end
--- Helper function for ShowTooltipAfterDelay that actually makes the tooltip
--- visible if it's still owned by the same element once the delay has expired.
---@param tooltip table Tooltip frame to show.
---@param expectedOwner table Frame that should own the tooltip.
---@param postDisplayCallback function?
function Bagshui:ShowTooltipIfStillOwned(tooltip, expectedOwner, postDisplayCallback)
if tooltip and tooltip.IsOwned and tooltip:IsOwned(expectedOwner) then
tooltip:Show()
self:ShortenTooltipDelay(expectedOwner, true) -- Update the time of last display for this tooltip group.
if postDisplayCallback then
postDisplayCallback()
end
end
end
-- Helper function for `Bagshui:ShowTooltipAfterDelay()` to update the
-- `_showTooltipAfterDelay_LastShown` property of the tracking element.
---@param element table Frame triggering the tooltip display.
---@param shorten boolean? `true` to set the LastShown property, which will shorten the next display of tooltips within this tooltip group.
function Bagshui:ShortenTooltipDelay(element, shorten)
-- Make sure the Bagshui table exists to avoid errors.
if not element.bagshuiData then
element.bagshuiData = {}
end
-- If this is an element that has a breadcrumb up to a parent element, use that instead.
-- This will be triggered most of the time, but it gives the option to pass in a parent frame
-- and reset tooltip delays directly if desired. (Initially this was going to be how things
-- worked and then it turned out to not be necessary but the functionality is staying I guess?).
if element.bagshuiData._showTooltipAfterDelay_TooltipGroupElement then
element = element.bagshuiData._showTooltipAfterDelay_TooltipGroupElement
-- Again, make sure the Bagshui table exists since we have changed elements.
if not element.bagshuiData then
element.bagshuiData = {}
end
end
-- Set or clear the LastShown property.
element.bagshuiData._showTooltipAfterDelay_LastShown = shorten and _G.GetTime() or nil
end
--- Hide GameTooltip and all Bagshui Info Tooltips if owned by `this` or `force == true`.
---@param this table? Frame to check for ownership instead of the global `this`.
---@param force boolean? Hide tooltips without checking ownership.
function Bagshui:HideTooltips(this, force)
this = this or _G.this
if force or _G.GameTooltip:IsOwned(this) then
_G.GameTooltip:Hide()
end
for _, tooltip in pairs(self.tooltips) do
if force or tooltip:IsOwned(this) then
tooltip:Hide()
end
end
end
--- Prepare an info tooltip to be displayed. Anchors above or below the parent tooltip
--- depending on where there is more vertical space.
---@param owner table Frame that owns the tooltip (parameter for `<tooltip>:SetOwner()`).
---@param managedElsewhere boolean? Don't do any automatic filling of tooltip content in `Bagshui:ManageInfoTooltip()`.
---@param forceBelow boolean? When true, always display info tooltip below `attachToTooltip` regardless of `attachToTooltip`'s vertical position.
---@param attachToTooltip table? "Parent" tooltip to which the info tooltip will attach (default: `GameTooltip`).
---@param infoTooltip table? Info tooltip, if `BsInfoTooltip` shouldn't be used.
function Bagshui:SetInfoTooltipPosition(owner, managedElsewhere, forceBelow, attachToTooltip, infoTooltip)
-- Always need an owner. Don't throw an error though. Upstream code in
-- Bagshui:ManageInfoTooltip() tries to guard against this, but let's be sure.
if not owner then
return
end
attachToTooltip = attachToTooltip or _G.GameTooltip
-- Sanity check.
if not attachToTooltip.bagshuiData then
return
end
infoTooltip = infoTooltip or BsInfoTooltip
infoTooltip:SetOwner(owner, "ANCHOR_PRESERVE")
infoTooltip:ClearAllPoints()
-- Anchor info tooltip to left/right/center of tooltip.
local leftRight = ""
if (attachToTooltip:GetNumPoints()) or 0 > 0 then
local point = attachToTooltip:GetPoint(1)
leftRight = BsUtil.GetAnchorLeftRight(point)
end
-- Consumed by `Bagshui:ShowInfoTooltip()`.
infoTooltip.anchorLeftRight = leftRight
-- Consumed by `Bagshui:ManageInfoTooltip()`.
attachToTooltip.bagshuiData.infoTooltipManagedElsewhere = managedElsewhere
-- Show info tooltip above or below depending on tooltip position.
if
not forceBelow
and (
(attachToTooltip:GetTop() and _G.GetScreenHeight() - attachToTooltip:GetTop() > attachToTooltip:GetBottom())
or attachToTooltip.bagshuiData.displayNextAbove
)
then
-- Above.
infoTooltip:SetPoint("BOTTOM" .. leftRight, attachToTooltip, "TOP" .. leftRight, 0, -BsSkin.infoTooltipYOffset)
else
-- Below.
infoTooltip:SetPoint("TOP" .. leftRight, attachToTooltip, "BOTTOM" .. leftRight, 0, BsSkin.infoTooltipYOffset)
end
end
--- Because info tooltips are anchored above/below another tooltip, sometimes
--- things run up against the edge of the screen and the tooltips can overlap.
--- We can (mostly) prevent this by doing some re-anchoring fancy footwork.
---@param infoTooltip any
function Bagshui:ShowInfoTooltip(infoTooltip)
infoTooltip = infoTooltip or BsInfoTooltip
-- First, get the tooltip on the screen so its size will be calculated.
infoTooltip:Show()
-- Info tooltip can't be used.
if not infoTooltip:IsVisible() then
return
end
if not ((infoTooltip:GetNumPoints() or 0) > 0) then
return
end
-- Determine where the info tooltip is currently anchored.
local point, tooltip = infoTooltip:GetPoint(1)
-- Anchoring tooltip can't be used.
if not tooltip.bagshuiData then
return
end
if not ((tooltip:GetNumPoints() or 0) > 0) then
return
end
-- Store anchor points for the tooltip to which the info tooltip is anchored
-- so that `Bagshui:ManageTooltip()` can restore them.
tooltip.bagshuiData.originalPoint, tooltip.bagshuiData.originalAnchorToFrame, tooltip.bagshuiData.originalAnchorToPoint, tooltip.bagshuiData.originalXOffset, tooltip.bagshuiData.originalYOffset =
tooltip:GetPoint(1)
-- Decide whether the info tooltip is above or below the tooltip to which it's anchored.
local topTooltip, bottomTooltip
if string.find(point, "^BOTTOM") then
-- Above.
topTooltip = infoTooltip
bottomTooltip = tooltip
else
-- Below.
topTooltip = tooltip
bottomTooltip = infoTooltip
end
-- Left/right/center-aligned?
local leftRight = BsUtil.GetAnchorLeftRight(point)
-- When one of the tooltips is at the extreme edge, re-anchor the other tooltip
-- to it so one can't slide under the other.
if topTooltip:GetTop() / _G.UIParent:GetEffectiveScale() >= _G.UIParent:GetTop() then
-- Top tooltip is at the top of the screen.
topTooltip:ClearAllPoints()
bottomTooltip:ClearAllPoints()
bottomTooltip:SetPoint("TOP" .. leftRight, topTooltip, "BOTTOM" .. leftRight, 0, BsSkin.infoTooltipYOffset)
elseif bottomTooltip:GetBottom() / _G.UIParent:GetEffectiveScale() <= _G.UIParent:GetBottom() then
-- Bottom tooltip is at the bottom of the screen.
topTooltip:ClearAllPoints()
bottomTooltip:ClearAllPoints()
topTooltip:SetPoint("BOTTOM" .. leftRight, bottomTooltip, "TOP" .. leftRight, 0, -BsSkin.infoTooltipYOffset)
end
end
--- Called from each hooked tooltip's OnShow, OnHide, and OnUpdate to decide whether
--- the info tooltip should be shown or hidden.
---@param tooltip any
function Bagshui:ManageInfoTooltip(tooltip)
if
-- Don't do anything if global tooltip hooks are disabled.
not BsSettings.globalInfoTooltips
-- Exception for Bagshui bag slot buttons. This would be nice to refactor out.
and not (tooltip.bagshuiData and tooltip.bagshuiData.infoTooltipAlwayShow)
then
-- When the setting is toggled, we need to hide the info tooltip unless it has a management exemption.
if
tooltip.bagshuiData
and tooltip.bagshuiData.infoTooltip
and not tooltip.bagshuiData.infoTooltipManagedElsewhere
then
tooltip.bagshuiData.infoTooltip:Hide()
end
return
end
-- Reasons not to proceed.
if
-- Tooltip to which we want to attach isn't visible.
not tooltip:IsVisible()
-- We don't have the information we need.
or not tooltip.bagshuiData
or not tooltip.bagshuiData.lastItemString
or not tooltip.bagshuiData.infoTooltip
-- Exempt from global management (stored when `Bagshui:SetInfoTooltipPosition()` is called).
or tooltip.bagshuiData.infoTooltipManagedElsewhere
then
-- Reset things if there's no exemption.
if tooltip.bagshuiData and not tooltip.bagshuiData.infoTooltipManagedElsewhere then
-- Hide the info tooltip when it's still owned by the previous frame or we don't have ownership data.
if
tooltip.bagshuiData.infoTooltip
and (
(tooltip.bagshuiData.lastOwner and tooltip.bagshuiData.infoTooltip:IsOwned(tooltip.bagshuiData.lastOwner))
or not tooltip.bagshuiData.lastOwner
)
then
tooltip.bagshuiData.infoTooltip:Hide()
end
tooltip.bagshuiData.lastOwner = nil
tooltip.bagshuiData.lastAltKeyState = nil
tooltip.bagshuiData.lastControlKeyState = nil
tooltip.bagshuiData.lastShiftKeyState = nil
end
return
end
-- Triggers to potentially show or hide tooltip.
if
tooltip.bagshuiData.lastAltKeyState ~= _G.IsAltKeyDown()
or tooltip.bagshuiData.lastControlKeyState ~= _G.IsControlKeyDown()
or tooltip.bagshuiData.lastShiftKeyState ~= _G.IsShiftKeyDown()
then
-- Move the parent tooltip back to its original spot if it was adjusted by `Bagshui:ShowInfoTooltip()`.
if tooltip.bagshuiData.originalPoint then
tooltip:ClearAllPoints()
tooltip:SetPoint(
tooltip.bagshuiData.originalPoint,
tooltip.bagshuiData.originalAnchorToFrame,
tooltip.bagshuiData.originalAnchorToPoint,
tooltip.bagshuiData.originalXOffset,
tooltip.bagshuiData.originalYOffset
)
end
-- Should we actually show or hide?
if
tooltip.bagshuiData.lastOwner
and (_G.IsAltKeyDown() or tooltip.bagshuiData.showInfoTooltipWithoutAlt or BsSettings.showInfoTooltipsWithoutAlt)
and not _G.IsShiftKeyDown()
then
-- Need to show.
-- Reset and position.
tooltip.bagshuiData.infoTooltip:ClearLines()
self:SetInfoTooltipPosition(
tooltip.bagshuiData.lastOwner,
false,
false,
tooltip,
tooltip.bagshuiData.infoTooltip
)
-- Determine which version of the tooltip to show.
if _G.IsControlKeyDown() then
-- Item property list.
BsItemInfo:Get(tooltip.bagshuiData.lastItemString, tooltip.bagshuiData.lastItemInfo, true)
BsItemInfo:AddTooltipInfo(tooltip.bagshuiData.lastItemInfo, tooltip.bagshuiData.infoTooltip)
else
-- Basic -- just account-wide counts.
BsCatalog:AddTooltipInfo(tooltip.bagshuiData.lastItemString, tooltip.bagshuiData.infoTooltip)
end
-- Any content to display?
if tooltip.bagshuiData.infoTooltip:NumLines() > 0 then
self:ShowInfoTooltip(tooltip.bagshuiData.infoTooltip)
end
elseif
not tooltip.bagshuiData.lastOwner or tooltip.bagshuiData.infoTooltip:IsOwned(tooltip.bagshuiData.lastOwner)
then
-- Need to hide.
tooltip.bagshuiData.infoTooltip:Hide()
end
-- Store states for next check.
tooltip.bagshuiData.lastAltKeyState = _G.IsAltKeyDown()
tooltip.bagshuiData.lastControlKeyState = _G.IsControlKeyDown()
tooltip.bagshuiData.lastShiftKeyState = _G.IsShiftKeyDown()
end
end
--- Hook the tooltips where we want to intercept `SetHyperlink()`, `SetBagItem()`, etc.
--- so that our info tooltip can be displayed when Alt is held.
function Bagshui:AddInfoTooltipHooks()
self:HookTooltip(_G.GameTooltip)
self:HookTooltip(_G.ItemRefTooltip, true, true)
end
--- Shared function for profession-related tooltips.
---@param self table Tooltip.
---@param type "Craft"|"TradeSkill" Profession type - used to decide what downstream functions to call.
---@param skill string Profession name (passed through from the `:Set*()` function).
---@param slot number? Item slot (passed through from the `:Set*()` function).
---@param subtype string? If the hooked function ends in something other than "Item", specify that here.
---@return unknown
local function hookTooltip_SetProfessionItem(self, type, skill, slot, subtype)
if slot then
self.bagshuiData.lastItemString = BsItemInfo:ParseItemLink(_G["Get" .. type .. "ReagentItemLink"](skill, slot))
else
self.bagshuiData.lastItemString = BsItemInfo:ParseItemLink(_G["Get" .. type .. "ItemLink"](skill))
end
return self.bagshuiData.hooked["Set" .. type .. (subtype or "Item")](self, skill, slot)
end
-- Functions we're hooking on each tooltip.
local hookTooltipFunctions = {
-- These are needed to capture items being displayed in the tooltip.
-- Some guidance taken from aux's [tooltip.lua](https://github.com/shirsig/aux-addon-vanilla/blob/master/core/tooltip.lua).
-- and pfUI's [libtooltip.lua](https://github.com/shagu/pfUI/blob/master/libs/libtooltip.lua)
SetBagItem = function(self, bagNum, slotNum)
self.bagshuiData.lastItemString = BsItemInfo:ParseItemLink(_G.GetContainerItemLink(bagNum, slotNum))
return self.bagshuiData.hooked.SetBagItem(self, bagNum, slotNum)
end,
SetAuctionItem = function(self, type, index)
self.bagshuiData.displayNextAbove = true -- Don't obscure auction listing row.
self.bagshuiData.lastItemString = BsItemInfo:ParseItemLink(_G.GetAuctionItemLink(type, index))
return self.bagshuiData.hooked.SetAuctionItem(self, type, index)
end,
SetAuctionSellItem = function(self)
local name, texture = _G.GetAuctionSellItemInfo()
self.bagshuiData.lastItemString = BsCatalog:FindItemByNameAndTexture(name, texture, "itemString")
return self.bagshuiData.hooked.SetAuctionSellItem(self)
end,
SetCraftItem = function(self, skill, slot)
return hookTooltip_SetProfessionItem(self, "Craft", skill, slot)
end,
SetCraftSpell = function(self, slot)
return hookTooltip_SetProfessionItem(self, "Craft", slot, nil, "Spell")
end,
SetHyperlink = function(self, link)
self.bagshuiData.lastItemString = BsItemInfo:ParseItemLink(link)
-- https://github.com/veechs/Bagshui/issues/52
-- Need to trap errors here because pfQuest adds "quest:" links to the game.
-- This in and of itself isn't a problem, but if the person clicking the
-- chat link isn't running pfQuest but IS using Bagshui, there will be an
-- error thrown that looks like it's a Bagshui issue. In reality, it's
-- simply that Vanilla WoW doesn't know what to do with quest links unless
-- an addon steps in and adds hooks to intercept them.
local ret, error = pcall(self.bagshuiData.hooked.SetHyperlink, self, link)
if error then
-- Display the error without the [Bagshui] prefix so it looks like a
-- standard game message (which it technically is).
Bagshui:ShowErrorMessage(error, nil, nil, nil, nil, nil, nil, nil, true)
end
return ret
end,
SetInboxItem = function(self, id, attachIndex)
local name, texture = _G.GetInboxItem(id)
self.bagshuiData.lastItemString = BsCatalog:FindItemByNameAndTexture(name, texture, "itemString")
return self.bagshuiData.hooked.SetInboxItem(self, id, attachIndex)
end,
SetInventoryItem = function(self, unit, slotNum, nameOnly)
self.bagshuiData.lastItemString = BsItemInfo:ParseItemLink(_G.GetInventoryItemLink(unit, slotNum))
return self.bagshuiData.hooked.SetInventoryItem(self, unit, slotNum, nameOnly)
end,
SetLootItem = function(self, lootSlot)
self.bagshuiData.lastItemString = BsItemInfo:ParseItemLink(_G.GetLootSlotLink(lootSlot))
return self.bagshuiData.hooked.SetLootItem(self, lootSlot)
end,
SetLootRollItem = function(self, id)
self.bagshuiData.lastItemString = BsItemInfo:ParseItemLink(_G.GetLootRollItemLink(id))
return self.bagshuiData.hooked.SetLootRollItem(self, id)
end,
SetMerchantItem = function(self, id)
self.bagshuiData.lastItemString = BsItemInfo:ParseItemLink(_G.GetMerchantItemLink(id))
return self.bagshuiData.hooked.SetMerchantItem(self, id)
end,
SetQuestItem = function(self, type, slot)
self.bagshuiData.lastItemString = BsItemInfo:ParseItemLink(_G.GetQuestItemLink(type, slot))
return self.bagshuiData.hooked.SetQuestItem(self, type, slot)
end,
SetQuestLogItem = function(self, type, slot)
self.bagshuiData.lastItemString = BsItemInfo:ParseItemLink(_G.GetQuestLogItemLink(type, slot))
return self.bagshuiData.hooked.SetQuestLogItem(self, type, slot)
end,
SetSendMailItem = function(self)
-- Vanilla doesn't have GetSendMailItemLink() so we need to figure it out.
local name, texture = _G.GetSendMailItem()
self.bagshuiData.lastItemString = BsCatalog:FindItemByNameAndTexture(name, texture, "itemString")
return self.bagshuiData.hooked.SetSendMailItem(self)
end,
SetTradePlayerItem = function(self, index)
self.bagshuiData.lastItemString = BsItemInfo:ParseItemLink(_G.GetTradePlayerItemLink(index))
return self.bagshuiData.hooked.SetTradePlayerItem(self, index)
end,
SetTradeSkillItem = function(self, skill, slot)
return hookTooltip_SetProfessionItem(self, "TradeSkill", skill, slot)
end,
SetTradeTargetItem = function(self, index)
self.bagshuiData.lastItemString = BsItemInfo:ParseItemLink(_G.GetTradeTargetItemLink(index))
return self.bagshuiData.hooked.SetTradeTargetItem(self, index)
end,
-- Reset all tracking when the tooltip owner changes.
SetOwner = function(self, owner, anchor, xOffset, yOffset)
self.bagshuiData.lastOwner = owner
self.bagshuiData.lastItemString = nil
self.bagshuiData.lastAltKeyState = -1
self.bagshuiData.lastControlKeyState = -1
self.bagshuiData.lastShiftKeyState = -1
self.bagshuiData.showInfoTooltipWithoutAlt = nil
self.bagshuiData.displayNextAbove = nil
self.bagshuiData.originalPoint = nil
self.bagshuiData.originalAnchorToFrame = nil
self.bagshuiData.originalAnchorToPoint = nil
self.bagshuiData.originalXOffset = nil
self.bagshuiData.originalYOffset = nil
-- aux is almost impenetrable and does absolutely wild things with tooltips
-- where it caches the text instead of calling SetHyperlink(), but we *can*
-- luckily discover the item during the SetOwner() call. Refer to `methods.OnIconEnter`
-- in aux's gui\auction_listing.lua file to see why this works.
if _G.IsAddOnLoaded("aux-addon") then
local parent = owner:GetParent()
self.bagshuiData.lastItemString =
BsItemInfo:ParseItemLink((parent and parent.row and parent.row.record and parent.row.record.link))
self.bagshuiData.displayNextAbove = (self.bagshuiData.lastItemString ~= nil) -- Don't obscure auction listing row.
end
-- Clearing this only when the tooltip isn't visible keeps us from
-- incorrectly overriding the value set by our ItemButton code.
if not self:IsVisible() then
self.bagshuiData.infoTooltipManagedElsewhere = false
end
return self.bagshuiData.hooked.SetOwner(self, owner, anchor, xOffset, yOffset)
end,
}
-- Frame scripts that need to be hooked for each tooltip in order to show or hide the info tooltip.
local hookTooltipScripts = {
OnUpdate = function()
Bagshui:ManageInfoTooltip(_G.this.bagshuiData.tooltip)
end,
OnShow = function()
-- Next-frame delay is necessary to prevent flickering tooltips with pfQuest's `/db scan`.
Bagshui:QueueClassCallback(Bagshui, Bagshui.ManageInfoTooltip, nil, nil, _G.this.bagshuiData.tooltip)
end,
OnHide = function()
-- Do a check on the next frame to see if the info tooltip should be hidden.
-- Delay is necessary due to Blizzard's bag slot buttons constantly hiding and
-- re-showing GameTooltip OnUpdate.
Bagshui:QueueClassCallback(Bagshui, Bagshui.ManageInfoTooltip, nil, nil, _G.this.bagshuiData.tooltip)
end,
}
--- Do the necessary work to insert all tooltip hooks.
--- Not using the Hooks class for this because it doesn't currently support hooking
--- functions outside the global namespace.
---@param tooltip table Tooltip to hook.
---@param createInfoTooltip boolean? Create a new info tooltip to work with this tooltip.
---@param showAbove boolean? Place the info tooltip above the hooked tooltip by default.
function Bagshui:HookTooltip(tooltip, createInfoTooltip, showAbove)
if not tooltip then
return
end
if not tooltip.bagshuiData then
tooltip.bagshuiData = {}
end
local tooltipName = (tooltip:GetName() or tostring(tooltip))
local infoTooltip = BsInfoTooltip
if createInfoTooltip then
infoTooltip = self:CreateTooltip(tooltipName .. "Info", nil, true)
end
tooltip.bagshuiData.infoTooltip = infoTooltip
-- Reusable table for `ItemInfo:Get()` results that are displayed when Control+Alt are held.
tooltip.bagshuiData.lastItemInfo = {}
tooltip.bagshuiData.showInfoTooltipAbove = showAbove
tooltip.bagshuiData.hooked = {}
-- Hook functions.
for functionName, hookFunction in pairs(hookTooltipFunctions) do
tooltip.bagshuiData.hooked[functionName] = tooltip[functionName]
tooltip[functionName] = hookFunction
end
-- Hook scripts.
-- Handled by creating a child frame instead of hooking the existing frame's scripts
-- because it seems to behave MUCH better this way.
tooltip.bagshuiData.hookFrame = _G.CreateFrame("Frame", tooltipName .. "BagshuiHookFrame", tooltip)
tooltip.bagshuiData.hookFrame.bagshuiData = {
tooltip = tooltip,
}
for scriptName, hookScript in pairs(hookTooltipScripts) do
tooltip.bagshuiData.hookFrame:SetScript(scriptName, hookScript)
end
end
-- Initialization.
-- Doing this here instead of Bagshui:Init() or Bagshui:AddonLoaded()
-- because we depend on Ui:CreateElementName().
Bagshui:InitTooltips()
Bagshui:AddInfoTooltipHooks()
end)