Quantcast
--[[ File
NAME: TitanUtils.lua
DESC: This file contains various utility routines used by Titan and routines available to plugin developers.
--]]
TITAN_ID = "Titan"
TITAN_DEBUG_ARRAY_MAX = 100
TITAN_PANEL_NONMOVABLE_PLUGINS = {};
TITAN_PANEL_MENU_FUNC_HIDE = "TitanPanelRightClickMenu_Hide";
TitanPlugins = {};  -- Used by plugins
TitanPluginsIndex = {};
TITAN_NOT_REGISTERED = _G["RED_FONT_COLOR_CODE"].."Not_Registered_Yet".._G["FONT_COLOR_CODE_CLOSE"]
TITAN_REGISTERED = _G["GREEN_FONT_COLOR_CODE"].."Registered".._G["FONT_COLOR_CODE_CLOSE"]
TITAN_REGISTER_FAILED = _G["RED_FONT_COLOR_CODE"].."Failed_to_Register".._G["FONT_COLOR_CODE_CLOSE"]

-- For debug across Titan Panel
TITAN_PANEL_VARS = {}
TITAN_PANEL_VARS.debug = {}
TITAN_PANEL_VARS.debug.movable = false
TITAN_PANEL_VARS.debug.events = false

local _G = getfenv(0);
local L = LibStub("AceLocale-3.0"):GetLocale(TITAN_ID, true)
local media = LibStub("LibSharedMedia-3.0")

-- The LibUIDropDownMenu lib is used over the Blizzard frame
local drop_down_1 = "DropDownList1" -- Boo!! Per hard-coded Blizz UIDropDownMenu.lua
--
-- The routines labeled API are useable by addon developers
--

--[[ API
NAME: TitanUtils_GetBarAnchors
DESC: Get the anchors of the bottom most top bar and the top most bottom bar.
   Intended for addons that modify the UI so they can adjust for Titan.
   The anchors adjust depending on what Titan bars the user displays.
:DESC
VAR:  None
OUT: frame - TitanPanelTopAnchor frame reference - Titan uses the space ABOVE this
OUT: frame - TitanPanelBottomAnchor frame reference - Titan uses the space BELOW this
NOTE:
-  The two anchors are implemented as 2 frames that are moved by Titan depending on which bars are shown.
:NOTE
--]]
function TitanUtils_GetBarAnchors() -- Used by addons
	return TitanPanelTopAnchor, TitanPanelBottomAnchor
end

--[[ API
NAME: TitanUtils_GetMinimapAdjust
DESC: Return the current setting of the Titan MinimapAdjust option.
VAR: None
OUT: The value of the MinimapAdjust option
--]]
function TitanUtils_GetMinimapAdjust() -- Used by addons
	return not TitanPanelGetVar("MinimapAdjust")
end

--[[ API
NAME: TitanUtils_SetMinimapAdjust
DESC: Set the current setting of the Titan MinimapAdjust option.
VAR:  bool - true (off) or false (on)
OUT:  None
--]]
function TitanUtils_SetMinimapAdjust(bool) -- Used by addons
	-- This routine allows an addon to turn on or off
	-- the Titan minimap adjust.
	TitanPanelSetVar("MinimapAdjust", not bool)
end

--[[ API
NAME: TitanUtils_AddonAdjust
DESC: Tell Titan to adjust (or not) a frame.
VAR: frame - is the name (string) of the frame
VAR: bool  - true if the addon will adjust the frame or false if Titan will adjust
OUT:  None
Note:
- Titan will NOT store the adjust value across a log out / exit.
- This is a generic way for an addon to tell Titan to not adjust a frame. The addon will take responsibility for adjusting that frame. This is useful for UI style addons so the user can run Titan and a modifed UI.
- The list of frames Titan adjusts is specified in TitanMovableData within TitanMovable.lua.
- If the frame name is not in TitanMovableData then Titan does not adjust that frame.
:NOTE
--]]
function TitanUtils_AddonAdjust(frame, bool) -- Used by addons
	TitanMovable_AddonAdjust(frame, bool)
end

--------------------------------------------------------------
--
-- Plugin button search & manipulation routines
--
--[[ API
NAME: TitanUtils_GetButton
DESC: Return the actual button frame and the plugin id.
VAR: id - is the id of the plugin
OUT: frame - The button table
OUT: string - The id of the plugin
--]]
function TitanUtils_GetButton(id) -- Used by plugins
	if (id) then
		return _G["TitanPanel"..id.."Button"], id;
	else
		return nil, nil;
	end
end

--[[ API
NAME: TitanUtils_GetRealPosition
DESC: Return whether the plugin is on the top or bottom bar.
VAR: id - is the id of the plugin
OUT: bottom(2) or top(1)-default
NOTE:
- This returns top or bottom NOT which bar (1-4) the plugin is on.
:NOTE
--]]
function TitanUtils_GetRealPosition(id) -- Used by plugins
	-- This will return top / bottom but it is a compromise.
	-- With the introduction of independent bars there is
	-- more than just top / bottom.
	-- This should work in the sense that the plugins using this
	-- would overlap the double bar.
	local bar = TitanUtils_GetWhichBar(id)
	local bar_pos = nil
	for idx,v in pairs (TitanBarData) do
		if bar == TitanBarData[idx].name then
			bar_pos = TitanBarData[idx].vert
		end
	end
	return (bar_pos == TITAN_BOTTOM and TITAN_PANEL_PLACE_BOTTOM or TITAN_PANEL_PLACE_TOP)
end

--[[ API
NAME: TitanUtils_GetButtonID
DESC: Return the plugin id of the given name. This can be used to see if a plugin exists.
VAR: name - is the id of the plugin
OUT: string - plugin id or nil
--]]
function TitanUtils_GetButtonID(name)
	if name then
		local s, e, id = string.find(name, "TitanPanel(.*)Button");
		return id;
	else
		return nil;
	end
end

--[[ API
NAME: TitanUtils_GetParentButtonID
DESC: Return the plugin id of the parent of the given name, if it exists.
VAR: name - is the id of the plugin
OUT: string - plugin id or nil
--]]
function TitanUtils_GetParentButtonID(name)
	local frame = TitanUtils_Ternary(name, _G[name], nil);

	if ( frame and frame:GetParent() ) then
		return TitanUtils_GetButtonID(frame:GetParent():GetName());
	end
end

--[[ API
NAME: TitanUtils_GetButtonIDFromMenu
DESC: Return the plugin id of whatever the mouse is over. Used in the right click menu on load.
VAR: self - is the id of the frame
OUT: string - plugin id or nil
NOTE:
- The plugin id returned could be the Titan bar or a plugin or nil.
:NOTE
--]]
function TitanUtils_GetButtonIDFromMenu(self)
	local ret = nil
	if self and self:GetParent() then
		local name = self:GetParent():GetName()
		local s, e, id = string.find(name, TITAN_PANEL_DISPLAY_PREFIX);
		local temp = ""

		if not s == nil then
			-- The click was on the Titan bar itself
			ret = "Bar";
		elseif self:GetParent():GetParent():GetName() then
			local pname = self:GetParent():GetParent():GetName()
			-- TitanPanelChildButton
			-- expecting this to be a TitanPanelChildButtonTemplate
			temp = TitanUtils_GetButtonID(pname)
			if temp then
				-- should be ok
				ret = temp
			else
				-- the frame container is expected to be without a name
				-- This trips when the user right clicks a LDB plugin...
			end
		else
			-- TitanPanelButton
			temp = TitanUtils_GetButtonID(self:GetParent():GetName())
			if temp then
				-- should be ok
				ret = temp
			else
				TitanDebug("Could not determine Titan ID for '"
				..(self:GetParent():GetParent() or "?").."'. "
				,"error")
			end
		end
	end

	return ret
end

--[[ API
NAME: TitanUtils_GetPlugin
DESC: Return the plugin itself (table and all).
VAR: id - is the id of the plugin
OUT: table - plugin or nil
--]]
function TitanUtils_GetPlugin(id)
	if (id) then
		return TitanPlugins[id];
	else
		return nil;
	end
end

--[[ API
NAME: TitanUtils_GetWhichBar
DESC: Return the bar the plugin is shown on.
VAR: id - is the id of the plugin
OUT: string - bar name or nil
--]]
function TitanUtils_GetWhichBar(id)
	local i = TitanPanel_GetButtonNumber(id);
	if TitanPanelSettings.Location[i] == nil then
		return
	else
		return TitanPanelSettings.Location[i];
	end
end

--[[ API
NAME: TitanUtils_PickBar
DESC: Return the first bar that is shown.
VAR:  None
OUT: string - bar name or nil
--]]
function TitanUtils_PickBar()
	-- Pick the 'first' bar shown.
	-- This is used for defaulting where plugins are put
	-- if using the Titan options screen.
	for idx,v in pairs (TitanBarOrder) do
		if TitanPanelGetVar(v.."_Show") then
			return v
		end
	end
	-- fail safe - return something
	return nil
end

