Quantcast
--[[
-- **************************************************************************
-- * TitanStarter.lua
-- *
-- * By: The Titan Panel Development Team
-- **************************************************************************
--]]
--[[ Example
This is an example Titan (Titen Panel) plugin based on Titan Bag.
It is intended to introduce Titan plugin essentials by using a basic addon
with a lot of comments. This can be run as is then changed to implement your great idea!

NOTE: The terms addon and plugin are essentially the same.
Here plugin is used when the addon is displayed by Titan.
--]]

--[[ Folder Structure
NOTE: Before running this addon, the folder and file prefix must be the same to be considered for loading into WoW!
For example to just run this as is, remove the 'Example' from the folder name then start or reload WoW.
This is explained in more detail below.


This plugin folder must be added to the Addon folder to be considered for loading into WoW.
Inside this folder you will notice :
- three .toc files
- one .lua file
- one .tga file.

There are sites (wowhead or wow wiki as examples) that have deeper explainations on addon development.
Please use these sites for more general addon information.

=== .toc
The folder and the .toc files MUST have the same name!
Sort of... the name prior to the underscore(_) must be the same. The name after that (postfix) has meaning to WoW.
WoW has three versions represented by the three postix values.
_Mainline : current retail version
_Wrath : Wrath of the Lich King version.
_Vanilla : Classic Era version
These values may change as the versions evolve, say Cata is added to Wrath.
Or they may not :). Years from now we may wonder why Wrath represents Dragonflight!

If your plugin is only for Classic Era then delete or rename the 'mainline' and 'wrath' .toc files.
Changing the filename will prevent WoW from loading the addon into that version of the game.

Titan uses this method. Notice Titan folder has no 'wrath' or 'vanilla' .toc.
TitanClassic has has both 'wrath' and 'vanilla' .toc but no 'mainline' .toc.
This allows Titan plugins intended for Classic versions to run without change.

NOTE: The ## Interface value should match the current interface value of the coorsponding WoW version.
In BattleNet this typcially shown below the 'Play' button.
DragonFlight 10.02.05 is represented without dots - 100205 - in the .toc.

If the interface value is close (but lower) WoW will complain that you are running 'older' addons.
If WoW finds a Classic Era value (say 11500) in a 'mainline' .toc, it will just ignore it (not load).
The reverse (a higher value) is true as well.

=== .lua
This is the code for the plugin - and this file.

=== .tga
This file is the icon used by the plugin.
It is specified in the .registry used by Titan.
WoW can use several different types of icons. This discussion is outside the scope of this example.

Anyone can extract the code and art from WoW. This can be handy to get code examples.
And to grab icons to use for a plugin. My undestanding is any icon can be used within WoW without violating the ToS.
WoW icons tend to be .blp files. These files are NOT easy to look at or manipulate!!
You will need to research third party tools to manipulate .blp files.
--]]

--[[ Editting
This example may seem daunting at first. We decided to leave Bag as is to give plenty of coding examples.

GetBagData will be replaced by your code. It uses IsProfessionBagID.
ToggleBags is called from OnClick. This will be replaced by your code.

TitanPanelRightClickMenu_PrepareStarterMenu will need its name changed.

The other routines will be modified to impement your idea.
--]]

