view Core.lua @ 10:c4d0e5d47e10

Ok, please don?t crash again you silly Ace3 multiselect dropdown box. I know you?re itchy, but there?s nothing I can do for you. Cleaned up group management layout, added a duplicate group button. Track at character data is erased when exporting/importing groups. Duplicate items are erased when importing groups. Bug fixes and speed increases in Summary. Added a button to refresh the cache. Added a slider to increase caching speed. Auction value will no longer be cached if the threshold was set to 0.
author Zerotorescue
date Tue, 12 Oct 2010 02:08:37 +0200
parents 3bac0bdd59e2
children 10a2244f7ff0
line wrap: on
line source
-- You can access this addon's object through: LibStub("AceAddon-3.0"):GetAddon("Inventory")
local addon = LibStub("AceAddon-3.0"):NewAddon("Inventory", "AceEvent-3.0");

local AceGUI = LibStub("AceGUI-3.0");

local Media = LibStub("LibSharedMedia-3.0");
Media:Register("sound", "Cartoon FX", [[Sound\Doodad\Goblin_Lottery_Open03.wav]]);
Media:Register("sound", "Cheer", [[Sound\Event Sounds\OgreEventCheerUnique.wav]]);
Media:Register("sound", "Explosion", [[Sound\Doodad\Hellfire_Raid_FX_Explosion05.wav]]);
Media:Register("sound", "Fel Nova", [[Sound\Spells\SeepingGaseous_Fel_Nova.wav]]);
Media:Register("sound", "Fel Portal", [[Sound\Spells\Sunwell_Fel_PortalStand.wav]]);
Media:Register("sound", "Magic Click", [[Sound\interface\MagicClick.wav]]);
Media:Register("sound", "Rubber Ducky", [[Sound\Doodad\Goblin_Lottery_Open01.wav]]);
Media:Register("sound", "Shing!", [[Sound\Doodad\PortcullisActive_Closed.wav]]);
Media:Register("sound", "Simon Chime", [[Sound\Doodad\SimonGame_LargeBlueTree.wav]]);
Media:Register("sound", "Simon Error", [[Sound\Spells\SimonGame_Visual_BadPress.wav]]);
Media:Register("sound", "Simon Start", [[Sound\Spells\SimonGame_Visual_GameStart.wav]]);
Media:Register("sound", "War Drums", [[Sound\Event Sounds\Event_wardrum_ogre.wav]]);
Media:Register("sound", "Wham!", [[Sound\Doodad\PVP_Lordaeron_Door_Open.wav]]);
Media:Register("sound", "Whisper Ping", [[Sound\interface\iTellMessage.wav]]);
Media:Register("sound", "You Will Die!", [[Sound\Creature\CThun\CThunYouWillDIe.wav]]);

local AceConfigDialog, AceConfigRegistry, AceSerializer;
local groupIdToName = {};
local options = {};

function addon:OnInitialize()
	self:Debug("OnInitialize");
	
	-- SAVED VARIABLES
	
	local defaults = {
		global = {
			groups = {},
			defaults = {
				minimumStock = 60,
				alertBelowMinimum = true,
				summaryThresholdShow = 10,
				restockTarget = 60,
				minCraftingQueue = 0.05,
				bonusQueue = 0.1,
				priceThreshold = 0,
				hideFromSummaryWhenBelowPriceThreshold = false,
				trackAtCharacters = {},
				colors = {
					red = 0;
					orange = 0.3;
					yellow = 0.6;
					green = 0.95;
				},
			},
		},
		factionrealm = {
			characters = {},
		},
	};
	
	-- Register our saved variables database
	self.db = LibStub("AceDB-3.0"):New("InventoryDB", defaults, true);
	
	-- SLASH COMMANDS
	
	-- Disable the AddonLoader slash commands
	SLASH_INVENTORY1 = nil;
	SLASH_IY1 = nil;
	
	-- Register our own slash commands
	SLASH_INVENTORY1 = "/inventory";
	SLASH_INVENTORY2 = "/iy";
	SlashCmdList["INVENTORY"] = function(msg)
		self:CommandHandler(msg);
	end
	
	-- INTERFACE OPTIONS
	
	 -- Attempt to remove the interface options added by AddonLoader (if enabled)
	if AddonLoader and AddonLoader.RemoveInterfaceOptions then
		AddonLoader:RemoveInterfaceOptions("Inventory");
	end
	
	-- Now create our own options frame
	local frame = CreateFrame("Frame", nil, UIParent);
	frame:Hide();
	frame.name = "Inventory";
	frame:HookScript("OnShow", function(self)
		-- Refresh the frame to instantly show the right options
		InterfaceOptionsFrame_OpenToCategory(self.name)
	end);
	-- And add it to the interface options
	InterfaceOptions_AddCategory(frame);
	
	self:MakeItemLinkButtonWidget();
	self:MakeConfigItemLinkButtonWidget();
	
	-- Remember this character is mine
	local playerName = UnitName("player");
	if not self.db.factionrealm.characters[playerName] then
		self.db.factionrealm.characters[playerName] = true;
		
		-- Default to tracking on all chars, untracking is a convenience, not tracking by default would probably get multiple issue reports.
		self.db.global.defaults.trackAtCharacters[playerName] = true;
	end
end

local slashArgs = {};
function addon:RegisterSlash(func, ...)
	for _, arg in pairs({ ... }) do
		slashArgs[arg] = func;
	end
end