--[[ API
NAME: TitanUtils_ToRight
DESC: See if the plugin is to be on the right.
   There are 3 methods to place a plugin on the right:
   1) DisplayOnRightSide saved variable logic (preferred)
   2) Create a plugin button using the TitanPanelIconTemplate
   3) Place a plugin in TITAN_PANEL_NONMOVABLE_PLUGINS (NOT preferred)
:DESC
VAR:  None
OUT: bool - true or nil. true if the plugin is to be placed on the right side of a bar.
--]]
function TitanUtils_ToRight(id)
	local found = nil
	for index, _ in ipairs(TITAN_PANEL_NONMOVABLE_PLUGINS) do
		if id == TITAN_PANEL_NONMOVABLE_PLUGINS[index] then
			found = true;
		end
	end

	if TitanGetVar(id, "DisplayOnRightSide")
	or TitanPanelButton_IsIcon(id)
	then
		found = true
	end

	return found
end

--------------------------------------------------------------
--
-- General util routines
--

--[[ API
NAME: TitanUtils_Ternary
DESC: Return b or c if true or false respectively
VAR: a - value to determine true or false
VAR: b - value to use if true
VAR: c - value to use if false or nil
OUT: value - b (true) or c (false)
--]]
function TitanUtils_Ternary(a, b, c) -- Used by plugins
	if (a) then
		return b;
	else
		return c;
	end
end

--[[ API
NAME: TitanUtils_Toggle
DESC: Flip the value assuming it is true or false
VAR: value - value to start with
OUT: bool - true or false
--]]
function TitanUtils_Toggle(value) -- Used by plugins
	if (value == 1 or value == true) then
		return nil;
	else
		return 1;
	end
end

--[[ API
NAME: TitanUtils_Min
DESC: Return the min of a or b
VAR: a - a value
VAR: b - a value
OUT:
- value of min (a, b)
--]]
function TitanUtils_Min(a, b)
	if (a and b) then
--		return ( a < b ) and a or b
		return TitanUtils_Ternary((a < b), a, b);
	end
end

--[[ API
NAME: TitanUtils_Max
DESC: Return the max of a or b
VAR: a - a value
VAR: b - a value
OUT: value - value of max (a, b)
--]]
function TitanUtils_Max(a, b)
	if (a and b) then
		return TitanUtils_Ternary((a > b), a, b);
---		return ( a > b ) and a or b
	end
end

--[[ API
NAME: TitanUtils_Round
DESC: Return the nearest integer
VAR: v - a value
OUT:
- value of nearest integer
--]]
function TitanUtils_Round(v)
	local f = math.floor(v)
	if v == f then
		return f
	else
		return math.floor(v + 0.5)
	end
end

--[[ local
NAME: GetTimeParts
DESC: Use the seconds (s) to return its parts.
VAR: s - a time value in seconds
OUT: int - days
OUT: int - hours
OUT: int - minutes
OUT: int - seconds
--]]
local function GetTimeParts(s)
	local days = 0
	local hours = 0
	local minutes = 0
	local seconds = 0
	if not s or (s < 0) then
		seconds = L["TITAN_NA"]
	else
		days = floor(s/24/60/60); s = mod(s, 24*60*60);
		hours = floor(s/60/60); s = mod(s, 60*60);
		minutes = floor(s/60); s = mod(s, 60);
		seconds = s;
	end

	return days, hours, minutes, seconds
end

--[[ API
NAME: TitanUtils_GetEstTimeText
DESC: Use the seconds (s) to return an estimated time.
VAR: s - a time value in seconds
OUT: string - string with localized, estimated elapsed time using spaces and leaving off the rest
--]]
function TitanUtils_GetEstTimeText(s)
	local timeText = "";
	local days, hours, minutes, seconds = GetTimeParts(s)
	local fracdays = days + (hours/24);
	local frachours = hours + (minutes/60);
	if seconds == L["TITAN_NA"] then
		timeText = L["TITAN_NA"];
	else
		if (days ~= 0) then
			timeText = timeText..format("%4.1f"..L["TITAN_DAYS_ABBR"].." ", fracdays);
		elseif (days ~= 0 or hours ~= 0) then
			timeText = timeText..format("%4.1f"..L["TITAN_HOURS_ABBR"].." ", frachours);
		elseif (days ~= 0 or hours ~= 0 or minutes ~= 0) then
			timeText = timeText..format("%d"..L["TITAN_MINUTES_ABBR"].." ", minutes);
		else
			timeText = timeText..format("%d"..L["TITAN_SECONDS_ABBR"], seconds);
		end
	end
	return timeText;
end

--[[ API
NAME: TitanUtils_GetFullTimeText
DESC: break the seconds (s) into days, hours, minutes, and seconds
VAR: s - a time value in seconds
OUT: string - string with localized days, hours, minutes, and seconds using commas and including zeroes
--]]
function TitanUtils_GetFullTimeText(s)
	local days, hours, minutes, seconds = GetTimeParts(s)
	if seconds == L["TITAN_NA"] then
		return L["TITAN_NA"];
	else
		return format("%d"..L["TITAN_DAYS_ABBR"]
			..", %2d"..L["TITAN_HOURS_ABBR"]
			..", %2d"..L["TITAN_MINUTES_ABBR"]
			..", %2d"..L["TITAN_SECONDS_ABBR"],
				days, hours, minutes, seconds);
	end
end

--[[ API
NAME: TitanUtils_GetAbbrTimeText
DESC: break the seconds (s) into days, hours, minutes, and seconds
VAR: s - a time value in seconds
OUT: string - string with localized days, hours, minutes, and seconds using spaces and including zeroes
--]]
function TitanUtils_GetAbbrTimeText(s) -- Used by plugins
	local timeText = "";
	local days, hours, minutes, seconds = GetTimeParts(s)
	if seconds == L["TITAN_NA"] then
		timeText = L["TITAN_NA"];
	else
		if (days ~= 0) then
			timeText = timeText..format("%d"..L["TITAN_DAYS_ABBR"].." ", days);
		end
		if (days ~= 0 or hours ~= 0) then
			timeText = timeText..format("%d"..L["TITAN_HOURS_ABBR"].." ", hours);
		end
		if (days ~= 0 or hours ~= 0 or minutes ~= 0) then
			timeText = timeText..format("%d"..L["TITAN_MINUTES_ABBR"].." ", minutes);
		end
		timeText = timeText..format("%d"..L["TITAN_SECONDS_ABBR"], seconds);
	end
	return timeText;
end

--[[ API
NAME: TitanUtils_GetControlFrame
DESC: return the control frame, if one was created.
VAR: id - id of the plugin
OUT: frame - nil or the control frame
NOTE
- This may not be used anymore.
:NOTE
--]]
function TitanUtils_GetControlFrame(id)
	if (id) then
		return _G["TitanPanel"..id.."ControlFrame"];
	else
		return nil;
	end
end

--[[ API
NAME: TitanUtils_TableContainsValue
DESC: Determine if the table contains the value.
VAR: table - table to search
VAR: value - value to find
OUT: int - nil or the index to value
--]]
function TitanUtils_TableContainsValue(table, value)
	if (table and value) then
		for i, v in pairs(table) do
			if (v == value) then
				return i;
			end
		end
	end
end

--[[ API
NAME: TitanUtils_TableContainsIndex
DESC: Determine if the table contains the index.
VAR: table - table to search
VAR: index - index to find
OUT: int - nil or the index
--]]
function TitanUtils_TableContainsIndex(table, index)
	if (table and index and table[index] ~= nil) then
		return index
	end
--[[
	if (table and index) then
		for i, v in pairs(table) do
			if (i == index) then
				return i;
			end
		end
	end
--]]
end

--[[ API
NAME: TitanUtils_GetCurrentIndex
DESC: Determine if the table contains the value.
VAR: table - table to search
VAR: value - value to find
OUT: int - nil or the index to value
--]]
function TitanUtils_GetCurrentIndex(table, value)
	return TitanUtils_TableContainsValue(table, value);
end

--[[ API
NAME: TitanUtils_PrintArray
DESC: Debug tool that will attempt to output the index and value of the array passed in.
VAR: array - array to output
OUT: table - Array output to the chat window
--]]
function TitanUtils_PrintArray(array)
	if (not array) then
		TitanDebug("array is nil");
	else
		TitanDebug("{");
		for i, v in array do
			TitanDebug("array[" .. tostring(i) .. "] = " .. tostring(v));
		end
		TitanDebug("}");
	end

end

--[[ API
NAME: TitanUtils_GetRedText
DESC: Make the given text red.
VAR: text - text to color
OUT: string - Red string with proper start and end font encoding
--]]
function TitanUtils_GetRedText(text) -- Used by plugins
	if (text) then
		return _G["RED_FONT_COLOR_CODE"]..text.._G["FONT_COLOR_CODE_CLOSE"];
	end
end

--[[ API
NAME: TitanUtils_GetGoldText
DESC: Make the given text gold.
VAR: text - text to color
OUT: string - Gold string with proper start and end font encoding
--]]
function TitanUtils_GetGoldText(text)
	if (text) then
		return "|cffffd700"..text.._G["FONT_COLOR_CODE_CLOSE"];
	end
end