--[[ Code flow
NOTE: The .toc states Titan is required [## Dependencies: Titan].
WoW insures Titan is loaded BEFORE this addon.

First step: ==== Starting WoW
Wow will 'load' this addon. Any code outside the Lua functions will be run.
- local VERSION = GetAddOnMetadata(add_on, "Version") will run GetAddOnMetadata
- bag_info will be populated
- ...
- Create_Frames is called

Create_Frames will create the addon frame - TITAN_BUTTON - and call local OnLoad.
Create_Frames also sets the event scripts for the addon.
This is how WoW and Titan interact with this addon.
NOTE: The frame is created using a Titan template. This template handles some frame processing.

OnLoad does two important things
1) Sets the .registry of the addon - See the .registry comment block
2) Registers for PLAYER_ENTERING_WORLD
NOTE: OnLoad should be small and not assume data is ready yet.

Next: ==== Waiting for WoW
WoW fires a bunch of events as this and other addons are loaded.

Eventually the game and all addons are loaded and this plugin receives the
PLAYER_ENTERING_WORLD event via OnEvent.
When processing PLAYER_ENTERING_WORLD only register for events and access local data.

Titan also receives the PLAYER_ENTERING_WORLD event. There is NO guarantee this plugin
receives the event before or after Titan!
Titan then processes its own data and saved variables.
Titan will process and set any saved variables specified in the .registry of this plugin!
Then Titan starts registering plugins using each addon .registry.
It also loads any LDB plugins found.

Next: ==== Still waiting for WoW
Titan shows the user requested bars with plugins.
OnShow is called by Titan when placing this plugin on a bar or the user requests the plugin to be shown.
Technically Titan calls the now registered frame - TITAN_BUTTON - :Show
then WoW calls the frame script :OnShow eventually calling OnShow in this addon set by Create_Frames.

NOTE: Do not assume any saved variables in .registry are ready until OnShow!

OnShow now does all processing to set up this plugin properly. Once done it calls TitanPanelButton_UpdateButton.
Now the plugin is ready for the user.

Next: ==== Ready to play WoW
The plugin is now 'idle' until :
- Titan calls TitanPanelButton_UpdateButton(TITAN_PLUGIN) to (re)display this plugin
- Any registered event is received
- A timer or callback calls the routine - via Titan or this plugin code

TitanPanelButton_UpdateButton(TITAN_PLUGIN) called when:
- User does a reload or user action - via Titan
- User changes a plugin option using Titan menu
- User changes a plugin option using Titan configuration
- A Titan timer expires such as auto hide bar is active or pet battle or ...

Events occur when:
- User clicks on this plugin to take an action or show menu - OnClick
- User mouses over this plugin to display tool tip - OnEnter
- Any registered event is received - OnEvent
- Your timer expires - be very careful here! :)
- Your callback happens such as waiting for WoW server info

OnClick is the TITAN_BUTTON frame script.
Left click is procssed by this plugin.
Right click is handled by the Titan template to invoke plugin menu (TitanPanelRightClickMenu_PrepareStarterMenu)

OnEnter and OnLeave are the TITAN_BUTTON frame scripts.
OnEnter is handled by the Titan template to show the tool tip (.registry.tooltipTextFunction).
OnLeave is handled by the Titan template to hide the tool tip.

Next: ====
The above step continues until:
- User hides the plugin
- The user logs out the character or exits WoW

Either action causes OnHide to be called.
OnHide should :
- Do any cleanup
- Stop any timers
- Unregister any events
These steps keep processing to a minimum and reduces the chance of errors to the user.

NOTE: Any saved variables - in .registry or specific to this plugin - are saved by WoW on logout or exit.
No specific actions are required.

--]]

--[[ Structure
The plugin starts with local constants used throughout.
Then local variables; then functions.

The design uses local functions sa much as practical. Mainly for clarity and a small speed increase.
Titan specific details will be noted and explained.
The comments will specify where names are important.
Any local routine or variable with 'bag' in the name can be changed to your design or deleted.

Suggested path is start with Create_Frames at the bottom of the file. It is where the magic starts.

Note: Feel free to look at Titan code. Pay attention to the comments. Any routine labeled as API
can be used by a plugin. API routines will be maintained and the functionality expected to remain stable.
Any parameter or functionality changes to API will be 'broadcast' on delivery sites and our sites (Discord, our web page).

Of course feel free to reach out on our Discord site with any questions or suggestions!
--]]

