Quantcast
---@diagnostic disable: duplicate-set-field
--[===[ File
Contains the basic routines of Titan.
All the event handler routines, initialization routines, Titan menu routines, and select plugin handler routines.
--]===]

-- Locals
local TPC = TITAN_PANEL_CONSTANTS -- shortcut
local TITAN_PANEL_BUTTONS_INIT_FLAG = nil;

local _G = _G --getfenv(0);
local InCombatLockdown = _G.InCombatLockdown;
local IsTitanPanelReset = nil;

-- Library references
local L = LibStub("AceLocale-3.0"):GetLocale(TITAN_ID, true)
local AceTimer = LibStub("AceTimer-3.0")
local media = LibStub("LibSharedMedia-3.0")
local AceConfigDialog = LibStub("AceConfigDialog-3.0")

--	TitanDebug (cmd.." : "..p1.." "..p2.." "..p3.." "..#cmd_list)

--------------------------------------------------------------
--

---Titan Give the user an are you sure popup whether to reload the UI or not.
function TitanPanel_OkToReload()
	StaticPopupDialogs["TITAN_RESET_RELOAD"] = {
		text = TitanUtils_GetNormalText(L["TITAN_PANEL_MENU_TITLE"])
			.. "\n\n" .. L["TITAN_PANEL_RESET_WARNING"],
		button1 = ACCEPT,
		button2 = CANCEL,
		OnAccept = function(self)
			ReloadUI()
		end,
		showAlert = 1,
		timeout = 0,
		whileDead = 1,
		hideOnEscape = 1
	};
	StaticPopup_Show("TITAN_RESET_RELOAD");
end

---Titan Give the user a 'are you sure' popup whether to reset current toon back to default Titan settings.
function TitanPanel_ResetToDefault()
	StaticPopupDialogs["TITAN_RESET_BAR"] = {
		text = TitanUtils_GetNormalText(L["TITAN_PANEL_MENU_TITLE"])
			.. "\n\n" .. L["TITAN_PANEL_RESET_WARNING"],
		button1 = ACCEPT,
		button2 = CANCEL,
		OnAccept = function(self)
			TitanVariables_UseSettings(TitanSettings.Player, TITAN_PROFILE_RESET);
			IsTitanPanelReset = true;
			ReloadUI()
		end,
		showAlert = 1,
		timeout = 0,
		whileDead = 1,
		hideOnEscape = 1
	};
	StaticPopup_Show("TITAN_RESET_BAR");
end

---Titan The user wants to save a custom Titan profile. Show the user the dialog boxes to make it happen.
--- The profile is written to the Titan saved variables. A reload of the UI is needed to ensure the profile is written to disk for the user to load later.
function TitanPanel_SaveCustomProfile()
	-- Create the dialog box code we'll need...

	-- helper to actually write the profile to the Titan saved vars
	local function Write_profile(name)
		local currentprofilevalue, _, _ = TitanUtils_GetPlayer()
		local profileName = TitanUtils_CreateName(name, TITAN_CUSTOM_PROFILE_POSTFIX)
		TitanSettings.Players[profileName] =
			TitanSettings.Players[currentprofilevalue]
		TitanPrint(L["TITAN_PANEL_MENU_PROFILE_SAVE_PENDING"]
			.. "'" .. name .. "'"
			, "info")
	end
	-- helper to ask the user to overwrite a profile
	local function Overwrite_profile(name)
		local dialogFrame =
			StaticPopup_Show("TITAN_OVERWRITE_CUSTOM_PROFILE", name);
		if dialogFrame then
			dialogFrame.data = name;
		end
	end
	-- helper to handle getting the profile name from the user
	local function Get_profile_name(self)
		local rawprofileName = self.editBox:GetText();
		-- remove any spaces the user may have typed in the name
		local conc2profileName = string.gsub(rawprofileName, " ", "");
		if conc2profileName == "" then return; end
		-- no '@' is allowed or it will mess with the Titan profile naming convention
		local concprofileName = string.gsub(conc2profileName, TITAN_AT, "-");
		local profileName = TitanUtils_CreateName(concprofileName, TITAN_CUSTOM_PROFILE_POSTFIX)
		if TitanSettings.Players[profileName] then
			-- Warn the user of an existing profile
			Overwrite_profile(rawprofileName)
			self:Hide();
			return;
		else
			-- Save the requested profile
			Write_profile(rawprofileName)
			self:Hide();
			StaticPopup_Show("TITAN_RELOADUI");
		end
	end
	-- Dialog box to warn the user that the UI will be reloaded
	-- This ensures the profile is written to disk
	StaticPopupDialogs["TITAN_RELOADUI"] = {
		text = TitanUtils_GetNormalText(L["TITAN_PANEL_MENU_TITLE"]) .. "\n\n"
			.. L["TITAN_PANEL_MENU_PROFILE_RELOADUI"],
		button1 = "OKAY",
		OnAccept = function(self)
			ReloadUI(); -- ensure profile is written to disk
		end,
		showAlert = 1,
		whileDead = 1,
		timeout = 0,
	};

	-- Dialog box to warn the user that an existing profile will be overwritten.
	StaticPopupDialogs["TITAN_OVERWRITE_CUSTOM_PROFILE"] = {
		text = TitanUtils_GetNormalText(L["TITAN_PANEL_MENU_TITLE"]) .. "\n\n"
			.. L["TITAN_PANEL_MENU_PROFILE_ALREADY_EXISTS"],
		button1 = ACCEPT,
		button2 = CANCEL,
		OnAccept = function(self, data)
			Write_profile(data)
			self:Hide();
			StaticPopup_Show("TITAN_RELOADUI");
		end,
		showAlert = 1,
		whileDead = 1,
		timeout = 0,
		hideOnEscape = 1
	};

	-- Dialog box to save the profile.
	StaticPopupDialogs["TITAN_SAVE_CUSTOM_PROFILE"] = {
		text = TitanUtils_GetNormalText(L["TITAN_PANEL_MENU_TITLE"]) .. "\n\n"
			.. L["TITAN_PANEL_MENU_PROFILE_SAVE_CUSTOM_TITLE"],
		button1 = ACCEPT,
		button2 = CANCEL,
		hasEditBox = 1,
		maxLetters = 20,
		OnAccept = function(self)
			-- self refers to this frame with the Accept button
			Get_profile_name(self)
		end,
		OnShow = function(self)
			self.editBox:SetFocus();
		end,
		OnHide = function(self)
			self.editBox:SetText("");
		end,
		EditBoxOnEnterPressed = function(self)
			-- We need to get the parent because self refers to the edit box.
			Get_profile_name(self:GetParent())
		end,
		EditBoxOnEscapePressed = function(self)
			self:GetParent():Hide();
		end,
		timeout = 0,
		exclusive = 1,
		whileDead = 1,
		hideOnEscape = 1
	};

	StaticPopup_Show("TITAN_SAVE_CUSTOM_PROFILE");

	-- Can NOT cleanup. Execution does not stop when a dialog box is invoked!
	--	StaticPopupDialogs["TITAN_RELOADUI"] = {}
	--	StaticPopupDialogs["TITAN_OVERWRITE_CUSTOM_PROFILE"] = {}
	--	StaticPopupDialogs["TITAN_SAVE_CUSTOM_PROFILE"] = {}
end

---Titan Set or change the font and font size of text on the Titan bar. This affects ALL plugins.
--- Each registered plugin will have its font updated. Then all plugins will be refreshed to show the new font.
---@param fontname string path to font file
---@param fontsize number in points
function TitanSetPanelFont(fontname, fontsize)
	-- a couple of arg checks to avoid unpleasant things...
	if not fontname then fontname = TPC.FONT_NAME end
	if not fontsize then fontsize = TPC.FONT_SIZE end
	local newfont = media:Fetch("font", fontname)
	for index, id in pairs(TitanPluginsIndex) do
		local button = TitanUtils_GetButton(id)
		if button then
			local buttonText = _G[button:GetName() .. TITAN_PANEL_TEXT];
			if buttonText then
				buttonText:SetFont(newfont, fontsize);
			end
			-- account for plugins with child buttons
			local childbuttons = { button:GetChildren() };
			for _, child in ipairs(childbuttons) do
				if child then
					local bname = _G[child:GetName()]
					if bname then
						local childbuttonText = _G[child:GetName() .. TITAN_PANEL_TEXT];
						if childbuttonText then
							childbuttonText:SetFont(newfont, fontsize);
						end
					end
				end
			end
		end
	end
	TitanPanel_RefreshPanelButtons();
end

local function RegisterForEvents()
	-- Need to be careful of regeristering for events that initiate
	-- show / hide of Bars before the Bars can be initialized...
	_G[TITAN_PANEL_CONTROL]:RegisterEvent("CVAR_UPDATE");
	_G[TITAN_PANEL_CONTROL]:RegisterEvent("PLAYER_LOGOUT");

	-- For the pet battle - for now we'll hide the Titan bars...
	-- Cannot seem to move the 'top' part of the pet battle frame.
	_G[TITAN_PANEL_CONTROL]:RegisterEvent("PET_BATTLE_OPENING_START");
	_G[TITAN_PANEL_CONTROL]:RegisterEvent("PET_BATTLE_CLOSE");

	-- Hide Titan bars in combat (global or per bar); may be useful when using Short bars
	_G[TITAN_PANEL_CONTROL]:RegisterEvent("PLAYER_REGEN_ENABLED");
	_G[TITAN_PANEL_CONTROL]:RegisterEvent("PLAYER_REGEN_DISABLED");

	-- User request to hide Top bar(s) in BG or arena; more areas later?
	_G[TITAN_PANEL_CONTROL]:RegisterEvent("ZONE_CHANGED");
	_G[TITAN_PANEL_CONTROL]:RegisterEvent("ZONE_CHANGED_INDOORS");
	_G[TITAN_PANEL_CONTROL]:RegisterEvent("ZONE_CHANGED_NEW_AREA");
end

--------------------------------------------------------------
_G[TITAN_PANEL_CONTROL]:RegisterEvent("ADDON_LOADED");
--
-- Event routine : redirects to TitanPanelBarButton:<registered event> routines below.
_G[TITAN_PANEL_CONTROL]:SetScript("OnEvent", function(_, event, ...)
	_G[TITAN_PANEL_CONTROL][event](_G[TITAN_PANEL_CONTROL], ...)
end)


---Titan Do all the setup needed when a user logs in / reload UI / enter or leave an instance.
--- This is called after the 'player entering world' event is fired by Blizz.
--- This is also called when a LDB plugin is created after Titan runs the 'player entering world' code.
---@param reload boolean true if reload; false if character 'first' enter
function TitanPanel_PlayerEnteringWorld(reload)
	--[[
print("PEW"
.." "..tostring(Titan__InitializedPEW)..""
)
--]]
	if Titan__InitializedPEW then
		-- Currently no additional steps needed
	else
		-- Get Profile and Saved Vars
		TitanVariables_InitTitanSettings();
		if TitanAllGetVar("Silenced") then
			-- No header output
		else
			TitanPrint("", "header")
		end

		if not ServerTimeOffsets then
			ServerTimeOffsets = {};
		end
		if not ServerHourFormat then
			ServerHourFormat = {};
		end

		-- Set the two anchors in their default positions
		-- until the Titan bars are drawn
		TitanPanelTopAnchor:ClearAllPoints();
		TitanPanelTopAnchor:SetPoint("TOPLEFT", "UIParent", "TOPLEFT", 0, 0);
		TitanPanelBottomAnchor:ClearAllPoints();
		TitanPanelBottomAnchor:SetPoint("BOTTOMLEFT", "UIParent", "BOTTOMLEFT", 0, 0);

		-- Ensure the bars are created before the plugins are registered.
		for idx, v in pairs(TitanBarData) do
			TitanPanelButton_CreateBar(idx)
		end
		--		Titan_AutoHide_Create_Frames()

		-- Set clock vars based on user setting
		if TitanPlugins["Clock"] then
			local realmName = GetRealmName()
			if ServerTimeOffsets[realmName] then
				TitanSetVar("Clock", "OffsetHour", ServerTimeOffsets[realmName])
			elseif TitanGetVar("Clock", "OffsetHour") then
				ServerTimeOffsets[realmName] = TitanGetVar("Clock", "OffsetHour")
			end

			if ServerHourFormat[realmName] then
				TitanSetVar("Clock", "Format", ServerHourFormat[realmName])
			elseif TitanGetVar("Clock", "Format") then
				ServerHourFormat[realmName] = TitanGetVar("Clock", "Format")
			end
		end

		--[[ With user able to move the UI around and short bars, remove this feature
		-- Check to see if we should kill off the OrderHallCommandBar
		if not TitanAllGetVar("OrderHall") then
			local TitanPanelAce = LibStub("AceAddon-3.0"):NewAddon("TitanPanelOHCB", "AceHook-3.0")
			TitanPanelAce:SecureHook("OrderHall_CheckCommandBar",
				function()
					if OrderHallCommandBar then
						OrderHallCommandBar:Hide()
						OrderHallCommandBar:UnregisterAllEvents()
						OrderHallCommandBar.Show = function () end
					end
				end
			)
		else
			local TitanPanelAce = LibStub("AceAddon-3.0"):NewAddon("TitanPanelOHCB", "AceHook-3.0")
			TitanPanelAce:Unhook("OrderHall_CheckCommandBar")
		end
--]]
		-- Should be safe to register for events that could show / hide Bars
		RegisterForEvents()
	end
	local _ = nil
	TitanSettings.Player, _, _ = TitanUtils_GetPlayer()
	-- Some addons wait to create their LDB component or a Titan addon could
	-- create additional buttons as needed.
	-- So we need to sync their variables and set them up
	TitanUtils_RegisterPluginList()

	TitanVariables_UseSettings(nil, TITAN_PROFILE_INIT)

	-- all addons are loaded so update the config (options)
	-- some could have registered late...
	TitanUpdateConfig("init")

	-- Init panel font
	local isfontvalid = media:IsValid("font", TitanPanelGetVar("FontName"))
	if isfontvalid then
		TitanSetPanelFont(TitanPanelGetVar("FontName"), TitanPanelGetVar("FontSize"))
	else
		-- if the selected font is not valid, revert to default (Friz Quadrata TT)
		TitanPanelSetVar("FontName", TPC.FONT_NAME);
		TitanSetPanelFont(TPC.FONT_NAME, TitanPanelGetVar("FontSize"))
	end

	-- Init panel frame strata
	TitanVariables_SetPanelStrata(TitanPanelGetVar("FrameStrata"))

	-- Titan Panel has initialized its variables and registered plugins.
	-- Allow Titan - and others - to adjust the bars
	Titan__InitializedPEW = true

	-- Move frames
	if TITAN_ID == "TitanClassic" then
		TitanMovable_SecureFrames()
		TitanPanel_AdjustFrames(true, "_PlayerEnteringWorld")
	else
		-- No need
	end

	-- Also sync LDB object text with their created Titan plugin
	TitanLDBRefreshButton()
end

--------------------------------------------------------------
--
-- Event handlers
--
--[===[
		local WoWClassicEra, WoWClassicTBC, WoWWOTLKC, WoWRetail
		if wowversion < 20000 then
			WoWClassicEra = true
		elseif wowversion < 30000 then
			WoWClassicTBC = true
		elseif wowversion < 40000 then
			WoWWOTLKC = true
		elseif wowversion > 90000 then
			WoWRetail = true
		else
			-- n/a
		end
--]===]


---Titan Handle ADDON_LOADED Minimal setup in prep for player login.
function TitanPanelBarButton:ADDON_LOADED(addon)
	if addon == TITAN_ID then
		_G[TITAN_PANEL_CONTROL]:RegisterEvent("PLAYER_ENTERING_WORLD")
		TitanVariables_InitTitanSettings() -- Min table setup to start.
		-- Unregister event - saves a few event calls.
		self:UnregisterEvent("ADDON_LOADED");
		self.ADDON_LOADED = nil
	end
end

---Titan Handle PLAYER_ENTERING_WORLD Initialize Titan, set and display Titan bars and plugins.
function TitanPanelBarButton:PLAYER_ENTERING_WORLD(arg1, arg2)
	local call_success = nil
	local ret_val = nil

	--[[
print("PLAYER_ENTERING_WORLD"
.." "..tostring(arg1)..""
.." "..tostring(arg2)..""
)
--]]
	call_success, -- needed for pcall
	ret_val =  -- actual return values
		pcall(TitanPanel_PlayerEnteringWorld, arg2)
	-- pcall does not allow errors to propagate out. Any error
	-- is returned as text with the success / fail.
	-- Think of it as a try - catch block
	--[[
print("_PlayerEnteringWorld"
.." "..tostring(call_success)..""
)
--]]
	if call_success then
		-- Titan initialized properly
	else
		-- something really bad occured...
		TitanPrint("Titan could not initialize!!!!  Cleaning up...", "error")
		TitanPrint("--" .. ret_val, "error")
		-- Clean up best we can and tell the user to submit a ticket.
		-- This could be the 1st log in or a reload (reload, instance, boat, ...)

		-- Hide the bars. At times they are there but at 0% transparency.
		-- They can be over the Blizz action bars creating havoc.
		TitanPrint("-- Hiding Titan bars...", "warning")
		TitanPanelBarButton_HideAllBars()

		-- Remove the options pages
		TitanUpdateConfig("nuke")
		-- What else to clean up???

		-- raise the error to WoW for display, if display errors is set.
		-- This *must be* the last statement of the routine!
		error(ret_val, 1)
	end
end

---Titan Handle CVAR_UPDATE React to user changed WoW options.
function TitanPanelBarButton:CVAR_UPDATE(cvarname, cvarvalue)
	if cvarname == "USE_UISCALE"
		or cvarname == "WINDOWED_MODE"
		or cvarname == "uiScale" then
		if TitanPlayerSettings and TitanPanelGetVar("Scale") then
			TitanPanel_InitPanelBarButton("CVAR_ " .. tostring(cvarname))
			if TITAN_ID == "TitanClassic" then
				-- Adjust frame positions
				TitanPanel_AdjustFrames(true, "CVAR_UPDATE Scale")
			else
				-- No need
			end
		end
	end
end

---Titan Handle PLAYER_LOGOUT On logout, set some debug data in saved variables.
function TitanPanelBarButton:PLAYER_LOGOUT()
	if not IsTitanPanelReset then
		-- for debug
		if TitanPanelRegister then
			TitanPanelRegister.ToBe = TitanPluginToBeRegistered
			TitanPanelRegister.ToBeNum = TitanPluginToBeRegisteredNum
			TitanPanelRegister.TitanPlugins = TitanPlugins
		end
	end
	Titan__InitializedPEW = false
end

---Titan Handle ZONE_CHANGED_INDOORS Hide Titan top bars if user requested to hide Top bar(s) in BG or arena
function TitanPanelBarButton:ZONE_CHANGED()
	TitanPanelBarButton_DisplayBarsWanted("ZONE_CHANGED")
end

---Titan Handle ZONE_CHANGED_INDOORS Hide Titan top bars if user requested to hide Top bar(s) in BG or arena
function TitanPanelBarButton:ZONE_CHANGED_INDOORS()
	TitanPanelBarButton_DisplayBarsWanted("ZONE_CHANGED_INDOORS")
end

---Titan Handle ZONE_CHANGED_INDOORS Hide Titan top bars if user requested to hide Top bar(s) in BG or arena
function TitanPanelBarButton:ZONE_CHANGED_NEW_AREA()
	TitanPanelBarButton_DisplayBarsWanted("ZONE_CHANGED_NEW_AREA")
end

---Titan Handle PET_BATTLE_CLOSE Hide Titan bars during pet battle.
function TitanPanelBarButton:PET_BATTLE_OPENING_START()
	TitanPanelBarButton_DisplayBarsWanted("PET_BATTLE_OPENING_START")
end

---Titan Handle PET_BATTLE_CLOSE Show Titan bars hidden bars during pet battle.
function TitanPanelBarButton:PET_BATTLE_CLOSE()
	TitanPanelBarButton_DisplayBarsWanted("PET_BATTLE_CLOSE")
end

local in_combat = false -- seems InCombatLockdown may not be set fast enough to reliably hide bars...

---Titan Handle PLAYER_REGEN_ENABLED Titan bars may be hidden during combat.
--- Use local in_combat - seems InCombatLockdown() may not be set fast enough to reliably hide bars...
function TitanPanelBarButton:PLAYER_REGEN_ENABLED()
	in_combat = false
	TitanPanelBarButton_DisplayBarsWanted("PLAYER_REGEN_ENABLED")

	if TITAN_ID == "TitanClassic" then
		-- Adjust frame positions
		TitanPanel_AdjustFrames(false, "PLAYER_REGEN_ENABLED")
	else
		-- No need
	end
end

---Titan Handle PLAYER_REGEN_DISABLED Titan bars may have been hidden during combat.
--- Use local in_combat - seems InCombatLockdown() may not be set fast enough to reliably hide bars...
function TitanPanelBarButton:PLAYER_REGEN_DISABLED()
	in_combat = true
	TitanPanelBarButton_DisplayBarsWanted("PLAYER_REGEN_DISABLED")
end

if TITAN_ID == "TitanClassic" then
	function TitanPanelBarButton:ACTIVE_TALENT_GROUP_CHANGED()
		-- Is this needed??
		--	TitanMovable_AdjustTimer("DualSpec")
	end

	function TitanPanelBarButton:UNIT_ENTERED_VEHICLE(self, ...)
		TitanUtils_CloseAllControlFrames();
		TitanUtils_CloseRightClickMenu();

		-- Needed because 8.0 made changes to the menu bar processing (see TitanMovable)
		TitanMovable_MenuBar_Disable()
	end

	function TitanPanelBarButton:UNIT_EXITED_VEHICLE(self, ...)
		-- A combat check will be done inside the adjust
		TitanPanel_AdjustFrames(true, "UNIT_ENTERED_VEHICLE")
	end

	--]]
	--