--[[ API
NAME: TitanUtils_GetGreenText
DESC: Make the given text green.
VAR: text - text to color
OUT: string - Green string with proper start and end font encoding
--]]
function TitanUtils_GetGreenText(text) -- Used by plugins
	if (text) then
		return _G["GREEN_FONT_COLOR_CODE"]..text.._G["FONT_COLOR_CODE_CLOSE"];
	end
end

--[[ API
NAME: TitanUtils_GetBlueText
DESC: Make the given text blue.
VAR: text - text to color
OUT: string - Blue string with proper start and end font encoding
--]]
function TitanUtils_GetBlueText(text)
	if (text) then
		return "|cff0000ff"..text.._G["FONT_COLOR_CODE_CLOSE"];
	end
end

--[[ API
NAME: TitanUtils_GetNormalText
DESC: Make the given text normal (gray-white).
VAR: text - text to color
OUT: string - Normal string with proper start and end font encoding
--]]
function TitanUtils_GetNormalText(text) -- Used by plugins
	if (text) then
		return _G["NORMAL_FONT_COLOR_CODE"]..text.._G["FONT_COLOR_CODE_CLOSE"];
	end
end

--[[ API
NAME: TitanUtils_GetHighlightText
DESC: Make the given text highlight (brighter white).
VAR: text - text to color
OUT: string - Highlight string with proper start and end font encoding
--]]
function TitanUtils_GetHighlightText(text) -- Used by plugins
	if (text) then
		return _G["HIGHLIGHT_FONT_COLOR_CODE"]..text.._G["FONT_COLOR_CODE_CLOSE"];
	end
end

--[[ API
NAME: TitanUtils_GetColoredText
DESC: Make the given text a custom color.
VAR: text - text to color
VAR: color - color is the color table with r, b, g values set.
OUT: string - Custom color string with proper start and end font encoding
--]]
function TitanUtils_GetColoredText(text, color) -- Used by plugins
	if (text and color) then
		local redColorCode = format("%02x", color.r * 255);
		local greenColorCode = format("%02x", color.g * 255);
		local blueColorCode = format("%02x", color.b * 255);
		local colorCode = "|cff"..redColorCode..greenColorCode..blueColorCode;
		return colorCode..text.._G["FONT_COLOR_CODE_CLOSE"];
	end
end

--[[ API
NAME: TitanUtils_GetHexText
DESC: Make the given text a custom color.
VAR: text - text to color
VAR: hex - color as a 6 char hex code
OUT: string - Custom color string with proper start and end font encoding
--]]
function TitanUtils_GetHexText(text, hex) -- Used by plugins
	if (text and hex) then
		return "|cff"..tostring(hex)..text.._G["FONT_COLOR_CODE_CLOSE"];
	end
end

--[[ API
NAME: TitanUtils_GetThresholdColor
DESC: Flexable routine that returns the threshold color for a given value using a table as input.
VAR: ThresholdTable - table holding the list of colors and values
VAR: value -
OUT: string - The color value from the treshhold table
--]]
function TitanUtils_GetThresholdColor(ThresholdTable, value)
	if ( not tonumber(value) or type(ThresholdTable) ~= "table"
	or ThresholdTable.Values == nil or ThresholdTable.Colors == nil
	or table.getn(ThresholdTable.Values) >= table.getn(ThresholdTable.Colors)
	) then
		return _G["GRAY_FONT_COLOR"];
	end

	local n = table.getn(ThresholdTable.Values) + 1;
	for i = 1, n do
		local low = TitanUtils_Ternary(i == 1, nil, ThresholdTable.Values[i-1]); -- lowest
		local high = TitanUtils_Ternary(i == n, nil, ThresholdTable.Values[i]);  -- highest

		if ( not low and not high ) then
			-- No threshold values
			return ThresholdTable.Colors[i];

		elseif ( not low and high ) then
			-- Value is smaller than the first threshold
			if ( value < high ) then return ThresholdTable.Colors[i] end

		elseif ( low and not high ) then
			-- Value is larger than the last threshold
			if ( low <= value ) then return ThresholdTable.Colors[i] end

		else
			-- Value is in between 2 adjacent thresholds
			if ( low <= value and value < high ) then
				return ThresholdTable.Colors[i]
			end
		end
	end

	-- Should never reach here
	return _G["GRAY_FONT_COLOR"];
end

--[[ API
NAME: TitanUtils_ToString
DESC: Routine that returns the text or an empty string.
VAR: text - text to check
OUT: string - string of text or ""
--]]
function TitanUtils_ToString(text)
	return TitanUtils_Ternary(text, text, "");
end

-------------------------------------------------
--[[
Right click menu routines for plugins
The expected global function name in the plugin is:
"TitanPanelRightClickMenu_Prepare"..<registry.id>.."Menu"

This section abstracts the menu routines built into WoW.
Over time Titan used the menu routines written by Blizzard and a lib under Ace3. Currently back to the Blizzard routines.
Whenever there is a change to the menu routines, the abstractions allows us to update Utils rather than updating Titan using search & replace.
--]]

--[[ API
NAME: TitanPanelRightClickMenu_GetDropdownLevel
DESC: Menu - Get the current level in the menus.
VAR: None
OUT:  int - dropdown menu level
--]]
function TitanPanelRightClickMenu_GetDropdownLevel()
	return UIDROPDOWNMENU_MENU_LEVEL
end

--[[ API
NAME: TitanPanelRightClickMenu_GetDropdMenuValue
DESC: Menu - Get the current value in the selected menu.
VAR: None
OUT:  int - dropdown menu value
--]]
function TitanPanelRightClickMenu_GetDropdMenuValue()
	return UIDROPDOWNMENU_MENU_VALUE
end

--[[ API
NAME: TitanPanelRightClickMenu_AddButton
DESC: Menu - add given info (button) at the given level in the form of a button.
VAR: info - text / button / command to show
VAR: level - level to put text
OUT:  None
--]]
function TitanPanelRightClickMenu_AddButton(info, level)
    if (info) then
        UIDropDownMenu_AddButton(info, level);
    end
end

--[[ API
NAME: TitanPanelRightClickMenu_AddToggleRightSide
DESC: Menu - add a toggle Right Side (localized) command at the given level in the form of a button. Titan will properly control the "DisplayOnRightSide"
VAR: id - id of the plugin
VAR: level - level to put the line
OUT:  None
--]]
function TitanPanelRightClickMenu_AddToggleRightSide(id, level)
    -- copy of TitanPanelRightClickMenu_AddToggleVar adding a remove button
    local info = {};
    info.text = L["TITAN_CLOCK_MENU_DISPLAY_ON_RIGHT_SIDE"];
    info.value = {id, "DisplayOnRightSide"};
    info.func = function()
        local bar = TitanUtils_GetWhichBar(id)
        TitanPanelRightClickMenu_ToggleVar({id, "DisplayOnRightSide"})
        TitanPanel_RemoveButton(id);
        TitanUtils_AddButtonOnBar(bar, id)
    end
    info.checked = TitanGetVar(id, "DisplayOnRightSide");
    info.keepShownOnClick = 1;
    UIDropDownMenu_AddButton(info, level);
end

--[[ API
NAME: TitanPanelRightClickMenu_AddTitle
DESC: Menu - add a title at the given level in the form of a button.
VAR: title - text to show
VAR: level - level to put text
OUT:  None
--]]
function TitanPanelRightClickMenu_AddTitle(title, level)
	if (title) then
		local info = {};
		info.text = title;
		info.notCheckable = true;
		info.notClickable = true;
		info.isTitle = 1;
		UIDropDownMenu_AddButton(info, level);
	end
end

--[[ API
NAME: TitanPanelRightClickMenu_AddCommand
DESC: Menu - add a command at the given level in the form of a button.
VAR: title - text to show
VAR: value - value of the command
VAR: functionName - routine to run when clicked
VAR: level - level to put command
OUT:  None
--]]
function TitanPanelRightClickMenu_AddCommand(text, value, functionName, level)
	local info = {};
	info.notCheckable = true;
	info.text = text;
	info.value = value;
	info.func = function()
	local callback = _G[functionName];
-- callback must be a function else do nothing (spank developer)
		if callback and type(callback)== "function" then
			callback(value)
		end
	end
	UIDropDownMenu_AddButton(info, level);
end

--[[ API
NAME: TitanPanelRightClickMenu_AddSeparator
DESC: Menu - add a line at the given level in the form of an inactive button.
VAR: level - level to put the line
OUT: None
--]]
function TitanPanelRightClickMenu_AddSeparator(level)
	UIDropDownMenu_AddSeparator(level)
end

--[[ API
NAME: TitanPanelRightClickMenu_AddSpacer
DESC: Menu - add a blank line at the given level in the form of an inactive button.
VAR: level - level to put the line
OUT: None
--]]
function TitanPanelRightClickMenu_AddSpacer(level)
	UIDropDownMenu_AddSpace(level)
end

--[[ API
NAME: TitanPanelRightClickMenu_Hide
DESC: This will remove the plugin from the Titan bar.
VAR: value - id of the plugin
OUT: None
--]]
function TitanPanelRightClickMenu_Hide(value)
	TitanPanel_RemoveButton(value);
end