--[[ .registry
This is the GUTS of a Titan plugin. The .registry on the frame contains all
the info to register / create a plugin.

Every registry with an id should appear in Titan > Configuration > Attempts.
Info is shown there along with pass / fail.
If the plugin failed to register, the error is shown there.

NOTE: Titan expects 3 routines to be in the global namespace:
- Routine that updates the plugin : .buttonTextFunction
- Routine that creates the tool tip : .tooltipTextFunction
- Routine that creates the options : .menuTextFunction  OR  "TitanPanelRightClickMenu_Prepare"<id>"Menu"

Attributes:
.id : Required : must be unique across plugins. If there are duplicates, the first one 'wins'.
.category : The list is in TITAN_PANEL_BUTTONS_PLUGIN_CATEGORY (TitanGlobal.lua)
	"Built-ins" is reserved for plugins that Titan releases.
.version : plugin version sown in menus and config.
.menuText : Used by as the title for the right click menu.
.menuTextFunction : This is called for the right click menu.
	NOTE : This is the newer, prefered method which makes the options menu routine explicit.
	OLD METHOD: Still supported!
	Titan builds the function name as "TitanPanelRightClickMenu_Prepare"<id>"Menu"
	TitanPanelRightClickMenu_PrepareStarterMenu in this example.
.buttonTextFunction : This is called whenever the button is to be updated - TitanPanelButton_UpdateButton(TITAN_PLUGIN)
	This is called from within the the plugin and from Titan core.
	The specified routine is expected to be global.
	It is called securely (pcall) so a plugin will not crash Titan (hopefully).
	Titan will usually show "<?>" if the routine dies.
	If this occurs and you need to see the error, search for this attribute in Titan folder and
	uncomment the print of the error message.
.tooltipTitle : Used as the title for the tool tip
.tooltipTextFunction : This is called when OnEnter of the plugin frame is triggered.
	It is expected to be global.
	It is called securely (pcall) so a plugin will not crash Titan (hopefully).
	Titan will usually show part of the error in the tool tip if the routine dies.
.icon : Allowed path to the icon to be used. It is recommended to store the icon in the plugin folder,
	even if copied from WoW folder.
.iconWidth : Best left at 16...
.notes : This is shown in Titan > Config > Plugins when this plugin is selected.
.controlVariables : This list is used by TitanPanelRightClickMenu_AddControlVars(TITAN_PLUGIN)
	true : Will be included in the menu
	false : Will not be included in the menu
.savedVariables : These are the variables stored in Titan saved variables.
	The initial values are used only if that particular is 'new' to that character (new Titan install, new character).
	If a value is removed then it is removed from the saved variables as Titan is run for each character.

--]]

-- ******************************** Constants *******************************
local add_on = ...
local _G = getfenv(0);
local artwork_path = "Interface\\AddOns\\TitanPlugin\\"
-- NOTE: This is the plugin id which should be unique across Titan plugins
-- It does not need to match the addon id.
local TITAN_PLUGIN = "Starter"
-- NOTE: The convention is TitanPanel <id> Button
local TITAN_BUTTON = "TitanPanel"..TITAN_PLUGIN.."Button"
local VERSION = GetAddOnMetadata(add_on, "Version")

local TITAN_BAG_THRESHOLD_TABLE = {
	Values = { 0.5, 0.75, 0.9 },
	Colors = { HIGHLIGHT_FONT_COLOR, NORMAL_FONT_COLOR, ORANGE_FONT_COLOR, RED_FONT_COLOR },
}
local updateTable = {TITAN_PLUGIN, TITAN_PANEL_UPDATE_BUTTON};
-- ******************************** Variables *******************************
local AceTimer = LibStub("AceTimer-3.0")
local L = LibStub("AceLocale-3.0"):GetLocale(TITAN_ID, true)
local BagTimer

local bag_info = { -- itemType : warcraft.wiki.gg/wiki/itemType
	[1] = -- Soul bag
		{ color = {r=0.96,g=0.55,b=0.73}}, -- PINK
	[2] = -- HERBALISM =
		{ color = {r=0,g=1,b=0}}, -- GREEN
	[3] = -- ENCHANTING =
		{ color = {r=0,g=0,b=1}}, -- BLUE
	[4] = -- ENGINEERING =
		{ color = {r=1,g=0.49,b=0.04}}, -- ORANGE
	[5] = -- JEWELCRAFTING =
		{ color = {r=1,g=0,b=0}}, -- RED
	[6] = -- MINING =
		{ color = {r=1,g=1,b=1}}, -- WHITE
	[7] = -- LEATHERWORKING =
		{ color = {r=0.78,g=0.61,b=0.43}}, -- TAN
	[8] = -- INSCRIPTION =
		{ color = {r=0.58,g=0.51,b=0.79}}, -- PURPLE
	[9] = -- FISHING =
		{ color = {r=0.41,g=0.8,b=0.94}}, -- LIGHT_BLUE
	[10] = -- COOKING =
		{ color = {r=0.96,g=0.55,b=0.73}}, -- PINK
	-- These are Classic arrow or bullet bags
	[22] = -- Classic arrow
		{ color = {r=1,g=.4,b=0}}, -- copper
	[23] = -- Classic bullet
		{ color = {r=0.8,g=0.8,b=0.8}}, -- silver
}