function addon:MakeItemLinkButtonWidget()
	--[[
	[ ItemLinkButton ]
	This custom widget has to show an icon with the item link next to it.
	Upon hover it must show the item tooltip.
	Upon click it must execute the function provided through user data.
	
	UserData: itemId, onClickEvent
	
	OnEnter: tooltip show
	OnLeave: tooltip hide
	OnClick: UserData.onClickEvent
	]]
	
	local widgetType = "ItemLinkButton";
	local widgetVersion = 1;
	
	local function Constructor()
	    local widget = AceGUI:Create("InteractiveLabel");
	    widget.type = widgetType;
	    
	    -- We overwrite the OnAcquire as we want to set our callbacks even
	    -- when the widget is re-used from the widget pool
	    widget.originalOnAcquire = widget.OnAcquire;
	    widget.OnAcquire = function(self, ...)
	    
	    
	    	-- We overwrite the setcallback because we don't want anything else 
	    	-- to overwrite our OnEnter, OnLeave and OnClick events
	    	-- which would be done by the AceConfigDialog after a widget gets re-used
		    if not self.originalSetCallBack then
			    self.originalSetCallBack = self.SetCallback;
			    self.SetCallback = function(this, event, func, ...)
			    	if event == "OnEnter" or event == "OnLeave" or event == "OnClick" then
			    		-- Don't allow overwriting of these events
			    		return;
			    	elseif event == "CustomOnEnter" then
			    		return this.originalSetCallBack(this, "OnEnter", func, ...);
			    	elseif event == "CustomOnLeave" then
			    		return this.originalSetCallBack(this, "OnLeave", func, ...);
			    	elseif event == "CustomOnClick" then
			    		return this.originalSetCallBack(this, "OnClick", func, ...);
			    	else
			    		return this.originalSetCallBack(this, event, func, ...);
			    	end
			    end;
		    end
		    
		    
		    -- Set our own events, since we disabled the normal event-names, we'll call them our custom versions
		    self:SetCallback("CustomOnEnter", function(this)
		    	local itemId = this:GetUserData("itemId");
		    	
		    	if itemId then
			    	GameTooltip:SetOwner(this.frame, "ANCHOR_TOPRIGHT");
			    	GameTooltip:SetHyperlink(("item:%d"):format(itemId));
					GameTooltip:Show();
		    	end
		    end);
		    self:SetCallback("CustomOnLeave", function(this)
		    	GameTooltip:Hide();
		    end);
		    self:SetCallback("CustomOnClick", function(this)
		    	if this.OnClick then
		    		this.OnClick(this);
		    	end
		    	
		    	local func = this:GetUserData("exec");
		    	local itemId = this:GetUserData("itemId");
		    	
		    	if func then
		    		-- If this is a config option we will need the group id
		    		local path = this:GetUserData("path");
		    		local groupId = (path and path[2]) or nil;
		    		
		    		func(groupId, itemId);
		    	end
		    end);
		    
		    
		    
		    -- Then also do whatever it wanted to do
		    self.originalOnAcquire(self, ...);
	    end;
	    
	    -- Remember the original SetText as this might get overwritten by the config-widget
	    widget.originalSetText = widget.SetText;
	    
	    widget.SetItemId = function(self, itemId)
 		   	self:SetUserData("itemId", itemId);
 		   	
	   		-- Put the icon in front of it
			self:SetImage(GetItemIcon(itemId));
			-- Standardize the size
			self:SetImageSize(16, 16);
    		
			-- Make readable font
    		self:SetFontObject(GameFontHighlight);
    		
    		-- We don't want to set the itemId as text, but rather the item link, so get that.
    		local itemLink = select(2, GetItemInfo(itemId)) or ("Unknown (#%d)"):format(itemId);
    		
    		self:originalSetText(itemLink);
	    end;
		
		return widget;
	end
    
	AceGUI:RegisterWidgetType(widgetType, Constructor, widgetVersion);
end

function addon:MakeConfigItemLinkButtonWidget()
	-- Define out custom item link button widget
	-- This will be called as if it's an input element, we overwrite some of the related functions which are called for default input fields
	
	local widgetType = "ConfigItemLinkButton";
	local widgetVersion = 1;
	
	-- Empty function for disabling functions
	local function Dummy() end
	
	-- Makes an instance of our ItemLinkButton widget
	local function GetItemLinkButton()
	    local widget = AceGUI:Create("ItemLinkButton");
	    widget.type = widgetType;
	    
	    -- We can only provide custom widgets for input, select and multiselect fields
	    -- Input being the simplest, we use that - however, it provides two parameters: label and text. We only need one, disable the other.
	    widget.SetLabel = Dummy;
	    
	    -- SetText is called when this button is being created and contains the itemId
	    -- Forward that itemId to the ItemLinkButton
	    widget.SetText = function(self, value, ...)
	    	if value and tonumber(value) then
	    		self:SetItemId(tonumber(value));
	    	end
		end;
		
		widget.OnClick = function(self, ...)
			local option = self:GetUserData("option");
	    	
	    	if option and option.set then
				self:SetUserData("exec", option.set);
			end
		end;
	    
	    return widget;
	end
    
	AceGUI:RegisterWidgetType(widgetType, GetItemLinkButton, widgetVersion);
end

function addon:CommandHandler(message)
	local cmd, arg = string.split(" ", (message or ""), 2);
	cmd = string.lower(cmd);
	
	if cmd == "c" or cmd == "config" or cmd == "conf" or cmd == "option" or cmd == "options" or cmd == "opt" or cmd == "setting" or cmd == "settings" then
		-- We don't want any other windows open at this time.
		for name, module in self:IterateModules() do
			if module.CloseFrame then
				module:CloseFrame();
			end
		end
		
		self:Show();
	elseif cmd == "d" or cmd == "debug" then
		self.debugChannel = false;
		for i = 1, NUM_CHAT_WINDOWS do
			local name = GetChatWindowInfo(i);
			
			if name:upper() == "DEBUG" then
				self.debugChannel = _G["ChatFrame" .. i];
			
				print("A debug channel already exists, used the old one. (" .. i .. ")");
				return;
			end
		end
		
		if not self.debugChannel then
			-- Create a new debug channel
			local chatFrame = FCF_OpenNewWindow('Debug');
			ChatFrame_RemoveAllMessageGroups(chatFrame);
			self.debugChannel = chatFrame;
			
			print("New debug channel created.");
		end
	elseif slashArgs[cmd] then
		slashArgs[cmd]();
	else
		print("Wrong command, available: /inventory config (or /iy c) and /inventory summary (or /iy s)");
	end
end

function addon:Load()
	if not AceConfigDialog and not AceConfigRegistry then
		self:FillOptions();
	
		-- Build options dialog
		AceConfigDialog = LibStub("AceConfigDialog-3.0");
		AceConfigRegistry = LibStub("AceConfigRegistry-3.0");
		-- Register options table
		LibStub("AceConfig-3.0"):RegisterOptionsTable("InventoryOptions", options);
		-- Set a nice default size (so that 4 normal sized elements fit next to eachother)
		AceConfigDialog:SetDefaultSize("InventoryOptions", 975, 600);
		
		 -- In case the addon is loaded from another condition, always call the remove interface options
		if AddonLoader and AddonLoader.RemoveInterfaceOptions then
			AddonLoader:RemoveInterfaceOptions("Inventory");
		end
		
		-- Add to the blizzard addons options thing
		--AceConfigDialog:AddToBlizOptions("InventoryOptions");
	end