--[[ API
NAME: TitanPanelRightClickMenu_AddToggleVar
DESC: Menu - add a toggle variable command at the given level in the form of a button.
VAR: text - text to show
VAR: id - id of the plugin
VAR: var - the saved variable of the plugin to toggle
VAR: toggleTable - control table (called with other than nil??)
VAR: level - level to put the line
OUT:  None
--]]
function TitanPanelRightClickMenu_AddToggleVar(text, id, var, toggleTable, level)
	local info = {};
	info.text = text;
	info.value = {id, var, toggleTable};
	info.func = function()
		TitanPanelRightClickMenu_ToggleVar({id, var, toggleTable})
	end
	info.checked = TitanGetVar(id, var);
	info.keepShownOnClick = 1;
	UIDropDownMenu_AddButton(info, level);
end

--[[ API
NAME: TitanPanelRightClickMenu_AddToggleIcon
DESC: Menu - add a toggle Icon (localized) command at the given level in the form of a button. Titan will properly control the "ShowIcon"
VAR: id - id of the plugin
VAR: level - level to put the line
OUT:  None
--]]
function TitanPanelRightClickMenu_AddToggleIcon(id, level)
	TitanPanelRightClickMenu_AddToggleVar(L["TITAN_PANEL_MENU_SHOW_ICON"],
	id, "ShowIcon", nil, level);
end

--[[ API
NAME: TitanPanelRightClickMenu_AddToggleLabelText
DESC: Menu - add a toggle Label (localized) command at the given level in the form of a button. Titan will properly control the "ShowLabelText"
VAR: id - id of the plugin
VAR: level - level to put the line
OUT:  None
--]]
function TitanPanelRightClickMenu_AddToggleLabelText(id, level)
	TitanPanelRightClickMenu_AddToggleVar(L["TITAN_PANEL_MENU_SHOW_LABEL_TEXT"],
	id, "ShowLabelText", nil, level);
end

--[[ API
NAME: TitanPanelRightClickMenu_AddToggleColoredText
DESC: Menu - add a toggle Colored Text (localized) command at the given level in the form of a button. Titan will properly control the "ShowColoredText"
VAR: id - id of the plugin
VAR: level - level to put the line
OUT:  None
--]]
function TitanPanelRightClickMenu_AddToggleColoredText(id, level)
	TitanPanelRightClickMenu_AddToggleVar(L["TITAN_PANEL_MENU_SHOW_COLORED_TEXT"],
	id, "ShowColoredText", nil, level);
end

--[[ API
NAME: TitanPanelRightClickMenu_AddHide
DESC: Menu - add a Hide (localized) command at the given level in the form of a button. When clicked this will remove the plugin from the Titan bar.
VAR: id - id of the plugin
VAR: level - level to put the line
OUT: None
--]]
function TitanPanelRightClickMenu_AddHide(id, level)
	local info = {};
	info.notCheckable = true;
	info.text = L["TITAN_PANEL_MENU_HIDE"];
	info.value = nil -- value; huh - what should this be?
	info.func = function()
		TitanPanelRightClickMenu_Hide(id)
	end
	UIDropDownMenu_AddButton(info, level);
end

--[[ API
NAME: TitanPanelRightClickMenu_ToggleVar
DESC: This will toggle the Titan variable and the update the button.
VAR: value - table of (id of the plugin, saved var to be updated, control table)
OUT:  None
--]]
function TitanPanelRightClickMenu_ToggleVar(value)
	local id, var, toggleTable = nil, nil, nil;

	-- table expected else do nothing
	if type(value)~="table" then return end

	if value and value[1] then id = value[1] end
	if value and value[2] then var = value[2] end
	if value and value[3] then toggleTable = value[3] end

	-- Toggle var
	TitanToggleVar(id, var);

	if ( TitanPanelRightClickMenu_AllVarNil(id, toggleTable) ) then
		-- Undo if all vars in toggle table nil
		TitanToggleVar(id, var);
	else
		-- Otherwise continue and update the button
		TitanPanelButton_UpdateButton(id, 1);
	end
end

--[[ API
NAME: TitanPanelRightClickMenu_AllVarNil
DESC: Check if all the variables in the table are nil/false.
VAR: id - id of the plugin
VAR: toggleTable - table of saved var to be checked
OUT: bool - true (1) or nil
--]]
function TitanPanelRightClickMenu_AllVarNil(id, toggleTable)
	if ( toggleTable ) and type(toggleTable)== "table" then
		for i, v in toggleTable do
			if ( TitanGetVar(id, v) ) then
				return nil;
			end
		end
		return 1;
	end
end

--[[ API
NAME: TitanPanelRightClickMenu_AddToggleColoredText
DESC: This will toggle the "ShowColoredText" Titan variable then update the button
VAR: id - id of the plugin
VAR: level - level to put the line
OUT:  None
--]]
function TitanPanelRightClickMenu_ToggleColoredText(value)
	TitanToggleVar(value, "ShowColoredText");
	TitanPanelButton_UpdateButton(value, 1);
end

--[[ API
NAME: TitanPanelRightClickMenu_SetCustomBackdrop
DESC: This will set the backdrop of the given button. This is used for custom created controls such as Clock offset or Volume sliders to give a consistent look.
VAR: frame - the control frame of the plugin
OUT:  None
--]]
function TitanPanelRightClickMenu_SetCustomBackdrop(frame)
	frame:SetBackdrop({
		bgFile="Interface\\Tooltips\\UI-Tooltip-Background",
		edgeFile="Interface\\Tooltips\\UI-Tooltip-Border",
		tile = true,
		tileEdge = true,
		insets = { left = 1, right = 1, top = 1, bottom = 1 },
		tileSize = 8,
		edgeSize = 8,
	})

	frame:SetBackdropBorderColor(
		TOOLTIP_DEFAULT_COLOR.r,
		TOOLTIP_DEFAULT_COLOR.g,
		TOOLTIP_DEFAULT_COLOR.b);
	frame:SetBackdropColor(
		TOOLTIP_DEFAULT_BACKGROUND_COLOR.r,
		TOOLTIP_DEFAULT_BACKGROUND_COLOR.g,
		TOOLTIP_DEFAULT_BACKGROUND_COLOR.b
		, 1);
end

--------------------------------------------------------------
--
-- Plugin manipulation routines
--
--[[ local
NAME: TitanUtils_SwapButtonOnBar
DESC: This will swap two buttons on the Titan bars. Once swapped then 'reinit' the buttons to show properly. This is currently used as part of the shift left / right.
VAR: from_id - id of the plugin
VAR: to_id - id of the plugin
OUT:  None
--]]
local function TitanUtils_SwapButtonOnBar(from_id, to_id)
	-- Used as part of the shift L / R to swap the buttons
	local button = TitanPanelSettings.Buttons[from_id]
	local locale = TitanPanelSettings.Location[from_id]

	TitanPanelSettings.Buttons[from_id] = TitanPanelSettings.Buttons[to_id]
	TitanPanelSettings.Location[from_id] = TitanPanelSettings.Location[to_id]
	TitanPanelSettings.Buttons[to_id] = button
	TitanPanelSettings.Location[to_id] = locale
	TitanPanel_InitPanelButtons();
end

--[[ local
NAME: TitanUtils_GetNextButtonOnBar
DESC: Find the next button that is on the same bar and is on the same side.
VAR: bar - The Titan bar to search
VAR: id - id of the plugin to see if there is a plugin next to it
VAR: side - right or left
OUT: int - index of the next button or nil if none found
NOTE:
-- buttons on Left are placed L to R; buttons on Right are placed R to L. Next and prev depend on which side we need to check.
:NOTE
--]]
local function TitanUtils_GetNextButtonOnBar(bar, id, side)
	-- find the next button that is on the same bar and is on the same side
	-- return nil if not found
	local index = TitanUtils_GetCurrentIndex(TitanPanelSettings.Buttons, id);

	for i, id in pairs(TitanPanelSettings.Buttons) do
		if TitanUtils_GetWhichBar(id) == bar
		and i > index
		and TitanPanel_GetPluginSide(id) == side
		and TitanUtils_IsPluginRegistered(id) then
			return i;
		end
	end
end

--[[ local
NAME: TitanUtils_GetPrevButtonOnBar
DESC: Find the previous button that is on the same bar and is on the same side.
VAR: bar - The Titan bar to search
VAR: id - id of the plugin to see if there is a plugin previous to it
VAR: side - right or left
OUT: int - index of the previous button or nil if none found
NOTE:
-- buttons on Left are placed L to R; buttons on Right are placed R to L. Next and prev depend on which side we need to check.
:NOTE
--]]
local function TitanUtils_GetPrevButtonOnBar(bar, id, side)
	-- find the prev button that is on the same bar and is on the same side
	-- return nil if not found
	local index = TitanUtils_GetCurrentIndex(TitanPanelSettings.Buttons, id);
	local prev_idx = nil

	for i, id in pairs(TitanPanelSettings.Buttons) do
		if TitanUtils_GetWhichBar(id) == bar
		and i < index
		and TitanPanel_GetPluginSide(id) == side
		and TitanUtils_IsPluginRegistered(id) then
			prev_idx = i; -- this might be the previous button
		end
		if i == index then
			return prev_idx;
		end
	end