local trace = false -- true / false    Make true when debug output is needed.

local MIN_BAGS = 0
local MAX_BAGS = Constants.InventoryConstants.NumBagSlots
local bag_data = {} -- to hold the user bag data

-- ******************************** Functions *******************************

local function IsProfessionBagID(slot)
	-- The info needed is available using GetItemInfoInstant; only the bag / item id is required
	-- itemType : warcraft.wiki.gg/wiki/itemType
	local res = false
	local style = ""
	local info, itemId, itemType, itemSubType, itemEquipLoc, itemTexture, classID, subclassID
	local inv_id = C_Container.ContainerIDToInventoryID(slot)

	if inv_id == nil then
		-- Only works on bag and bank bags NOT backpack!
	else
		info = GetInventoryItemLink("player", inv_id)
		itemId, itemType, itemSubType, itemEquipLoc, itemTexture, classID, subclassID	= GetItemInfoInstant(info)
		style = subclassID
--[[
TitanDebug("T isP 0:"
	.." "..tostring(slot)..""
	.." "..tostring(itemId)..""
	.." '"..tostring(itemType).."'"
	.." '"..tostring(itemSubType).."'"
	.." "..tostring(itemEquipLoc)..""
	.." '"..tostring(itemTexture).."'"
	.." "..tostring(classID)..""
	.." "..tostring(subclassID)..""
	)
--]]
		if classID == 1 then -- is a container / bag
			if subclassID >= 1 then
				-- profession bag of some type [2 - 10] Jan 2024 (DragonFlight / Wrath / Classic Era)
				-- OR soul bag [1]
				res = true
			else
				-- is a arrow or bullet; only two options
			end
		elseif classID == 6 then -- is a 'projectile' holder
			res = true
			-- is a ammo bag or quiver; only two options
		elseif classID == 11 then -- is a 'quiver'; Wrath and CE
			res = true
			-- is a ammo pouch or quiver; only two options
			style = subclassID + 20 -- change to get local color for name
		else
			-- not a bag
		end
	end

	if trace then
		TitanDebug("T isP:"
			.." "..tostring(res)..""
			.." "..tostring(style)..""
			.." "..tostring(itemId)..""
			.." "..tostring(classID)..""
			.." "..tostring(subclassID)..""
			.." "..tostring(inv_id)..""
			)
	end

	return res, style
end

local function ToggleBags()
	if TitanGetVar(TITAN_PLUGIN, "OpenBags") then
		ToggleAllBags()
	else
	end
end

--[[
Where the magic happens!
It is good practice - and good memory - to document the 'why' the code does what it does.
And give details that are not obvious to the reader who did not write the code.
--]]
local function GetBagData(id)
	--[[
	The bag name is not always available when player entering world
	Grabbing the total slots is available though to determine if a bag exists.
	The user may see bag name as UNKNOWN until an event triggers a bag check AND the name is available.
	--]]

	local total_slots = 0
	local total_free = 0
	local total_used = 0

	local count_prof = TitanGetVar(TITAN_PLUGIN, "CountProfBagSlots")

	for bag_slot = MIN_BAGS, MAX_BAGS do -- assuming 0 (Backpack) will not be a profession bag
		local slots = C_Container.GetContainerNumSlots(bag_slot)

		-- Ensure a blank structure exists
		bag_data[bag_slot] = {
			has_bag = false,
			name = "",
			maxi_slots = 0,
			free_slots = 0,
			used_slots = 0,
			style = "",
			color = "",
			}

		if slots > 0 then
			bag_data[bag_slot].has_bag = true

			local bag_name = (C_Container.GetBagName(bag_slot) or UNKNOWN)
			bag_data[bag_slot].name = bag_name
			bag_data[bag_slot].maxi_slots = slots

			local free = C_Container.GetContainerNumFreeSlots(bag_slot)
			local used = slots - free
			bag_data[bag_slot].free_slots = free
			bag_data[bag_slot].used_slots = used

			-- some info is not known until the name is available...
			-- The API requires name to get the bag ID.
			local bag_type = "none"
			local color = {r=0,g=0,b=0} -- black (should never be used...)

			if bag_name == UNKNOWN then
				-- name not available yet
			else
			end

			-- Jan 2024 : Moved away from named based to an id based. Allows name to come later from server
			local is_prof_bag, style = IsProfessionBagID(bag_slot)
