Thread Tools Display Modes
07-29-19, 04:40 PM   #1
LudiusMaximus
A Rage Talon Dragon Guard
 
LudiusMaximus's Avatar
AddOn Author - Click to view addons
Join Date: Mar 2018
Posts: 320
Inserting a line in GameTooltip at arbitrary position?

I want to put an extra line in the item tooltip directly beneath the "Sell price" line.

I am hooking OnTooltipSetItem to do so:

Code:
GameTooltip:HookScript("OnTooltipSetItem", function(self)
  -- my code
end)
The problem is that I have other addons running which also append their info to the item tooltip.
I guess there is no way to enforce that my addon gets to hook its script first, unless I put it into the optional dependencies of the other addons?

If so, is there another option than scanning the content of the current GameTooltip line by line and inserting my new line after the line beginning with SELL_PRICE?

What would be the most efficient way to do this?
Do I really have to read every line from GameTooltip to a new tooltip or table, then do a GameTooltip:ClearLines() and then GameTooltip:AddLine() everything back?
  Reply With Quote
07-29-19, 06:34 PM   #2
Seerah
Fishing Trainer
 
Seerah's Avatar
WoWInterface Super Mod
Featured
Join Date: Oct 2006
Posts: 10,860
No, just everything after where you want your text. Just change the text using :SetText() on the line you want, and then use :SetText() again for every line after that. (You might need to end with an AddLine().)

/edit: but I'd check to see if the last line in the tooltip is the sell price first before doing all that
__________________
"You'd be surprised how many people violate this simple principle every day of their lives and try to fit square pegs into round holes, ignoring the clear reality that Things Are As They Are." -Benjamin Hoff, The Tao of Pooh

  Reply With Quote
07-30-19, 10:28 AM   #3
LudiusMaximus
A Rage Talon Dragon Guard
 
LudiusMaximus's Avatar
AddOn Author - Click to view addons
Join Date: Mar 2018
Posts: 320
Thanks a lot, this helped me a great deal already.

But copying a tooltip line is not so straight forward, is it?

First of all, I have to check if it is a single or double line by checking
Code:
if (_G[self:GetName().."TextRight"..i]:GetText()) then

And then I have to not just copy the text content but also the formating.

For instance, if I want to duplicate the last line, I would do:

Code:
local copyText = _G["GameTooltipTextLeft"..GameTooltip:NumLines()]:GetText()
local copyR, copyG, copyB = _G["GameTooltipTextLeft"..GameTooltip:NumLines()]:GetTextColor()

GameTooltip:AddLine(copyText, copyR, copyG, copyB)
But what about the wrapText attribute of a tooltip line?
I could not find the equivalent to GetText() and GetTextColor() for that.

I tried _G["GameTooltipTextLeft"..GameTooltip:NumLines()]:CanNonSpaceWrap() but this always returns false.
EDIT: The same for GetIndentedWordWrap().

Last edited by LudiusMaximus : 07-30-19 at 02:30 PM.
  Reply With Quote
07-30-19, 03:38 PM   #4
LudiusMaximus
A Rage Talon Dragon Guard
 
LudiusMaximus's Avatar
AddOn Author - Click to view addons
Join Date: Mar 2018
Posts: 320
When I apply SetText() to a tooltip line like
Code:
_G["GameTooltipTextLeft1"]:SetText("bla", 1, 0, 0, 1, true)
it does not even change the text colour. Only the text is updated.

So if I want to have the same text colours in the modified tooltip, I would have to built my copy tooltip from scratch only using AddLine() and AddDoubleLine().


EDIT: This is not viable either. When I do GameTooltip:ClearLines() and refill the tooltip with my stored plus added lines, future calls of GameTooltip:GetItem() will only get nil, as the item is apparently cleared by ClearLines().

I can overcome this by

Code:
local originalGetItem = GameTooltip.GetItem
GameTooltip:HookScript("OnHide", function(self)
  GameTooltip.GetItem = originalGetItem
end)