end

--[[ Titan
NAME: TitanUtils_AddButtonOnBar
DESC: Add the given plugin to the given bar. Then reinit the plugins to show it properly.
VAR: bar - The Titan bar to add the plugin
VAR: id - id of the plugin to add
OUT:  None.
--]]
function TitanUtils_AddButtonOnBar(bar, id)
	-- Add the button to the requested bar, if shown
	if (not bar)
	or (not id)
	or (not TitanPanelSettings)
	or (not TitanPanelGetVar(bar.."_Show"))
	then
		return;
	end

	local i = TitanPanel_GetButtonNumber(id)
--[[
TitanDebug("AddB: "..(id or "?").." "..(bar or "?").." "
..(TitanPanelSettings and "T" or "F").." "..(TitanPanelGetVar(bar.."_Show") and "T" or "F").." "
..(i or "?").." "
)
--]]
	-- The _GetButtonNumber returns +1 if not found so it is 'safe' to
	-- update / add to the Location
	TitanPanelSettings.Buttons[i] = (id or "?")
	TitanPanelSettings.Location[i] = (bar or "Bar")
	TitanPanel_InitPanelButtons();
end

--[[ Titan
NAME: TitanUtils_GetFirstButtonOnBar
DESC: Find the first button that is on the given bar and is on the given side.
VAR: bar - The Titan bar to search
VAR: side - right or left
OUT: int - index of the first button or nil if none found
NOTE:
-- buttons on Left are placed L to R; buttons on Right are placed R to L. Next and prev depend on which side we need to check.
-- buttons on Right are placed R to L
:NOTE
--]]
function TitanUtils_GetFirstButtonOnBar(bar, side)
	-- find the first button that is on the same bar and is on the same side
	-- return nil if not found
	local index = 0

	for i, id in pairs(TitanPanelSettings.Buttons) do
		if TitanUtils_GetWhichBar(id) == bar
		and i > index
		and TitanPanel_GetPluginSide(id) == side
		and TitanUtils_IsPluginRegistered(id) then
			return i;
		end
	end
end

--[[ Titan
NAME: TitanUtils_ShiftButtonOnBarLeft
DESC: Find the button that is on the bar and is on the side and left of the given button
VAR:
- name - id of the plugin
OUT:  None
--]]
function TitanUtils_ShiftButtonOnBarLeft(name)
	-- Find the button to the left. If there is one, swap it in the array
	local from_idx = TitanUtils_GetCurrentIndex(TitanPanelSettings.Buttons,name)
	local side = TitanPanel_GetPluginSide(name)
	local bar = TitanUtils_GetWhichBar(name)
	local to_idx = nil

	-- buttons on Left are placed L to R;
	-- buttons on Right are placed R to L
	if side and side == TITAN_LEFT then
		to_idx = TitanUtils_GetPrevButtonOnBar (TitanUtils_GetWhichBar(name), name, side)
	elseif side and side == TITAN_RIGHT then
		to_idx = TitanUtils_GetNextButtonOnBar (TitanUtils_GetWhichBar(name), name, side)
	end

	if to_idx then
		TitanUtils_SwapButtonOnBar(from_idx, to_idx);
	else
		return
	end
end

--[[ Titan
NAME: TitanUtils_ShiftButtonOnBarRight
DESC: Find the button that is on the bar and is on the side and right of the given button
VAR:
- name - id of the plugin
OUT:  None
--]]
function TitanUtils_ShiftButtonOnBarRight(name)
	-- Find the button to the right. If there is one, swap it in the array
	local from_idx = TitanUtils_GetCurrentIndex(TitanPanelSettings.Buttons,name)
	local to_idx = nil
	local side = TitanPanel_GetPluginSide(name)
	local bar = TitanUtils_GetWhichBar(name)

	-- buttons on Left are placed L to R;
	-- buttons on Right are placed R to L
	if side and side == TITAN_LEFT then
		to_idx = TitanUtils_GetNextButtonOnBar (bar, name, side)
	elseif side and side == TITAN_RIGHT then
		to_idx = TitanUtils_GetPrevButtonOnBar (bar, name, side)
	end

	if to_idx then
		TitanUtils_SwapButtonOnBar(from_idx, to_idx);
	else
		return
	end
end

--------------------------------------------------------------
--
-- Frame check & manipulation routines
--
function TitanUtils_CheckFrameCounting(frame, elapsed)
	if (frame:IsVisible()) then
		if (not frame.frameTimer or not frame.isCounting) then
			return;
		elseif ( frame.frameTimer < 0 ) then
			frame:Hide();
			frame.frameTimer = nil;
			frame.isCounting = nil;
		else
			frame.frameTimer = frame.frameTimer - elapsed;
		end
	end
end

function TitanUtils_StartFrameCounting(frame, frameShowTime)
	frame.frameTimer = frameShowTime;
	frame.isCounting = 1;
end

function TitanUtils_StopFrameCounting(frame)
	frame.isCounting = nil;
end

function TitanUtils_CloseAllControlFrames()
	for index, value in pairs(TitanPlugins) do
		local frame = _G["TitanPanel"..index.."ControlFrame"];
		if (frame and frame:IsVisible()) then
			frame:Hide();
		end
	end
end

function TitanUtils_IsAnyControlFrameVisible() -- need?
	for index, value in TitanPlugins do
		local frame = _G["TitanPanel"..index.."ControlFrame"];
		if (frame:IsVisible()) then
			return true;
		end
	end
	return false;
end

function TitanUtils_GetOffscreen(frame)
	local offscreenX, offscreenY;
	local ui_scale = UIParent:GetEffectiveScale()
	if not frame then
		return
	end
	local fr_scale = frame:GetEffectiveScale()

	if ( frame and frame:GetLeft()
	and frame:GetLeft() * fr_scale < UIParent:GetLeft() * ui_scale ) then
		offscreenX = -1;
	elseif ( frame and frame:GetRight()
	and frame:GetRight() * fr_scale > UIParent:GetRight() * ui_scale ) then
		offscreenX = 1;
	else
		offscreenX = 0;
	end

	if ( frame and frame:GetTop()
	and frame:GetTop() * fr_scale > UIParent:GetTop() * ui_scale ) then
		offscreenY = -1;
	elseif ( frame and frame:GetBottom()
	and frame:GetBottom() * fr_scale < UIParent:GetBottom() * ui_scale ) then
		offscreenY = 1;
	else
		offscreenY = 0;
	end

	return offscreenX, offscreenY;
end

--------------------------------------------------------------
--
-- Plugin registration routines
--
--[[ Titan
NAME: TitanUtils_PluginToRegister
DESC: Place the plugin to be registered later by Titan
VAR:
- self - frame of the plugin (must be a Titan template)
- isChildButton - true if the frame is a child of a Titan frame
OUT:  None
NOTE:
- .registry is part of 'self' (the Titan plugin frame) which works great for Titan specific plugins.
  Titan plugins create the registry as part of the frame _OnLoad.
  But this does not work for LDB buttons. The frame is created THEN the registry is added to the frame.
- Any read of the registry must assume it may not exist. Also assume the registry could be updated after this routine.
- This is called when a Titan plugin frame is created. Normally these are held until the player 'enters world' then the plugin is registered.
  Sometimes plugin frames are created after this process. Right now only LDB plugins are handled. If someone where to start creating Titan frames after the registration process were complete then it would fail to be registered...
-!For LDB plugins the 'registry' is attached to the frame AFTER the frame is created...
- The fields put into "Attempted" are defaulted here in preperation of being registered.
--]]
function TitanUtils_PluginToRegister(self, isChildButton)
	TitanPluginToBeRegisteredNum = TitanPluginToBeRegisteredNum + 1
	local cat = ""
	local notes = ""
	if self and self.registry then
		cat = (self.registry.category or "")
		notes = (self.registry.notes or "")
	end
	-- Some of the fields in this record are displayed in the "Attempts"
	-- so they are defaulted here.
	TitanPluginToBeRegistered[TitanPluginToBeRegisteredNum] = {
		self = self,
		button = ((self and self:GetName()
			or "Nyl".."_"..TitanPluginToBeRegisteredNum)),
		isChild = (isChildButton and true or false),
		-- fields below are updated when registered
		name = "?",
		issue = "",
		status = TITAN_NOT_REGISTERED,
		category = cat,
		plugin_type = "",
		notes = notes,
	}

	--[[ For updated menu lib (Dec 2018)
	Old way was to use the XML file to declare the frame, now it needs to be in Lua
	<Frame name="$parentRightClickMenu" inherits="L_UIDropDownMenuTemplate" id="1" hidden="true"></Frame>
	--]]
	local f = CreateFrame("Frame", self:GetName().."RightClickMenu", self or nil, "UIDropDownMenuTemplate")
end