--[[
if trace then
TitanDebug("T Bag...:"
.." "..tostring(bag_slot)..""
.." "..tostring(is_prof_bag)..""
.." '"..tostring(style).."'"
.." "..tostring(itemClassID)..""
.." "..tostring(itemSubClassID)..""
)
end
--]]
			if is_prof_bag then
				color = bag_info[style].color
				bag_type = "profession"
			else
				bag_type = "normal"
			end
			bag_data[bag_slot].style = bag_type
			bag_data[bag_slot].color = color

			-- add to total
			if bag_data[bag_slot].style == "profession" then
				if count_prof then
					total_slots = total_slots + slots
					total_free = total_free + free
					total_used = total_used + used
				else
					-- ignore in totals
				end
			else
				total_slots = total_slots + slots
				total_free = total_free + free
				total_used = total_used + used
			end
		else
			bag_data[bag_slot].has_bag = false
		end

		if trace then
			TitanDebug("T info"
			.." "..tostring(bag_slot)..""
			.." ?:"..tostring(bag_data[bag_slot].has_bag)..""
			.." max: "..tostring(bag_data[bag_slot].maxi_slots)..""
			.." used: "..tostring(bag_data[bag_slot].used_slots)..""
			.." free: "..tostring(bag_data[bag_slot].free_slots)..""
			.." type: "..tostring(bag_data[bag_slot].style)..""
			.." count: "..tostring(count_prof)..""
			.." '"..tostring(bag_data[bag_slot].name).."'"
			)
		end
	end

	bag_data.total_slots = total_slots
	bag_data.total_free = total_free
	bag_data.total_used = total_used

	local bagText = ""
	if (TitanGetVar(TITAN_PLUGIN, "ShowUsedSlots")) then
		bagText = format(L["TITAN_BAG_FORMAT"], total_used, total_slots);
	else
		bagText = format(L["TITAN_BAG_FORMAT"], total_free, total_slots);
	end

	local bagRichText = ""
	if ( TitanGetVar(TITAN_PLUGIN, "ShowColoredText") ) then
		local color = ""
		color = TitanUtils_GetThresholdColor(TITAN_BAG_THRESHOLD_TABLE, total_used / total_slots);
		bagRichText = TitanUtils_GetColoredText(bagText, color);
	else
		bagRichText = TitanUtils_GetHighlightText(bagText);
	end

	bagRichText = bagRichText --..bagRichTextProf[1]..bagRichTextProf[2]..bagRichTextProf[3]..bagRichTextProf[4]..bagRichTextProf[5];

	return L["TITAN_BAG_BUTTON_LABEL"], bagRichText
end