end

function addon:Show()
	self:Load();
	
	AceConfigDialog:Open("InventoryOptions");
end

function addon:FillOptions()
	options = {
		type = "group",	
		name = "Inventory",
		childGroups = "tree",
		args = {
		},
	};
	
	self:FillGeneralOptions();
	
	options.args.profiles = LibStub("AceDBOptions-3.0"):GetOptionsTable(self.db, true);
	options.args.profiles.order = 200;
	
	self:MakeGroupOptions();
	
	self:FillGroupOptions();
end

local goldText = "%s%d|cffffd700g|r ";
local silverText = "%s%d|cffc7c7cfs|r ";
local copperText = "%s%d|cffeda55fc|r";

function addon:ReadableMoney(copper, clean)
	local text = "";
	
	local gold = floor( copper / COPPER_PER_GOLD );
	if gold > 0 then
		text = goldText:format(text, gold);
	end
	
	if not clean or (not gold or gold < 100) then
		local silver = floor( ( copper % COPPER_PER_GOLD ) / COPPER_PER_SILVER );
		if silver > 0 then
			text = silverText:format(text, silver);
		end
		
		if not clean or (not gold or gold < 10) then
			local copper = floor( copper % COPPER_PER_SILVER );
			if copper > 0 or text == "" then
				text = copperText:format(text, copper);
			end
		end
	end
	
	
	return string.trim(text);
end

function addon:ReadableMoneyToCopper(value)
	-- If a player enters a value it will be filled without color codes
	-- If it is retrieved from the database, it will be colored coded
	-- Thus we look for both
	local gold = tonumber(string.match(value, "(%d+)|c[a-fA-F0-9]+g|r") or string.match(value, "(%d+)g"));
	local silver = tonumber(string.match(value, "(%d+)|c[a-fA-F0-9]+s|r") or string.match(value, "(%d+)s"));
	local copper = tonumber(string.match(value, "(%d+)|c[a-fA-F0-9]+c|r") or string.match(value, "(%d+)c"));
		
	return ( (gold or 0) * COPPER_PER_GOLD ) + ( (silver or 0) * COPPER_PER_SILVER ) + (copper or 0);
end

function addon:ValidateReadableMoney(info, value)
	-- If a player enters a value it will be filled without color codes
	-- If it is retrieved from the database, it will be colored coded
	-- Thus we look for both
	local gold = tonumber(string.match(value, "(%d+)|c[a-fA-F0-9]+g|r") or string.match(value, "(%d+)g"));
	local silver = tonumber(string.match(value, "(%d+)|c[a-fA-F0-9]+s|r") or string.match(value, "(%d+)s"));
	local copper = tonumber(string.match(value, "(%d+)|c[a-fA-F0-9]+c|r") or string.match(value, "(%d+)c"));
	
	if not gold and not silver and not copper then
		return "The provided amount of money is invalid. Please provide the amount of money as #g#s#c, e.g. 591617g24s43c.";
	else
		return true;
	end
end