--[[ Titan
NAME: TitanUtils_PluginFail
DESC: Place the plugin to be registered later by Titan
VAR:
- plugin - frame of the plugin (must be a Titan template)
OUT:  None
NOTE:
- This is called when a plugin is unsupported. Cuurently this is used if a LDB data object is not supported. See SupportedDOTypes in LDBToTitan.lua for more detail.
  It is intended mainly for developers. It is a place to put relevant info for debug and so users can supply troubleshooting info.
  The key is set the status to 'fail' so there is no further attempt to register the plugin.
- The results will show in "Attempted" so the developer has a shot at figuring out what was wrong.
- plugin is expected to hold as much relevant info as possible...
--]]
function TitanUtils_PluginFail(plugin)
	TitanPluginToBeRegisteredNum = TitanPluginToBeRegisteredNum + 1
	TitanPluginToBeRegistered[TitanPluginToBeRegisteredNum] =
		{
		self = plugin.self,
		button = (plugin.button and plugin.button:GetName() or ""),
		isChild = (plugin.isChild and true or false),
		name = (plugin.name or "?"),
		issue = (plugin.issue or "?"),
		status = TITAN_REGISTER_FAILED,
		category = (plugin.category or ""),
		plugin_type = (plugin.plugin_type or ""),
		}
end

local function NoColor(name)
	local no_color = name

	-- Remove any color formatting from the name in the list
	no_color = string.gsub(no_color, "|c........", "")
	no_color = string.gsub(no_color, "|r", "")

	return no_color
end

--[[ local
NAME: TitanUtils_RegisterPluginProtected
DESC: This routine is intended to be called in a protected manner (pcall) by Titan when it attempts to register a plugin.
VAR:
- plugin - frame of the plugin (must be a Titan template)
OUT:
- table
	.issue	: Show the user what prevented the plugin from registering
	.result	: Used so we know which plugins were processed
	.id		: The name used to lookup the plugin
	.cat		: The 'bucket' to use off the main Titan menu
	.ptype	: For now just Titan or LDB type
NOTE:
- We try to anticipate the various ways a plugin could fail to register or just plain fail.
  The intent is to keep Titan whole so a plugin does not prevent Titan from loading.
  And attempt to tell the user / developer what went wrong.
- If successful the plugin will be in TitanPlugins as a registered plugin and will be available for display on the Titan bars.
--]]
local function TitanUtils_RegisterPluginProtected(plugin)
	local result = ""
	local issue = ""
	local id = ""
	local cat = ""
	local ptype = ""
	local notes = ""

	local self = plugin.self
	local isChildButton = (plugin.isChild and true or false)

	if self and self:GetName() then
		if (isChildButton) then
			-- This is a button within a button
			self:RegisterForClicks("LeftButtonUp", "RightButtonUp", "MiddleButtonUp");
			self:RegisterForDrag("LeftButton")
			TitanPanelDetectPluginMethod(self:GetName(), true);
			result = TITAN_REGISTERED
			-- give some indication that this is valid...
			id = (self:GetName() or "").."<child>"
		else
			-- Check for the .registry where all the Titan plugin info is expected
			if (self.registry and self.registry.id) then
				id = self.registry.id
				if TitanUtils_IsPluginRegistered(id) then
					-- We have already registered this plugin!
					issue =  "Plugin already loaded. "
					.."Please see if another plugin (Titan or LDB) is also loading "
					.."with the same name.\n"
					.."<Titan>.registry.id or <LDB>.label"
				else
					-- A sanity check just in case it was already in the list
					if (not TitanUtils_TableContainsValue(TitanPluginsIndex, id)) then
						-- Herein lies any special per plugin variables Titan wishes to control
						-- These will be overwritten from saved vars, if any
						--
						-- Sanity check
						if self.registry.savedVariables then
							-- Custom labels
							self.registry.savedVariables.CustomLabelTextShow = false
							self.registry.savedVariables.CustomLabelText = ""
						end

						-- Assign and Sort the list of plugins
						TitanPlugins[id] = self.registry;
						-- Set the name used for menus
						if TitanPlugins[id].menuText == nil then
							TitanPlugins[id].menuText = TitanPlugins[id].id;
						end
						TitanPlugins[id].menuText = NoColor(TitanPlugins[id].menuText)

						table.insert(TitanPluginsIndex, self.registry.id);
						table.sort(TitanPluginsIndex,
							function(a, b)
--[[
								-- if the .menuText is missing then use .id
								if TitanPlugins[a].menuText == nil then
									TitanPlugins[a].menuText = TitanPlugins[a].id;
								end
								if TitanPlugins[b].menuText == nil then
									TitanPlugins[b].menuText = TitanPlugins[b].id;
								end
--]]
								return string.lower(TitanPlugins[a].menuText)
									< string.lower(TitanPlugins[b].menuText);
							end
						);
					end
				end
				if issue ~= "" then
					result = TITAN_REGISTER_FAILED
				else
					-- We are almost done-
					-- Allow mouse clicks on the plugin
					local pluginID = TitanUtils_GetButtonID(self:GetName());
					local plugin_id = TitanUtils_GetPlugin(pluginID);
					if (plugin_id) then
						self:RegisterForClicks("LeftButtonUp", "RightButtonUp", "MiddleButtonUp");
						self:RegisterForDrag("LeftButton")
						if (plugin_id.id) then
							TitanPanelDetectPluginMethod(plugin_id.id);
						end
					end
					result = TITAN_REGISTERED
					-- determine the plugin category
					cat = (self.registry.category or nil)
					ptype = TITAN_ID -- Assume it is created for Titan
					if self.registry.ldb then
						-- Override the type with the LDB type
						ptype = "LDB: '"..self.registry.ldb.."'"
					end
				end
				notes = (self.registry.notes or "")
			else
				-- There could be a couple reasons the .registry was not found
				result = TITAN_REGISTER_FAILED
				if (not self.registry) then
					issue = "Can not find registry for plugin (self.registry)"
				end
				if (self.registry and not self.registry.id) then
					issue = "Can not determine plugin name (self.registry.id)"
				end
			end
		end
	else
		-- The button could not be determined - the plugin is hopeless
		result = TITAN_REGISTER_FAILED
		issue = "Can not determine plugin button name"
	end

	-- create and return the results
	local ret_val = {}
	ret_val.issue = (issue or "")
	ret_val.result = (result or TITAN_REGISTER_FAILED)
	ret_val.id = (id or "")
	ret_val.cat = (cat or "General")
	ret_val.ptype = ptype
	ret_val.notes = notes
	return ret_val
end

--[[ Titan
NAME: TitanUtils_RegisterPlugin
DESC: Attempt to register a plugin that has requested to be registered
VAR:
- plugin - frame of the plugin (must be a Titan template)
OUT:  None
NOTE:
- Lets be extremely paranoid here because registering plugins that do not play nice can cause real headaches...
--]]
function TitanUtils_RegisterPlugin(plugin)
	local call_success, ret_val
	-- Ensure we have a glimmer of a plugin and that the plugin has not
	-- already been registered.
	if plugin and plugin.status == TITAN_NOT_REGISTERED then
		-- See if the request to register has a shot at success
		if plugin.self then
			-- Just in case, catch any errors
			call_success, -- needed for pcall
			ret_val =  -- actual return values
				pcall (TitanUtils_RegisterPluginProtected, plugin)
			-- pcall does not allow errors to propagate out. Any error
			-- is returned as text with the success / fail.
			-- Think of it as sort of a try - catch block
			if call_success then
				-- all is good so write the return values to the plugin
				plugin.status = ret_val.result
				plugin.issue = ret_val.issue
				plugin.name = ret_val.id
				plugin.category = ret_val.cat
				plugin.notes = ret_val.notes
				plugin.plugin_type = ret_val.ptype
			else
				-- write enough to the plugin so the user or developer
				-- can see Titan at least tried...
				plugin.status = TITAN_REGISTER_FAILED
				plugin.issue = (ret_val.issue or "Unknown error")
				plugin.name = "?"
				plugin.notes = ret_val.notes or ""
			end
		else
			-- write enough to the plugin so the user or developer can see something
			plugin.status = TITAN_REGISTER_FAILED
			plugin.issue = "Can not determine plugin button name"
			plugin.name = "?"
		end

		-- If there was an error tell the user.
		if not plugin.issue == ""
		or plugin.status ~= TITAN_REGISTERED then
			TitanDebug(TitanUtils_GetRedText("Error Registering Plugin")
				..TitanUtils_GetGreenText(
					": "
					.."name: '"..(plugin.name or "?_").."' "
					.."issue: '"..(plugin.issue or "?_").."' "
					.."button: '"..plugin.button.."' "
					)
				)
		end
	end
end

--[[ Titan
NAME: TitanUtils_RegisterPluginList
DESC: Attempt to register the list of plugins that have requested to be registered
VAR:  None
OUT:  None
NOTE:
- Tell the user when this starts and ends only on the first time.
  This could be called if a plugin requests to be registered after the first loop through.
--]]
function TitanUtils_RegisterPluginList()
	-- Loop through the plugins that have requested to be loaded into Titan.
	local result = ""
	local issue = ""
	local id
	local cnt = 0
	if TitanPluginToBeRegisteredNum > 0 then
		if not Titan__InitializedPEW and TitanAllGetVar("Registered") then
			TitanDebug(L["TITAN_PANEL_REGISTER_START"], "normal")
		end
		for index, value in ipairs(TitanPluginToBeRegistered) do
			if TitanPluginToBeRegistered[index] then
				TitanUtils_RegisterPlugin(TitanPluginToBeRegistered[index])
			end
			cnt = cnt + 1
		end
		if not Titan__InitializedPEW and TitanAllGetVar("Registered") then
			TitanDebug((L["TITAN_PANEL_REGISTER_END"].." "..cnt), "normal")
		end
	end