local function CreateMenu()
	if trace then
		TitanDebug("TS event"
		.." "..tostring(TitanPanelRightClickMenu_GetDropdownLevel())..""
		.." '"..tostring(TitanPanelRightClickMenu_GetDropdMenuValue()).."'"
		)
	end
	local info
	-- level 2
	if TitanPanelRightClickMenu_GetDropdownLevel() == 2 then
		if TitanPanelRightClickMenu_GetDropdMenuValue() == "Options" then
			TitanPanelRightClickMenu_AddTitle(L["TITAN_PANEL_OPTIONS"], TitanPanelRightClickMenu_GetDropdownLevel())
			info = {};
			info.text = L["TITAN_BAG_MENU_SHOW_USED_SLOTS"];
			info.func = function()
				TitanSetVar(TITAN_PLUGIN, "ShowUsedSlots", 1);
				TitanPanelButton_UpdateButton(TITAN_PLUGIN);
				end
			info.checked = TitanGetVar(TITAN_PLUGIN, "ShowUsedSlots");
			TitanPanelRightClickMenu_AddButton(info, TitanPanelRightClickMenu_GetDropdownLevel());

			info = {};
			info.text = L["TITAN_BAG_MENU_SHOW_AVAILABLE_SLOTS"];
			info.func = function()
				TitanSetVar(TITAN_PLUGIN, "ShowUsedSlots", nil);
				TitanPanelButton_UpdateButton(TITAN_PLUGIN);
				end
			info.checked = TitanUtils_Toggle(TitanGetVar(TITAN_PLUGIN, "ShowUsedSlots"));
			TitanPanelRightClickMenu_AddButton(info, TitanPanelRightClickMenu_GetDropdownLevel());

			info = {};
			info.text = L["TITAN_BAG_MENU_SHOW_DETAILED"];
			info.func = function()
				TitanToggleVar(TITAN_PLUGIN, "ShowDetailedInfo");
				end
			info.checked = TitanGetVar(TITAN_PLUGIN, "ShowDetailedInfo");
			TitanPanelRightClickMenu_AddButton(info, TitanPanelRightClickMenu_GetDropdownLevel());

			info = {};
			info.text =  L["TITAN_BAG_MENU_OPEN_BAGS"]
			info.func = function()
				TitanToggleVar(TITAN_PLUGIN, "OpenBags")
				end
			info.checked = TitanGetVar(TITAN_PLUGIN, "OpenBags");
			TitanPanelRightClickMenu_AddButton(info, TitanPanelRightClickMenu_GetDropdownLevel());
		end
		return
	end

	-- level 1
	TitanPanelRightClickMenu_AddTitle(TitanPlugins[TITAN_PLUGIN].menuText);

	info = {};
	info.notCheckable = true
	info.text = L["TITAN_PANEL_OPTIONS"];
	info.value = "Options"
	info.hasArrow = 1;
	TitanPanelRightClickMenu_AddButton(info, TitanPanelRightClickMenu_GetDropdownLevel());

	TitanPanelRightClickMenu_AddSpacer();
	info = {};
	info.text = L["TITAN_BAG_MENU_IGNORE_PROF_BAGS_SLOTS"];
	info.func = function()
				TitanToggleVar(TITAN_PLUGIN, "CountProfBagSlots");
				TitanPanelButton_UpdateButton(TITAN_PLUGIN);
				end
	info.checked = not TitanGetVar(TITAN_PLUGIN, "CountProfBagSlots")
	TitanPanelRightClickMenu_AddButton(info, TitanPanelRightClickMenu_GetDropdownLevel());

	TitanPanelRightClickMenu_AddControlVars(TITAN_PLUGIN)
end

local function GetButtonText(id)
	local strA, strB = GetBagData(id)
	return strA, strB
end

local function GetTooltipText()
	local totalSlots, usedSlots, availableSlots;
	local returnstring = "";

	if trace then
		TitanDebug("TS tool tip"
		.." detail "..tostring(TitanGetVar(TITAN_PLUGIN, "ShowDetailedInfo"))..""
		.." used "..tostring(TitanGetVar(TITAN_PLUGIN, "ShowUsedSlots"))..""
		.." prof "..tostring(TitanGetVar(TITAN_PLUGIN, "CountProfBagSlots"))..""
		.." color "..tostring(TitanGetVar(TITAN_PLUGIN, "ShowColoredText"))..""
		.." open "..tostring(TitanGetVar(TITAN_PLUGIN, "OpenBags"))..""
		)
	end

	if TitanGetVar(TITAN_PLUGIN, "ShowDetailedInfo") then
		returnstring = "\n";
		if TitanGetVar(TITAN_PLUGIN, "ShowUsedSlots") then
			returnstring = returnstring..TitanUtils_GetNormalText(L["TITAN_BAG_MENU_TEXT"])
				..":\t"..TitanUtils_GetNormalText(L["TITAN_BAG_USED_SLOTS"])..":\n";
		else
			returnstring = returnstring..TitanUtils_GetNormalText(L["TITAN_BAG_MENU_TEXT"])
				..":\t"..TitanUtils_GetNormalText(L["TITAN_BAG_FREE_SLOTS"])..":\n";
		end

		for bag = MIN_BAGS, MAX_BAGS do
			local bagText, bagRichText, color;