and

Code:
local name, link = GameTooltip:GetItem()
GameTooltip:ClearLines()
GameTooltip.GetItem = function(self) return name, link end
But this is getting more and more cumbersome...

Last edited by LudiusMaximus : 07-30-19 at 05:12 PM.
  Reply With Quote
07-31-19, 01:49 AM   #5
SDPhantom
A Pyroguard Emberseer
 
SDPhantom's Avatar
AddOn Author - Click to view addons
Join Date: Jul 2006
Posts: 2,323
A simpler way would be to wait until later in the loading process to hook the tooltip and create a prehook the old fashioned way.
Lua Code:
  1. local loader=CreateFrame("Frame");
  2. loader:RegisterForEvents("PLAYER_LOGIN");-- Register for an event that appears late in the loading process. This makes it more likely our hook is the last to be set up
  3. loader:SetScript("OnEvent",function()
  4.     local oldscript=GameTooltip:GetScript("OnTooltipSetItem");--    Save any previously registered scripts
  5.     GameTooltip:SetScript("OnTooltipSetItem",function(self,...)--   Futureproofing, support extra args to pass to previous scripts
  6. --      Add your text here
  7.  
  8. --      Run any other addons' functions
  9.         if oldscript then return oldscript(self,...); end
  10.     end);
  11. end);
This is done because GameTooltip:HookScript() sets up a posthook, meaning the function you give it runs after any script that's already there. A prehook in contrast runs before any existing function. No protected function should be run from tooltip code, so taint shouldn't be an issue here.

In any occasion, trying to place your addon's text in a certain spot in the tooltip opens up race conditions competing with any other addon that might have that same design choice. The best and most efficient option is to let it be and do nothing.
__________________
WoWInterface AddOns
"All I want is a pretty girl, a decent meal, and the right to shoot lightning at fools."
-Anders (Dragon Age: Origins - Awakening)

Last edited by SDPhantom : 07-31-19 at 02:02 AM.
  Reply With Quote
07-31-19, 06:46 PM   #6
LudiusMaximus
A Rage Talon Dragon Guard
 
LudiusMaximus's Avatar
AddOn Author - Click to view addons
Join Date: Mar 2018
Posts: 320
@SDPhantom: Thanks, but when I do a pre-hook of OnTooltipSetItem(), will there be anything at all in the GameTooltip for me to modify? I do not care about what other addons my later append but I want to rearrange the original GameTooltip content which the BlizzardUI has put there.

Anyway, here is how I solved this now by manually copy-pasting every line.
Unfortunately, I still do not know how to copy the "intended word wrap" of a tooltip line.
But I have thus far not seen any case where the wrap would have been set to false, so I am setting it to true by default for every copied line.