end

--[[ API
NAME: TitanUtils_IsPluginRegistered
DESC: See if the given plugin was registered successfully.
VAR:
- id - id of the plugin
OUT:  None
- true (successful) or false
--]]
function TitanUtils_IsPluginRegistered(id)
	if (id and TitanPlugins[id]) then
		return true;
	else
		return false;
	end
end

--------------------------------------------------------------
-- Right click menu routines for Titan Panel bars

--[[ Titan
NAME: TitanUtils_CloseRightClickMenu
DESC: Close the right click menu of any plugin if it was open. Only one can be open at a time.
VAR:  None
OUT:  None
--]]
function TitanUtils_CloseRightClickMenu()
	if (_G["DropDownList1"]:IsVisible()) then
		_G["DropDownList1"]:Hide();
	end
end

--[[ local
NAME: TitanRightClick_UIScale
DESC: Scale the right click menu to the user requested value.
VAR:  None
OUT:
- float - x scaled
- float - y scaled
- float - scale used

--]]
local function TitanRightClick_UIScale()
	-- take UI Scale into consideration
	local listFrame = _G[drop_down_1];
	local listframeScale = listFrame:GetScale();

	local uiScale;
	local uiParentScale = UIParent:GetScale();

	local x, y = GetCursorPosition(UIParent)

	if ( GetCVar("useUIScale") == "1" ) then
		uiScale = tonumber(GetCVar("uiscale"));
		if ( uiParentScale < uiScale ) then
			uiScale = uiParentScale;
		end
	else
		uiScale = uiParentScale;
	end

	x = x/uiScale;
	y = y/uiScale;

	listFrame:SetScale(uiScale);

	return x, y, uiScale
end

--[[ local
NAME: TitanRightClickMenu_OnLoad
DESC: Prepare the plugin right click menu using the function given by the plugin.
VAR:
- plugin - frame of the plugin (must be a Titan template)
OUT:  None
NOTE:
- The function name is assumed to be "TitanPanelRightClickMenu_Prepare"..plugin_id.."Menu".
- This routine is for Titan plugins. There is a similar routine for the Titan bar.
--]]
local function TitanRightClickMenu_OnLoad(self)
	local id = TitanUtils_GetButtonIDFromMenu(self);
	if id then
		local prepareFunction = _G["TitanPanelRightClickMenu_Prepare"..id.."Menu"]
		if prepareFunction and type(prepareFunction) == "function" then
			UIDropDownMenu_Initialize(self, prepareFunction, "MENU");
		end
	else
		-- TitanDebug("Could not display tooltip. "
		-- .."Could not determine Titan ID for "
		-- .."'"..(self:GetName() or "?").."'. "
		-- ,"error")
	end
end

--[[ local
NAME: TitanDisplayRightClickMenu_OnLoad
DESC: Prepare the Titan bar right click menu using the given function.
VAR:
- self - frame of the Titan bar
- func - function to create the menu
OUT:  None
NOTE:
- This routine is for Titan bar. There is a similar routine for the Titan plugins.
--]]
local function TitanDisplayRightClickMenu_OnLoad(self, func)
	local prepareFunction = _G[func];
	if prepareFunction and type(prepareFunction) == "function" then
		-- Nasty "hack", load Blizzard_Calendar if not loaded,
		-- for it to secure init 24 dropdown menu buttons,
		-- to avoid action blocked by tainting
		if not IsAddOnLoaded("Blizzard_Calendar") then
			LoadAddOn("Blizzard_Calendar")
		end
		-- not good practice but there seems to be no other way to get
		-- the actual bar (frame parent) to the dropdown implementation
		TitanPanel_DropMenu = self
		UIDropDownMenu_Initialize(self, prepareFunction, "MENU");
	end
end

--[[ local
NAME: TitanPanelRightClickMenu_Toggle
DESC: Call the routine to build the plugin menu then place it properly.
VAR:
- self - frame of the plugin (must be a Titan template)
- isChildButton - function to create the menu
OUT:  None
NOTE:
- This routine is for Titan plugins. There is a similar routine for the Titan bar.
--]]
function TitanPanelRightClickMenu_Toggle(self, isChildButton)
	local x, y, scale
	-- Get top / bottom
	local name = self:GetName() -- assuming this is a plugin
	local parent = self:GetParent():GetName()
	local menu = _G[self:GetName().."RightClickMenu"]
	local vert
	local position
	local id
	local frame = ""

	TitanRightClickMenu_OnLoad(menu)

	-- if this is a child button then use the parent to get the plugin info
	-- otherwise use self as passed in
	if isChildButton then
		id = TitanUtils_GetButtonID(parent)
	else
		id = TitanUtils_GetButtonID(name)
	end
	local i = TitanPanel_GetButtonNumber(id)
	frame = TITAN_PANEL_DISPLAY_PREFIX..TitanPanelSettings.Location[i]
	-- .Location would tell us the bar but we still need vert (top / bottom)
	vert = TitanBarData[frame].vert
	position = (vert == TITAN_TOP and TITAN_PANEL_PLACE_TOP or TITAN_PANEL_PLACE_BOTTOM)

	if position == TITAN_PANEL_PLACE_TOP then
		menu.point = "TOPLEFT";
		menu.relativePoint = "BOTTOMLEFT";
	else
		menu.point = "BOTTOMLEFT";
		menu.relativePoint = "TOPLEFT";
	end

	x, y, scale = TitanRightClick_UIScale()

	ToggleDropDownMenu(1, nil, menu, frame, TitanUtils_Max(x - 40, 0), 0, nil, self);
end

--[[ Titan
NAME: TitanPanelDisplayRightClickMenu_Toggle
DESC: Call the routine to build the Titan bar menu then place it properly.
VAR:
- self - frame of the Titan bar
- isChildButton - function to create the menu
OUT:  None
NOTE:
- This routine is for Titan bar. There is a similar routine for the Titan plugins.
- This is close to TitanPanelRightClickMenu_Toggle but geared to the Titan display bars. This routine allows the Titan display bars to be independent  rather than rely on bars being a 'sort of' plugin.
- This relies on name="$parentRightClickMenu" being part of the display bar template.
--]]
function TitanPanelDisplayRightClickMenu_Toggle(self, isChildButton)
	if not self:GetName() then
		return
	end

	local frame = (isChildButton and self:GetParent():GetName() or self:GetName())
	if not frame then
		-- Only Titan display bars should be processed here!!!
		return
	end

	local vert = TitanBarData[frame].vert
	local position = (vert == TITAN_TOP and TITAN_PANEL_PLACE_TOP or TITAN_PANEL_PLACE_BOTTOM)
	local x, y, scale
	local menu

	-- Per updated menu lib LibUIDropDownMenu Dec 2018
	-- This could have been done in some initialize code but here it can react
	-- better to future Titan frame code changes
	local desired_frame = frame.."RightClickMenu"
	if _G[desired_frame] then
		-- all is good - frame exists
	else
		-- need to create the frame
		-- The _G is needed but it is explicit & shows what needs to done
		_G[desired_frame] = CreateFrame("Frame", desired_frame, self or nil, "UIDropDownMenuTemplate")
	end
	menu = _G[desired_frame];

	-- Initialize the DropDown Menu if not already initialized
	TitanDisplayRightClickMenu_OnLoad(menu, "TitanPanelRightClickMenu_PrepareBarMenu")

	if position == TITAN_PANEL_PLACE_TOP then
		menu.point = "TOPLEFT";
		menu.relativePoint = "BOTTOMLEFT";
	else
		menu.point = "BOTTOMLEFT";
		menu.relativePoint = "TOPLEFT";
	end

	x, y, scale = TitanRightClick_UIScale()

	ToggleDropDownMenu(1, nil, menu, frame, TitanUtils_Max(x - 40, 0), 0, nil, self)
end

--[[ Titan
NAME: TitanPanelRightClickMenu_IsVisible
DESC: Determine if a right click menu is shown. There can only be one.
VAR:  None
OUT:
- true (IsVisible) or false
--]]
function TitanPanelRightClickMenu_IsVisible()
	local res = false
	if _G[drop_down_1] and _G[drop_down_1]:IsVisible() then
		res = true
	else
		res = false
	end
	return res
end

--[[ Titan
NAME: TitanPanelRightClickMenu_Close
DESC: Close the right click menu if shown. There can only be one.
VAR:  None
OUT:  None
--]]
function TitanPanelRightClickMenu_Close()
	if _G[drop_down_1] and _G[drop_down_1]:IsVisible() then
		_G[drop_down_1]:Hide()
	end
end

--------------------------------------------------------------
-- Titan utility routines