--[[
TitanDebug("T Bag: TT"
.." "..tostring(bag)..""
.." "..tostring(bag_data[bag].has_bag)..""
.." "..tostring(bag_data[bag].name)..""
.." "..tostring(bag_data[bag].maxi_slots)..""
.." "..tostring(bag_data[bag].used_slots)..""
.." "..tostring(bag_data[bag].free_slots)..""
)
--]]
			if bag_data[bag] and bag_data[bag].has_bag then
				if (TitanGetVar(TITAN_PLUGIN, "ShowUsedSlots")) then
					bagText = format(L["TITAN_BAG_FORMAT"], bag_data[bag].used_slots, bag_data[bag].maxi_slots);
				else
					bagText = format(L["TITAN_BAG_FORMAT"], bag_data[bag].free_slots, bag_data[bag].maxi_slots);
				end

				if bag_data[bag].style == "profession"
				and not TitanGetVar(TITAN_PLUGIN, "CountProfBagSlots")
				then
					bagRichText = "|cffa0a0a0"..bagText.."|r" -- show as gray
				elseif ( TitanGetVar(TITAN_PLUGIN, "ShowColoredText") ) then
					if bag_data[bag].maxi_slots == 0 then
						color = TitanUtils_GetThresholdColor(TITAN_BAG_THRESHOLD_TABLE, 1 );
					else
						color = TitanUtils_GetThresholdColor(TITAN_BAG_THRESHOLD_TABLE, bag_data[bag].used_slots / bag_data[bag].maxi_slots);
					end
					bagRichText = TitanUtils_GetColoredText(bagText, color);
				else
					-- use without color
					bagRichText = TitanUtils_GetHighlightText(bagText);
				end

				local name_text = bag_data[bag].name
				if bag_data[bag].style == "profession"
				then
					name_text = TitanUtils_GetColoredText(name_text, bag_data[bag].color)
				else
					-- use without color
				end
				returnstring = returnstring..name_text.."\t"..bagRichText.."\n";
			else
				returnstring = returnstring..NONE.."\n";
			end
		end
		returnstring = returnstring.."\n";
	end

	if TitanGetVar(TITAN_PLUGIN, "ShowUsedSlots") then
		local xofy = ""..tostring(bag_data.total_used)
			.."/"..tostring(bag_data.total_slots).."\n"
		returnstring = returnstring..TitanUtils_GetNormalText(L["TITAN_BAG_USED_SLOTS"])
			..":\t"..xofy
	else
		local xofy = ""..tostring(bag_data.total_free)
			.."/"..tostring(bag_data.total_slots).."\n"
		returnstring = returnstring..TitanUtils_GetNormalText(L["TITAN_BAG_USED_SLOTS"])
			..":\t"..xofy
	end

	-- Add Hint
	if TitanGetVar(TITAN_PLUGIN, "OpenBags") then
		returnstring = returnstring..TitanUtils_GetGreenText(L["TITAN_BAG_TOOLTIP_HINTS"])
	else
		-- nop
	end
	return returnstring
end

local function OnLoad(self)
	local notes = ""
		.."Adds bag and free slot information to Titan Panel.\n"
--		.."- xxx.\n"
	self.registry = {
		id = TITAN_PLUGIN,
		category = "Information",
		version = VERSION,
		menuText = L["TITAN_BAG_MENU_TEXT"],
		menuTextFunction = CreateMenu,
		buttonTextFunction = GetButtonText,
		tooltipTitle = L["TITAN_BAG_TOOLTIP"],
		tooltipTextFunction = GetTooltipText,
		icon = artwork_path.."TitanStarter",
		iconWidth = 16,
		notes = notes,
		controlVariables = {
			ShowIcon = true,
			ShowLabelText = true,
			ShowColoredText = true,
			DisplayOnRightSide = true,
		},
		savedVariables = {
			ShowUsedSlots = 1,
			ShowDetailedInfo = false,
			CountProfBagSlots = false,
			ShowIcon = 1,
			ShowLabelText = 1,
			ShowColoredText = 1,
			DisplayOnRightSide = false,
			OpenBags = false,
			OpenBagsClassic = "new_install",
		}
	};
	if TITAN_ID == "Titan" then -- 10.* / Retail
		-- for taint issue
		self.registry.savedVariables.OpenBags = false
	else
		-- does not taint so default to open bags on click
		self.registry.savedVariables.OpenBags = true
	end

	self:RegisterEvent("PLAYER_ENTERING_WORLD");

	if trace then
		TitanDebug("TS OnLoad"
		.." complete"
		)
	end