function addon:FillGeneralOptions()
	options.args.general = {
		order = 100,
		type = "group",
		name = "General",
		desc = "Change general Inventory settings.",
		args = {
			general = {
				order = 0,
				type = "group",
				inline = true,
				name = "General",
				args = {
					description = {
						order = 0,
						type = "description",
						name = "Change general settings unrelated to groups.",
					},
					header = {
						order = 5,
						type = "header",
						name = "",
					},
					auctionAddon = {
						order = 10,
						type = "select",
						name = "Prefered pricing addon",
						values = {
							Auctioneer = "Auctioneer",
							Auctionator = "Auctionator",
						},
						get = function() end,
						set = function(i, v) end,
					},
					itemCountAddon = {
						order = 20,
						type = "select",
						name = "Prefered item count addon",
						values = {
							Altoholic = "Altoholic",
							DataStore = "DataStore",
							ItemCount = "ItemCount",
						},
						get = function() end,
						set = function(i, v) end,
					},
				},
			},
			minimumStock = {
				order = 10,
				type = "group",
				inline = true,
				name = "Minimum stock",
				args = {
					description = {
						order = 0,
						type = "description",
						name = "Here you can specify the default minimum amount of items you wish to keep in stock and related settings. The settings entered here will be used when you choose not to override the settings within an individual group.",
					},
					header = {
						order = 5,
						type = "header",
						name = "",
					},
					minimumStock = {
						order = 10,
						type = "range",
						min = 0,
						max = 100000,
						softMax = 1000,
						step = 1,
						name = "Minimum stock",
						desc = "You can manually enter a value between 1.000 and 100.000 in the edit box if the provided range is insufficient.",
						get = function() return self.db.global.defaults.minimumStock; end,
						set = function(i, v) self.db.global.defaults.minimumStock = v; end,
					},
					summaryThresholdShow = {
						order = 20,
						type = "range",
						min = 0,
						max = 100,
						softMax = 10,
						step = 0.05,
						isPercent = true,
						name = "Show in summary when below",
						desc = "Show items in the summary when below this percentage of the minimum stock.\n\nYou can manually enter a value between 1.000% and 10.000% in the edit box if the provided range is insufficient.",
						get = function() return self.db.global.defaults.summaryThresholdShow; end,
						set = function(i, v) self.db.global.defaults.summaryThresholdShow = v; end,
					},
					alertBelowMinimum = {
						order = 30,
						type = "toggle",
						name = "Alert when below minimum",
						desc = "Show an alert when this item gets below this threshold.",
						get = function() return self.db.global.defaults.alertBelowMinimum; end,
						set = function(i, v) self.db.global.defaults.alertBelowMinimum = v; end,
					},
					trackAtCharacters = {
						order = 40,
						type = "multiselect",
						name = "Track at",
						desc = "Select at which characters this should appear in the summary and generate alerts.",
						values = function()
							local temp = {};
							for charName in pairs(self.db.factionrealm.characters) do
								temp[charName] = charName;
							end
							
							return temp;
						end,
						get = function(i, v) return self.db.global.defaults.trackAtCharacters[v]; end,
						set = function(i, v, e)
							self.db.global.defaults.trackAtCharacters[v] = e or nil;
							
							-- We MUST close this pullout or we can get errors or even game client crashes once we click another group or close the config dialog!
							local count = AceGUI:GetNextWidgetNum("Dropdown-Pullout");
							for i = 1, count do
								if _G['AceGUI30Pullout' .. i] then
									_G['AceGUI30Pullout' .. i]:Hide();
								end
							end
						end,
						confirm = true,
						dialogControl = "Dropdown", -- this is not standard, normal multiselect control gives us a list of all chars with toggle-boxes. UGLY! We want a multiselect-box instead.
					},
				},
			},
			refill = {
				order = 20,
				type = "group",
				inline = true,
				name = "Replenishing stock",
				args = {
					description = {
						order = 0,
						type = "description",
						name = function()
							local r = "Here you can specify the default amount of items to which you wish to restock when you are collecting new items. This may be higher than the minimum stock. The settings entered here will be used when you choose not to override the settings within an individual group.\n\n";
							
							r = r .. "When restocking the target amount is |cfffed000" .. self.db.global.defaults.restockTarget .. "|r of every item. Not queueing craftable items when only missing |cfffed000" .. floor( self.db.global.defaults.minCraftingQueue * self.db.global.defaults.restockTarget ) .. "|r (|cfffed000" .. ( self.db.global.defaults.minCraftingQueue * 100 ) .. "%|r) of the restock target.";
							
							return r;
						end,
					},
					header = {
						order = 5,
						type = "header",
						name = "",
					},
					restockTarget = {
						order = 10,
						type = "range",
						min = 0,
						max = 100000,
						softMax = 1000,
						step = 1,
						name = "Restock target",
						desc = "You can manually enter a value between 1.000 and 100.000 in the edit box if the provided range is insufficient.",
						get = function() return self.db.global.defaults.restockTarget; end,
						set = function(i, v) self.db.global.defaults.restockTarget = v; end,
					},
					minCraftingQueue = {
						order = 20,
						type = "range",
						min = 0,
						max = 1,
						step = 0.01, -- 1%
						isPercent = true,
						name = "Don't queue if I only miss",
						desc = "Don't add a craftable item to the queue if I only miss this much or less of the restock target.\n\nExample: if your restock target is set to 60 and this is set to 5%, an item won't be queued unless you are missing more than 3 of it.",
						get = function() return self.db.global.defaults.minCraftingQueue; end,
						set = function(i, v) self.db.global.defaults.minCraftingQueue = v; end,
					},
					bonusQueue = {
						order = 30,
						type = "range",
						min = 0,
						max = 10, -- 1000%
						step = 0.01, -- 1%
						isPercent = true,
						name = "Bonus queue",
						desc = "Get additional items when there are none left.\n\nExample: if your restock target is set to 60 and this is set to 10%, you will get 66 items instead of just 60 if you end up with none left while queueing.",
						get = function() return self.db.global.defaults.bonusQueue; end,
						set = function(i, v) self.db.global.defaults.bonusQueue = v; end,
					},
					priceThreshold = {
						order = 40,
						type = "input",
						name = "Price threshold",
						desc = "Only queue craftable items when they are worth at least this much according to your auction house addon.\n\nSet to 0 to ignore auction prices.",
						validate = function(info, value) return self:ValidateReadableMoney(info, value); end,
						get = function() return self:ReadableMoney(self.db.global.defaults.priceThreshold); end,
						set = function(i, v) self.db.global.defaults.priceThreshold = self:ReadableMoneyToCopper(v); end,
					},
					hideFromSummaryWhenBelowPriceThreshold = { -- I wish this var could be a little bit shorter...
						order = 50,
						type = "toggle",
						name = "Hide when below threshold",
						desc = "Hide items from the summary when their value is below the set price threshold.",
						get = function() return self.db.global.defaults.hideFromSummaryWhenBelowPriceThreshold; end,
						set = function(i, v) self.db.global.defaults.hideFromSummaryWhenBelowPriceThreshold = v; end,
					},
				},
			},
			colorCodes = {
				order = 30,
				type = "group",
				inline = true,
				name = "Color codes",
				args = {
					description = {
						order = 0,
						type = "description",
						name = "Change the color code thresholds based on the current stock remaining of the required minimum stock.",
					},
					header = {
						order = 5,
						type = "header",
						name = "",
					},
					green = {
						order = 10,
						type = "range",
						min = 0,
						max = 1,
						step = 0.01,
						isPercent = true,
						name = "|cff00ff00Green|r",
						desc = "Show quantity in green when at least this much of the minimum stock is available.",
						get = function() return self.db.global.defaults.colors.green; end,
						set = function(i, v) self.db.global.defaults.colors.green = v; end,
					},
					yellow = {
						order = 20,
						type = "range",
						min = 0,
						max = 1,
						step = 0.01,
						isPercent = true,
						name = "|cffffff00Yellow|r",
						desc = "Show quantity in yellow when at least this much of the minimum stock is available.",
						get = function() return self.db.global.defaults.colors.yellow; end,
						set = function(i, v) self.db.global.defaults.colors.yellow = v; end,
					},
					orange = {
						order = 30,
						type = "range",
						min = 0,
						max = 1,
						step = 0.01,
						isPercent = true,
						name = "|cffff9933Orange|r",
						desc = "Show quantity in orange when at least this much of the minimum stock is available.",
						get = function() return self.db.global.defaults.colors.orange; end,
						set = function(i, v) self.db.global.defaults.colors.orange = v; end,
					},
					red = {
						order = 40,
						type = "range",
						min = 0,
						max = 1,
						step = 0.01,
						isPercent = true,
						name = "|cffff0000Red|r",
						desc = "Show quantity in red when at least this much of the minimum stock is available.",
						get = function() return self.db.global.defaults.colors.red; end,
						set = function(i, v) self.db.global.defaults.colors.red = v; end,
					},
				},
			},
		},
	};
end

local count = 0;
local temp = {};

