diff --git a/api/api.lua b/api/api.lua index 7555f321..cdeca109 100644 --- a/api/api.lua +++ b/api/api.lua @@ -651,45 +651,21 @@ function hooksecurefunc(tbl, name, func) return _G.hooksecurefunc(tbl, name, func) end --- [ HookAddonOrVariable ] --- Sets a function to be called automatically once an addon gets loaded --- 'addon' [string] addon or variable name +-- [ HookAddon ] +-- Runs a function once an addon has loaded, but never before PLAYER_LOGIN. +-- Module and skin bodies run inside pfUI's own ADDON_LOADED, so an addon that +-- is already loaded there would otherwise run its hook before later pfUI +-- modules exist and before the addon's own VARIABLES_LOADED setup. A +-- load-on-demand addon runs its hook on its ADDON_LOADED. Hooks for addons +-- that are not installed are dropped. Hooks on the same addon run in no +-- particular order. +-- 'addon' [string] addon name -- 'func' [function] function that should run -do - local lurker - local pending = {} - - local function ProcessPending() - if not lurker.foundConfig then return end - for i = table.getn(pending), 1, -1 do - local hook = pending[i] - if IsAddOnLoaded(hook.addon) or _G[hook.addon] then - hook.func() - table.remove(pending, i) - end - end - if table.getn(pending) == 0 then - lurker:UnregisterAllEvents() - end - end - - function pfUI.api.HookAddonOrVariable(addon, func) - if not lurker then - lurker = CreateFrame("Frame", nil) - lurker:SetScript("OnEvent", function() - if event == "VARIABLES_LOADED" or event == "PLAYER_ENTERING_WORLD" then - this.foundConfig = true - end - ProcessPending() - end) - end - - table.insert(pending, { addon = addon, func = func }) - lurker:RegisterEvent("ADDON_LOADED") - lurker:RegisterEvent("VARIABLES_LOADED") - lurker:RegisterEvent("PLAYER_ENTERING_WORLD") - ProcessPending() - end +function pfUI.api.HookAddon(addon, func) + if not C_AddOns.DoesAddOnExist(addon) then return end + EventUtil.ContinueOnPlayerLogin(function() + EventUtil.ContinueOnAddOnLoaded(addon, func) + end) end -- [ QueueFunction ] diff --git a/modules/eqcompare.lua b/modules/eqcompare.lua index 81b5b214..8e9f82df 100644 --- a/modules/eqcompare.lua +++ b/modules/eqcompare.lua @@ -135,11 +135,9 @@ pfUI:RegisterModule("eqcompare", function () -- spot. Reading the rect inside the Set* call picks the side against the -- position the tooltip is about to leave, so place the compare a frame later. local deferred - if C_AddOns.DoesAddOnExist("Guda") then - EventUtil.ContinueOnAddOnLoaded("Guda", function() - deferred = true - end) - end + HookAddon("Guda", function() + deferred = true + end) local function makeHook(getter) return function(tooltip, arg1, arg2, arg3) diff --git a/modules/friends.lua b/modules/friends.lua index 3a131904..b0c679b9 100644 --- a/modules/friends.lua +++ b/modules/friends.lua @@ -456,7 +456,7 @@ pfUI:RegisterSkin("Friends", function () end -- all RaidFrame buttons are in Blizzard_RaidUI in Turtle WoW - HookAddonOrVariable("Blizzard_RaidUI", function() + HookAddon("Blizzard_RaidUI", function() if RaidFrameRaidInfoButton then SkinButton(RaidFrameRaidInfoButton) end if RaidFrameConvertToRaidButton then SkinButton(RaidFrameConvertToRaidButton) end if RaidFrameReadyCheckButton then SkinButton(RaidFrameReadyCheckButton) end diff --git a/modules/macroicons.lua b/modules/macroicons.lua index a5a200a9..84cfb5c1 100644 --- a/modules/macroicons.lua +++ b/modules/macroicons.lua @@ -8,7 +8,7 @@ -- on both the modern (Turtle/Octo) and stock-vanilla macro UIs. The surrounding -- MacroFrame (list + body editor) is Blizzard's, already skinned elsewhere. pfUI:RegisterModule("macroicons", function () - HookAddonOrVariable("Blizzard_MacroUI", function() + HookAddon("Blizzard_MacroUI", function() -- ============================================================ -- Popup frame -- ============================================================ diff --git a/modules/mapcolors.lua b/modules/mapcolors.lua index 6d03908f..f697668b 100644 --- a/modules/mapcolors.lua +++ b/modules/mapcolors.lua @@ -138,7 +138,7 @@ pfUI:RegisterModule("mapcolors", function () end -- BattlefieldMinimap - HookAddonOrVariable("Blizzard_BattlefieldMinimap", function() + HookAddon("Blizzard_BattlefieldMinimap", function() Initialize('BattlefieldMinimap') hooksecurefunc('BattlefieldMinimap_OnUpdate', function() diff --git a/modules/raid.lua b/modules/raid.lua index f4022de6..e184233a 100644 --- a/modules/raid.lua +++ b/modules/raid.lua @@ -3,7 +3,7 @@ pfUI:RegisterModule("raid", function () if C.unitframes.disable == "1" then return end -- tell RaidFrame.lua pfUI replaces party frames - HookAddonOrVariable("Blizzard_RaidUI", function() + HookAddon("Blizzard_RaidUI", function() -- must reach _G: RaidFrame.lua reads this global, and pfUI.env has -- __index but no __newindex, so a bare assignment stays in the sandbox _G.GROUP_REPLACE_PARTY = "1" diff --git a/modules/thirdparty-vanilla.lua b/modules/thirdparty-vanilla.lua index 787d4033..f0ff2601 100644 --- a/modules/thirdparty-vanilla.lua +++ b/modules/thirdparty-vanilla.lua @@ -3,7 +3,7 @@ pfUI:RegisterModule("thirdparty-vanilla", function() if not pfUI.thirdparty then return end local rawborder, default_border = GetBorderSize() - HookAddonOrVariable("KLHThreatMeter", function() + HookAddon("KLHThreatMeter", function() local ktm_scale = 1 local function GetKtmWidthDiff(view, match_size) local default_width = 0 @@ -159,7 +159,7 @@ pfUI:RegisterModule("thirdparty-vanilla", function() end end) - HookAddonOrVariable("TWThreat", function() + HookAddon("TWThreat", function() local docktable = { "twt", "TODO", "TWTMain", function() -- single TWTMain:ClearAllPoints() @@ -232,7 +232,7 @@ pfUI:RegisterModule("thirdparty-vanilla", function() end end) - HookAddonOrVariable("SW_Stats", function() + HookAddon("SW_Stats", function() local docktable = { "swstats", "TODO", "SW_BarFrame1", function() -- single SW_BarFrame1:SetWidth(pfUI.chat.right:GetWidth()) @@ -518,7 +518,7 @@ pfUI:RegisterModule("thirdparty-vanilla", function() end end) - HookAddonOrVariable("WIM", function() + HookAddon("WIM", function() if C.thirdparty.wim.enable == "0" then return end -- use pfUI link handler if available @@ -798,7 +798,7 @@ pfUI:RegisterModule("thirdparty-vanilla", function() -- SortBags -- Vanilla: https://github.com/shirsig/SortBags-vanilla - HookAddonOrVariable("SortBags", function() + HookAddon("SortBags", function() if C.thirdparty.sortbags.enable == "0" then return end local sort = CreateFrame("Frame", nil) @@ -828,7 +828,7 @@ pfUI:RegisterModule("thirdparty-vanilla", function() end) end) - HookAddonOrVariable("FlightMap", function() + HookAddon("FlightMap", function() if C.thirdparty.flightmap.enable == "0" then return end FlightMapTimesBorder:Hide() @@ -844,7 +844,7 @@ pfUI:RegisterModule("thirdparty-vanilla", function() FlightMapTimesText:SetFont(pfUI.font_default, 12, "OUTLINE") end) - HookAddonOrVariable("TheoryCraft_SetUpButton", function() + HookAddon("TheoryCraft", function() if C.thirdparty.theorycraft.enable == "0" then return end -- set pfUI bar font @@ -862,7 +862,7 @@ pfUI:RegisterModule("thirdparty-vanilla", function() end end) - HookAddonOrVariable("SM_GetActionSpell", function() + HookAddon("SuperMacro", function() if C.thirdparty.supermacro.enable == "0" then return end if not pfUI.bars then return end @@ -908,7 +908,7 @@ pfUI:RegisterModule("thirdparty-vanilla", function() -- CleverMacro -- https://github.com/DanielAdolfsson/CleverMacro - HookAddonOrVariable("CleverMacro", function() + HookAddon("CleverMacro", function() if C.thirdparty.clevermacro.enable == "0" then return end -- get pfUI actionbar event handler @@ -925,7 +925,7 @@ pfUI:RegisterModule("thirdparty-vanilla", function() end) -- SuperCleveRoidMacros - HookAddonOrVariable("SuperCleveRoidMacros", function() + HookAddon("SuperCleveRoidMacros", function() if C.thirdparty.supercleveroidmacros.enable == "0" then return end if not pfUI.bars then return end @@ -933,7 +933,7 @@ pfUI:RegisterModule("thirdparty-vanilla", function() pfUI.bars.skip_macro = true end) - HookAddonOrVariable("AtlasLoot", function() + HookAddon("AtlasLoot", function() if C.thirdparty.atlasloot.enable == "0" then return end CreateBackdrop(AtlasLootTooltip) @@ -948,7 +948,7 @@ pfUI:RegisterModule("thirdparty-vanilla", function() end end) - HookAddonOrVariable("crafty", function() + HookAddon("crafty", function() if C.thirdparty.crafty.enable == "0" then return end local craftyskin = CreateFrame("Frame", nil, UIParent) @@ -1011,7 +1011,7 @@ pfUI:RegisterModule("thirdparty-vanilla", function() end) end) - HookAddonOrVariable("BetterCharacterStats", function() + HookAddon("BetterCharacterStats", function() if C.thirdparty.bcs.enable == "0" then return end StripTextures(BetterCharacterAttributesFrame) @@ -1028,7 +1028,7 @@ pfUI:RegisterModule("thirdparty-vanilla", function() end end) - HookAddonOrVariable("MyRolePlay", function() + HookAddon("MyRolePlay", function() if C.thirdparty.myroleplay.enable == "0" then return end if pfUI.uf.target then @@ -1053,7 +1053,7 @@ pfUI:RegisterModule("thirdparty-vanilla", function() end end) - HookAddonOrVariable("DruidManaBarBackground", function() + HookAddon("DruidManaBarBackground", function() if C.thirdparty.druidmana.enable == "0" then return end DruidManaBar:SetParent(UIParent) DruidManaBar.bd:Hide() @@ -1082,7 +1082,7 @@ pfUI:RegisterModule("thirdparty-vanilla", function() UpdateMovable(DruidManaBar) end) - HookAddonOrVariable("NoteIt", function() + HookAddon("NoteIt", function() if C.thirdparty.noteit.enable == "0" then return end -- Main window @@ -1178,15 +1178,15 @@ pfUI:RegisterModule("thirdparty-vanilla", function() pfUI.api.libpredict.sender.enabled = nil end end - HookAddonOrVariable("HealComm", EnableHealComm) - HookAddonOrVariable("LunaUnitFrames", EnableHealComm) - HookAddonOrVariable("NotGrid", EnableHealComm) + HookAddon("HealComm", EnableHealComm) + HookAddon("LunaUnitFrames", EnableHealComm) + HookAddon("NotGrid", EnableHealComm) - HookAddonOrVariable("HardcoreDeath", function() + HookAddon("HardcoreDeath", function() GameMenuFrame:SetHeight(GameMenuFrame:GetHeight() + 20) end) - HookAddonOrVariable("MacroExtender", function() + HookAddon("MacroExtender", function() -- Macro Extender moves the character dialog from frame-stata "dialog" -- to "low" and by that moves the frame below backgrounds, quest-trackers, -- and what not... it does this because the buttons it attempts to add, are @@ -1208,7 +1208,7 @@ pfUI:RegisterModule("thirdparty-vanilla", function() -- UnitXP SP3 compatibility -- https://github.com/allfoxwy/UnitXP_SP3 - HookAddonOrVariable("UnitXP_SP3_Addon", function() + HookAddon("UnitXP_SP3_Addon", function() -- skin UnitXP SP3 window and elements StripTextures(xpsp3Frame) CreateBackdrop(xpsp3Frame) diff --git a/modules/thirdparty.lua b/modules/thirdparty.lua index 5e3cd3fe..13fd3a06 100644 --- a/modules/thirdparty.lua +++ b/modules/thirdparty.lua @@ -139,7 +139,7 @@ pfUI:RegisterModule("thirdparty", function() -- MrPlow Bag Sorting Addon. -- Vanilla: https://www.wowace.com/projects/mr-plow/files/288059 -- TBC: https://www.wowace.com/projects/mr-plow/files/136162 - HookAddonOrVariable("MrPlow", function() + HookAddon("MrPlow", function() if C.thirdparty.mrplow.enable == "0" then return end local sort = CreateFrame("Frame", nil) @@ -175,7 +175,7 @@ pfUI:RegisterModule("thirdparty", function() -- ShaguDPS Damage Meter -- Vanilla: https://github.com/shagu/ShaguDPS -- TBC: https://github.com/shagu/ShaguDPS - HookAddonOrVariable("ShaguDPS", function() + HookAddon("ShaguDPS", function() local docktable = { "shagudps", "ShaguDPS", "ShaguDPSWindow", function() -- single ShaguDPSWindow:ClearAllPoints() @@ -280,7 +280,7 @@ pfUI:RegisterModule("thirdparty", function() -- dock slot; a second window (GreedMeter.UI.frames[2]) takes the "threat" -- slot so the two tile the panel side-by-side. Docking triggers a -- LayoutBars/RefreshFrame so the bars refit the new width. - HookAddonOrVariable("GreedMeter", function() + HookAddon("GreedMeter", function() local UI = GreedMeter.UI local function frameAt(i) @@ -373,7 +373,12 @@ pfUI:RegisterModule("thirdparty", function() -- DPSMate Damage Meter -- Vanilla: https://github.com/Geigerkind/DPSMate -- TBC: https://github.com/Geigerkind/DPSMateTBC - HookAddonOrVariable("DPSMate_DPSMate", function() + HookAddon("DPSMate", function() + -- DPSMate builds its windows on VARIABLES_LOADED, each named after its + -- settings entry, so DPSMate_DPSMate exists only while the first window + -- keeps the default name "DPSMate" + if not DPSMate_DPSMate then return end + local docktable = { "dpsmate", "DPSMate", "DPSMate_DPSMate", function() -- single DPSMate_DPSMate:ClearAllPoints() diff --git a/modules/turtle-wow.lua b/modules/turtle-wow.lua index 28a66d6f..d2cf0eb1 100644 --- a/modules/turtle-wow.lua +++ b/modules/turtle-wow.lua @@ -61,7 +61,7 @@ pfUI:RegisterModule("turtle-wow", function () end end - HookAddonOrVariable("GroupFrame", function() + HookAddon("Turtle_GroupUI", function() -- After Turtle's own init, hide frames if pfUI handles them hooksecurefunc("GroupFrame_Toggle", function() if pfUIHandlesGroupOrRaid() then @@ -268,7 +268,7 @@ pfUI:RegisterModule("turtle-wow", function () if pfUI.skin["Inspect"] and pfUI_config["disabled"]["skin_Inspect"] ~= "1" then local initialized = false - HookAddonOrVariable("Blizzard_InspectUI", function() + HookAddon("Blizzard_InspectUI", function() hooksecurefunc("InspectFrame_Show", function() -- break if theres nothing left to do if initialized then return end @@ -326,7 +326,7 @@ pfUI:RegisterModule("turtle-wow", function () end -- rearrange twow's profession window additions - HookAddonOrVariable("Blizzard_TradeSkillUI", function() + HookAddon("Blizzard_TradeSkillUI", function() if TradeSkillSkillCheckButton and pfUI.skin["Profession"] and pfUI_config["disabled"]["skin_Profession"] ~= "1" then SkinCheckbox(TradeSkillSkillCheckButton) TradeSkillSkillCheckButton:SetWidth(24) @@ -348,7 +348,7 @@ pfUI:RegisterModule("turtle-wow", function () end) if C.disabled["macroicons"] == "1" then - HookAddonOrVariable("Blizzard_MacroUI", function() + HookAddon("Blizzard_MacroUI", function() if type(UpdateMacroIconFilenames) ~= "function" then return end function _G.UpdateMacroIconFilenames() wipe(MACRO_ICON_FILENAMES) diff --git a/skins/blizzard/auction.lua b/skins/blizzard/auction.lua index 6f566e36..273b6d2d 100644 --- a/skins/blizzard/auction.lua +++ b/skins/blizzard/auction.lua @@ -1,7 +1,7 @@ pfUI:RegisterSkin("Auctionhouse", function () local rawborder, border = GetBorderSize() local bpad = rawborder > 1 and border - GetPerfectPixel() or GetPerfectPixel() - HookAddonOrVariable("Blizzard_AuctionUI", function() + HookAddon("Blizzard_AuctionUI", function() SkinArrowButton(BidPrevPageButton, "left", 18) SkinArrowButton(BidNextPageButton, "right", 18) SkinArrowButton(AuctionsPrevPageButton, "left", 18) diff --git a/skins/blizzard/battlefield_minimap.lua b/skins/blizzard/battlefield_minimap.lua index e56a30d0..15249de3 100644 --- a/skins/blizzard/battlefield_minimap.lua +++ b/skins/blizzard/battlefield_minimap.lua @@ -1,7 +1,7 @@ pfUI:RegisterSkin("Battlefield Minimap", function () local rawborder, border = GetBorderSize() - HookAddonOrVariable("Blizzard_BattlefieldMinimap", function() + HookAddon("Blizzard_BattlefieldMinimap", function() StripTextures(BattlefieldMinimap) CreateBackdrop(BattlefieldMinimap, nil, nil, 0) CreateBackdropShadow(BattlefieldMinimap) diff --git a/skins/blizzard/friends.lua b/skins/blizzard/friends.lua index bd571b7b..3750f725 100644 --- a/skins/blizzard/friends.lua +++ b/skins/blizzard/friends.lua @@ -458,7 +458,7 @@ pfUI:RegisterSkin("Friends", function () SkinButton(RaidFrameRaidInfoButton) SkinButton(RaidFrameConvertToRaidButton) - HookAddonOrVariable("Blizzard_RaidUI", function() + HookAddon("Blizzard_RaidUI", function() for i = 1, MAX_RAID_MEMBERS do StripTextures(_G["RaidGroupButton"..i]) CreateBackdrop(_G["RaidGroupButton"..i], nil, true) diff --git a/skins/blizzard/inspect.lua b/skins/blizzard/inspect.lua index bf648382..e7b766ac 100644 --- a/skins/blizzard/inspect.lua +++ b/skins/blizzard/inspect.lua @@ -24,7 +24,7 @@ pfUI:RegisterSkin("Inspect", function () local rawborder, border = GetBorderSize() local bpad = rawborder > 1 and border - GetPerfectPixel() or GetPerfectPixel() - HookAddonOrVariable("Blizzard_InspectUI", function() + HookAddon("Blizzard_InspectUI", function() local cache = {} CreateBackdrop(InspectFrame, nil, nil, .75) diff --git a/skins/blizzard/keybindings.lua b/skins/blizzard/keybindings.lua index 17775315..fe7c1a89 100644 --- a/skins/blizzard/keybindings.lua +++ b/skins/blizzard/keybindings.lua @@ -2,7 +2,7 @@ pfUI:RegisterSkin("KeyBindings", function () local rawborder, border = GetBorderSize() local bpad = rawborder > 1 and border - GetPerfectPixel() or GetPerfectPixel() - HookAddonOrVariable("Blizzard_BindingUI", function() + HookAddon("Blizzard_BindingUI", function() StripTextures(KeyBindingFrame) CreateBackdrop(KeyBindingFrame, nil, nil, .75) CreateBackdropShadow(KeyBindingFrame) diff --git a/skins/blizzard/macro.lua b/skins/blizzard/macro.lua index c50477d2..eb83f571 100644 --- a/skins/blizzard/macro.lua +++ b/skins/blizzard/macro.lua @@ -2,7 +2,7 @@ pfUI:RegisterSkin("Macro", function () local rawborder, border = GetBorderSize() local bpad = rawborder > 1 and border - GetPerfectPixel() or GetPerfectPixel() - HookAddonOrVariable("Blizzard_MacroUI", function() + HookAddon("Blizzard_MacroUI", function() StripTextures(MacroFrame) CreateBackdrop(MacroFrame, nil, nil, .75) CreateBackdropShadow(MacroFrame) diff --git a/skins/blizzard/mail.lua b/skins/blizzard/mail.lua index b780308c..6001232f 100644 --- a/skins/blizzard/mail.lua +++ b/skins/blizzard/mail.lua @@ -28,7 +28,7 @@ pfUI:RegisterSkin("Mailbox", function () end) skin:RegisterEvent("MAIL_SHOW") - HookAddonOrVariable("Mail", function() + HookAddon("Mail", function() skin._Mail = true for i = 1, 21 do local button = _G["MailAttachment"..i] diff --git a/skins/blizzard/professions.lua b/skins/blizzard/professions.lua index 5608ac14..563150d6 100644 --- a/skins/blizzard/professions.lua +++ b/skins/blizzard/professions.lua @@ -16,7 +16,7 @@ pfUI:RegisterSkin("Profession", function () local maxreagents = ext[4] local frame = name .. "Frame" - HookAddonOrVariable(addon, function() + HookAddon(addon, function() local SetSelection = frame.."_SetSelection" -- Turtle WoW uses "SkillIcon"/"SkillName" instead of "Icon"/"Name" local icon = _G[template.."Icon"] or _G[template.."SkillIcon"] diff --git a/skins/blizzard/raidui.lua b/skins/blizzard/raidui.lua index 686a1e3d..4f095a9e 100644 --- a/skins/blizzard/raidui.lua +++ b/skins/blizzard/raidui.lua @@ -1,5 +1,5 @@ pfUI:RegisterSkin("RaidUI", function () - HookAddonOrVariable("Blizzard_RaidUI", function() + HookAddon("Blizzard_RaidUI", function() for i = 1, (NUM_RAID_GROUPS or 8) do local group = _G["RaidGroup"..i] diff --git a/skins/blizzard/readycheck.lua b/skins/blizzard/readycheck.lua index f7cadb33..6434d8e1 100644 --- a/skins/blizzard/readycheck.lua +++ b/skins/blizzard/readycheck.lua @@ -1,5 +1,5 @@ pfUI:RegisterSkin("Readycheck", function () - HookAddonOrVariable("Blizzard_RaidUI", function() + HookAddon("Blizzard_RaidUI", function() StripTextures(ReadyCheckFrame, true) CreateBackdrop(ReadyCheckFrame, nil, nil, .75) CreateBackdropShadow(ReadyCheckFrame) diff --git a/skins/blizzard/survey.lua b/skins/blizzard/survey.lua index cc36aca3..1166d8c0 100644 --- a/skins/blizzard/survey.lua +++ b/skins/blizzard/survey.lua @@ -1,5 +1,5 @@ pfUI:RegisterSkin("GM Survey", function () - HookAddonOrVariable("Blizzard_GMSurveyUI", function() + HookAddon("Blizzard_GMSurveyUI", function() StripTextures(GMSurveyFrame) CreateBackdrop(GMSurveyFrame, nil, nil, .75) CreateBackdropShadow(GMSurveyFrame) diff --git a/skins/blizzard/talents.lua b/skins/blizzard/talents.lua index 1ba18243..f59fb48f 100644 --- a/skins/blizzard/talents.lua +++ b/skins/blizzard/talents.lua @@ -2,7 +2,7 @@ pfUI:RegisterSkin("Talents", function () local rawborder, border = GetBorderSize() local bpad = rawborder > 1 and border - GetPerfectPixel() or GetPerfectPixel() - HookAddonOrVariable("Blizzard_TalentUI", function() + HookAddon("Blizzard_TalentUI", function() -- Compatibility local TALENT_FRAME, TALENT_FRAME_NAME = _G.TalentFrame, _G.TalentFrame:GetName() diff --git a/skins/blizzard/trainer.lua b/skins/blizzard/trainer.lua index aebc9028..153c07fd 100644 --- a/skins/blizzard/trainer.lua +++ b/skins/blizzard/trainer.lua @@ -2,7 +2,7 @@ pfUI:RegisterSkin("Trainer", function () local rawborder, border = GetBorderSize() local bpad = rawborder > 1 and border - GetPerfectPixel() or GetPerfectPixel() - HookAddonOrVariable("Blizzard_TrainerUI", function() + HookAddon("Blizzard_TrainerUI", function() StripTextures(ClassTrainerFrame) CreateBackdrop(ClassTrainerFrame, nil, nil, .75) CreateBackdropShadow(ClassTrainerFrame)