You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

210 lines
6.6 KiB

--[[
****************************************************************************************
LibStrataFix
Author: oscarucb
****************************************************************************************
]]--
local _G = _G
local UIParent, pairs, string, select, type =
UIParent, pairs, string, select, type
local MAJOR, MINOR = "LibStrataFix", 10000+tonumber(string.match("$Revision: 34 $","%d+"))
local lib, oldminor
if LibStub then
lib, oldminor = LibStub:NewLibrary(MAJOR, MINOR)
else
lib = {}
end
if not lib then return end
_G["LibStrataFix"] = lib
lib.debug = false
lib.version = MINOR
lib.spamfilter = setmetatable({}, {__mode = "k"})
local function chatMsg(msg)
DEFAULT_CHAT_FRAME:AddMessage(MAJOR..": "..msg)
end
local function debug(msg)
if lib.debug then chatMsg(msg) end
end
local function name(frame)
if not frame then return "nil" end
local s = frame.GetName and frame:GetName()
local p = (frame.IsProtected and frame:IsProtected() and "(P)") or ""
if s then
return s..p
else
local t = (frame.GetObjectType and frame:GetObjectType()) or "?"
local a = tostring(frame)
a = (a and (a:match("%s+(.+)$") or a)) or "?"
return "<"..t..p..":"..a..">"
end
end
local function namelvl(frame)
if not frame then return "nil" end
local l = (frame.GetFrameLevel and frame:GetFrameLevel()) or "*"
return name(frame)..":"..l
end
local function SetLevel(frame, level, context)
local parent = frame:GetParent()
if not frame.SetFrameLevel then
debug("Missing frame.SetFrameLevel on "..name(frame).." in "..(context or "unknown"))
return
end
if lib.debug then
local msgcnt = lib.spamfilter[frame] or 0
if msgcnt < 5 then
debug("Fixing level of "..namelvl(frame).." child of "..namelvl(parent).." in "..(context or "unknown"))
end
lib.spamfilter[frame] = msgcnt + 1
end
frame:SetFrameLevel(level)
end
local CheckHelper
local function CheckOne(frame, parent, context, recurse)
local p = parent.GetFrameLevel and parent:GetFrameLevel()
local c = frame.GetFrameLevel and frame:GetFrameLevel()
if not c or not p then return end
if c <= p then
SetLevel(frame, p+1, context)
if lib.debug and (frame:GetFrameLevel() <= parent:GetFrameLevel()) then
-- Most common cause of failure: useParentLevel=true set in XML frame template
-- unfortunately there seems to be no way to query that property.
-- Luckily there are no reported cases of the bug on these frames,
-- and SetFrameLevel on them seems to have no effect
debug("SetLevel fix FAILED: child="..namelvl(frame).." parent="..namelvl(parent))
end
end
if recurse and (frame:GetNumChildren() ~= 0) then
local ok = pcall(CheckHelper,frame,context,frame:GetChildren()) -- pcall in case of stack overflow
if not ok and lib.debug then
debug("Skipped recursive check on "..name(frame).." in "..(context or "nil"))
end
end
end
function CheckHelper(frame, context, ...)
local n = select("#",...)
for i=1,n do
local child = select(i, ...)
if child then
CheckOne(child, frame, context, true)
end
end
--debug("Scanned "..n.." children in "..name(frame).." in "..(context or "nil"))
end
-- template exceptions: Templates can have a parent attribute that overrides the CreateFrame argument
-- This is an awful programming practice which is thankfully also very rare. We handle such cases here.
lib.templateExceptions = {
-- template name = parent frame ("name" or reference)
["TaxiButtonTemplate"] = "TaxiFrame",
}
--------------------------------------------------------------------
local function GetChild(parent,num)
return select(num,parent:GetChildren())
end
--------------------------------------------------------------------
-- upgradeable hooks
function lib.CreateFrameHook(frameType, name, parent, template)
local te = template and lib.templateExceptions[template]
if te then
if type(te) == "string" and _G[te] then
te = _G[te]
end
if type(te) == "table" and te.IsObjectType and te:IsObjectType("Frame") then
parent = te
end
end
if parent and not parent:IsForbidden() then
local num = parent:GetNumChildren()
if not num or (num <= 0) then -- should never happen
debug("Something very strange happenned in CreateFrameHook: num="..(num or "nil"))
else
-- ticket 3: In extreme cases, Frame:GetChildren() can cause an internal stack overflow
-- for frames with too many children. Unfortunately, there is no other way to retrieve the last child
-- So we use pcall to catch the potential overflow and ignore it
local ok, frame = pcall(GetChild,parent,num)
if ok and frame and template and #template > 0 then
CheckOne(frame, parent, "CreateFrame/template", true)
elseif ok and frame then
CheckOne(frame, parent, "CreateFrame")
elseif lib.debug then
debug("Skipped check for CreateFrame on "..name(parent).." due to "..(frame or "nil"))
end
end
end
end
function lib.SetParentHook(self,parent)
--debug("SetParentHook: "..name(self))
if type(parent) == "string" then -- handle Frame:SetParent("name")
parent = _G[parent]
end
if parent
and not parent:IsForbidden() then -- ticket 9: skip forbidden frames, which generate errors
CheckOne(self, parent, "SetParent")
end
end
--------------------------------------------------------------------
-- hook init
local function SetParentHookWrap(...)
return lib.SetParentHook(...)
end
local function CreateFrameHookWrap(...)
return lib.CreateFrameHook(...)
end
lib.hookDone = lib.hookDone or {}
lib.hookTypes = {
"Frame",
"Button",
"CheckButton",
"Cooldown",
"ColorSelect",
"Slider",
"EditBox",
"SimpleHTML",
"StatusBar",
"ScrollFrame",
"MessageFrame",
"ScrollingMessageFrame",
}
-- ensure we only hook once despite library upgrades
local function InitHooks()
if not lib.hookDone["CreateFrame"] then
lib.hookDone["CreateFrame"] = true
hooksecurefunc("CreateFrame", CreateFrameHookWrap)
end
for _,t in pairs(lib.hookTypes) do
if not lib.hookDone[t] then
lib.hookDone[t] = true
local o = CreateFrame(t)
local mt = getmetatable(o)
if mt.__index and mt.__index.SetParent then
hooksecurefunc( mt.__index, "SetParent", SetParentHookWrap)
end
o:Hide()
o:UnregisterAllEvents()
end
end
debug(" v"..MINOR.." loaded.")
end
InitHooks()