Lua Code:
  1. -- Have to override GameTooltip.GetItem() after calling ClearLines().
  2. -- This will restore the original after the tooltip is closed.
  3. local originalGetItem = GameTooltip.GetItem
  4. GameTooltip:HookScript("OnHide", function(self)
  5.   GameTooltip.GetItem = originalGetItem
  6. end)
  7.  
  8.  
  9.  
  10. GameTooltip:HookScript("OnTooltipSetItem", function(self)
  11.  
  12.   -- Only interested in tooltips with money frame, as I want to put my text below this.
  13.   if not self.shownMoneyFrames then return end
  14.  
  15.   -- The money frame is anchored to a blank line of the tootlip.
  16.   -- Find out which line it is and what the sell price is.
  17.   local moneyFrameLineNumber = nil
  18.   local money = nil
  19.  
  20.   -- Check all shown money frames of the tooltip.
  21.   -- (There is normally only one, except other addons have added more.)
  22.   for i = 1, self.shownMoneyFrames, 1 do
  23.  
  24.     local moneyFrameName = self:GetName().."MoneyFrame"..i
  25.  
  26.     -- If the money frame's PrefixText is "SELL_PRICE:", we assume it is the one we are looking for.
  27.     if _G[moneyFrameName.."PrefixText"]:GetText() == string_format("%s:", SELL_PRICE) then
  28.       local _, moneyFrameAnchor = _G[moneyFrameName]:GetPoint(1)
  29.      
  30.       -- Get line number and amount of money.
  31.       moneyFrameLineNumber = tonumber(string_match(moneyFrameAnchor:GetName(), self:GetName().."TextLeft(%d+)"))
  32.       money = _G[moneyFrameName].staticMoney
  33.  
  34.       break
  35.     end
  36.    
  37.   end
  38.  
  39.  
  40.   if not moneyFrameLineNumber then return end
  41.  
  42.  
  43.   -- Store all text and text colours of the original tooltip lines.
  44.   -- TODO: Unfortunately I do not know how to store the "indented word wrap".
  45.   --       Therefore, we have to put wrap=true for all lines in the new tooltip.
  46.   local leftText = {}
  47.   local leftTextR = {}
  48.   local leftTextG = {}
  49.   local leftTextB = {}
  50.  
  51.   local rightText = {}
  52.   local rightTextR = {}
  53.   local rightTextG = {}
  54.   local rightTextB = {}
  55.  
  56.   -- Store the number of lines for after ClearLines().
  57.   local numLines = self:NumLines()
  58.  
  59.   -- Store all lines of the original tooltip.
  60.   for i = 1, numLines, 1 do
  61.     leftText[i] = _G[self:GetName().."TextLeft"..i]:GetText()
  62.     leftTextR[i], leftTextG[i], leftTextB[i] = _G[self:GetName().."TextLeft"..i]:GetTextColor()
  63.  
  64.     rightText[i] = _G[self:GetName().."TextRight"..i]:GetText()
  65.     rightTextR[i], rightTextG[i], rightTextB[i] = _G[self:GetName().."TextRight"..i]:GetTextColor()
  66.   end
  67.  
  68.  
  69.   self:ClearLines()
  70.   -- Got to override GameTooltip.GetItem(), such that other addons can still use it
  71.   -- to learn which item is displayed. Will be restored after GameTooltip:OnHide() (see above).
  72.   local name, link = self:GetItem()
  73.   self.GetItem = function(self) return name, link end
  74.  
  75.   -- Refill the tooltip with the stored lines plus my added lines.
  76.   for i = 1, moneyFrameLineNumber-1, 1 do
  77.  
  78.     if rightText[i] then
  79.       self:AddDoubleLine(leftText[i], rightText[i], leftTextR[i], leftTextG[i], leftTextB[i], rightTextR[i], rightTextG[i], rightTextB[i])
  80.     else
  81.       -- TODO: Unfortunately I do not know how to store the "indented word wrap".
  82.       --       Therefore, we have to put wrap=true for all lines in the new tooltip.
  83.       self:AddLine(leftText[i], leftTextR[i], leftTextG[i], leftTextB[i], true)
  84.     end
  85.  
  86.   end
  87.  
  88.   -- Set original money frame.
  89.   SetTooltipMoney(self, money, nil, string_format("%s:", SELL_PRICE))
  90.  
  91.   -- Set my new stuff!
  92.   self:AddLine("My new stuff here...")
  93.  
  94.   -- Set the remaining original tooltip lines.
  95.   for i = moneyFrameLineNumber+1, numLines, 1 do
  96.  
  97.     if rightText[i] then
  98.       self:AddDoubleLine(leftText[i], rightText[i], leftTextR[i], leftTextG[i], leftTextB[i], rightTextR[i], rightTextG[i], rightTextB[i])
  99.     else
  100.       -- TODO: Unfortunately I do not know how to store the "indented word wrap".
  101.       --       Therefore, we have to put wrap=true for all lines in the new tooltip.
  102.       self:AddLine(leftText[i], leftTextR[i], leftTextG[i], leftTextB[i], true)
  103.     end
  104.  
  105.   end
  106.  
  107. end
  108. )
  Reply With Quote