local function SetOption(info, value, multiSelectEnabled)
	local groupName = groupIdToName[info[2]];
	local optionName = info[#info];
	
	-- No need to store a setting if it's disabled (false)
	if not value and info.arg and not info.arg:find("override") then
		value = nil;
		
		-- If this is an override toggler then also set the related field to nil
		addon.db.global.groups[groupName][info.arg] = nil;
	end
	
	if multiSelectEnabled ~= nil then
		-- The saved vars for a multiselect will always be an array, it may not yet exist in which case it must be created.
		if not addon.db.global.groups[groupName][optionName] then
			addon.db.global.groups[groupName][optionName] = {};
		end
		
		addon.db.global.groups[groupName][optionName][value] = multiSelectEnabled or nil;
	else
		addon.db.global.groups[groupName][optionName] = value;
	end
end

function addon:GetOptionByKey(groupName, optionName, noDefault)
	if addon.db.global.groups[groupName][optionName] ~= nil then
		return addon.db.global.groups[groupName][optionName];
	elseif addon.db.global.defaults[optionName] and not noDefault then
		return addon.db.global.defaults[optionName];
	else
		return nil;
	end
end

local function GetOption(info)
	local groupName = groupIdToName[info[2]];
	local optionName = info[#info];
	
	return addon:GetOptionByKey(groupName, optionName);
end

local function GetMultiOption(info, value)
	local groupName = groupIdToName[info[2]];
	local optionName = info[#info];
	
	if addon.db.global.groups[groupName][optionName] ~= nil then
		return addon.db.global.groups[groupName][optionName][value];
	elseif addon.db.global.defaults[optionName] then
		return addon.db.global.defaults[optionName][value];
	else
		return nil;
	end
end

local function GetDisabled(info)
	if not info.arg or not info.arg:find("override") then
		return false;
	end
	
	local groupName = groupIdToName[info[2]];
	local optionName = info[#info];
	
	return (addon:GetOptionByKey(groupName, info.arg, true) == nil);
end

local function ValidateGroupName(_, value)
	value = string.lower(string.trim(value or ""));
	
	for name, _ in pairs(addon.db.global.groups) do
		if string.lower(name) == value then
			return ("A group named \"%s\" already exists."):format(name);
		end
	end
	
	return true;
end

local function InGroup(itemId)
	-- Go through all groups to see if this item is already somewhere
	for groupName, values in pairs(addon.db.global.groups) do
		if values.items and values.items[itemId] then
			return groupName;
		end
	end
	
	return;
end

local function AddToGroup(groupName, itemId)
	if InGroup(itemId) then
		return false;
	end
	
	if not addon.db.global.groups[groupName].items then
		addon.db.global.groups[groupName].items = {};
	end
	
	-- Set this item
	addon.db.global.groups[groupName].items[itemId] = true;
						    	
	-- Now rebuild the list
	AceConfigRegistry:NotifyChange("InventoryOptions");
	
	return true;
end

local tblAddItemTemplate = {
	order = 0,
	type = "input",
	name = function(info)
		local itemName, _, itemRarity = GetItemInfo(info[#info]);
		return tostring( 7 - (itemRarity or 0) ) .. (itemName or "");
	end,
	get = function(info)
		return tostring(info[#info]); -- Ace is going to be anal about this if it's a numeric value, so we transmute it into a string here then back to a number on the other side
	end,
	set = function(groupId, itemId)
    	-- This is NOT a real "set", we pass the widget reference to this function which contains similar, but not the same, info.
    	
    	if itemId then
	    	local groupName = groupIdToName[groupId];
	    	
	    	if not AddToGroup(groupName, itemId) then
	    		print("|cffff0000Couldn't add the item with itemId (" .. itemId .. ") because it is already in a group.|r");
	    	end
    	end
	end,
	width = "double",
	dialogControl = "ConfigItemLinkButton",
};

local tblRemoveItemTemplate = {
	order = 0,
	type = "input",
	name = function(info)
		local itemName, _, itemRarity = GetItemInfo(info[#info]);
		return tostring( 7 - (itemRarity or 0) ) .. (itemName or "");
	end,
	get = function(info)
		return tostring(info[#info]); -- Ace is going to be anal about this if it's a numeric value, so we transmute it into a string here then back to a number on the other side
	end,
	set = function(groupId, itemId)
    	-- This is NOT a real "set", we pass the widget reference to this function which contains similar, but not the same, info.
    	
    	if itemId then
	    	local groupName = groupIdToName[groupId];
	    	
	    	-- Unset this item
	    	addon.db.global.groups[groupName].items[itemId] = nil;
	    	
	    	-- Now rebuild the list
	    	AceConfigRegistry:NotifyChange("InventoryOptions");
    	end
	end,
	width = "double",
	dialogControl = "ConfigItemLinkButton",
};

local function UpdateAddItemList(info)
	local groupName = groupIdToName[info[2]];
	
	if not addon.db.global.groups[groupName].items then
		addon.db.global.groups[groupName].items = {};
	end
	
	-- Merge all items from all groups together
	local items = {};
	for groupName, values in pairs(addon.db.global.groups) do
		if values.items then
			for itemId, _ in pairs(values.items) do
				items[itemId] = true;
			end
		end
	end
	
	local ref = options.args.groups.args[info[2]].args.add.args.list.args;
	
	-- Parse bags and show these
	for bagID = 4, 0, -1 do
		for slot = 1, GetContainerNumSlots(bagID) do
			local itemId = addon:GetItemId(GetContainerItemLink(bagID, slot));
			
			if itemId then
				if not items[itemId] then
					-- If this item isn't used in any group yet
					ref[itemId] = tblAddItemTemplate;
				else
					-- It's already used in a group, don't show it
					ref[itemId] = nil;
				end
			end
		end
	end
end

local function UpdateRemoveItemList(info)
	local groupName = groupIdToName[info[2]];
	
	if not addon.db.global.groups[groupName].items then
		addon.db.global.groups[groupName].items = {};
	end
	
	local ref = options.args.groups.args[info[2]].args.remove.args.list.args;
	
	-- Unset all
	for itemId, _ in pairs(ref) do
		ref[itemId] = nil;
	end
	
	-- Parse items in group and show these
	for itemId, _ in pairs(addon.db.global.groups[groupName].items) do
		ref[itemId] = tblRemoveItemTemplate;
	end
end

function addon:GetItemId(itemLink)
	itemLink = itemLink and select(3, string.find(itemLink, "|Hitem:([-0-9]+):")); -- if itemLink is nil, it won't execute the second part
	itemLink = itemLink and tonumber(itemLink);
	
	return itemLink;
end

-- Default group
local defaultGroup = {
	order = 0,
	type = "group",
	childGroups = "tab",
	name = function(info)
		return groupIdToName[info[#info]];
	end,
	args = {
		general = {
			order = 10,
			type = "group",
			name = "Stock settings",
			desc = "Change the stock settings for just this group.",
			args = {
				minimumStock = {
					order = 10,
					type = "group",
					inline = true,
					name = "Minimum stock",
					set = SetOption,
					get = GetOption,
					disabled = GetDisabled,
					args = {
						description = {
							order = 0,
							type = "description",
							name = "Here you can specify the minimum amount of items you wish to keep in stock and related settings for the currently selected group.",
						},
						header = {
							order = 5,
							type = "header",
							name = "",
						},
						overrideMinimumStock = {
							order = 9,
							type = "toggle",
							name = "Override min stock",
							desc = "Allows you to override the minimum stock setting for this group.",
							arg = "minimumStock",
						},
						minimumStock = {
							order = 10,
							type = "range",
							min = 0,
							max = 100000,
							softMax = 1000,
							step = 1,
							name = "Minimum stock",
							desc = "You can manually enter a value between 1.000 and 100.000 in the edit box if the provided range is insufficient.",
							arg = "overrideMinimumStock",
						},
						overrideSummaryThresholdShow = {
							order = 19,
							type = "toggle",
							name = "Override summary showing",
							desc = "Allows you to override when this group should appear in the summary.",
							arg = "summaryThresholdShow",
						},
						summaryThresholdShow = {
							order = 20,
							type = "range",
							min = 0,
							max = 100,
							softMax = 10,
							step = 0.05,
							isPercent = true,
							name = "Show in summary when below",
							desc = "Show items in the summary when below the specified percentage of the minimum stock.\n\nYou can manually enter a value between 1.000% and 10.000% in the edit box if the provided range is insufficient.",
							arg = "overrideSummaryThresholdShow",
						},
						overrideAlertBelowMinimum = {
							order = 29,
							type = "toggle",
							name = "Override minimum alert",
							desc = "Allows you to override wether an alert should be shown when an item in this group gets below the minimum stock threshold.",
							arg = "alertBelowMinimum",
						},
						alertBelowMinimum = {
							order = 30,
							type = "toggle",
							name = "Alert when below minimum",
							desc = "Show an alert when an item in this group gets below the minimum stock threshold.",
							arg = "overrideAlertBelowMinimum",
						},
						overrideTrackAtCharacters = {
							order = 39,
							type = "toggle",
							name = "Override track at",
							desc = "Allows you to override at which characters items in this group should appear in the summary and generate alerts.",
							arg = "trackAtCharacters",
						},
						trackAtCharacters = {
							order = 40,
							type = "multiselect",
							name = "Track at",
							desc = "Select at which characters this should appear in the summary and generate alerts.",
							values = function()
								local temp = {};
								for charName in pairs(addon.db.factionrealm.characters) do
									temp[charName] = charName;
								end
								
								-- We MUST close this pullout or we can get errors or even game client crashes once we click another group or close the config dialog!
								local count = AceGUI:GetNextWidgetNum("Dropdown-Pullout");
								for i = 1, count do
									if _G['AceGUI30Pullout' .. i] then
										_G['AceGUI30Pullout' .. i]:Hide();
									end
								end
								
								return temp;
							end,
							get = GetMultiOption,
							confirm = true,
							dialogControl = "Dropdown", -- this is not standard, normal multiselect control gives us a list of all chars with toggle-boxes. UGLY! We want a multiselect-box instead.
							arg = "overrideTrackAtCharacters",
						},
					},
				},
				refill = {
					order = 20,
					type = "group",
					inline = true,
					name = "Replenishing stock",
					set = SetOption,
					get = GetOption,
					disabled = GetDisabled,
					args = {
						description = {
							order = 0,
							type = "description",
							name = function(info)
								local groupName = groupIdToName[info[2]];
								local r = "Here you can specify the amount of items to which you wish to restock when you are collecting new items for the currently selected group. This may be higher than the minimum stock.\n\n";
								
								r = r .. "When restocking the target amount is |cfffed000" .. addon:GetOptionByKey(groupName, "restockTarget") .. "|r of every item. Not queueing craftable items when only missing |cfffed000" .. floor( addon:GetOptionByKey(groupName, "minCraftingQueue") * addon:GetOptionByKey(groupName, "restockTarget") ) .. "|r (|cfffed000" .. ( addon:GetOptionByKey(groupName, "minCraftingQueue") * 100 ) .. "%|r) of the restock target.";
								
								return r;
							end,
						},
						header = {
							order = 5,
							type = "header",
							name = "",
						},
						overrideRestockTarget = {
							order = 9,
							type = "toggle",
							name = "Override restock target",
							desc = "Allows you to override the restock target setting for this group.",
							arg = "restockTarget",
						},
						restockTarget = {
							order = 10,
							type = "range",
							min = 0,
							max = 100000,
							softMax = 1000,
							step = 1,
							name = "Restock target",
							desc = "You can manually enter a value between 1.000 and 100.000 in the edit box if the provided range is insufficient.",
							arg = "overrideRestockTarget",
						},
						overrideMinCraftingQueue = {
							order = 19,
							type = "toggle",
							name = "Override min queue",
							desc = "Allows you to override the minimum craftable items queue setting for this group.",
							arg = "minCraftingQueue",
						},
						minCraftingQueue = {
							order = 20,
							type = "range",
							min = 0,
							max = 1,
							step = 0.01,
							isPercent = true,
							name = "Don't queue if I only miss",
							desc = "Don't add a craftable item to the queue if I only miss this much or less of the restock target.\n\nExample: if your restock target is set to 60 and this is set to 5%, an item won't be queued unless you are missing more than 3 of it.",
							arg = "overrideMinCraftingQueue",
						},
						overrideBonusQueue = {
							order = 29,
							type = "toggle",
							name = "Override bonus queue",
							desc = "Allows you to override the bonus craftable items queue setting for this group.",
							arg = "bonusQueue",
						},
						bonusQueue = {
							order = 30,
							type = "range",
							min = 0,
							max = 10, -- 1000%
							step = 0.01, -- 1%
							isPercent = true,
							name = "Bonus queue",
							desc = "Get additional items when there are none left.\n\nExample: if your restock target is set to 60 and this is set to 10%, you will get 66 items instead of just 60 if you end up with none left while queueing.",
							arg = "overrideBonusQueue",
						},
						overridePriceThreshold = {
							order = 39,
							type = "toggle",
							name = "Override price threshold",
							desc = "Allows you to override the price threshold setting for this group.",
							arg = "priceThreshold",
						},
						priceThreshold = {
							order = 40,
							type = "input",
							name = "Price threshold",
							desc = "Only queue craftable items when they are worth at least this much according to your auction house addon.\n\nSet to 0 to ignore auction prices.",
							validate = function(info, value) return addon:ValidateReadableMoney(info, value); end,
							get = function(i) return addon:ReadableMoney(GetOption(i)); end,
							set = function(i, v) SetOption(i, addon:ReadableMoneyToCopper(v)); end,
							arg = "overridePriceThreshold",
						},
						overrideHideFromSummaryWhenBelowPriceThreshold = {
							order = 49,
							type = "toggle",
							name = "Override summary showing",
							desc = "Allows you to override if items in this group should be hidden from the summary while their value is below the price threshold.",
							arg = "hideFromSummaryWhenBelowPriceThreshold",
						},
						hideFromSummaryWhenBelowPriceThreshold = { -- I wish this var could be a little bit shorter...
							order = 50,
							type = "toggle",
							name = "Hide when below threshold",
							desc = "Hide items from the summary when their value is below the set price threshold.",
							arg = "overrideHideFromSummaryWhenBelowPriceThreshold",
						},
					},
				},
			},
		},
		group = {
			order = 20,
			type = "group",
			name = "Group Management",
			desc = "Rename, delete or export this group.",
			args = {
				actions = {
					order = 10,
					type = "group",
					name = "Actions",
					inline = true,
					args = {
						rename = {
							order = 10,
							type = "input",
							name = "Rename group - New name",
							desc = "Change the name of this group to something else. You can also use item links here as you wish.",
							validate = ValidateGroupName,
							set = function(info, value)
								local oldGroupName = groupIdToName[info[2]];
								
								addon.db.global.groups[value] = CopyTable(addon.db.global.groups[oldGroupName]);
								addon.db.global.groups[oldGroupName] = nil;
								
								groupIdToName[info[2]] = value;
								groupIdToName[value] = true;
								groupIdToName[oldGroupName] = nil;
								
								addon:FillGroupOptions();
							end,
							get = function(info)
								return groupIdToName[info[2]];
							end,
						},
						duplicate = {
							order = 20,
							type = "input",
							name = "Duplicate group - New name",
							desc = "Duplicate this group. You can also use item links here as you wish.\n\nAll item data will be erased.",
							validate = ValidateGroupName,
							set = function(info, value)
								local oldGroupName = groupIdToName[info[2]];
								
								addon.db.global.groups[value] = CopyTable(addon.db.global.groups[oldGroupName]);
								
								-- Reset item data (duplicate items me no want)
								addon.db.global.groups[value].items = nil;
								
								addon:FillGroupOptions();
							end,
							get = false,
						},
						delete = {
							order = 30,
							type = "execute",
							name = "Delete group",
							desc = "Delete the currently selected group.",
							confirm = true,
							confirmText = "Are you sure you wish to |cffff0000DELETE|r this group? This action is not reversable!",
							func = function(info)
								local groupName = groupIdToName[info[2]];
								
								addon.db.global.groups[groupName] = nil;
								
								addon:FillGroupOptions();
							end,
						},
					},
				},
				export = {
					order = 40,
					type = "group",
					name = "Export",
					inline = true,
					args = {
						input = {
							order = 10,
							type = "input",
							multiline = true,
							name = "Group data",
							width = "full",
							desc = "Export the group data for the currently selected group. Press CTRL-A to select all and CTRL-C to copy the text.",
							set = false,
							get = function(info)
								local groupName = groupIdToName[info[2]];
								
								-- We want to include the group name, so we copy the table then set another value
								local temp = CopyTable(addon.db.global.groups[groupName]);
								temp.name = groupName;
								temp.trackAtCharacters = nil;
								temp.overrideTrackAtCharacters = nil;
								
								if not AceSerializer then
									AceSerializer = LibStub("AceSerializer-3.0");
								end
								
								return AceSerializer:Serialize(temp);
							end,
						},
					},
				},
			},
		},
		add = {
			order = 30,
			type = "group",
			name = "Add items",
			args = {
				singleAdd = {
					order = 10,
					type = "group",
					inline = true,
					name = "Add items",
					args = {
						help = {
							order = 10,
							type = "description",
							name = "You can add a single item to this group at a time by pasting the item-id or an item-link in the field to the left or you can also import multiple items at once by pasting exported item data in the field to the right. Scroll further down to add items based on your inventory contents.",
						},
						itemLink = {
							order = 20,
							type = "input",
							name = "Single item add (item-link or item-id)",
							desc = "Shift-click an item-link or enter an item-id to add the related item to this group. You can only add one item link or item id at a time.",
							validate = function(info, value)
								-- If the value is empty we'll allow passing to clear the carret
								if value == "" then return true; end
								
								local groupName = groupIdToName[info[2]];
								
								local itemId = addon:GetItemId(string.trim(value or "")) or tonumber(string.trim(value or ""));
								
								if not itemId then
									return "This is not a valid item link.";
								elseif InGroup(itemId) then
									return ("This item is already in the group \"%s\"."):format(InGroup(itemId));
								end
								
								return true;
							end,
							set = function(info, value)
								if value and value ~= "" then
									local groupName = groupIdToName[info[2]];
									
									local itemId = addon:GetItemId(string.trim(value or "")) or tonumber(string.trim(value or ""));
									
									AddToGroup(groupName, itemId);
							    	
									print(("Added %s"):format(select(2, GetItemInfo(itemId)) or ("Unknown (#%d)"):format(itemId)));
								end
							end,
							get = false,
						},
						import = {
							order = 40,
							type = "input",
							name = "Import item data",
							desc = "Import item data from an exported item data-string. Any items already grouped will be skipped.",
							set = function(info, value)
								local groupName = groupIdToName[info[2]];
								
								local allItemIds = { string.split(";", value or "") };
								
								for _, value in pairs(allItemIds) do
									local itemId = tonumber(value);
									
									if not itemId then
										print(("\"%s\" is not a number."):format(value));
									elseif InGroup(itemId) then
										print(("Skipping %s (#%d) as it is already in the group |cfffed000%s|r."):format(select(2, GetItemInfo(itemId)) or "Unknown", itemId, InGroup(itemId)));
									else
										AddToGroup(groupName, itemId);
									end
								end
							end,
							get = false,
						},
					},
				},
				massAdd = {
					order = 20,
					type = "group",
					inline = true,
					name = "Mass add",
					args = {
						help = {
							order = 10,
							type = "description",
							name = "Click the items you wish to add to this group or add multiple of these items at once by providing a name filter in the field below.",
						},
						massAdd = {
							order = 20,
							type = "input",
							name = "Add all items matching...",
							desc = "Add every item in your inventory matching the name entered in this field. If you enter \"Glyph\" as a filter, any items in your inventory containing this in their name will be added to this group.",
							--set = massAddItems,
							get = false,
						},
					},
				},
				list = {
					order = 30,
					type = "group",
					inline = true,
					name = "Item list",
					hidden = UpdateAddItemList,
					args = {
					
					},
				},
			},
		},
		remove = {
			order = 40,
			type = "group",
			name = "Current items",
			args = {
				help = {
					order = 10,
					type = "group",
					inline = true,
					name = "Help",
					hidden = false,
					args = {
						help = {
							order = 10,
							type = "description",
							name = "Click the items you wish to remove from this group.",
						},
					},
				},
				list = {
					order = 20,
					type = "group",
					inline = true,
					name = "Item list",
					hidden = UpdateRemoveItemList,
					args = {
					
					},
				},
				export = {
					order = 30,
					type = "group",
					name = "Export",
					inline = true,
					args = {
						input = {
							order = 10,
							type = "input",
							name = "Item data",
							width = "full",
							desc = "Export the item data for the currently selected group. Press CTRL-A to select all and CTRL-C to copy the text.",
							set = false,
							get = function(info)
								local groupName = groupIdToName[info[2]];
								
								local combinedItemIds;
								-- Parse items in group and show these
								for itemId, _ in pairs(addon.db.global.groups[groupName].items) do
									if not combinedItemIds then
										combinedItemIds = tostring(itemId);
									else
										combinedItemIds = combinedItemIds .. (";%d"):format(itemId);
									end
								end
								
								return combinedItemIds; -- We don't serialize this because we actually DO want people to be able to manually modify it - besides, parsing it isn't going to be hard
							end,
						},
					},
				},
			},
		},
	},
};

function addon:MakeGroupOptions()
	options.args.groups = {
		order = 1100,
		type = "group",
		name = "Groups",
		desc = "Change a group.",
		args = {
			create = {
				order = 10,
				type = "group",
				inline = true,
				name = "Create a brand new group",
				args = {
					name = {
						order = 10,
						type = "input",
						name = "Group name",
						desc = "The name of the group. You can also use item links as you wish.",
						validate = ValidateGroupName,
						set = function(_, value)
							self.db.global.groups[value] = {};
								
							addon:FillGroupOptions();
						end,
						get = false,
						width = "double",
					},
				},
			},
			import = {
				order = 20,
				type = "group",
				inline = true,
				name = "Import a group",
				args = {
					input = {
						order = 10,
						type = "input",
						multiline = true,
						name = "Group data",
						desc = "Paste the group data as provided by a group export. If you are trying to import multiple groups at the same time, make sure to use newlines to seperate them.",
						set = function(info, value)
							local data = { string.split("\n", value or "") };
							
							for _, current in pairs(data) do
								if not AceSerializer then
									AceSerializer = LibStub("AceSerializer-3.0");
								end
								
								local result, temp = AceSerializer:Deserialize(current);
								
								if not temp.name then
									print("|cffff0000The provided data is not supported.|r");
								elseif ValidateGroupName(nil, temp.name) ~= true then
									print(("|cffff0000Aborting: A group named \"%s\" already exists.|r"):format(temp.name));
								else
									local name = temp.name;
									temp.name = nil;
									print(("Importing %s..."):format(name));
									
									-- Remove items that are already in another group
									for value, _ in pairs(temp.items) do
										local itemId = tonumber(itemid);
										
										if not itemId then
											print(("\"%s\" is not a number."):format(value));
											temp.items[value] = nil;
										elseif InGroup(itemId) then
											print(("Skipping %s (#%d) as it is already in the group |cfffed000%s|r."):format(select(2, GetItemInfo(itemId)) or "Unknown", itemId, InGroup(itemId)));
											temp.items[value] = nil;
										else
											-- Ensure the keys are numeric
											temp.items[value] = nil;
											temp.items[itemId] = true;
										end
									end
									
									-- Ensure this data isn't received (this would be buggy as exports from other accounts won't know what to do with this)
									temp.trackAtCharacters = nil;
									temp.overrideTrackAtCharacters = nil;
									
									self.db.global.groups[name] = temp;
								end
							end
							
							self:FillGroupOptions();
						end,
						get = false,
						width = "full",
					},
				},
			},
		},
	};
end

function addon:FillGroupOptions()
	for id, name in pairs(groupIdToName) do
		if type(name) == "string" and not self.db.global.groups[name] then
			options.args.groups.args[id] = nil;
			groupIdToName[id] = nil;
			groupIdToName[name] = nil;
		end
	end
	
	for name, values in pairs(self.db.global.groups) do
		if not groupIdToName[name] then
			options.args.groups.args[tostring(count)] = CopyTable(defaultGroup);
			
			groupIdToName[tostring(count)] = name;
			groupIdToName[name] = true;
			
			count = ( count + 1 );
		end
	end
end

function addon:GetItemCount(itemId)
	return Altoholic:GetItemCount(itemId);
end

function addon:GetAuctionValue(link)
	if GetAuctionBuyout then
		-- Auctionator support
		
		local lowBuy = GetAuctionBuyout(link);
		
		if lowBuy == nil then
			-- No auctions up at this time
			return -1;
		end
		
		return lowBuy;
	elseif AucAdvanced ~= nil and AucAdvanced.Modules.Util.SimpleAuction ~= nil and AucAdvanced.Modules.Util.SimpleAuction.Private.GetItems ~= nil then
		-- Auctioneer support
		
		local imgSeen, _, _, _, _, lowBuy, _, _ = AucAdvanced.Modules.Util.SimpleAuction.Private.GetItems(link);
		--local imgseen, image, matchBid, matchBuy, lowBid, lowBuy, aveBuy, aSeen 
		
		if imgSeen <= 0 then
			-- No auctions up at this time
			return -1;
		end
	
		return lowBuy;
	end
	
	return -2;
end




function addon:Debug(t)
	if not self.debugChannel and self.debugChannel ~= false then
		-- We want to check just once, so if you add a debug channel later just do a /reload (registering an event for this is wasted resources)
		self.debugChannel = false;
		
		for i = 1, NUM_CHAT_WINDOWS do
			local name = GetChatWindowInfo(i);
			
			if name:upper() == "DEBUG" then
				self.debugChannel = _G["ChatFrame" .. i];
			end
		end
	end
	
	if self.debugChannel then
		self.debugChannel:AddMessage(t);
	end
end