else
	-- No need
end

---Titan Handle the button clicks on any Titan bar.
--- This only reacts to the right or left mouse click without modifiers.
--- Used in the set script for the Titan display and hider frames
---@param self table Titan bar frame
---@param button string Button clicked
function TitanPanelBarButton_OnClick(self, button)
	-- ensure that the right-click menu will not appear on "hidden" bottom bar(s)
	if (button == "LeftButton") then
		TitanUtils_CloseAllControlFrames();
		TitanUtils_CloseRightClickMenu();
	elseif (button == "RightButton") then
		TitanUtils_CloseAllControlFrames();
		TitanPanelRightClickMenu_Close();
		-- Show RightClickMenu anyway
		TitanPanelRightClickMenu_Toggle(self)
	end
end

--
-- Slash command handler
--


---local Helper to parse the user commands from Chat.
---@param cmd any
---@return table cmds List of 'words' user typed
--- each 'word' is made lower case for comparison simplicity
local function TitanPanel_ParseSlashCmd(cmd)
	local words = {}
	for w in string.gmatch(cmd, "%w+") do
		words[#words + 1] = (w and string.lower(w) or "?")
	end
	--[[
	local tmp = ""
	for idx,v in pairs (words) do
		tmp = tmp.."'"..words[idx].."' "
	end

	TitanDebug (tmp.." : "..#words)
--]]
	return words
end

---local Helper to tell the user the relevant Titan help commands.
---@param cmd string? 'all' default | 'reset' | 'gui' | 'silent'
local function handle_slash_help(cmd)
	cmd = cmd or "all"

	--	Give the user the general help if we can not figure out what they want
	TitanPrint("", "header")
	-- Cannot count registered plugins after initial registration  TitanUtils_RegisterPluginList()

	if cmd == "reset" then
		TitanPrint(L["TITAN_PANEL_SLASH_RESET_0"], "plain")
		TitanPrint(L["TITAN_PANEL_SLASH_RESET_1"], "plain")
		TitanPrint(L["TITAN_PANEL_SLASH_RESET_2"], "plain")
		TitanPrint(L["TITAN_PANEL_SLASH_RESET_3"], "plain")
		TitanPrint(L["TITAN_PANEL_SLASH_RESET_4"], "plain")
		TitanPrint(L["TITAN_PANEL_SLASH_RESET_5"], "plain")
	end
	if cmd == "gui" then
		TitanPrint(L["TITAN_PANEL_SLASH_GUI_0"], "plain")
		TitanPrint(L["TITAN_PANEL_SLASH_GUI_1"], "plain")
		TitanPrint(L["TITAN_PANEL_SLASH_GUI_2"], "plain")
		TitanPrint(L["TITAN_PANEL_SLASH_GUI_3"], "plain")
	end
	if cmd == "profile" then
		TitanPrint(L["TITAN_PANEL_SLASH_PROFILE_0"], "plain")
		TitanPrint(L["TITAN_PANEL_SLASH_PROFILE_1"], "plain")
		TitanPrint(L["TITAN_PANEL_SLASH_PROFILE_2"], "plain")
		TitanPrint(L["TITAN_PANEL_SLASH_PROFILE_3"], "plain")
	end
	if cmd == "silent" then
		TitanPrint(L["TITAN_PANEL_SLASH_SILENT_0"], "plain")
		TitanPrint(L["TITAN_PANEL_SLASH_SILENT_1"], "plain")
	end
	if cmd == "orderhall" then
		TitanPrint(L["TITAN_PANEL_SLASH_ORDERHALL_0"], "plain")
		TitanPrint(L["TITAN_PANEL_SLASH_ORDERHALL_1"], "plain")
	end
	if cmd == "help" then
		TitanPrint(L["TITAN_PANEL_SLASH_HELP_0"], "plain")
		TitanPrint(L["TITAN_PANEL_SLASH_HELP_1"], "plain")
	end
	if cmd == "all" then
		TitanPrint(L["TITAN_PANEL_SLASH_ALL_0"], "plain")
		TitanPrint(L["TITAN_PANEL_SLASH_ALL_1"], "plain")
	end
end

---local Helper to handle 'reset' commands.
---@param cmd_list table  'tipfont' | 'panelscale' | "spacing" | none - Reset to Titan defaults
local function handle_reset_cmds(cmd_list)
	local cmd = cmd_list[1]
	local p1 = cmd_list[2] or nil
	-- sanity check
	if (not cmd == "reset") then
		return
	end

	if p1 == nil then
		TitanPanel_ResetToDefault();
	elseif p1 == "tipfont" then
		TitanPanelSetVar("TooltipFont", 1);
		GameTooltip:SetScale(TitanPanelGetVar("TooltipFont"));
		TitanPrint(L["TITAN_PANEL_SLASH_RESP1"], "info")
		--[[
	elseif p1 == "tipalpha" then
		TitanPanelSetVar("TooltipTrans", 1);
		local red, green, blue, _ = GameTooltip:GetBackdropColor();
		local red2, green2, blue2, _ = GameTooltip:GetBackdropBorderColor();
		GameTooltip:SetBackdropColor(red,green,blue,TitanPanelGetVar("TooltipTrans"));
		GameTooltip:SetBackdropBorderColor(red2,green2,blue2,TitanPanelGetVar("TooltipTrans"));
		TitanPrint(L["TITAN_PANEL_SLASH_RESP2"], "info")
--]]
	elseif p1 == "panelscale" then
		if not InCombatLockdown() then
			TitanPanelSetVar("Scale", 1);
			TitanPanel_InitPanelBarButton("/panelscale reset ")
			if TITAN_ID == "TitanClassic" then
				-- Adjust frame positions
				TitanPanel_AdjustFrames(true, "/panelscale reset ")
			else
				-- No need
			end
			TitanPrint(L["TITAN_PANEL_SLASH_RESP3"], "info")
		else
			TitanPrint(L["TITAN_PANEL_MENU_IN_COMBAT_LOCKDOWN"], "warning")
		end
	elseif p1 == "spacing" then
		TitanPanelSetVar("ButtonSpacing", 20);
		TitanPanel_InitPanelButtons();
		TitanPrint(L["TITAN_PANEL_SLASH_RESP4"], "info")
	else
		handle_slash_help("reset")
	end
end

---local Helper to handle 'gui' commands - open Titan options.
---@param cmd_list table
local function handle_giu_cmds(cmd_list)
	local cmd = cmd_list[1]
	local p1 = cmd_list[2] or nil
	-- sanity check
	if (not cmd == "gui") then
		return
	end

	-- DF changed how options are called. The best I get is the Titan 'about', not deeper.
	Settings.OpenToCategory(TITAN_PANEL_CONFIG.topic.About, TITAN_PANEL_CONFIG.topic.scale)
	-- so the below does not work as expected...
end

---local Helper to handle 'profile' commands - Set to profile if not using global profile.
---@param cmd_list table
local function handle_profile_cmds(cmd_list)
	local cmd = cmd_list[1]
	local p1 = cmd_list[2] or nil
	local p2 = cmd_list[3] or nil
	local p3 = cmd_list[4] or nil
	-- sanity check
	if (not cmd == "profile") then
		return
	end

	if p1 == "use" and p2 and p3 then
		if TitanAllGetVar("GlobalProfileUse") then
			TitanPrint(L["TITAN_PANEL_GLOBAL_ERR_1"], "info")
		else
			TitanVariables_UseSettings(TitanUtils_CreateName(p2, p3), TITAN_PROFILE_USE)
		end
	else
		handle_slash_help("profile")
	end
end

---local Helper to handle 'silent' commands - Toggle "Silenced" setting.
---@param cmd_list table
local function handle_silent_cmds(cmd_list)
	local cmd = cmd_list[1]
	-- sanity check
	if (not cmd == "silent") then
		return
	end

	if TitanAllGetVar("Silenced") then
		TitanAllSetVar("Silenced", false);
		TitanPrint(L["TITAN_PANEL_MENU_SILENT_LOAD"] .. " " .. L["TITAN_PANEL_MENU_DISABLED"], "info")
	else
		TitanAllSetVar("Silenced", true);
		TitanPrint(L["TITAN_PANEL_MENU_SILENT_LOAD"] .. " " .. L["TITAN_PANEL_MENU_ENABLED"], "info")
	end
end

---local Helper to handle 'orderhall' commands - Toggle "OrderHall" setting.
---@param cmd_list table
local function handle_orderhall_cmds(cmd_list)
	local cmd = cmd_list[1]
	local p1 = cmd_list[2] or nil
	-- sanity check
	if (not cmd == "orderhall") then
		return
	end

	if TitanAllGetVar("OrderHall") then
		TitanAllSetVar("OrderHall", false);
		TitanPrint(L["TITAN_PANEL_MENU_HIDE_ORDERHALL"] .. " " .. L["TITAN_PANEL_MENU_ENABLED"], "info")
		StaticPopupDialogs["TITAN_RELOAD"] = {
			text = TitanUtils_GetNormalText(L["TITAN_PANEL_MENU_TITLE"]) .. "\n\n"
				.. L["TITAN_PANEL_RELOAD"],
			button1 = ACCEPT,
			button2 = CANCEL,
			OnAccept = function(self)
				ReloadUI();
			end,
			showAlert = 1,
			timeout = 0,
			whileDead = 1,
			hideOnEscape = 1
		};
		StaticPopup_Show("TITAN_RELOAD");
	else
		TitanAllSetVar("OrderHall", true);
		TitanPrint(L["TITAN_PANEL_MENU_HIDE_ORDERHALL"] .. " " .. L["TITAN_PANEL_MENU_DISABLED"], "info")
		StaticPopupDialogs["TITAN_RELOAD"] = {
			text = TitanUtils_GetNormalText(L["TITAN_PANEL_MENU_TITLE"]) .. "\n\n"
				.. L["TITAN_PANEL_RELOAD"],
			button1 = ACCEPT,
			button2 = CANCEL,
			OnAccept = function(self)
				ReloadUI();
			end,
			showAlert = 1,
			timeout = 0,
			whileDead = 1,
			hideOnEscape = 1
		};
		StaticPopup_Show("TITAN_RELOAD");
	end
end

--[=[ local
DESC: Helper to execute the help commands from the user.
VAR: cmd_list - A table containing the list of 'words' the user typed in
OUT: None
local function handle_help_cmds(cmd_list)
	local cmd = cmd_list[1]
	local p1 = cmd_list[2] or nil
	-- sanity check
	if (not cmd == "help") then
		return
	end

	handle_slash_help(p1 or "all")
end
--]=]

---local Helper to parse and execute all the Titan slash commands from the user.
---@param cmd_str string
local function TitanPanel_RegisterSlashCmd(cmd_str)
	local cmd_list = {}
	-- parse what the user typed
	cmd_list = TitanPanel_ParseSlashCmd(cmd_str)
	local cmd = cmd_list[1] or ""
	local p1 = cmd_list[2] or ""
	local p2 = cmd_list[3] or ""
	local p3 = cmd_list[4] or ""

	if (cmd == "reset") then
		handle_reset_cmds(cmd_list)
	elseif (cmd == "gui") then
		handle_giu_cmds(cmd_list)
	elseif (cmd == "profile") then
		handle_profile_cmds(cmd_list)
	elseif (cmd == "silent") then
		handle_silent_cmds(cmd_list)
	elseif (cmd == "orderhall") then
		handle_orderhall_cmds(cmd_list)
	elseif (cmd == "help") then
		handle_slash_help(p1)
	else
		handle_slash_help("all")
	end
end

--====== Register slash commands for Titan Panel
SlashCmdList["TitanPanel"] = TitanPanel_RegisterSlashCmd;
SLASH_TitanPanel1 = "/titanpanel";
SLASH_TitanPanel2 = "/titan";

--------------------------------------------------------------
--
-- Texture routines


---local Set the Titan bar color per user selection
---@param frame string Titan bar name
---@param tex table Texture frame to set
---@param color table Color - RBGA
local function Set_Color(frame, tex, color)
	--[[
print("_Set bar color"
.." "..tostring(TitanBarData[frame].tex_name)..""
--.." "..tostring(tex:GetName())..""
.." "..tostring(format("%0.1f", color.r))..""
.." "..tostring(format("%0.1f", color.g))..""
.." "..tostring(format("%0.1f", color.b))..""
.." "..tostring(format("%0.1f", color.alpha))..""
)
--]]
	_G[frame]:SetBackdrop({
		bgFile = "Interface\\Tooltips\\UI-Tooltip-Background",
		--		edgeFile="Interface\\Tooltips\\UI-Tooltip-Border",
		--		edgeFile="Interface\\DialogFrame\\UI-DialogBox-Gold-Border",
		edgeFile = "Interface\\Glues\\Common\\TextPanel-Border",
		tile = true,
		tileEdge = true,
		--		insets = { left = 1, right = 1, top = 1, bottom = 1 },
		tileSize = 8,
		edgeSize = 8,
	})

	_G[frame]:SetBackdropBorderColor(
		TOOLTIP_DEFAULT_COLOR.r,
		TOOLTIP_DEFAULT_COLOR.g,
		TOOLTIP_DEFAULT_COLOR.b);
	_G[frame]:SetBackdropColor(
		color.r,
		color.g,
		color.b,
		color.alpha);
	--[[
	-- Apply the texture to the bar, using the system repeat to fill it
	tex:SetColorTexture(
		color.r,
		color.g,
		color.b,
		color.alpha
		)
	tex:SetAllPoints()
	tex:SetHorizTile(true) -- ensures repeat; 'smears' if not sest to true
	tex:SetVertTile(true)  -- ensures image is 'full' height of frame
--]]
end

---local Set the Titan bar texture / skin per user selectable options
---@param frame string Titan bar name
---@param tex table Texture frame to set
---@param skin table Skin to use
local function Set_Skin(frame, tex, skin)
	-- skins are in two parts - top & bottom...
	-- TODO : have Short bars choose top or bottom skin??
	local edge = ""
	if TitanBarData[frame].vert == TITAN_BOTTOM
	then
		edge = TITAN_BOTTOM
	else
		edge = TITAN_TOP
	end

	-- Apply the texture to the bar, using the system repeat to fill it
	local texture_file = skin.path .. "TitanPanelBackground" .. edge .. "0"
	--[[
print("_Skin"
.." "..tostring(TitanBarData[frame].tex_name..""
--.." "..tostring(tex:GetName())..""
.." "..tostring(skin.path)..""
--.."\n "..tostring(edge)..""
--.." "..tostring(skin.alpha)..""
--.."\n "..tostring(tex:GetTexture())..""
)
--]]
	--[[ -- appears seeting image this way just smears image...
	_G[frame]:SetBackdrop({
		bgFile=texture_file,
--		edgeFile=nil,
		tile = true,
--		tileSize = 256,
--		tileEdge = true,
--		insets = { left = 1, right = 1, top = 1, bottom = 1 },
--		tileSize = 8,
--		edgeSize = 8,
	})
--]]
	tex:SetAllPoints()
	tex:SetHorizTile(true) -- ensures repeat; 'smears' if not sest to true
	tex:SetTexture(texture_file, "REPEAT")
	tex:SetVertTile(true) -- ensures image is 'full' height of frame
	--	tex:SetHeight(TITAN_PANEL_BAR_TEXTURE_HEIGHT) -- leaves a gap if used
	tex:SetAlpha(skin.alpha)
end

---Titan Set the texture / skin of the bar per the user selection.
---@param frame string Titan bar frame name
function TitanPanel_SetBarTexture(frame)
	if frame and TitanBarData[frame] then
		-- proceed
	else
		return
	end

	-- Create the path & file name to the texture
	local tex = TitanBarData[frame].tex_name
	local titanTexture = {}
	if _G[tex] then
		titanTexture = _G[tex]
	else
		titanTexture = _G[frame]:CreateTexture(tex, "BACKGROUND")
	end
	titanTexture:SetTexture()
	_G[frame]:SetBackdrop({
		bgFile = "",
	})

	--[[
print("_Tex"
.." "..tostring(TitanBarData[frame].name)..""
--.." "..tostring(tex)..""
.." "..tostring(titanTexture:GetName())..""
--.." "..tostring(skin.path)..""
--.."\n "..tostring(edge)..""
--.." "..tostring(skin.alpha)..""
--.."\n "..tostring(tex:GetTexture())..""
.." "..tostring(TitanBarDataVars["Global"].texure)..""
.." "..tostring(TitanBarDataVars[frame].texure)..""
)
--]]
	-- Use the texture / skin per user selectable options
	if TitanBarDataVars["Global"].texure == Titan_Global.SKIN then
		Set_Skin(frame, titanTexture, TitanBarDataVars["Global"].skin) -- tex_path = TitanPanelGetVar("TexturePath")
	elseif TitanBarDataVars["Global"].texure == Titan_Global.COLOR then
		Set_Color(frame, titanTexture, TitanBarDataVars["Global"].color)
	elseif TitanBarDataVars[frame].texure == Titan_Global.SKIN then
		Set_Skin(frame, titanTexture, TitanBarDataVars[frame].skin)
	elseif TitanBarDataVars[frame].texure == Titan_Global.COLOR then
		Set_Color(frame, titanTexture, TitanBarDataVars[frame].color)
	end
end

--------------------------------------------------------------
--
-- auto hide event handlers


---Titan On leaving the display check if we have to hide the Titan bar. A timer is used - when it expires the bar is hidden.
---@param self table Titan bar frame
function TitanPanelBarButton_OnLeave(self)
	local frame = (self and self:GetName() or nil)
	local bar = (TitanBarData[frame] and TitanBarData[frame].name or nil)

	-- if auto hide is active then let the timer hide the bar
	local hide = (bar and TitanBarDataVars[frame].auto_hide or nil)
	--	local hide = (bar and TitanPanelGetVar(bar.."_Hide") or nil)
	if hide then
		Titan_AutoHide_Timers(frame, "Leave")
	end
end

---Titan No code - this is a place holder for the XML template.
---@param self table Titan Hider bar frame
function TitanPanelBarButton_OnEnter(self)
	-- no work to do
end

---Titan No code - this is a place holder for the XML template.
---@param self table Titan Hider bar frame
function TitanPanelBarButtonHider_OnLeave(self)
	-- no work to do
end

---Titan On entering the hider, check if we need to show the display bar.
--- No action is taken if the user is on combat.
---@param self table Titan Hider bar frame
function TitanPanelBarButtonHider_OnEnter(self)
	-- make sure self is valid
	local index = self and self:GetName() or nil
	if not index then return end -- sanity check

	-- so the bar does not 'appear' when moused over in combat
	if TitanPanelGetVar("LockAutoHideInCombat") and InCombatLockdown() then return end

	-- find the relevant bar data
	local frame = nil
	for idx, v in pairs(TitanBarData) do
		if index == TitanBarData[idx].hider then
			frame = idx
		end
	end
	-- Now process that bar
	if frame then
		Titan_AutoHide_Timers(frame, "Enter")
		TitanPanelBarButton_Show(frame)
	end
end

--====== Titan Frames for CLASSIC versions

--
--==========================
-- Routines to handle adjusting some UI frames
--

--[[ Appears unsed...
---Titan Align the buttons per the user's new choice.
---@param align number left or center
function TitanPanelBarButton_ToggleAlign(align)
	-- toggle between left or center
	if (TitanPanelGetVar(align) == TITAN_PANEL_BUTTONS_ALIGN_CENTER) then
		TitanPanelSetVar(align, TITAN_PANEL_BUTTONS_ALIGN_LEFT);
	else
		TitanPanelSetVar(align, TITAN_PANEL_BUTTONS_ALIGN_CENTER);
	end

	-- Justify button position
	TitanPanelButton_Justify();
end
--]]

---Titan Toggle the auto hide of the given Titan bar per the user's new choice.
---@param frame string Frame mame of the Titan bar
function TitanPanelBarButton_ToggleAutoHide(frame)
	local frName = _G[frame]
	local plugin = (TitanBarData[frame] and TitanBarData[frame].auto_hide_plugin or nil)

	if frName then
		Titan_AutoHide_ToggleAutoHide(_G[plugin])
	end
end

---Titan Toggle whether Titan adjusts 'top' frames around Titan bars per the user's new choice.
--- Another addon can tell Titan to NOT adjust some or all frames.
function TitanPanelBarButton_ToggleScreenAdjust()
	-- Turn on / off adjusting of other frames around Titan
	TitanPanelToggleVar("ScreenAdjust");
	TitanPanel_AdjustFrames(true, "_ToggleScreenAdjust")
end

---Titan Toggle whether Titan adjusts 'bottom' frames around Titan bars per the user's new choice.
--- Another addon can tell Titan to NOT adjust some or all frames.
function TitanPanelBarButton_ToggleAuxScreenAdjust()
	-- turn on / off adjusting of frames at the bottom of the screen
	TitanPanelToggleVar("AuxScreenAdjust");
	TitanPanel_AdjustFrames(true, "_ToggleAuxScreenAdjust")
end

--====== Titan Bar
--
--==========================
-- Routines to handle moving and sizing of short bars
--

---local Check the change in width; snap to edge of any part goes off screen.
---@param self table Titan short bar frame
---@param width number New width
---@return table result .ok boolean; .err string
local function CheckBarBounds(self, width)
	--- This is a touchy routine - change with care!! :)
	local trace = false -- true false
	local result = {}
	result.ok = true
	result.err = ""
	local err = ""

	local f_name = self:GetName()
	local bar_name = TitanBarData[f_name].name
	local locale_name = TitanBarData[f_name].locale_name
	local escale = UIParent:GetEffectiveScale()

	if TitanBarData[f_name].user_move
		and TitanBarDataVars[f_name].show
	then
		if trace then
			print("Bounds"
				.. " " .. tostring(bar_name) .. ""
				.. " " .. tostring(width) .. ""
			)
		end

		local tscale = TitanPanelGetVar("Scale")
		local screen = TitanUtils_ScreenSize()
		local screen_right_scaled = screen.scaled_x
		local screen_top_scaled = screen.scaled_y
		local screen_right = screen.x
		local screen_top = screen.y
		local screen_right_t = screen.x * tscale
		local screen_top_t = screen.y * tscale


		-- Apply the Titan scaling to get 'real' position within WoW window;
		-- Use floor to trunc decimal places where the side could be right on the edge of the screen.
		local orig_w = self:GetWidth() -- * tscale --math.floor(self:GetWidth() * tscale)
		local l_off = math.floor(self:GetLeft() * tscale)
		local r_off = math.floor(self:GetRight() * tscale)
		local t_off = math.floor(self:GetTop() * tscale)
		local b_off = math.floor(self:GetBottom() * tscale)
		local hght = math.floor(t_off - b_off)

		if trace then
			print(">Bounds"
				.. "\n"
				.. " L " .. tostring(format("%0.1f", l_off)) .. ""
				.. " R " .. tostring(format("%0.1f", r_off)) .. ""
				.. " T " .. tostring(format("%0.1f", t_off)) .. ""
				.. " B " .. tostring(format("%0.1f", b_off)) .. ""
				.. " W " .. tostring(format("%0.1f", orig_w)) .. ""
				.. " H " .. tostring(format("%0.1f", hght)) .. ""
				.. "\n"
				.. " SR " .. tostring(format("%0.1f", screen_right)) .. ""
				.. " ST " .. tostring(format("%0.1f", screen_top)) .. ""
				.. " SR_t " .. tostring(format("%0.1f", screen_right_t)) .. ""
				.. " ST_t " .. tostring(format("%0.1f", screen_top_t)) .. ""
			)
		end

		local w = 0
		local x_off = 0
		local y_off = 0
		local w_off = 0

		-- Assume all ok :)
		x_off = l_off
		y_off = b_off

		if (width == 0) then -- drag & drop OR entry / reload
			-- Assumes BOTTOMLEFT of screen per Short bar defaults.
			-- if resolution is not 'pixel perfect' rounding could cause algorithm to think bar is off screen

			-- Keep the width
			w_off = orig_w

			if l_off < 0 then
				x_off = 0
				err = "Off left of screen, snap to edge"
			elseif (r_off) > screen_right then
				x_off = math.floor(screen_right - (r_off - l_off))
				err = "Off right side of screen, snap to edge"
			end
			if err ~= "" then
				result.ok = false
				result.err = err
				if trace then
					TitanPrint(locale_name .. " " .. err .. "!!!!"
					, "warning")
				end
			end
			err = ""
			if (t_off) > screen_top then
				y_off = math.floor(screen_top - (t_off - b_off))
				err = "Off top of screen, snap to edge"
			elseif b_off < 0 then
				y_off = 0
				err = "Off bottom of screen, snap to edge"
			end
			if err ~= "" then
				result.ok = false
				result.err = result.err .. "\n" .. err
				if trace then
					TitanPrint(locale_name .. " " .. err .. "!!!!"
						.. " [" .. tostring(format("%0.1f", x_off)) .. "]"
						.. " [" .. tostring(format("%0.1f", y_off)) .. "]"
						, "warning")
				end
			end
		else -- width change
			local min_w, min_h, max_w, max_h = self:GetResizeBounds()
			-- Keep the X and Y
			local w_new = orig_w + width
			if w_new < min_w then
				-- do nothing - too small
				w_off = min_w
				err = "Width too small. Set to min width."
			elseif w_new > max_w then
				w_off = max_w
				err = "Width too big. Set to max width." -- too wide
			elseif x_off + (w_new * tscale) > screen_right then
				w_off = orig_w
				err = "Off right of screen, snap to edge"
			else
				w_off = w_new
			end

			self:SetSize(w_off, TITAN_PANEL_BAR_HEIGHT)
			--			self:SetWidth(w_off)
		end

		-- Back out Titan scaling
		x_off = x_off / tscale
		y_off = y_off / tscale
		w_off = w_off --/ tscale
		TitanVariables_SetBarPos(self, false, x_off, y_off, w_off)

		if trace then
			print(">>Bounds"
				.. " " .. tostring(result.ok) .. ""
				.. " X " .. tostring(format("%0.1f", x_off)) .. ""
				.. " Y " .. tostring(format("%0.1f", y_off)) .. ""
				.. " W " .. tostring(format("%0.1f", w_off)) .. ""
			)
			if err ~= "" then
				TitanPrint(locale_name .. " " .. err .. "!!!!"
					.. " [" .. tostring(format("%0.1f", x_off)) .. "]"
					.. " [" .. tostring(format("%0.1f", y_off)) .. "]"
					.. " [" .. tostring(format("%0.1f", w_off)) .. "]"
					, "warning")
			end
		end
	else
		-- Controlled with anchor points; cannot move so no check is needed
	end


	return result
end

---local Start the grap of a Short Titan bar if Shift and left mouse are held.
---@param self table
local function OnMoveStart(self)
	if IsShiftKeyDown() then
		if self:IsMovable() then
			self.isMoving = true
			self:StartMoving()
			_G.GameTooltip:Hide()
		end
	else
		-- Do not move
	end
end

---local When a Short Titan bar drag is stopped.
---@param self table
local function OnMovingStop(self)
	self:StopMovingOrSizing()
	self.isMoving = nil

	local res = CheckBarBounds(self, 0)
	if res.ok then
		-- placement ok
	else
		-- Need to 'snap' it to an edge
		TitanPanel_InitPanelBarButton("OnMovingStop")
	end
	-- Seems overkill - this will recalc all bars...
end

---local Change the width of a Short Titan bar when mouse is over the bar and Shift is held when the mouse wheel is used.
---@param self table Frame mouse is over
---@param d integer Mouse wheel direction (1 larger; -1 smaller)
--- Assuming a 1 'click' is a pixel
local function OnMouseWheel(self, d)
	-- Can get noisy, "Initializes" all bars at each click to ensure the bar is drawn.
	if IsShiftKeyDown() then
		local old_w = self:GetWidth()
		local delta = d
		if IsControlKeyDown() then
			delta = d * 10
		else
			-- use 1
		end
		local res = CheckBarBounds(self, delta)
		if res.ok then
		end
		--[[
print("wheel"
.." "..tostring(self:GetName())..""
.." "..tostring(d)..""
.." old: "..tostring(format("%0.1f", old_w))..""
.." new: "..tostring(format("%0.1f", self:GetWidth()))..""
.." ok: "..tostring(res.ok)..""
)
--]]
		--		TitanPanel_InitPanelBarButton("OnMouseWheel")
	end
end

---Titan Force all plugins created from LDB addons, visible or not, to be on the right side of the Titan bar.
--- Any visible plugin will be forced to the right side on the same bar it is currently on.
function TitanPanelBarButton_ForceLDBLaunchersRight()
	local plugin = {}
	for index, id in pairs(TitanPluginsIndex) do
		plugin = TitanUtils_GetPlugin(id);
		if plugin and plugin.ldb == "launcher"
			and not TitanGetVar(id, "DisplayOnRightSide") then
			TitanToggleVar(id, "DisplayOnRightSide");
			local button = TitanUtils_GetButton(id)
			if button then
				local buttonText = _G[button:GetName() .. TITAN_PANEL_TEXT];
				if not TitanGetVar(id, "ShowIcon") then
					TitanToggleVar(id, "ShowIcon");
				end
				TitanPanelButton_UpdateButton(id);
				if buttonText then
					buttonText:SetText("")
					button:SetWidth(16);
					TitanPlugins[id].buttonTextFunction = nil;
					_G[TitanUtils_ButtonName(id) .. TITAN_PANEL_TEXT] = nil;
					if button:IsVisible() then
						local bar = TitanUtils_GetWhichBar(id)
						TitanPanel_RemoveButton(id);
						TitanUtils_AddButtonOnBar(bar, id)
					end
				end
			end
		end
	end
end

---local Helper to create the 'anchor' frames used by other addons that need to adjust so Titan can be visible.
---The anchor frames are adjusted depending on which Titan bars the user selects to show.
--- - TitanPanelTopAnchor - the frame at the bottom of the top bar(s) shown.
--- - TitanPanelBottomAnchor - the frame at the top of the bottom bar(s) shown.
local function TitanAnchors()
	local anchor_top = TitanMovable_GetPanelYOffset(TITAN_PANEL_PLACE_TOP)
	local anchor_bot = TitanMovable_GetPanelYOffset(TITAN_PANEL_PLACE_BOTTOM)
	anchor_top = anchor_top <= TITAN_WOW_SCREEN_TOP and anchor_top or TITAN_WOW_SCREEN_TOP
	anchor_bot = anchor_bot >= TITAN_WOW_SCREEN_BOT and anchor_bot or TITAN_WOW_SCREEN_BOT

	local top_point, top_rel_to, top_rel_point, top_x, top_y = TitanPanelTopAnchor:GetPoint(TitanPanelTopAnchor
	:GetNumPoints())
	local bot_point, bot_rel_to, bot_rel_point, bot_x, bot_y = TitanPanelBottomAnchor:GetPoint(TitanPanelBottomAnchor
	:GetNumPoints())
	top_y = floor(tonumber(top_y) + 0.5)
	bot_y = floor(tonumber(bot_y) + 0.5)
	--[[
TitanDebug("Anc top: "..top_y.." bot: "..bot_y
.." a_top: "..anchor_top.." a_bot: "..anchor_bot
)
--]]
	if top_y ~= anchor_top then
		TitanPanelTopAnchor:ClearAllPoints()
		TitanPanelTopAnchor:SetPoint(top_point, top_rel_to, top_rel_point, top_x, anchor_top);
	end
	if bot_y ~= anchor_bot then
		TitanPanelBottomAnchor:ClearAllPoints()
		TitanPanelBottomAnchor:SetPoint(bot_point, bot_rel_to, bot_rel_point, bot_x, anchor_bot)
	end
end

---Titan Show all the Titan bars the user has selected.
---@param reason string Debug note on where the call initiated
function TitanPanelBarButton_DisplayBarsWanted(reason)
	local trace = false
	if trace then
		print("_DisplayBarsWanted"
			.. " " .. tostring(reason) .. ""
		)
	end

	-- Check all bars to see if the user has requested they be shown
	for idx, v in pairs(TitanBarData) do
		-- Show / hide plus kick auto hide, if needed
		Titan_AutoHide_Init(idx)
	end

	-- Set anchors for other addons to use.
	TitanAnchors()

	if TITAN_ID == "TitanClassic" then
		-- Adjust other frames because the bars shown / hidden may have changed
		TitanPanel_AdjustFrames(true, "_DisplayBarsWanted")
	else
		-- Not needed with UI movable widgets
	end
end

---Titan This routine will hide all the Titan bars (and hiders) regardless of what the user has selected.
--- For example when the pet battle is active. We cannot figure out how to move the pet battle frame so we are punting and hiding Titan...
--- We only need to hide the bars (and hiders) - not adjust frames
function TitanPanelBarButton_HideAllBars()
	for idx, v in pairs(TitanBarData) do
		TitanPanelBarButton_Hide(idx)
	end
end

---local Show the requested Titan bar.
---@param frame_str string Titan bar
---@return boolean CanShow False if there is a restricting condition
local function showBar(frame_str)
	local flag = true -- only set false for known conditions

	if frame_str == TitanVariables_GetFrameName("Bar")
		or frame_str == TitanVariables_GetFrameName("Bar2")
	then
		-- ===== Battleground or Arena : User selected
		if (TitanPanelGetVar("HideBarsInPVP"))
			and (C_PvP.IsBattleground()
				or C_PvP.IsArena()
			--			or GetZoneText() == "Stormwind City"
			--			or GetZoneText() == "Tempest Keep"
			)
		then
			flag = false
		end
	end

	-- ===== In Combat : User selected
	if TitanBarDataVars[frame_str].hide_in_combat
		or TitanPanelGetVar("HideBarsInCombat") then
		if in_combat then -- InCombatLockdown() too slow
			flag = false
		end
	end

	if TITAN_ID == "TitanClassic" then
		-- skip, no pet battles yet
	else
		-- ===== In Pet Battle
		if C_PetBattles.IsInBattle() then
			if TitanBarData[frame_str].user_move then
				-- leave as is
			else
				flag = false
			end
		end
	end
	--[[
print("showBar"
--.." "..tostring(C_PetBattles.IsInBattle())..""
.." > "..tostring(flag)..""
.." '"..tostring(frame_str).."'"
)
--]]
	return flag
end

---local Set the position of the requested Titan bar.
---@param frame string Titan bar
local function SetBar(frame)
	local trace = false
	local display = _G[frame];

	--	local res = CheckBarBounds(display, 0)

	local x, y, w = TitanVariables_GetBarPos(frame)
	local tscale = TitanPanelGetVar("Scale")
	local show = TitanBarData[frame].show
	local bott = TitanBarData[frame].bott


	display:ClearAllPoints()
	if trace then
		--		local screen = TitanUtils_ScreenSize()
		--		local sx = screen.scaled_x
		--		local sy = screen.scaled_y
		print("SetBar"
			.. " " .. tostring(TitanBarData[frame].name) .. ""
			.. " x:" .. tostring(format("%0.1f", x)) .. ""
			.. " y:" .. tostring(format("%0.1f", y)) .. ""
			.. " w:" .. tostring(format("%0.1f", w)) .. ""
		)
	end

	if TitanBarData[frame].user_move then
		display:SetPoint(show.pt, show.rel_fr, show.rel_pt, x, y)
		display:SetSize(w, TITAN_PANEL_BAR_HEIGHT)
	else
		display:SetPoint(show.pt, show.rel_fr, show.rel_pt, x, y)
		local h = TITAN_PANEL_BAR_HEIGHT
		display:SetPoint(bott.pt, bott.rel_fr, bott.rel_pt, x, y - h)
	end
end

---Titan Toggle the given Titan bar based on the user selection.
---@param frame string Frame mame of the Titan bar
function TitanPanelBarButton_Show(frame)
	local display = _G[frame];

	if display and TitanBarData[frame].name
	then
		if TitanBarDataVars[frame].show -- User requested
			and showBar(frame)    -- No preventing condition
		then                      -- Place Bar
			SetBar(frame)
			---[[
			-- The bar may need to be moved back onto the screen.
			local res = CheckBarBounds(display, 0)
			if res.ok then
				-- placement ok
			else
				-- Need to 'snap' it to an edge
				SetBar(frame)
			end
			--]]
			TitanPanel_SetBarTexture(frame)

			if TitanBarData[frame].hider then
				_G[TitanBarData[frame].hider]:Hide()
			else
				-- not allowed for this bar
			end
		else
			-- The user has not elected to show this bar
			TitanPanelBarButton_Hide(frame)
		end
	end
end

---Titan Hide the given Titan bar based on the user selection.
--- Hide moves rather than just 'not shown'. Otherwise the buttons will stay visible defeating the purpose of hide.
--- Also moves the hider bar if auto hide is not selected.
---@param frame string Frame mame of the Titan bar
function TitanPanelBarButton_Hide(frame)
	if TITAN_PANEL_MOVING == 1 then return end

	local display = _G[frame]
	local data = TitanBarData[frame]

	if display and data
	then
		local x, y, w = TitanVariables_GetBarPos(frame)
		-- This moves rather than hides. If we just hide then the plugins will still show.
		-- Hide by ensuriing the Y offset is off the screen.
		display:ClearAllPoints()
		local h = (math.abs(y) + TITAN_PANEL_BAR_HEIGHT * 2) * (-1 * y)
		local h = data.hide_y
		--[[
print("_Hide"
--.." "..tostring(frame)..""
.." "..tostring(data.name)..""
.." "..tostring(TitanBarDataVars[frame].show)..""
.." "..tostring(h)..""
)
--]]
		if TitanBarData[frame].user_move then
			display:SetPoint(data.show.pt, data.show.rel_fr, data.show.rel_pt, x, h)
		else
			display:SetPoint(data.show.pt, data.show.rel_fr, data.show.rel_pt, x, h)
			display:SetPoint(data.bott.pt, data.bott.rel_fr, data.bott.rel_pt, x, h - TITAN_PANEL_BAR_HEIGHT)
		end

		if TitanBarData[frame].hider then
			local hider = _G[data.hider]
			if TitanBarDataVars[frame].show
				and TitanBarDataVars[frame].auto_hide then
				--			if (TitanPanelGetVar(data.name.."_Show")) and (TitanPanelGetVar(data.name.."_Hide")) then
				-- Auto hide is requested so show the hider bar in the right place
				hider:ClearAllPoints();
				hider:SetPoint(data.show.pt, data.show.rel_fr, data.show.rel_pt, x, y);
				hider:Show()
			else
				-- The bar was not requested
				hider:Hide()
			end
		else
			-- not allowed for this bar
		end
	end
end

---Titan Show all user selected plugins on the Titan bar(s) then justify per the user selection.
--- This is done an all bars whether shown or not.
function TitanPanel_InitPanelButtons()
	local button
	local r_prior = {}
	local l_prior = {}
	local scale = TitanPanelGetVar("Scale");
	local button_spacing = TitanPanelGetVar("ButtonSpacing") * scale
	local icon_spacing = TitanPanelGetVar("IconSpacing") * scale

	local prior = {}
	-- set prior to the starting offsets
	-- The right side plugins are set here.
	-- Justify adjusts the left side start according to the user setting
	-- The effect is left side plugins has spacing on the right side and
	-- right side plugins have spacing on the left.
	for idx, v in pairs(TitanBarData) do
		local bar = TitanBarData[idx].name
		local y_off = TitanBarData[idx].plugin_y_offset
		prior[bar] = {
			right = {
				button = TitanVariables_GetFrameName(bar),
				anchor = "RIGHT",
				x = 5, -- Offset of first plugin to right side of screen
				y = y_off,
			},
			left = {
				button = TitanVariables_GetFrameName(bar),
				anchor = "LEFT",
				x = 0, -- Justify adjusts - center or not
				y = y_off,
			},
		}
	end
	--
	TitanPanelBarButton_DisplayBarsWanted("TitanPanel_InitPanelButtons");

	-- Position all the buttons
	for i = 1, table.maxn(TitanPanelSettings.Buttons) do
		local id = TitanPanelSettings.Buttons[i];
		if (TitanUtils_IsPluginRegistered(id)) then
			local i = TitanPanel_GetButtonNumber(id);
			button = TitanUtils_GetButton(id);

			if button then
				-- If the plugin has asked to be on the right
				if TitanUtils_ToRight(id) then
					-- =========================
					-- position the plugin relative to the prior plugin
					-- or the bar if it is the 1st
					r_prior = prior[TitanPanelSettings.Location[i]].right
					-- =========================
					button:ClearAllPoints();
					button:SetPoint("RIGHT", _G[r_prior.button]:GetName(), r_prior.anchor, (-(r_prior.x) * scale),
						r_prior.y)

					-- =========================
					-- capture the button for the next plugin
					r_prior.button = TitanUtils_ButtonName(id)
					-- set prior[x] the anchor points and offsets for the next plugin
					r_prior.anchor = "LEFT"
					r_prior.x = icon_spacing
					r_prior.y = 0
					-- =========================
				else
					--  handle plugins on the left side of the bar
					--
					-- =========================
					-- position the plugin relative to the prior plugin
					-- or the bar if it is the 1st
					l_prior = prior[TitanPanelSettings.Location[i]].left
					--[===[
print("Bar plugins"
.." "..tostring(i)..""
.." "..tostring(TitanPanelSettings.Location[i])..""
.." "..tostring(id)..""
)
--]===]
					-- =========================
					--
					button:ClearAllPoints();
					button:SetPoint("LEFT", _G[l_prior.button]:GetName(), l_prior.anchor, l_prior.x * scale, l_prior.y);

					-- =========================
					-- capture the next plugin
					l_prior.button = TitanUtils_ButtonName(id)
					-- set prior[x] (anchor points and offsets) for the next plugin
					l_prior.anchor = "RIGHT"
					l_prior.x = (button_spacing)
					l_prior.y = 0
					-- =========================
				end
				button:Show()
			end
		end
	end
	-- Set panel button init flag
	TITAN_PANEL_BUTTONS_INIT_FLAG = 1;
	TitanPanelButton_Justify();
end

---Titan Reorder all the shown all user selected plugins on the Titan bar(s). Typically used after a button has been removed / hidden.
---@param index number of the plugin removed
function TitanPanel_ReOrder(index)
	for i = index, table.getn(TitanPanelSettings.Buttons) do
		TitanPanelSettings.Location[i] = TitanPanelSettings.Location[i + 1]
	end
end

---Titan Remove a plugin then show all the shown all user selected plugins on the Titan bar(s).
--- This cancels all timers of name "TitanPanel"..id as a safeguard to destroy any active plugin timers
--- based on a fixed naming convention : TitanPanel..id, eg. "TitanPanelClock" this prevents "rogue"
--- timers being left behind by lack of an OnHide check
---@param id string Unique ID of the plugin
function TitanPanel_RemoveButton(id)
	if (not TitanPanelSettings) then
		return;
	end

	local i = TitanPanel_GetButtonNumber(id)
	local currentButton = TitanUtils_GetButton(id);

	-- safeguard ...
	if id then AceTimer.CancelAllTimers({}) end -- ??? seems confused 0 or 1 params  "TitanPanel" .. id

	TitanPanel_ReOrder(i);
	table.remove(TitanPanelSettings.Buttons, TitanUtils_GetCurrentIndex(TitanPanelSettings.Buttons, id));
	--TitanDebug("_Remove: "..(id or "?").." "..(i or "?"))
	if currentButton then
		currentButton:Hide();
	end
	-- Show the existing buttons
	TitanPanel_InitPanelButtons();
end

--- Titan Get the index of the given plugin from the Titan plugin displayed list.
--- The routine returns +1 if not found so it is 'safe' to add to the list
---@param id string Unique ID of the plugin
---@return number num position or num + 1 for end
function TitanPanel_GetButtonNumber(id)
	if (TitanPanelSettings) then
		for i = 1, table.getn(TitanPanelSettings.Buttons) do
			if (TitanPanelSettings.Buttons[i] == id) then
				return i;
			end
		end
		return table.getn(TitanPanelSettings.Buttons) + 1;
	else
		return 0;
	end
end

---Titan Update / refresh each plugin from the Titan plugin list. Used when a Titan option is changed that effects all plugins.
function TitanPanel_RefreshPanelButtons()
	if (TitanPanelSettings) then
		for i = 1, table.getn(TitanPanelSettings.Buttons) do
			TitanPanelButton_UpdateButton(TitanPanelSettings.Buttons[i], 1);
		end
	end
end

---Titan Justify the plugins on each Titan bar.
--- Used when :
---- Init / show of a Titan bar
----the user changes the 'center' option on a Titan bar
function TitanPanelButton_Justify()
	-- Only the left side buttons are justified.
	if (not TITAN_PANEL_BUTTONS_INIT_FLAG or not TitanPanelSettings) then
		return;
	end
	if InCombatLockdown() then
		--TitanDebug("_Justify during combat!!!")
		return;
		-- Issue 856 where some taint is caused if the plugin size is updated during combat. Seems since Mists was released...
	end

	local bar
	local x_offset
	local y_offset
	local firstLeftButton
	local scale = TitanPanelGetVar("Scale");
	local button_spacing = TitanPanelGetVar("ButtonSpacing") * scale
	local icon_spacing = TitanPanelGetVar("IconSpacing") * scale
	local leftWidth = 0;
	local rightWidth = 0;
	local counter = 0;
	local align = 0;
	local center_offset = 0;

	-- Look at each bar for plugins.
	for idx, v in pairs(TitanBarData) do
		bar = TitanBarData[idx].name
		y_offset = TitanBarData[idx].plugin_y_offset
		x_offset = TitanBarData[idx].plugin_x_offset
		firstLeftButton = TitanUtils_GetButton(TitanPanelSettings.Buttons
		[TitanUtils_GetFirstButtonOnBar(bar, TITAN_LEFT)])
		align = TitanBarDataVars[idx].align --TitanPanelGetVar(bar.."_Align")
		leftWidth = 0;
		rightWidth = 0;
		counter = 0;
		-- If there is a plugin on this bar then justify the first button.
		-- The other buttons are relative to the first.
		if (firstLeftButton) then
			if (align == TITAN_PANEL_BUTTONS_ALIGN_LEFT) then
				-- Now offset the plugins
				firstLeftButton:ClearAllPoints();
				firstLeftButton:SetPoint("LEFT", idx, "LEFT", x_offset, y_offset);
			end
			-- Center if requested
			if (align == TITAN_PANEL_BUTTONS_ALIGN_CENTER) then
				leftWidth = 0;
				rightWidth = 0;
				counter = 0;
				-- Calc the total width of the icons so we know where to start
				for index, id in pairs(TitanPanelSettings.Buttons) do
					local button = TitanUtils_GetButton(id);
					if button and button:GetWidth() then
						if TitanUtils_GetWhichBar(id) == bar then
							if (TitanGetVar(id, "DisplayOnRightSide")) then
								rightWidth = rightWidth
									+ icon_spacing
									+ button:GetWidth();
							else
								counter = counter + 1;
								leftWidth = leftWidth
									+ button_spacing
									+ button:GetWidth()
							end
						end
					end
				end
				-- Now offset the plugins on the bar
				firstLeftButton:ClearAllPoints();
				-- remove the last spacing otherwise the buttons appear justified too far left
				center_offset = (0 - (leftWidth - button_spacing) / 2)
				firstLeftButton:SetPoint("LEFT", idx, "CENTER", center_offset, y_offset);
			end
		end
	end
end

--------------------------------------------------------------
--
-- Local routines for Titan menu creation
local R_ADDONS = "Addons_"
local R_PLUGIN = "Plugin_"
local R_SETTINGS = "Settings"
local R_PROFILE = "Profile_"

---local Show main Titan (right click) menu.
---@param frame string Frame to add to
local function BuildMainMenu(frame)
	local locale_bar = TitanBarData[frame].locale_name
	local info = {};
	-----------------
	-- Menu title
	TitanPanelRightClickMenu_AddTitle(L["TITAN_PANEL_MENU_TITLE"] .. " - " .. locale_bar);
	TitanPanelRightClickMenu_AddSeparator(TitanPanelRightClickMenu_GetDropdownLevel());

	TitanPanelRightClickMenu_AddTitle(L["TITAN_PANEL_MENU_PLUGINS"]);

	-----------------
	-- Plugin Categories
	-- Both arrays are in TitanGlobal
---@diagnostic disable-next-line: param-type-mismatch
	for index, id in pairs(L["TITAN_PANEL_MENU_CATEGORIES"]) do
		info = {};
		info.notCheckable = true
		info.text = L["TITAN_PANEL_MENU_CATEGORIES"][index];
		info.value = R_ADDONS .. TITAN_PANEL_BUTTONS_PLUGIN_CATEGORY[index];
		info.hasArrow = 1;
		TitanPanelRightClickMenu_AddButton(info);
	end

	TitanPanelRightClickMenu_AddSeparator(TitanPanelRightClickMenu_GetDropdownLevel());

	-----------------
	-- Options - just one button to open the first Titan option screen
	do
		info = {};
		info.notCheckable = true
		info.text = L["TITAN_PANEL_MENU_CONFIGURATION"];
		info.value = "Bars";
		info.func = function()
			TitanUpdateConfig("init")
			Settings.OpenToCategory(TITAN_PANEL_CONFIG.topic.About)
		end
		TitanPanelRightClickMenu_AddButton(info);
	end

	TitanPanelRightClickMenu_AddSeparator(TitanPanelRightClickMenu_GetDropdownLevel());

	-----------------
	-- Profiles
	TitanPanelRightClickMenu_AddTitle(L["TITAN_PANEL_MENU_PROFILES"]);

	-----------------
	-- Load/Delete
	info = {};
	info.notCheckable = true
	info.text = L["TITAN_PANEL_MENU_MANAGE_SETTINGS"];
	info.value = R_SETTINGS
	info.hasArrow = 1;
	-- lock this menu in combat
	if InCombatLockdown() then
		info.disabled = 1;
		info.hasArrow = nil;
		info.text = info.text .. " "
			.. _G["GREEN_FONT_COLOR_CODE"]
			.. L["TITAN_PANEL_MENU_IN_COMBAT_LOCKDOWN"];
	end
	TitanPanelRightClickMenu_AddButton(info);

	-----------------
	-- Save
	info = {};
	info.notCheckable = true
	info.text = L["TITAN_PANEL_MENU_SAVE_SETTINGS"];
	info.value = "SettingsCustom";
	info.func = TitanPanel_SaveCustomProfile;
	-- lock this menu in combat
	if InCombatLockdown() then
		info.disabled = 1;
		info.text = info.text .. " "
			.. _G["GREEN_FONT_COLOR_CODE"]
			.. L["TITAN_PANEL_MENU_IN_COMBAT_LOCKDOWN"];
	end
	TitanPanelRightClickMenu_AddButton(info);

	--	TitanPanelRightClickMenu_AddSeparator(TitanPanelRightClickMenu_GetDropdownLevel());
	local glob, toon, player, server = TitanUtils_GetGlobalProfile()
	info = {};
	--  info.text = "Use Global Profile\n   "..toon
	info.text = L["TITAN_PANEL_GLOBAL_USE"] .. "\n   " .. toon;
	info.value = "Use Global Profile"
	info.func = function()
		TitanUtils_SetGlobalProfile(not glob, toon)
		TitanVariables_UseSettings(nil, TITAN_PROFILE_USE)
	end;
	info.checked = glob --TitanAllGetVar("GlobalProfileUse")
	info.keepShownOnClick = nil
	TitanPanelRightClickMenu_AddButton(info, TitanPanelRightClickMenu_GetDropdownLevel());

	TitanPanelRightClickMenu_AddSeparator(TitanPanelRightClickMenu_GetDropdownLevel());

	-----------------
	-- Hide this bar
	info = {};
	info.text = (HIDE or "Hide")
	info.value = "HideMe"
	info.notCheckable = true
	info.arg1 = frame;
	info.func = function(self, frame_str)
		TitanBarDataVars[frame_str].show = not TitanBarDataVars[frame_str].show
		TitanPanelBarButton_DisplayBarsWanted(frame_str .. " user clicked Hide")
	end
	info.keepShownOnClick = nil
	TitanPanelRightClickMenu_AddButton(info, TitanPanelRightClickMenu_GetDropdownLevel());
end

---local Show list of servers / custom submenu off Profiles/Manage from the Titan (right click) menu.
local function BuildServerProfilesMenu()
	local info = {};
	local servers = {};
	local player = nil;
	local server = nil;
	local s, e, ident;
	local setonce = 0;

	if (TitanPanelRightClickMenu_GetDropdMenuValue() == R_SETTINGS) then
		TitanPanelRightClickMenu_AddTitle(L["TITAN_PANEL_MENU_PROFILE_SERVERS"],
			TitanPanelRightClickMenu_GetDropdownLevel());
		-- Normal profile per toon
		for index, id in pairs(TitanSettings.Players) do
			player, server = TitanUtils_ParseName(index)

			if TitanUtils_GetCurrentIndex(servers, server) == nil then
				if server ~= TITAN_CUSTOM_PROFILE_POSTFIX then
					table.insert(servers, server);
					info = {};
					info.notCheckable = true
					info.text = server;
					info.value = R_PROFILE .. server;
					info.hasArrow = 1;
					TitanPanelRightClickMenu_AddButton(info, TitanPanelRightClickMenu_GetDropdownLevel());
				end
			end
		end
		-- Custom profiles
		for index, id in pairs(TitanSettings.Players) do
			player, server = TitanUtils_ParseName(index)

			if TitanUtils_GetCurrentIndex(servers, server) == nil then
				if server == TITAN_CUSTOM_PROFILE_POSTFIX then
					if setonce and setonce == 0 then
						TitanPanelRightClickMenu_AddTitle("", TitanPanelRightClickMenu_GetDropdownLevel());
						TitanPanelRightClickMenu_AddTitle(L["TITAN_PANEL_MENU_PROFILE_CUSTOM"],
							TitanPanelRightClickMenu_GetDropdownLevel());
						setonce = 1;
					end
					info = {};
					info.notCheckable = true
					info.text = player;
					info.value = R_PROFILE .. player;
					info.hasArrow = 1;
					TitanPanelRightClickMenu_AddButton(info, TitanPanelRightClickMenu_GetDropdownLevel());
				end
			end
		end
	end
end

---local Show list of plugin defined options from the Titan right click menu.
local function BuildPluginMenu()
	--
	local info = {};

	-- Handle the plugins

	for index, id in pairs(TitanPluginsIndex) do
		local plugin = TitanUtils_GetPlugin(id)
		local par_val = TitanPanelRightClickMenu_GetDropdMenuValue()
		local menu_plugin = string.gsub(par_val, R_PLUGIN, "")
		if plugin and plugin.id and plugin.id == menu_plugin then
			--title
			info = {};
			info.text = TitanPlugins[plugin.id].menuText;
			info.notCheckable = true
			info.notClickable = 1;
			info.isTitle = 1;
			TitanPanelRightClickMenu_AddButton(info, TitanPanelRightClickMenu_GetDropdownLevel());

			--ShowIcon
			if plugin.controlVariables.ShowIcon then
				info = {};
				info.text = L["TITAN_PANEL_MENU_SHOW_ICON"];
				info.value = plugin.id
				info.arg1 = plugin.id
				info.func = function(self, p_id) -- (self, info.arg1, info.arg2)
					TitanPanelRightClickMenu_ToggleVar({ p_id, "ShowIcon", nil })
				end
				info.keepShownOnClick = 1;
				info.checked = TitanGetVar(plugin.id, "ShowIcon");
				info.disabled = nil;
				TitanPanelRightClickMenu_AddButton(info, TitanPanelRightClickMenu_GetDropdownLevel());
			end

			--ShowLabel
			if plugin.controlVariables.ShowLabelText then
				info = {};
				info.text = L["TITAN_PANEL_MENU_SHOW_LABEL_TEXT"];
				info.value = plugin.id
				info.arg1 = plugin.id
				info.func = function(self, p_id) -- (self, info.arg1, info.arg2)
					TitanPanelRightClickMenu_ToggleVar({ p_id, "ShowLabelText", nil })
				end
				info.keepShownOnClick = 1;
				info.checked = TitanGetVar(plugin.id, "ShowLabelText");
				info.disabled = nil;
				TitanPanelRightClickMenu_AddButton(info, TitanPanelRightClickMenu_GetDropdownLevel());
			end

			--ShowRegularText (LDB data sources only atm)
			if plugin.controlVariables.ShowRegularText then
				info = {};
				info.text = L["TITAN_PANEL_MENU_SHOW_PLUGIN_TEXT"]
				info.value = plugin.id
				info.arg1 = plugin.id
				info.func = function(self, p_id) -- (self, info.arg1, info.arg2)
					TitanPanelRightClickMenu_ToggleVar({ p_id, "ShowRegularText", nil })
				end
				info.keepShownOnClick = 1;
				info.checked = TitanGetVar(plugin.id, "ShowRegularText");
				info.disabled = nil;
				TitanPanelRightClickMenu_AddButton(info, TitanPanelRightClickMenu_GetDropdownLevel());
			end

			--ShowColoredText
			if plugin.controlVariables.ShowColoredText then
				info = {};
				info.text = L["TITAN_PANEL_MENU_SHOW_COLORED_TEXT"];
				info.value = plugin.id
				info.arg1 = plugin.id
				info.func = function(self, p_id) -- (self, info.arg1, info.arg2)
					TitanPanelRightClickMenu_ToggleVar({ p_id, "ShowColoredText", nil })
				end
				info.keepShownOnClick = 1;
				info.checked = TitanGetVar(plugin.id, "ShowColoredText");
				info.disabled = nil;
				TitanPanelRightClickMenu_AddButton(info, TitanPanelRightClickMenu_GetDropdownLevel());
			end

			-- Right-side plugin
			if plugin.controlVariables.DisplayOnRightSide then
				info = {};
				info.text = L["TITAN_PANEL_MENU_LDB_SIDE"];
				info.value = plugin.id
				info.arg1 = plugin.id
				info.func = function(self, p_id) -- (self, info.arg1, info.arg2)
					TitanToggleVar(p_id, "DisplayOnRightSide")
					local bar = TitanUtils_GetWhichBar(p_id)
					TitanPanel_RemoveButton(p_id);
					TitanUtils_AddButtonOnBar(bar, p_id);
				end
				info.checked = TitanGetVar(plugin.id, "DisplayOnRightSide");
				info.disabled = nil;
				TitanPanelRightClickMenu_AddButton(info, TitanPanelRightClickMenu_GetDropdownLevel());
			end
		end
	end
end

---local Show alphabetical list of toons submenu off Profiles/Manage/<server or custom> from the Titan right click menu.
local function BuildProfileMenu()
	--
	local info = {};
	local setonce = 0;

	--
	-- Handle the profiles
	--
	for idx = 1, #Titan_Global.players do
		local index = Titan_Global.players[idx]
		local player, server = TitanUtils_ParseName(index)
		local off = (index == TitanSettings.Player)
			or ((index == TitanAllGetVar("GlobalProfileUse")) and (TitanAllGetVar("GlobalProfileUse")))
		local par_val = TitanPanelRightClickMenu_GetDropdMenuValue()
		local menu_val = string.gsub(par_val, R_PROFILE, "")

		-- handle custom profiles here
		if server == TITAN_CUSTOM_PROFILE_POSTFIX
			and player == menu_val then
			info = {};
			info.notCheckable = true
			info.disabled = TitanAllGetVar("GlobalProfileUse")
			info.text = L["TITAN_PANEL_MENU_LOAD_SETTINGS"];
			info.value = index;
			info.func = function()
				TitanVariables_UseSettings(index, TITAN_PROFILE_USE)
			end
			TitanPanelRightClickMenu_AddButton(info, TitanPanelRightClickMenu_GetDropdownLevel());

			info = {};
			info.notCheckable = true
			info.disabled = off
			info.text = L["TITAN_PANEL_MENU_DELETE_SETTINGS"];
			info.value = index;
			info.arg1 = index;
			info.func = function(self, player) -- (self, info.arg1, info.arg2)
				if TitanSettings.Players[player] then
					TitanSettings.Players[player] = nil;
					local profname = TitanUtils_ParseName(index)
					TitanPrint(
						L["TITAN_PANEL_MENU_PROFILE"]
						.. " '" .. profname .. "' "
						.. L["TITAN_PANEL_MENU_PROFILE_DELETED"]
						, "info")
					table.remove(Titan_Global.players, idx)
					TitanPanelRightClickMenu_Close();
				end
			end
			TitanPanelRightClickMenu_AddButton(info, TitanPanelRightClickMenu_GetDropdownLevel());
		end -- if server and player

		-- handle regular profiles here
		if server == menu_val then
			-- Set the label once
			if setonce and setonce == 0 then
				TitanPanelRightClickMenu_AddTitle(L["TITAN_PANEL_MENU_PROFILE_CHARS"],
					TitanPanelRightClickMenu_GetDropdownLevel());
				setonce = 1;
			end
			info = {};
			info.disabled = off
			info.notCheckable = true
			info.text = player;
			info.value = index;
			info.hasArrow = 1;
			TitanPanelRightClickMenu_AddButton(info, TitanPanelRightClickMenu_GetDropdownLevel());
		end
	end -- for players
end

---local Show save / load submenu off Profiles/Manage/<server or custom>/<profile> from the Titan (right click) menu.
local function BuildAProfileMenu()
	local info = {};

	info = {};
	info.notCheckable = true
	info.disabled = TitanAllGetVar("GlobalProfileUse")
	info.text = L["TITAN_PANEL_MENU_LOAD_SETTINGS"];
	info.value = TitanPanelRightClickMenu_GetDropdMenuValue();
	info.func = function()
		TitanVariables_UseSettings(TitanPanelRightClickMenu_GetDropdMenuValue(), TITAN_PROFILE_USE)
	end
	TitanPanelRightClickMenu_AddButton(info, TitanPanelRightClickMenu_GetDropdownLevel());

	info = {};
	info.notCheckable = true
	info.disabled = (TitanPanelRightClickMenu_GetDropdMenuValue() == TitanSettings.Player)
		or ((TitanPanelRightClickMenu_GetDropdMenuValue() == TitanAllGetVar("GlobalProfileName"))
			and (TitanAllGetVar("GlobalProfileUse")))
	info.text = L["TITAN_PANEL_MENU_DELETE_SETTINGS"];
	info.value = TitanPanelRightClickMenu_GetDropdMenuValue();
	info.func = function()
		-- do not delete if current profile - .disabled
		if TitanSettings.Players[info.value] then
			TitanSettings.Players[info.value] = nil;
			TitanPrint(
				L["TITAN_PANEL_MENU_PROFILE"]
				.. " '" .. info.value .. "' "
				.. L["TITAN_PANEL_MENU_PROFILE_DELETED"]
				, "info")
			TitanPanelRightClickMenu_Close();
		end
	end
	TitanPanelRightClickMenu_AddButton(info, TitanPanelRightClickMenu_GetDropdownLevel());
end

---local Build the list of plugins for the category the mouse is over - Titan (right click) menu.
---@param frame string Frame to add to
local function BuildPluginCategoryMenu(frame)
	local info = {};
	local plugin;

	for index, id in pairs(TitanPluginsIndex) do
		plugin = TitanUtils_GetPlugin(id)
		if plugin then -- add the plugin to the menu
			plugin.category = plugin and plugin.category or "General";
			if (TitanPanelRightClickMenu_GetDropdMenuValue() == R_ADDONS .. plugin.category) then
				if not TitanGetVar(id, "ForceBar")
					or (TitanGetVar(id, "ForceBar") == TitanBarData[frame].name) then
					info = {};
					local ver = plugin and plugin.version or ""
					if TitanPanelGetVar("VersionShown") then
						if ver == nil or ver == "" then
							ver = "" -- safety in case of nil
						else
							ver = TitanUtils_GetGreenText(" (" .. ver .. ")")
						end
					else
						ver = "" -- not requested
					end
					info.text = plugin and plugin.menuText .. ver or ""

					-- Add Bar
					local internal_bar, which_bar = TitanUtils_GetWhichBar(id)
					if which_bar == nil then
						-- Plugin not shown
					else
--						if internal_bar == TitanBarData[frame].name then
--							info.text = info.text .. TitanUtils_GetGreenText(" (" .. which_bar .. ")")
--						else
							info.text = info.text .. TitanUtils_GetGoldText(" (" .. which_bar .. ")")
--						end
					end

					if plugin.controlVariables then
						info.hasArrow = 1;
					end
					info.value = R_PLUGIN .. id; -- for next level dropdown
					info.arg1 = frame;
					info.arg2 = id;
					info.func = function(self, frame_str, plugin_id) -- (self, info.arg1, info.arg2)
						-- frame_str is the bar the user clicked to get the menu...
						local bar = TitanBarData[frame_str].name

						if TitanPanel_IsPluginShown(plugin_id) then
							TitanPanel_RemoveButton(plugin_id);
						else
							TitanUtils_AddButtonOnBar(bar, plugin_id)
						end
					end
					info.checked = TitanPanel_IsPluginShown(id) or nil
					info.keepShownOnClick = 1;
					TitanPanelRightClickMenu_AddButton(info, TitanPanelRightClickMenu_GetDropdownLevel());
				end
			end
		else
		end
	end
end

---Titan This is the controller for the Titan (right click) menu.
---@param self table Titan bar frame that was right clicked
--- Frame name used is <Titan bar name>RightClickMenu
function TitanPanelRightClickMenu_PrepareBarMenu(self)
	-- Determine which bar was clicked on
	--	local s, e, frame = string.find(self:GetName(), "(.*)RightClickMenu");
	local s, e, frame = string.find(self:GetName(), "(.*)" .. TITAN_PANEL_CLICK_MENU_SUFFIX);
	local lev = (TitanPanelRightClickMenu_GetDropdownLevel() or 1)
	--[[
print("_prep R click"
.." "..tostring(frame)..""
.." "..tostring(lev)..""
)
--]]

	-- Level 1
	--[===[
		Title - <Bar name>
		----
		Plugins
		<list of Categories>
		----
		Configuration => Opens Titan Options
		-----
		Profiles
		Manage > <Level 2>
		Save => Save current profile (used for Global)
		-----
		Use Global Profile
			<Profile name used or <>>
		----
		Hide => Hide this Bar
	--]===]
	if lev == 1 then
		BuildMainMenu(frame)
	end

	-- Level 2
	-- Plugin Categories => Plugins in that category
	-- OR
	-- Profiles => Server / Realm list
	if (lev == 2) then
		if string.find(TitanPanelRightClickMenu_GetDropdMenuValue(), R_ADDONS) then
			BuildPluginCategoryMenu(frame)
		end

		if (TitanPanelRightClickMenu_GetDropdMenuValue() == R_SETTINGS) then
			BuildServerProfilesMenu()
		end
		return;
	end

	-- Level 3
	-- Plugin Categories => Plugins in that category => Plugin defined options
	-- OR
	-- Profiles > Server / Realm list > Character on realm list
	if (lev == 3) then
		if string.find(TitanPanelRightClickMenu_GetDropdMenuValue(), R_PLUGIN) then
			BuildPluginMenu()
		end
		if string.find(TitanPanelRightClickMenu_GetDropdMenuValue(), R_PROFILE) then
			BuildProfileMenu()
		end
		return;
	end

	-- Level 4
	-- Profiles > Server / Realm list > Character on realm list > Load / Delete
	if (lev == 4) then
		BuildAProfileMenu()
		return;
	end
end

---Titan Determine if the given plugin is on any Titan bar.
---@param id string Unique ID of the plugin
---@return boolean shown True on a Titan bar even if hidden or on auto hide
function TitanPanel_IsPluginShown(id)
	if (id and TitanPanelSettings) then
		return TitanUtils_TableContainsValue(TitanPanelSettings.Buttons, id)
	else
		return false
	end
end

---Titan Determine if the given plugin is / would be on right or left of a Titan bar.
---@param id string Unique ID of the plugin
---@return string R_L  TITAN_RIGHT("Right") or TITAN_Left("Left")
function TitanPanel_GetPluginSide(id)
	if (TitanGetVar(id, "DisplayOnRightSide")) then
		return TITAN_RIGHT;
	else
		return TITAN_LEFT;
	end
end

--[[
print("OnMoveStart"
.." "..tostring(self:GetName())..""
.." "..tostring(IsShiftKeyDown())..""
.." "..tostring(IsAltKeyDown())..""
.."\n"
.." x "..tostring(format("%0.1f", self:GetLeft()))..""
.." y "..tostring(format("%0.1f", self:GetTop()))..""
)
--]]

---Titan Set the scale, texture (graphic), and transparancy of all the Titan bars based on the user selection.
---@param reason string Debug note on where the call initiated
function TitanPanel_InitPanelBarButton(reason)
	-- Set initial Panel Scale
	TitanPanel_SetScale();
	--[[
print("_InitPanelBarButton"
.." "..tostring(reason)..""
)
--]]

	TitanPanelBarButton_DisplayBarsWanted("InitPanelBarButton")
end

--
--==========================
-- Routines to handle creation of Titan bars
--


---Titan Create a Titan bar that can show plugins.
---@param frame_str string Unique ID of the plugin
function TitanPanelButton_CreateBar(frame_str)
	local this_bar = frame_str
	local a_bar = CreateFrame("Button", this_bar, UIParent, "Titan_Bar__Display_Template")

	local bar_data = TitanBarData[this_bar]

	-- ======
	-- Scripts
	a_bar:SetScript("OnEnter", function(self) TitanPanelBarButton_OnEnter(self) end)
	a_bar:SetScript("OnLeave", function(self) TitanPanelBarButton_OnLeave(self) end)
	a_bar:SetFrameStrata("DIALOG")

	--	local x, y, w = TitanVariables_GetBarPos(frame_str)
	--	local tscale = TitanPanelGetVar("Scale")
	--[[
print("_Create bar"
.." "..tostring(bar_data.name)..""
.."\n "
.." "..tostring(bar_data.name)..""
.." "..tostring(bar_data.show.pt)..""
.." "..tostring(bar_data.show.rel_fr)..""
.." "..tostring(bar_data.show.rel_pt)..""
.."\n "
.." "..tostring(bar_data.name)..""
.." "..tostring(format("%0.1f", x))..""
.." "..tostring(format("%0.1f", y))..""
.." "..tostring(format("%0.1f", w))..""
)
--]]
	if bar_data.user_move then
		--		a_bar:SetPoint(bar_data.show.pt, bar_data.show.rel_fr, bar_data.show.rel_pt, x, y)
		a_bar:SetMovable(true)
		a_bar:SetResizable(true)
		a_bar:EnableMouse(true)
		a_bar:RegisterForDrag("LeftButton")
		a_bar:SetScript("OnDragStart", OnMoveStart)
		a_bar:SetScript("OnDragStop", OnMovingStop)
		a_bar:SetScript("OnMouseWheel", OnMouseWheel)
		--		a_bar:SetSize(w / tscale, TITAN_PANEL_BAR_HEIGHT)
	else
		-- Static full width bar
		--		a_bar:SetPoint(bar_data.show.pt, bar_data.show.rel_fr, bar_data.show.rel_pt, x, y)
		--		a_bar:SetPoint(bar_data.show.pt, bar_data.show.rel_fr, bar_data.show.rel_pt, x, y - TITAN_PANEL_BAR_HEIGHT)
	end

	-- ======
	-- Bounds only effective on Short bars for now
	-- Min : No smaller than the padding & one icon
	-- Max : No wider than the screen
	-- does not seem to work to restrict size automatically...
	local screen = TitanUtils_ScreenSize()
	a_bar:SetResizeBounds(TitanBarData[this_bar].plugin_x_offset + 16, TITAN_PANEL_BAR_HEIGHT, screen.x,
		TITAN_PANEL_BAR_HEIGHT)
	a_bar:RegisterForClicks("LeftButtonUp", "RightButtonUp");
	a_bar:SetScript("OnClick", function(self, button) TitanPanelBarButton_OnClick(self, button) end)

	-- ======
	-- Frame for right clicks
	-- Use one bar to rule them all
	-- Use the plugin naming scheme
	-- 2024 Feb : Change to match plugin right click menu scheme so one routine can be used.
	local f = CreateFrame("Frame", this_bar .. TITAN_PANEL_CLICK_MENU_SUFFIX, UIParent, "UIDropDownMenuTemplate")
	--[[
	local tool_tip = Titan_Panel.plugin.PRE.."Bar"..Titan_Panel.plugin.POST
	if _G[tool_tip] then
		-- already created
	else
		f = CreateFrame("Frame", tool_tip, UIParent, "UIDropDownMenuTemplate")
	end
--]]
	-- ======
	-- Hider for auto hide feature
	local hide_bar_name = TITAN_PANEL_HIDE_PREFIX .. bar_data.name
	if bar_data.hider then
	--[[
print("_Create hide bar"
.." "..tostring(bar_data.name)..""
.." "..tostring(hide_bar_name)..""
)
--]]

		local hide_bar = CreateFrame("Button", hide_bar_name, UIParent, "TitanPanelBarButtonHiderTemplate")
		hide_bar:SetFrameStrata("DIALOG")
		--		hide_bar:SetPoint(bar_data.show.pt, bar_data.show.rel_fr, bar_data.show.rel_pt, x, -y)

		-- Set script handlers for display
		hide_bar:RegisterForClicks("LeftButtonUp", "RightButtonUp");
		hide_bar:SetScript("OnEnter", function(self) TitanPanelBarButtonHider_OnEnter(self) end)
		hide_bar:SetScript("OnLeave", function(self) TitanPanelBarButtonHider_OnLeave(self) end)
		hide_bar:SetScript("OnClick", function(self, button) TitanPanelBarButton_OnClick(self, button) end)

		hide_bar:SetFrameStrata("BACKGROUND")
		hide_bar:SetSize(screen.x, TITAN_PANEL_BAR_HEIGHT)
	else
		-- Not allowed for this bar
	end
end

--====== deprecated / Unused
--[====[

--[[ local
NAME: TitanPanel_CreateABar
DESC: Helper to add scripts to the Titan bar passed in.
VAR: frame - The frame name (string) of the Titan bar to create
OUT: None
NOTE:
- This also creates the hider bar in case the user want to use auto hide.
:NOTE
--]]
local function TitanPanel_CreateABar(frame)
	if frame then
		local bar_name = TitanBarData[frame].name
		local bar_width = TitanBarData[frame].width

		if bar_name then
			-- Set script handlers for display
			_G[frame]:RegisterForClicks("LeftButtonUp", "RightButtonUp");
			_G[frame]:SetScript("OnEnter", function(self) TitanPanelBarButton_OnEnter(self) end)
			_G[frame]:SetScript("OnLeave", function(self) TitanPanelBarButton_OnLeave(self) end)
			_G[frame]:SetScript("OnClick", function(self, button) TitanPanelBarButton_OnClick(self, button) end)
			_G[frame]:SetWidth(bar_width)

			local hide_name = TitanBarData[frame].hider
			if hide_name then
				-- Set script handlers for display
				_G[hide_name]:RegisterForClicks("LeftButtonUp", "RightButtonUp");
				_G[hide_name]:SetScript("OnEnter", function(self) TitanPanelBarButtonHider_OnEnter(self) end)
				_G[hide_name]:SetScript("OnLeave", function(self) TitanPanelBarButtonHider_OnLeave(self) end)
				_G[hide_name]:SetScript("OnClick", function(self, button) TitanPanelBarButton_OnClick(self, button) end)

				_G[hide_name]:SetFrameStrata("BACKGROUND")
				_G[hide_name]:SetWidth(bar_width)
				_G[hide_name]:SetHeight(TITAN_PANEL_BAR_HEIGHT/2);
			end

			-- Set the display bar
			local container = _G[frame]
			container:SetHeight(TITAN_PANEL_BAR_HEIGHT);
			-- Set local identifier
			local container_text = _G[frame.."_Text"]
			if container_text then -- was used for debug/creating of the independent bars
				container_text:SetText(tostring(bar_name))
				-- for now show it
				container:Show()
			end
		end
	else
	end
end

--]====]