/
## Title: Lib: QTip-1.0 |
## Notes: Library providing multi-column tooltips. |
## Author: Torhal, Adirelle, Elkano, Tristanian |
## Version: r126-release |
## Version: r130-release |
## LoadOnDemand: 1 |
## X-Date: 2010-01-16T19:03:33Z |
## X-Date: 2010-02-08T16:37:07Z |
## X-Credits: Kaelten (input on initial design) |
## X-Category: Library, Tooltip |
## X-License: Ace3 BSD-like license |
## X-Curse-Packaged-Version: r126-release |
## X-Curse-Packaged-Version: r130-release |
## X-Curse-Project-Name: LibQTip-1.0 |
## X-Curse-Project-ID: libqtip-1-0 |
## X-Curse-Repository-ID: wow/libqtip-1-0/mainline |
local MAJOR = "LibQTip-1.0" |
local MINOR = 30 -- Should be manually increased |
local MINOR = 31 -- Should be manually increased |
assert(LibStub, MAJOR.." requires LibStub") |
local lib, oldminor = LibStub:NewLibrary(MAJOR, MINOR) |
-- @usage Acquire a tooltip with at least 5 columns, justification : left, center, left, left, left |
-- <pre>local tip = LibStub('LibQTip-1.0'):Acquire('MyFooBarTooltip', 5, "LEFT", "CENTER")</pre> |
function lib:Acquire(key, ...) |
if key == nil then error("attempt to use a nil key", 2) end |
if key == nil then |
error("attempt to use a nil key", 2) |
end |
local tooltip = activeTooltips[key] |
if not tooltip then |
tooltip = AcquireTooltip() |
InitializeTooltip(tooltip, key) |
activeTooltips[key] = tooltip |
end |
if select('#', ...) > 0 then |
-- Here we catch any error to properly report it for the calling code |
-- Here we catch any error to properly report it for the calling code |
local ok, msg = pcall(tooltip.SetColumnLayout, tooltip, ...) |
if not ok then error(msg, 2) end |
if not ok then |
error(msg, 2) |
end |
end |
return tooltip |
end |
function lib:Release(tooltip) |
local key = tooltip and tooltip.key |
if not key or activeTooltips[key] ~= tooltip then return end |
if not key or activeTooltips[key] ~= tooltip then |
return |
end |
ReleaseTooltip(tooltip) |
activeTooltips[key] = nil |
end |
function lib:IsAcquired(key) |
if key == nil then error("attempt to use a nil key", 2) end |
if key == nil then |
error("attempt to use a nil key", 2) |
end |
return not not activeTooltips[key] |
end |
if max_width and min_width and (max_width < min_width) then |
error("maximum width cannot be lower than minimum width: "..tostring(max_width).." < "..tostring(min_width), 2) |
end |
if min_width and width < min_width then width = min_width end |
if max_width and max_width < width then width = max_width end |
if min_width and width < min_width then |
width = min_width |
end |
if max_width and max_width < width then |
width = max_width |
end |
fs:SetWidth(width) |
fs:Show() |
-- Returns a tooltip |
function AcquireTooltip() |
local tooltip = tremove(tooltipHeap) |
if not tooltip then |
tooltip = CreateFrame("Frame", nil, UIParent) |
local scrollFrame = CreateFrame("ScrollFrame", nil, tooltip) |
scrollFrame:SetPoint("TOP", tooltip, "TOP", 0, -TOOLTIP_PADDING) |
scrollFrame:SetPoint("BOTTOM", tooltip, "BOTTOM", 0, TOOLTIP_PADDING) |
scrollFrame:SetPoint("LEFT", tooltip, "LEFT", TOOLTIP_PADDING, 0) |
scrollFrame:SetPoint("RIGHT", tooltip, "RIGHT", -TOOLTIP_PADDING, 0) |
tooltip.scrollFrame = scrollFrame |
local scrollChild = CreateFrame("Frame", nil, tooltip.scrollframe) |
local scrollChild = CreateFrame("Frame", nil, tooltip.scrollFrame) |
scrollFrame:SetScrollChild(scrollChild) |
tooltip.scrollChild = scrollChild |
setmetatable(tooltip, tipMetatable) |
tooltip:Hide() |
tooltip:ClearAllPoints() |
tooltip:Clear() |
if tooltip.slider then |
tooltip.slider:SetValue(0) |
tooltip.slider:Hide() |
tooltip:EnableMouseWheel(false) |
tooltip:SetScript("OnMouseWheel", nil) |
end |
for i, column in ipairs(tooltip.columns) do |
tooltip.columns[i] = ReleaseFrame(column) |
end |
-- Returns a cell for the given tooltip from the given provider |
function AcquireCell(tooltip, provider) |
local cell = provider:AcquireCell(tooltip) |
cell:SetParent(tooltip.scrollChild) |
cell:SetFrameLevel(tooltip.scrollChild:GetFrameLevel() + 3) |
cell._provider = provider |
tooltip.regularFont = GameTooltipText |
tooltip.headerFont = GameTooltipHeaderText |
tooltip.labelProvider = labelProvider |
tooltip.cell_margin_h = tooltip.cell_margin_h or CELL_MARGIN_H |
tooltip.cell_margin_v = tooltip.cell_margin_v or CELL_MARGIN_V |
---------------------------------------------------------------------- |
-- Finishing procedures |
end |
function tipPrototype:SetDefaultProvider(myProvider) |
if not myProvider then return end |
if not myProvider then |
return |
end |
self.labelProvider = myProvider |
end |
local function checkJustification(justification, level, silent) |
if justification ~= "LEFT" and justification ~= "CENTER" and justification ~= "RIGHT" then |
if silent then return false end |
if silent then |
return false |
end |
error("invalid justification, must one of LEFT, CENTER or RIGHT, not: "..tostring(justification), level+1) |
end |
return true |
if type(numColumns) ~= "number" or numColumns < 1 then |
error("number of columns must be a positive number, not: "..tostring(numColumns), 2) |
end |
for i = 1, numColumns do |
local justification = select(i, ...) or "LEFT" |
checkJustification(justification, 2) |
if self.columns[i] then |
self.columns[i].justification = justification |
else |
column:SetPoint("BOTTOM", self.scrollChild) |
if colNum > 1 then |
column:SetPoint("LEFT", self.columns[colNum - 1], "RIGHT", CELL_MARGIN_H, 0) |
SetTooltipSize(self, self.width + CELL_MARGIN_H, self.height) |
local h_margin = self.cell_margin_h or CELL_MARGIN_H |
column:SetPoint("LEFT", self.columns[colNum - 1], "RIGHT", h_margin, 0) |
SetTooltipSize(self, self.width + h_margin, self.height) |
else |
column:SetPoint("LEFT", self.scrollChild) |
end |
if not self.slider then |
local slider = CreateFrame("Slider", nil, self) |
self.slider = slider |
slider:SetOrientation("VERTICAL") |
slider:SetPoint("TOPRIGHT", self, "TOPRIGHT", -TOOLTIP_PADDING, -TOOLTIP_PADDING) |
slider:SetPoint("BOTTOMRIGHT", self, "BOTTOMRIGHT", -TOOLTIP_PADDING, TOOLTIP_PADDING) |
function tipPrototype:Clear() |
for i, line in ipairs(self.lines) do |
for j, cell in pairs(line.cells) do |
if cell then ReleaseCell(cell) end |
if cell then |
ReleaseCell(cell) |
end |
end |
ReleaseTable(line.cells) |
line.cells = nil |
ReleaseFrame(line) |
self.lines[i] = nil |
end |
for i, column in ipairs(self.columns) do |
column.width = 0 |
column:SetWidth(1) |
end |
wipe(self.colspans) |
self.cell_margin_h = nil |
self.cell_margin_v = nil |
ResetTooltipSize(self) |
end |
function tipPrototype:SetCellMarginH(size) |
if #self.lines > 0 then |
error("Unable to set horizontal margin while the tooltip has lines.", 2) |
end |
if not size or type(size) ~= "number" or size < 0 then |
error("Margin size must be a positive number or zero.", 2) |
end |
self.cell_margin_h = size |
end |
function tipPrototype:SetCellMarginV(size) |
if #self.lines > 0 then |
error("Unable to set vertical margin while the tooltip has lines.", 2) |
end |
if not size or type(size) ~= "number" or size < 0 then |
error("Margin size must be a positive number or zero.", 2) |
end |
self.cell_margin_v = size |
end |
local function checkFont(font, level, silent) |
if not font or type(font) ~= 'table' or type(font.IsObjectType) ~= 'function' or not font:IsObjectType("Font") then |
if silent then return false end |
error("font must be Font instance, not: "..tostring(font), level+1) |
if silent then |
return false |
end |
error("font must be Font instance, not: "..tostring(font), level + 1) |
end |
return true |
end |
-- Add 2 pixels to height so dangling letters (g, y, p, j, etc) are not clipped. |
function ResetTooltipSize(tooltip) |
SetTooltipSize(tooltip, max(0, (CELL_MARGIN_H * (#tooltip.columns - 1)) + (CELL_MARGIN_H / 2)), 2) |
local h_margin = tooltip.cell_margin_h or CELL_MARGIN_H |
SetTooltipSize(tooltip, max(0, (h_margin * (#tooltip.columns - 1)) + (h_margin / 2)), 2) |
end |
local function EnlargeColumn(tooltip, column, width) |
function LayoutColspans(tooltip) |
local columns = tooltip.columns |
for colRange, width in pairs(tooltip.colspans) do |
local h_margin = tooltip.cell_margin_h or CELL_MARGIN_H |
local left, right = colRange:match("^(%d+)%-(%d+)$") |
left, right = tonumber(left), tonumber(right) |
for col = left, right-1 do |
width = width - columns[col].width - CELL_MARGIN_H |
width = width - columns[col].width - h_margin |
end |
EnlargeColumn(tooltip, columns[right], width) |
end |
-- Unset: be quick |
if value == nil then |
local cell = cells[colNum] |
if cell then |
for i = colNum, colNum + cell._colSpan - 1 do |
cells[i] = nil |
-- Check previous cell |
local cell |
local prevCell = cells[colNum] |
if prevCell then |
-- There is a cell here |
font = font or prevCell._font |
justification = justification or prevCell._justification |
colSpan = colSpan or prevCell._colSpan |
-- Clear the currently marked colspan |
for i = colNum + 1, colNum + prevCell._colSpan - 1 do |
cells[i] = nil |
end |
if provider == nil or prevCell._provider == provider then |
-- Reuse existing cell |
cell = prevCell |
else |
error("overlapping cells at column "..colNum, 3) |
end |
local tooltipWidth = #tooltip.columns |
local rightColNum |
if colSpan > 0 then |
rightColNum = colNum + colSpan - 1 |
if rightColNum > tooltipWidth then |
error("ColSpan too big, cell extends beyond right-most column", 3) |
end |
-- Cleanup colspans |
for i = colNum + 1, rightColNum do |
local cell = cells[i] |
if cell then |
ReleaseCell(cell) |
elseif cell == false then |
if colSpan > 1 then |
-- Postpone width changes until the tooltip is shown |
local colRange = colNum.."-"..rightColNum |
tooltip.colspans[colRange] = max(tooltip.colspans[colRange] or 0, width) |
layoutCleaner:RegisterForCleanup(tooltip) |
else |
end |
local lineNum = #tooltip.lines + 1 |
local line = tooltip.lines[lineNum] or AcquireFrame(tooltip.scrollChild) |
line:SetFrameLevel(tooltip.scrollChild:GetFrameLevel() + 2) |
line:SetPoint('LEFT', tooltip.scrollChild) |
line:SetPoint('RIGHT', tooltip.scrollChild) |
if lineNum > 1 then |
line:SetPoint('TOP', tooltip.lines[lineNum-1], 'BOTTOM', 0, -CELL_MARGIN_V) |
SetTooltipSize(tooltip, tooltip.width, tooltip.height + CELL_MARGIN_V) |
local v_margin = tooltip.cell_margin_v or CELL_MARGIN_V |
line:SetPoint('TOP', tooltip.lines[lineNum-1], 'BOTTOM', 0, -v_margin) |
SetTooltipSize(tooltip, tooltip.width, tooltip.height + v_margin) |
else |
line:SetPoint('TOP', tooltip.scrollChild) |
end |
line:Show() |
local colNum = 1 |
for i = 1, #tooltip.columns do |
local value = select(i, ...) |
if value ~= nil then |
lineNum, colNum = _SetCell(tooltip, lineNum, i, value, font, nil, 1, tooltip.labelProvider) |
end |
-- Variable argument checking |
local font, justification, colSpan, provider |
local i, arg = 1, ... |
if arg == nil or checkFont(arg, 2, true) then |
i, font, arg = 2, ... |
end |
if arg == nil or checkJustification(arg, 2, true) then |
i, justification, arg = i + 1, select(i, ...) |
end |
if arg == nil or type(arg) == 'number' then |
i, colSpan, arg = i + 1, select(i, ...) |
end |
if arg == nil or type(arg) == 'table' and type(arg.AcquireCell) == 'function' then |
i, provider = i + 1, arg |
end |
end |
frame["_"..script.."_func"] = func |
frame["_"..script.."_arg"] = arg |
if script == "OnMouseDown" or script == "OnMouseUp" then |
if func then |
frame:SetScript(script, scripts[script]) |
frame:SetScript(script, nil) |
end |
end |
-- if at least one script is set, set the OnEnter/OnLeave scripts for the highlight |
if frame._OnEnter_func or frame._OnLeave_func or frame._OnMouseDown_func or frame._OnMouseUp_func then |
frame:EnableMouse(true) |
end |
end |
------------------------------------------------------------------------------ |
-- Auto-hiding feature |
------------------------------------------------------------------------------ |
local tipCache = tostring(#tooltipHeap) |
local frameCache = tostring(#frameHeap) |
local tableCache = tostring(#tableHeap) |
local header = false |
print("Tooltips used: "..usedTooltips..", Cached: "..tipCache..", Total: "..tipCache + usedTooltips) |
print("Frames used: "..usedFrames..", Cached: "..frameCache..", Total: "..frameCache + usedFrames) |
print("Tables used: "..usedTables..", Cached: "..tableCache..", Total: "..tableCache + usedTables) |
local header = false |
for k, v in pairs(activeTooltips) do |
if not header then |
print("Active tooltips:") |