Quantcast

- Add an Example LDB for devs to play with (TitanExampleLDB)

urnati [01-31-24 - 23:57]
- Add an Example LDB for devs to play with (TitanExampleLDB)
- Add example Titan plugin for devs to play with (TitanExamplePlugin)
Filename
TitanExampleLDB/Artwork/Starter.tga
TitanExampleLDB/Starter.tga
TitanExampleLDB/StarterLDB.lua
TitanExampleLDB/TitanLDB_Mainline.toc
TitanExampleLDB/TitanLDB_Vanilla.toc
TitanExampleLDB/TitanLDB_Wrath.toc
TitanExampleLDB/libs/LibDataBroker-1.1.lua
TitanExamplePlugin/TitanPlugin_Mainline.toc
TitanExamplePlugin/TitanPlugin_Vanilla.toc
TitanExamplePlugin/TitanPlugin_Wrath.toc
TitanExamplePlugin/TitanStarter.lua
TitanExamplePlugin/TitanStarter.tga
diff --git a/TitanExampleLDB/Artwork/Starter.tga b/TitanExampleLDB/Artwork/Starter.tga
new file mode 100644
index 0000000..6a7ddda
Binary files /dev/null and b/TitanExampleLDB/Artwork/Starter.tga differ
diff --git a/TitanExampleLDB/Starter.tga b/TitanExampleLDB/Starter.tga
new file mode 100644
index 0000000..6a7ddda
Binary files /dev/null and b/TitanExampleLDB/Starter.tga differ
diff --git a/TitanExampleLDB/StarterLDB.lua b/TitanExampleLDB/StarterLDB.lua
new file mode 100644
index 0000000..a9e5e1c
--- /dev/null
+++ b/TitanExampleLDB/StarterLDB.lua
@@ -0,0 +1,288 @@
+--[[
+StarterLDB.lua
+This is a simplistic example of a LDB (LibDataBroker) Addon.
+It is assumed that you understand the Blizzard addon basics.
+It is based loosely on the Titan Bag addon but is not Titan specific.
+
+It is not required that a display addon exist for your addon to run; Such as Titan :-)
+However your addon will need to enable command line commands or the user will not be able to see or do anything.
+
+Enjoy!
+
+By: The Titan Development Team
+--]]
+
+--[[ 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 .obj created for the LDB init routine.
+WoW can use several different types of icons. This discussion is outside the scope of this example.
+
+=== libs
+This file implementing the LDB functions.
+See https://github.com/tekkub/libdatabroker-1-1/wiki/ for an API description.
+LibDataBroker-1.1.lua at https://github.com/tekkub/libdatabroker-1-1/
+It *should* be included in the LDB compliant display addon.
+It *should* included in the display addon.
+
+
+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.
+--]]
+
+-- ******************************** Constants *******************************
+local ADDON_NAME = ...
+-- Set the name we want in the global name space. Ensure the name is unique across all addons.
+StarterLDB = {}
+
+local id = "LDBStarter";  -- What the user will see as the name
+local addon = ADDON_NAME  -- addon name / folder name / toc name
+
+-- Localized strings are outside the scope of this example.
+
+--[[
+The artwork path must start with Interface\\AddOns
+Then the name of the plugin
+Then any additional folder(s) to your artwork / icons.
+--]]
+local artwork_path = "Interface\\AddOns\\TitanLDB\\"
+
+--  Get data from the TOC file.
+local version = tostring(GetAddOnMetadata(addon, "Version")) or "Unknown"
+local author = GetAddOnMetadata(addon, "Author") or "Unknown"
+-- NOTE: GetAddOnMetadata expects the addon name :
+--       The addon folder name or .toc name needs to be the same.
+-- ******************************** Variables *******************************
+local trace = false -- toggle to show / hide debug statements in this addon
+
+-- ******************************** Functions *******************************
+local function Debug(debug_message, debug_type)
+	if trace then
+		local dtype = ""
+		local time_stamp = ""
+		local msg = ""
+		if debug_type == "error" then
+			dtype = "Error: "
+		elseif debug_type == "warning" then
+			dtype = "Warning: "
+		end
+		if debug_type == "normal" then
+			time_stamp = ""
+		else
+			time_stamp = date("%H:%M:%S")..": "
+		end
+
+		msg =
+			"Debug".." "
+			..time_stamp
+			..dtype
+			..debug_message
+
+		_G["DEFAULT_CHAT_FRAME"]:AddMessage(msg)
+	else
+		-- not requested
+	end
+	--date("%m/%d/%y %H:%M:%S")
+end
+
+-- Calculate bag space then return text and icon to display
+local function GetBagSlotInfo()
+	local totalSlots, usedSlots, availableSlots, icon
+	totalSlots = 0;
+	usedSlots = 0;
+	for bag = 0, 4 do
+		local size = C_Container.GetContainerNumSlots(bag);
+		if (size and size > 0) then
+			totalSlots = totalSlots + size;
+			local free = C_Container.GetContainerNumFreeSlots(bag)
+			local used = size - free
+			usedSlots = usedSlots + used;
+		end
+	end
+	availableSlots = totalSlots - usedSlots;
+
+	local i,r = math.modf(availableSlots/2)
+	if (r == 0) then
+		icon = artwork_path.."Starter.tga"
+	else
+		icon = "Interface\\PetPaperDollFrame\\UI-PetHappiness" --PET_DISMISS_TEXTURE
+	end
+
+	local bagText
+	bagText = format("%d/%d", availableSlots, totalSlots);
+
+	bagText = HIGHLIGHT_FONT_COLOR_CODE..bagText..FONT_COLOR_CODE_CLOSE
+
+	return bagText, icon
+--]]
+end
+
+-- Create the tooltip
+local function LDB_OnTooltipShow(tooltip)
+	tooltip = tooltip or GameTooltip
+	local tt_str = ""
+
+	tt_str =
+		GREEN_FONT_COLOR_CODE
+		..id.." Info"
+		..FONT_COLOR_CODE_CLOSE
+	tooltip:AddLine(tt_str)
+
+	local text, icon = GetBagSlotInfo()
+	tt_str = "Available bag slots"
+		.." "..text.."\n"
+		.."\n".."Hint: Left-click to open all bags."
+
+	tooltip:AddLine(tt_str)
+end
+
+local function LDB_Init(LDB_frame)
+	Debug(id.." Init ...");
+	--[[
+	Initialize the Data Broker 'button'.
+	This is the heart of a LDB plugin. It determines how the display addon is to treat this addon.
+
+	Setting the type is required so the LDB lib and display addon know what to do. See the LDB spec.
+	--]]
+	-- The .obj is the key link to the display addon!!
+	LDB_frame.obj =
+		LibStub("LibDataBroker-1.1"):NewDataObject(id, {
+			type = "data source", -- required
+			-- SDK: The two options are:
+			--      "data source" - A data source is expected to show some type of info
+			--      "launcher" - Expected to open another window or perform some action
+
+			-- For some unknown reason, Classic Era shows a tiny icon... Wrath and Retail show fine...
+			icon = artwork_path.."Starter", -- The icon to display on the display addon
+			label = id, -- label is the text the user will use to find this addon in the display addon.
+			text  = "nyl", -- will be updated later
+			OnTooltipShow = function(tooltip)
+				LDB_OnTooltipShow(tooltip)
+			end,
+			OnClick = function(self, button)
+				if ( button == "LeftButton" ) then
+					-- Just a simple action to illustrate an LDB addon.
+					ToggleAllBags();
+				elseif ( button == "RightButton" ) then
+					-- There is no action to take in this example.
+					--[[ Add code here if your addon needs to do something on right click.
+						Typically an options menu which is outside the scope of this example.
+					--]]
+				end
+			end,
+		})
+	Debug(id.." Init fini.");
+--]===]
+end
+
+-- Update the Bags Data Broker 'button'
+local function LDB_Update(LDB_frame)
+	local text, icon = GetBagSlotInfo()
+	LDB_frame.obj.text = text
+	LDB_frame.obj.icon = icon
+end
+
+-- Parse events registered to plugin and act on them
+local function Button_OnEvent(self, event, ...)
+	Debug("OnEvent"
+		.." "..tostring(event)..""
+		)
+	if (event == "PLAYER_ENTERING_WORLD") then
+		-- Do any additional set up needed
+		--
+		print(""
+			.." "..tostring(id)..""
+			.." "..tostring(version)..""
+			.." by "..tostring(author)..""
+			)
+
+		-- Now that events have settled, register the one(s) we really want.
+		-- This may not be needed but it could reduce churn and possible timing issues.
+		self:RegisterEvent("BAG_UPDATE");
+
+		-- Unregister events no longer needed.
+		-- Good practice although this event is only fired on login
+		self:UnregisterEvent("PLAYER_ENTERING_WORLD");
+	end
+	if event == "BAG_UPDATE" then
+		LDB_Update(self)
+	end
+end
+
+-- ====== Create needed frames
+local function Create_Frames()
+	-- general container frame
+	-- The frame pointer is passed as a parameter rather than an addon local.
+	local window = CreateFrame("Frame", "StarterLDBExample", UIParent)
+--	window:Hide()
+
+	-- Set strata as desired
+	window:SetFrameStrata("FULLSCREEN")
+	-- Using SetScript("OnLoad",   does not work
+
+	window:SetScript("OnEvent", function(self, event, ...)
+		Button_OnEvent(self, event, ...)
+	end)
+
+	-- Tell Blizzard this frame needs player entering world event.
+	window:RegisterEvent("PLAYER_ENTERING_WORLD");
+
+	-- Any other addon specific "on load" code here
+	LDB_Init(window)
+	-- Update the text (bag numbers)
+	LDB_Update(window)
+
+	-- shamelessly print a load message to chat window
+	DEFAULT_CHAT_FRAME:AddMessage(
+		GREEN_FONT_COLOR_CODE
+		..addon..id.." "..version
+		.." by "
+		..FONT_COLOR_CODE_CLOSE
+		.."|cFFFFFF00"..author..FONT_COLOR_CODE_CLOSE);
+end
+
+Create_Frames() -- do the work
diff --git a/TitanExampleLDB/TitanLDB_Mainline.toc b/TitanExampleLDB/TitanLDB_Mainline.toc
new file mode 100644
index 0000000..44af1ac
--- /dev/null
+++ b/TitanExampleLDB/TitanLDB_Mainline.toc
@@ -0,0 +1,12 @@
+## Interface: 100205
+## Title: StarterLDB
+## Notes: LDB example
+## Author: Titan Dev Team
+## DefaultState: Enabled
+## SavedVariables:
+## OptionalDeps:
+## Dependencies:
+## Version: 1.1
+## X-Child-Of:
+libs/LibDataBroker-1.1.lua
+StarterLDB.lua
diff --git a/TitanExampleLDB/TitanLDB_Vanilla.toc b/TitanExampleLDB/TitanLDB_Vanilla.toc
new file mode 100644
index 0000000..a294486
--- /dev/null
+++ b/TitanExampleLDB/TitanLDB_Vanilla.toc
@@ -0,0 +1,12 @@
+## Interface: 11500
+## Title: StarterLDB
+## Notes: LDB example
+## Author: Titan Dev Team
+## DefaultState: Enabled
+## SavedVariables:
+## OptionalDeps:
+## Dependencies:
+## Version: 1.1
+## X-Child-Of:
+libs/LibDataBroker-1.1.lua
+StarterLDB.lua
diff --git a/TitanExampleLDB/TitanLDB_Wrath.toc b/TitanExampleLDB/TitanLDB_Wrath.toc
new file mode 100644
index 0000000..40aa326
--- /dev/null
+++ b/TitanExampleLDB/TitanLDB_Wrath.toc
@@ -0,0 +1,12 @@
+## Interface: 30403
+## Title: StarterLDB
+## Notes: LDB example
+## Author: Titan Dev Team
+## DefaultState: Enabled
+## SavedVariables:
+## OptionalDeps:
+## Dependencies:
+## Version: 1.1
+## X-Child-Of:
+libs/LibDataBroker-1.1.lua
+StarterLDB.lua
diff --git a/TitanExampleLDB/libs/LibDataBroker-1.1.lua b/TitanExampleLDB/libs/LibDataBroker-1.1.lua
new file mode 100644
index 0000000..f47c0cd
--- /dev/null
+++ b/TitanExampleLDB/libs/LibDataBroker-1.1.lua
@@ -0,0 +1,90 @@
+
+assert(LibStub, "LibDataBroker-1.1 requires LibStub")
+assert(LibStub:GetLibrary("CallbackHandler-1.0", true), "LibDataBroker-1.1 requires CallbackHandler-1.0")
+
+local lib, oldminor = LibStub:NewLibrary("LibDataBroker-1.1", 4)
+if not lib then return end
+oldminor = oldminor or 0
+
+
+lib.callbacks = lib.callbacks or LibStub:GetLibrary("CallbackHandler-1.0"):New(lib)
+lib.attributestorage, lib.namestorage, lib.proxystorage = lib.attributestorage or {}, lib.namestorage or {}, lib.proxystorage or {}
+local attributestorage, namestorage, callbacks = lib.attributestorage, lib.namestorage, lib.callbacks
+
+if oldminor < 2 then
+	lib.domt = {
+		__metatable = "access denied",
+		__index = function(self, key) return attributestorage[self] and attributestorage[self][key] end,
+	}
+end
+
+if oldminor < 3 then
+	lib.domt.__newindex = function(self, key, value)
+		if not attributestorage[self] then attributestorage[self] = {} end
+		if attributestorage[self][key] == value then return end
+		attributestorage[self][key] = value
+		local name = namestorage[self]
+		if not name then return end
+		callbacks:Fire("LibDataBroker_AttributeChanged", name, key, value, self)
+		callbacks:Fire("LibDataBroker_AttributeChanged_"..name, name, key, value, self)
+		callbacks:Fire("LibDataBroker_AttributeChanged_"..name.."_"..key, name, key, value, self)
+		callbacks:Fire("LibDataBroker_AttributeChanged__"..key, name, key, value, self)
+	end
+end
+
+if oldminor < 2 then
+	function lib:NewDataObject(name, dataobj)
+		if self.proxystorage[name] then return end
+
+		if dataobj then
+			assert(type(dataobj) == "table", "Invalid dataobj, must be nil or a table")
+			self.attributestorage[dataobj] = {}
+			for i,v in pairs(dataobj) do
+				self.attributestorage[dataobj][i] = v
+				dataobj[i] = nil
+			end
+		end
+		dataobj = setmetatable(dataobj or {}, self.domt)
+		self.proxystorage[name], self.namestorage[dataobj] = dataobj, name
+		self.callbacks:Fire("LibDataBroker_DataObjectCreated", name, dataobj)
+		return dataobj
+	end
+end
+
+if oldminor < 1 then
+	function lib:DataObjectIterator()
+		return pairs(self.proxystorage)
+	end
+
+	function lib:GetDataObjectByName(dataobjectname)
+		return self.proxystorage[dataobjectname]
+	end
+
+	function lib:GetNameByDataObject(dataobject)
+		return self.namestorage[dataobject]
+	end
+end
+
+if oldminor < 4 then
+	local next = pairs(attributestorage)
+	function lib:pairs(dataobject_or_name)
+		local t = type(dataobject_or_name)
+		assert(t == "string" or t == "table", "Usage: ldb:pairs('dataobjectname') or ldb:pairs(dataobject)")
+
+		local dataobj = self.proxystorage[dataobject_or_name] or dataobject_or_name
+		assert(attributestorage[dataobj], "Data object not found")
+
+		return next, attributestorage[dataobj], nil
+	end
+
+	local ipairs_iter = ipairs(attributestorage)
+	function lib:ipairs(dataobject_or_name)
+		local t = type(dataobject_or_name)
+		assert(t == "string" or t == "table", "Usage: ldb:ipairs('dataobjectname') or ldb:ipairs(dataobject)")
+
+		local dataobj = self.proxystorage[dataobject_or_name] or dataobject_or_name
+		assert(attributestorage[dataobj], "Data object not found")
+
+		return ipairs_iter, attributestorage[dataobj], 0
+	end
+end
diff --git a/TitanExamplePlugin/TitanPlugin_Mainline.toc b/TitanExamplePlugin/TitanPlugin_Mainline.toc
new file mode 100644
index 0000000..2b4dc88
--- /dev/null
+++ b/TitanExamplePlugin/TitanPlugin_Mainline.toc
@@ -0,0 +1,8 @@
+## Interface: 100205
+## Title: Titan Starter
+## Version: 1.0.0
+## Notes: Adds bag and free slot information to Titan Panel
+## Author: Titan Panel Development Team (http://www.titanpanel.org)
+## SavedVariables:
+## Dependencies: Titan
+TitanStarter.lua
diff --git a/TitanExamplePlugin/TitanPlugin_Vanilla.toc b/TitanExamplePlugin/TitanPlugin_Vanilla.toc
new file mode 100644
index 0000000..d781ee4
--- /dev/null
+++ b/TitanExamplePlugin/TitanPlugin_Vanilla.toc
@@ -0,0 +1,9 @@
+## Interface: 11500
+## Title: TitanStarter
+## Version: 1.0.0
+## Notes: Adds bag and free slot information to Titan Panel
+## Author: Titan Panel Development Team (http://www.titanpanel.org)
+## SavedVariables:
+## OptionalDeps:
+## Dependencies: TitanClassic
+TitanStarter.lua
diff --git a/TitanExamplePlugin/TitanPlugin_Wrath.toc b/TitanExamplePlugin/TitanPlugin_Wrath.toc
new file mode 100644
index 0000000..4f74b27
--- /dev/null
+++ b/TitanExamplePlugin/TitanPlugin_Wrath.toc
@@ -0,0 +1,9 @@
+## Interface: 30403
+## Title: Titan Starter
+## Version: 1.0.0
+## Notes: Adds bag and free slot information to Titan Panel
+## Author: Titan Panel Development Team (http://www.titanpanel.org)
+## SavedVariables:
+## OptionalDeps:
+## Dependencies: TitanClassic
+TitanStarter.lua
diff --git a/TitanExamplePlugin/TitanStarter.lua b/TitanExamplePlugin/TitanStarter.lua
new file mode 100644
index 0000000..a46b79a
--- /dev/null
+++ b/TitanExamplePlugin/TitanStarter.lua
@@ -0,0 +1,839 @@
+--[[
+-- **************************************************************************
+-- * 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.
+
+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.
+	NOTE : Titan builds the function name rather than it being listed in the registery.
+	It is expected to be global and named "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
+
+function TitanPanelBagButton_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"],
+		buttonTextFunction = "TitanPanelBagButton_GetButtonText",
+		tooltipTitle = L["TITAN_BAG_TOOLTIP"],
+		tooltipTextFunction = "TitanPanelBagButton_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
+
+function TitanPanelBagButton_GetButtonText(id)
+	local strA, strB = GetBagData(id)
+	return strA, strB
+end
+
+function TitanPanelRightClickMenu_PrepareStarterMenu()
+	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 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
diff --git a/TitanExamplePlugin/TitanStarter.tga b/TitanExamplePlugin/TitanStarter.tga
new file mode 100644
index 0000000..6a7ddda
Binary files /dev/null and b/TitanExamplePlugin/TitanStarter.tga differ