end

local function OnEvent(self, event, a1, a2, ...)
	if trace then
		TitanDebug("TS event"
		.." "..tostring(event)..""
		.." "..tostring(a1)..""
		)
	end

	if event == "PLAYER_ENTERING_WORLD" then
		if a1 == true and TITAN_ID == "Titan" then -- 10.* / Retail
			-- initial login

			TitanPrint(L["TITAN_BAG_TAINT_TEXT"], "warning")
		else -- either Classic version
			local open = TitanGetVar(TITAN_PLUGIN, "OpenBagsClassic")
			if open == "new_install" then --
				-- set to default behavior of opening bag on left click
				TitanSetVar(TITAN_PLUGIN, "OpenBags", true)
				TitanSetVar(TITAN_PLUGIN, "OpenBagsClassic", "processed") -- don't do again
			else
				-- already processed...
			end
		end
	end

	if event == "BAG_UPDATE" then
		-- update the plugin text
		TitanPanelButton_UpdateButton(TITAN_PLUGIN);
	end
end

local function OnClick(self, button)
	if trace then
		TitanDebug("TS click"
		.." "..tostring(button)..""
		)
	end

	if (button == "LeftButton") then
		ToggleBags();
	end
end

local function OnShow(self)
	if trace then
		TitanDebug("TS OnShow"
		.." register"
		)
	end
	-- Register for bag updates and update the plugin text
	self:RegisterEvent("BAG_UPDATE")
	TitanPanelButton_UpdateButton(TITAN_PLUGIN);
end

local function OnHide(self)
	if trace then
		TitanDebug("TS OnShow"
		.." unregister"
		)
	end
	self:UnregisterEvent("BAG_UPDATE")
end

-- ====== Create needed frames
local function Create_Frames()
	if _G[TITAN_BUTTON] then
		return -- if already created
	end

	if trace then
		TitanDebug("TS frames"
		.." '"..tostring(TITAN_BUTTON).."'"
		)
	end

	-- general container frame
	local f = CreateFrame("Frame", nil, UIParent)
--	f:Hide()

	-- Titan plugin button
	--[[
	The plugin frame is created here. The typical plugin is a 'combo' which includes
	- an icon (can be shown or hidden)
	- label - value pair where the label can be turned off
	There can be multiple label - value pairs; TitanPerformance uses this scheme.

	The frame is 'forever' as are most of WoW game frames.
	--]]
	local window = CreateFrame("Button", TITAN_BUTTON, f, "TitanPanelComboTemplate")
	window:SetFrameStrata("FULLSCREEN")
	-- Using SetScript to set "OnLoad" does not work
	--
	-- This routine sets the guts of the plugin - the .registry
	OnLoad(window);

	--[[
	Below are the frame 'events' that need to be processed.
	A couple have Titan routines that ensure the plugin is properly on / off a Titan bar.

	The combined Titan changed design to register for events when the user places the plugin
	on the bar (OnShow)	and unregister events when the user hids the plugin (OnHide).
	This reduces cycles the plugin uses when the user does not want the plugin.

	NOTE: If a Titan bar is hidden, the plugins on it will still run.
	NOTE: Titan plugins are NOT child frames!! Meaning plugins are not automatically hidden when the
	bar they visible on is hidden!
	--]]
	window:SetScript("OnShow", function(self)
		OnShow(self)
		-- This routine ensures the plugin is put where the user requested it.
		-- Titan saves the bar the plugin was on. It does not save the relative order.
		TitanPanelButton_OnShow(self);
	end)
	window:SetScript("OnHide", function(self)
		OnHide(self)
		-- We use the Blizzard frame hide to visually remove the frame
	end)
	window:SetScript("OnEvent", function(self, event, ...)
		-- Handle any events the plugin is interested in
		OnEvent(self, event, ...)
	end)
	window:SetScript("OnClick", function(self, button)
		-- Typically this routine handles actions on left click
		OnClick(self, button);
		-- Typically this routine handles the menu creation on right click
		TitanPanelButton_OnClick(self, button);
	end)
end

Create_Frames() -- do the work

-- ====== 3 routines required to be in the global namespace

-- ======