--[[ Titan
NAME: TitanUtils_ParseName
DESC: Parse the player name and return the parts.
VAR:
- name - the name to break up
OUT:
- string player name only
- string realm name only
--]]
function TitanUtils_ParseName(name)
	local server = ""
	local player = ""
	if name and name ~= TITAN_PROFILE_NONE then
		local s, e, ident = string.find(name, TITAN_AT);
		if s ~= nil then
			server = string.sub(name, s+1);
			player = string.sub(name, 1, s-1);
		end
	else
	end
	return player, server
end

--[[ Titan
NAME: TitanUtils_CreateName
DESC: Given the player name and server and return the Titan name.
VAR:
- player - 1st part
- realm - 2nd part. Could be realm or 'custom'
OUT:
- string - Titan name
--]]
function TitanUtils_CreateName(player, realm)
	local p1 = player or "?"
	local p2 = realm or "?"

	return p1..TITAN_AT..p2
end

--[[ Titan
NAME: TitanUtils_GetPlayer
DESC: Create the player name (toon being played) and return the parts.
VAR:  None
OUT:
- string Titan player name or nil
- string player name only
- string realm name only
--]]
function TitanUtils_GetPlayer()
	local playerName = UnitName("player");
	local serverName = GetRealmName();
	local toon = nil

	if (playerName == nil
	or serverName == nil
	or playerName == UKNOWNBEING) then
		-- Do nothing if player name is not available
	else
		toon = playerName..TITAN_AT..serverName
	end

	return toon, playerName, serverName
end

--[[ Titan
NAME: TitanUtils_GetGlobalProfile
DESC: Return the global profile setting and the global profile name, if any.
VAR:  None
OUT:
- bool Global profile value
- string Global profile name or default
- string player name only or blank
- string realm name only or blank
--]]
function TitanUtils_GetGlobalProfile()
	local playerName = ""
	local serverName = ""
	local glob = TitanAllGetVar("GlobalProfileUse")
	local toon = TitanAllGetVar("GlobalProfileName")

	if not toon then
		-- this is a new install or toon
		toon = TITAN_PROFILE_NONE
		TitanAllSetVar("GlobalProfileName", TITAN_PROFILE_NONE)
	end
	if (toon == TITAN_PROFILE_NONE) then
		--
	else
		-- If the profile name is not the default then split the name
		playerName, serverName = TitanUtils_ParseName(toon)
	end

	return glob, toon, playerName, serverName
end

--[[ Titan
NAME: TitanUtils_SetGlobalProfile
DESC: Return the global profile setting and the global profile name, if any.
VAR:
- bool Global profile value
- string Global profile name or default
OUT:  None
--]]
function TitanUtils_SetGlobalProfile(glob, toon)
	TitanAllSetVar("GlobalProfileUse", glob)
	if glob then
		-- The user asked for global
		if toon == nil or toon == TITAN_PROFILE_NONE then
			-- nothing was set before so use current player
			toon = TitanUtils_GetPlayer()
		end
	end
	TitanAllSetVar("GlobalProfileName", toon or TITAN_PROFILE_NONE)
end

--------------------------------------------------------------
-- Various debug routines
--[[
local function Debug_array(message)
local idx = TitanDebugArray.index
	TitanDebugArray.index = mod(TitanDebugArray.index + 1, TITAN_DEBUG_ARRAY_MAX)
	TitanDebugArray.lines[TitanDebugArray.index] = (date("%m/%d/%y %H:%M:%S".." : ")..message)
end
--]]
--[[ Titan
NAME: TitanPanel_GetVersion
DESC: Get the Titan version into a string.
VAR:  None
OUT:
- string containing the version
--]]
function TitanPanel_GetVersion()
	return tostring(GetAddOnMetadata(TITAN_ID, "Version")) or L["TITAN_NA"];
end
--[[ Titan
NAME: TitanPrint
DESC: Output a message to the user in a consistent format.
VAR:
- message - string to output
- msg_type - "info" | "warning" | "error" | "plain"
OUT:
- string - message to chat window
--]]
function TitanPrint(message, msg_type)
	local dtype = ""
	local pre = TitanUtils_GetGoldText(L["TITAN_PRINT"]..": ".._G["FONT_COLOR_CODE_CLOSE"])
	local msg = ""
	if msg_type == "error" then
		dtype = TitanUtils_GetRedText("Error: ").._G["FONT_COLOR_CODE_CLOSE"]
	elseif msg_type == "warning" then
		dtype = "|cFFFFFF00".."Warning: ".._G["FONT_COLOR_CODE_CLOSE"]
	elseif msg_type == "plain" then
		pre = ""
	elseif msg_type == "header" then
		local ver = TitanPanel_GetVersion()
		pre = TitanUtils_GetGoldText(L["TITAN_PANEL"])
			..TitanUtils_GetGreenText(" "..ver)
			..TitanUtils_GetGoldText(L["TITAN_PANEL_VERSION_INFO"]
			)
	end

	msg = pre..dtype..TitanUtils_GetGreenText(message)
	DEFAULT_CHAT_FRAME:AddMessage(msg)
--	Debug_array(msg)
end

function TitanDebug(debug_message, debug_type)
	local dtype = ""
	local time_stamp = ""
	local msg = ""
	if debug_type == "error" then
		dtype = TitanUtils_GetRedText("Error: ")
	elseif debug_type == "warning" then
		dtype = TitanUtils_GetHighlightText("Warning: ")
	end
	if debug_type == "normal" then
		time_stamp = ""
	else
		time_stamp = TitanUtils_GetGoldText(date("%H:%M:%S")..": ")
	end
	if debug_message == true then
		debug_message = "<true>";
	end
	if debug_message == false then
		debug_message = "<false>";
	end
	if debug_message == nil then
		debug_message = "<nil>";
	end

	msg =
		TitanUtils_GetGoldText(L["TITAN_DEBUG"].." ")
		..time_stamp
		..dtype
		..TitanUtils_GetGreenText(debug_message)

	if not TitanAllGetVar("Silenced") then
		_G["DEFAULT_CHAT_FRAME"]:AddMessage(msg)
	end
--	Debug_array(msg)
	--date("%m/%d/%y %H:%M:%S")
end

function TitanDumpPluginList()
	-- Just dump the current list of plugins
	local plug_in = {}
	for idx, value in pairs(TitanPluginsIndex) do
		plug_in = TitanUtils_GetPlugin(TitanPluginsIndex[idx])
		if plug_in then
			TitanDebug("TitanDumpPluginList "
				.."'"..idx.."'"
				..": '"..(plug_in.id or "?").."'"
				..": '"..(plug_in.version or "?").."'"
			)
		end
	end
end

function TitanDumpPlayerList()
	-- Just dump the current list of toons in Titan config
	local cnt = 0
	TitanDebug("TitanDumpPlayerList ==== start")
	if TitanSettings.Players then
		for idx, value in pairs(TitanSettings.Players) do
			TitanDebug("-- "
				.."'"..(idx or "?").."'"
			)
			cnt = cnt + 1
		end
	else
		TitanDebug("No player list found!!! "
			)
	end
	TitanDebug("TitanDumpPlayerList ==== done "..cnt)
end

function TitanDumpFrameName(self)
	local frame
	local parent
	if self then
		frame = self:GetName()
	else
		frame = "?"
	end
	if frame == "?" then
		parent = "?"
	else
		parent = self:GetParent():GetName()
	end
--[
TitanDebug("_GetFrameName "
..(self and "T" or "F").." "
..(frame or "?").." "
..(parent or "?").." "
)
--]]
end

function TitanDumpTimers()
	local str = "Titan-timers: "
		.."'"..(TitanAllGetVar("TimerPEW") or "?").."' "
		.."'"..(TitanAllGetVar("TimerDualSpec") or "?").."' "
		.."'"..(TitanAllGetVar("TimerLDB") or "?").."' "
		.."'"..(TitanAllGetVar("TimerAdjust") or "?").."' "
		.."'"..(TitanAllGetVar("TimerVehicle") or "?").."' "
	TitanPrint(str, "plain")
end

function TitanArgConvert (event, a1, a2, a3, a4, a4, a5, a6)
	local t1 = type(a1)
	local t2 = type(a2)
	local t3 = type(a3)
	local t4 = type(a4)
	local t5 = type(a5)
	local t6 = type(a6)
	if type(a1) == "boolean" then a1 = (a1 and "T" or "F") end
	if type(a2) == "boolean" then a2 = (a2 and "T" or "F") end
	if type(a3) == "boolean" then a3 = (a3 and "T" or "F") end
	if type(a4) == "boolean" then a4 = (a4 and "T" or "F") end
	if type(a5) == "boolean" then a5 = (a5 and "T" or "F") end
	if type(a6) == "boolean" then a6 = (a6 and "T" or "F") end
	TitanDebug(event.." "
		.."1: "..(a1 or "?").."("..t1..") "
		.."2: "..(a2 or "?").."("..t2..") "
		.."3: "..(a3 or "?").."("..t3..") "
		.."4: "..(a4 or "?").."("..t4..") "
		.."5: "..(a5 or "?").."("..t5..") "
		.."6: "..(a6 or "?").."("..t6..") "
	)
end

--------------------------------------------------------------
--
-- Deprecated routines
-- These routines will be commented out for a couple releases then deleted.
--
--[[

--]]