08-01-19, 01:07 AM   #7
SDPhantom
A Pyroguard Emberseer
 
SDPhantom's Avatar
AddOn Author - Click to view addons
Join Date: Jul 2006
Posts: 2,323
Since the "Sell Price" line you're wanting to put your text under is the last line in the standard tooltip text, there is no need to try to manually reconstruct the tooltip when the solution I provided has your code run before anything else does. And yes, all the standard text is there by the time the script is run, otherwise, you'd have addons' text popping up at the top instead of the bottom.
__________________
WoWInterface AddOns
"All I want is a pretty girl, a decent meal, and the right to shoot lightning at fools."
-Anders (Dragon Age: Origins - Awakening)
  Reply With Quote
08-01-19, 04:02 AM   #8
LudiusMaximus
A Rage Talon Dragon Guard
 
LudiusMaximus's Avatar
AddOn Author - Click to view addons
Join Date: Mar 2018
Posts: 320
All right, thanks for the clarification! Now I understand the difference between SetScript and HookScript. :-D

However, if other Addons use the same method as you suggested, I have again no controll over which one is loaded first, right?

E.g. the author of LibExtraTip (used by TheUndermineJournal) does it like this (LibExtraTip.lua, L545):

Lua Code:
  1. -- Called to install/modify a pre-hook on the given tooltip's event
  2. -- Currently we do not need any posthooks on scripts
  3. local function hookscript(tip, script, prehook)
  4.     if not lib.hookStore[tip] then lib.hookStore[tip] = {} end
  5.     local control
  6.     -- check for existing hook
  7.     control = lib.hookStore[tip][script]
  8.     if control then
  9.         control[1] = prehook or control[1]
  10.         return
  11.     end
  12.     -- prepare upvalues
  13.     local orig = tip:GetScript(script)
  14.     control = {prehook}
  15.     lib.hookStore[tip][script] = control
  16.     -- install hook stub
  17.     local stub = function(...)
  18.         local h
  19.         -- prehook
  20.         h = control[1]
  21.         if h then h(...) end
  22.         -- original hook
  23.         if orig then orig(...) end
  24.     end
  25.     tip:SetScript(script, stub)
  26. end

With your code, my lines always get appended after those of TheUndermineJournal.
  Reply With Quote
08-01-19, 01:20 PM   #9
LudiusMaximus
A Rage Talon Dragon Guard
 
LudiusMaximus's Avatar
AddOn Author - Click to view addons
Join Date: Mar 2018
Posts: 320
Apparently PLAYER_LOGIN was just not late enough. It works, when I am using a timer to make sure that I am really the last. Thanks again!!


Lua Code:
  1. local folderName = ...
  2. local L = LibStub("AceAddon-3.0"):NewAddon(folderName, "AceTimer-3.0")
  3. local startupFrame = CreateFrame("Frame")
  4. startupFrame:RegisterEvent("PLAYER_LOGIN")
  5. startupFrame:SetScript("OnEvent", function(self, event, ...)
  6.   L:ScheduleTimer("initCode", 5.0)
  7. end)
  8.  
  9. function L:initCode()
  10.   -- Save any previously registered scripts.
  11.   local oldscript=GameTooltip:GetScript("OnTooltipSetItem")
  12.  
  13.   -- Futureproofing, support extra args to pass to previous scripts
  14.   GameTooltip:SetScript("OnTooltipSetItem", function(self, ...)
  15.    
  16.     self:AddLine("I am first!")
  17.  
  18.     if oldscript then return oldscript(self, ...) end
  19.   end);
  20. end
  Reply With Quote

WoWInterface » Developer Discussions » Lua/XML Help » Inserting a line in GameTooltip at arbitrary position?

Thread Tools
Display Modes

Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

vB code is On
Smilies are On
[IMG] code is On
HTML code is Off