Quantcast

Clique:

James Whitehead II [12-05-06 - 03:31]
Clique:
* Too many updates to list
* Added a profile window where you can change/set profiles
* Added an options window where you can disable click-casting for named frames
Filename
Clique.lua
Clique.toc
CliqueOptions.lua
Dongle.lua
images/RadioChecked.tga
images/RadioEmpty.tga
images/myborder.tga
diff --git a/Clique.lua b/Clique.lua
index 5405744..0ea2b42 100644
--- a/Clique.lua
+++ b/Clique.lua
@@ -18,12 +18,6 @@ eventFrame:SetScript("OnUpdate", function()
 	end
 end)
 eventFrame:Hide()
-
-if not InCombatLockdown then
-    function InCombatLockdown()
-	return UnitAffectingCombat("player")
-    end
-end

 function Clique:Enable()
 	-- Grab the localisation header
@@ -31,28 +25,40 @@ function Clique:Enable()

 	self.defaults = {
 		profile = {
-			[L.CLICKSET_DEFAULT] = {},
-			[L.CLICKSET_HARMFUL] = {},
-			[L.CLICKSET_HELPFUL] = {},
-			[L.CLICKSET_OOC] = {},
+			clicksets = {
+				[L.CLICKSET_DEFAULT] = {},
+				[L.CLICKSET_HARMFUL] = {},
+				[L.CLICKSET_HELPFUL] = {},
+				[L.CLICKSET_OOC] = {},
+			},
+			blacklist = {
+			},
 		}
 	}

 	self.db = self:InitializeDB("CliqueDB", self.defaults)
 	self.profile = self.db.profile
+	self.clicksets = self.profile.clicksets

-    self.editSet = self.profile[L.CLICKSET_DEFAULT]
+    self.editSet = self.clicksets[L.CLICKSET_DEFAULT]
+
+	ClickCastFrames = ClickCastFrames or {}
+	self.ccframes = ClickCastFrames

     local newindex = function(t,k,v)
-		Clique:RegisterFrame(k)
-		rawset(t,k,v)
+		if v == nil then
+			Clique:UnregisterFrame(k)
+			rawset(self.ccframes, k, nil)
+		else
+			Clique:RegisterFrame(k)
+			rawset(self.ccframes, k, v)
+		end
     end

-	ClickCastFrames = ClickCastFrames or {}
-    setmetatable(ClickCastFrames, {__newindex=newindex})
+	ClickCastFrames = setmetatable({}, {__newindex=newindex})

     -- Register all frames that snuck in before we did =)
-    for frame in pairs(ClickCastFrames) do
+    for frame in pairs(self.ccframes) do
 		self:RegisterFrame(frame)
     end

@@ -86,10 +92,14 @@ function Clique:Enable()
 	self:RegisterEvent("LEARNED_SPELL_IN_TAB")
 	self:LEARNED_SPELL_IN_TAB()

+	-- Register for dongle events
+	self:RegisterEvent("DONGLE_PROFILE_CHANGED")
+	self:RegisterEvent("DONGLE_PROFILE_DELETED")
+
 	-- Run the OOC script if we need to
 	Clique:CombatUnlock()

-    -- Securehook the RaidFrame_LoadUI
+    -- Securehook CreateFrame to catch any new raid frames
     local raidFunc = function(type, name, parent, template)
 		if template == "RaidPulloutButtonTemplate" then
 			ClickCastFrames[getglobal(name.."ClearButton")] = true
@@ -123,8 +133,8 @@ function Clique:LEARNED_SPELL_IN_TAB()
 	for i=1,num do
 		local name = GetSpellName(i, BOOKTYPE_SPELL)
 		if forms[name] then
-			profile[name] = profile[name] or {}
-			self.profile[name] = self.profile[name] or {}
+			--profile[name] = profile[name] or {}
+			--self.profile[name] = self.profile[name] or {}
 		end
 	end
 end
@@ -166,9 +176,9 @@ function Clique:SpellBookButtonPressed()
     local type = "spell"
 	local button

-	if self.editSet == self.profile[L.CLICKSET_HARMFUL] then
+	if self.editSet == self.clicksets[L.CLICKSET_HARMFUL] then
 		button = string.format("%s%d", "harmbutton", self:GetButtonNumber())
-	elseif self.editSet == self.profile[L.CLICKSET_HELPFUL] then
+	elseif self.editSet == self.clicksets[L.CLICKSET_HELPFUL] then
 		button = string.format("%s%d", "helpbutton", self:GetButtonNumber())
 	else
 		button = self:GetButtonNumber()
@@ -200,21 +210,21 @@ end
 function Clique:CombatLockdown()
 	self:Debug(1, "Going into combat mode")
 	-- Remove all OOC clicks
-	for k,v in pairs(self.profile[L.CLICKSET_OOC]) do
+	for k,v in pairs(self.clicksets[L.CLICKSET_OOC]) do
 		self:DeleteAction(v)
 		self:Debug(1, "Removing %s, %s", v.type, tostring(v.arg1))
 	end

 	-- Just bluntly force our clicks back onto the frames
-    for frame in pairs(ClickCastFrames) do
+    for frame in pairs(self.ccframes) do
 		self:RegisterFrame(frame)
     end
 end

 function Clique:CombatUnlock()
 	self:Debug(1, "Setting any out of combat clicks")
-    for frame in pairs(ClickCastFrames) do
-		for k,v in pairs(self.profile[L.CLICKSET_OOC]) do
+    for frame in pairs(self.cc.frames) do
+		for k,v in pairs(self.clicksets[L.CLICKSET_OOC]) do
 			self:SetAttribute(v,frame)
 		end
 	end
@@ -253,11 +263,24 @@ function Clique:ClearQueue()
 end

 function Clique:RegisterFrame(frame)
-	if self:CombatDelay(frame) then return end
+	local name = frame:GetName()
+	if self.profile.blacklist[name] then
+		rawset(self.ccframes, frame, false)
+		return
+	end
+
+--	if self:CombatDelay(frame) then return end
+	if not ClickCastFrames[frame] then
+		rawset(self.ccframes, frame, true)
+		if CliqueTextListFrame then
+			Clique:TextListScrollUpdate()
+		end
+	end
+
 	-- Ensure we have all the buttons registered
 	frame:RegisterForClicks("LeftButtonUp", "MiddleButtonUp", "RightButtonUp", "Button4Up", "Button5Up")

-	for name,set in pairs(self.profile) do
+	for name,set in pairs(self.clicksets) do
 		if name ~= L.CLICKSET_OOC then
 			for modifier,entry in pairs(set) do
 				self:SetAttribute(entry, frame)
@@ -266,26 +289,62 @@ function Clique:RegisterFrame(frame)
 	end
 end

-function Clique:ProfileChanged(new)
-	for name,set in pairs(self.profile) do
-	    for modifier,entry in pairs(set) do
-			self:DeleteAction(entry)
-	    end
+function Clique:UnregisterFrame(frame)
+	for name,set in pairs(self.clicksets) do
+		for modifier,entry in pairs(set) do
+			local type,button,value
+
+			if not tonumber(entry.button) then
+				type,button = select(3, string.find(entry.button, "(%a+)button(%d+)"))
+				frame:SetAttribute(entry.modifier..entry.button, nil)
+				button = string.format("-%s%s", type, button)
+			end
+
+			button = button or entry.button
+
+			entry.delete = true
+
+			frame:SetAttribute(entry.modifier.."type"..button, nil)
+			frame:SetAttribute(entry.modifier..entry.type..button, nil)
+		end
 	end
+end

-	self.profile = self.db.profile
-    self.editSet = self.profile[L.CLICKSET_DEFAULT]
-	self.profileKey = new
+function Clique:DONGLE_PROFILE_CHANGED(event, addon, name)
+	if addon == "Clique" then
+		self:Print("Profile has changed to '%s'.", name)
+		for name,set in pairs(self.clicksets) do
+			for modifier,entry in pairs(set) do
+				self:DeleteAction(entry)
+			end
+		end
+
+		self.profile = self.db.profile
+		self.clicksets = self.profile.clicksets
+		self.editSet = self.clicksets[L.CLICKSET_DEFAULT]
+		self.profileKey = new

-	-- refresh the dropdown if its active
-	CliqueDropDownProfile:Hide()
-	CliqueDropDownProfile:Show()
+		-- Refresh the profile editor if it exists
+		self.textlistSelected = nil
+		self:TextListScrollUpdate()
+		self:ListScrollUpdate()

-    for frame in pairs(ClickCastFrames) do
-		self:RegisterFrame(frame)
-    end
+		for frame in pairs(self.ccframes) do
+			self:RegisterFrame(frame)
+		end
+	end
 end

+function Clique:DONGLE_PROFILE_DELETED(event, addon, name)
+	if addon == "Clique" then
+		self:Print("Profile '%s' has been deleted.", name)
+
+		self.textlistSelected = nil
+		self:TextListScrollUpdate()
+		self:ListScrollUpdate()
+	end
+end
+
 function Clique:SetAttribute(entry, frame)
 	-- Set up any special attributes
 	local type,button,value
@@ -328,6 +387,7 @@ function Clique:SetAttribute(entry, frame)
 	elseif entry.type == "macro" then
 		frame:SetAttribute(entry.modifier.."type"..button, entry.type)
 		frame:SetAttribute(entry.modifier.."macro"..button, entry.arg1)
+		frame:SetAttribute(entry.modifier.."macrotext"..button, entry.arg2)
 	elseif entry.type == "stop" then
 		frame:SetAttribute(entry.modifier.."type"..button, entry.type)
 	elseif entry.type == "target" then
@@ -347,8 +407,10 @@ end

 function Clique:SetAction(entry)
 	if self:CombatDelay(entry) then return end
-	for frame in pairs(ClickCastFrames) do
-		self:SetAttribute(entry, frame)
+	for frame,enabled in pairs(self.ccframes) do
+		if enabled then
+			self:SetAttribute(entry, frame)
+		end
 	end
 end

@@ -357,7 +419,7 @@ function Clique:DeleteAction(entry)

 	if not tonumber(entry.button) then
 		type,button = select(3, string.find(entry.button, "(%a+)button(%d+)"))
-		for frame in pairs(ClickCastFrames) do
+		for frame in pairs(self.ccframes) do
 			frame:SetAttribute(entry.modifier..entry.button, nil)
 		end
 		button = string.format("-%s%s", type, button)
@@ -368,7 +430,7 @@ function Clique:DeleteAction(entry)
 	entry.delete = true

 	if self:CombatDelay(entry) then return end
-	for frame in pairs(ClickCastFrames) do
+	for frame in pairs(self.ccframes) do
 		frame:SetAttribute(entry.modifier.."type"..button, nil)
 		frame:SetAttribute(entry.modifier..entry.type..button, nil)
 	end
diff --git a/Clique.toc b/Clique.toc
index 437e21e..c8a4ebe 100644
--- a/Clique.toc
+++ b/Clique.toc
@@ -5,6 +5,7 @@
 ## SavedVariables: CliqueDB
 ## OptionalDeps: Dongle

+Dongle.lua
 Clique.lua
 Clique.xml
 Localization.en.lua
diff --git a/CliqueOptions.lua b/CliqueOptions.lua
index 8e1bac9..07b0d4c 100644
--- a/CliqueOptions.lua
+++ b/CliqueOptions.lua
@@ -12,6 +12,37 @@ function Clique:OptionsOnLoad()
         this.updateTooltip = nil
         GameTooltip:Hide()
     end
+
+	self.special = CreateFrame("Frame", UIParent)
+	self.special:SetFrameStrata("DIALOG")
+	self.special:SetHeight(32)
+	self.special:SetWidth(32)
+	self.special.texture = self.special:CreateTexture("ARTWORK")
+	self.special.texture:SetTexture("Interface\\AddOns\\Clique\\Images\\myborder")
+	self.special.texture:SetAllPoints()
+	self.special:SetAlpha(1.0)
+
+	CreateFrame("Button", "CliqueSpecialButton")
+
+	CliqueSpecialButton:SetScript("OnClick", function()
+		if not self.sequence then self.sequence = 1 end
+		ClearOverrideBindings(CliqueSpecialButton)
+		if self.sequence == 1 then
+			SetOverrideBindingClick(CliqueSpecialButton, true, "O", "CliqueSpecialButton")
+		elseif self.sequence == 2 then
+			SetOverrideBindingClick(CliqueSpecialButton, true, "L", "CliqueSpecialButton")
+		elseif self.sequence == 3 then
+			SetOverrideBindingClick(CliqueSpecialButton, true, "E", "CliqueSpecialButton")
+		elseif self.sequence == 4 then
+			self:Print("Special mode activated")
+			for k,v in pairs(self.ccframes) do
+				k:SetScript("PostClick", function()
+					self.special:SetPoint("CENTER", k, "CENTER", 0, 0)
+				end)
+			end
+		end
+		self.sequence = self.sequence + 1
+	end)

     for i=1,12 do
         local parent = getglobal("SpellButton"..i)
@@ -33,7 +64,7 @@ function Clique:OptionsOnLoad()
     CliquePulloutTab:SetScript("OnEnter", function() local i = 1 end)
     CliquePulloutTab:SetScript("OnShow", function()
 		Clique.inuse = nil
-        for k,v in pairs(self.profile) do
+        for k,v in pairs(self.clicksets) do
             if next(v) then
                 Clique.inuse = true
             end
@@ -131,6 +162,10 @@ function Clique:Toggle()
         Clique:CreateOptionsFrame()
 		CliqueFrame:Hide()
 		CliqueFrame:Show()
+		SetOverrideBindingClick(CliqueSpecialButton, true, "M", "CliqueSpecialButton")
+		CliqueFrame:SetScript("OnHide", function()
+			ClearOverrideBindings(CliqueSpecialButton)
+		end)
 	else
         if CliqueFrame:IsVisible() then
             CliqueFrame:Hide()
@@ -138,6 +173,7 @@ function Clique:Toggle()
         else
             CliqueFrame:Show()
 			CliquePulloutTab:SetChecked(true)
+			SetOverrideBindingClick(CliqueSpecialButton, true, "M", "CliqueSpecialButton")
         end
     end

@@ -164,6 +200,7 @@ function Clique:SkinFrame(frame)
 	});

 	frame:EnableMouse()
+	frame:SetClampedToScreen(true)

 	frame.titleBar = CreateFrame("Button", nil, frame)
 	frame.titleBar:SetHeight(32)
@@ -229,6 +266,15 @@ function Clique:CreateOptionsFrame()
 			CliqueHelpText:Show()
 		end
 	end)
+
+	CliqueFrame:SetScript("OnShow", function()
+		if InCombatLockdown() then
+			CliqueFrame:Hide()
+		end
+		Clique:ToggleSpellBookButtons()
+	end)
+
+	CliqueFrame:SetScript("OnHide", function() Clique:ToggleSpellBookButtons() end)

     local frame = CreateFrame("Frame", "CliqueListFrame", CliqueFrame)
     frame:SetAllPoints()
@@ -271,10 +317,10 @@ function Clique:CreateOptionsFrame()
         entry.icon:SetWidth(24)
         entry.icon:SetPoint("LEFT", 5, 0)

-        entry.name = entry:CreateFontString(nil, "ARTWORK", "GameFontHighlightSmall")
+        entry.name = entry:CreateFontString(nil, "ARTWORK", "GameFontHighlight")
         entry.name:SetPoint("LEFT", entry.icon, "RIGHT", 5, 0)

-        entry.binding = entry:CreateFontString(nil, "ARTWORK", "GameFontHighlightSmall")
+        entry.binding = entry:CreateFontString(nil, "ARTWORK", "GameFontHighlight")
         entry.binding:SetPoint("RIGHT", entry, "RIGHT", -5, 0)
         frames[i] = entry
     end
@@ -306,6 +352,118 @@ function Clique:CreateOptionsFrame()
     CliqueListScroll:SetScript("OnVerticalScroll", function() FauxScrollFrame_OnVerticalScroll(ENTRY_SIZE, update) end)
     CliqueListScroll:SetScript("OnShow", update)

+    local frame = CreateFrame("Frame", "CliqueTextListFrame", CliqueFrame)
+    frame:SetHeight(300)
+    frame:SetWidth(250)
+    frame:SetPoint("BOTTOMLEFT", CliqueFrame, "BOTTOMRIGHT", 0, 0)
+	self:SkinFrame(frame)
+	frame:SetFrameStrata("HIGH")
+
+    local onclick = function()
+	    local offset = FauxScrollFrame_GetOffset(CliqueTextListScroll)
+		if self.textlistSelected == offset + this.id then
+			self.textlistSelected = nil
+		else
+			self.textlistSelected = offset + this.id
+		end
+		if self.textlist == "FRAMES" then
+			local name = this.name:GetText()
+			local frame = getglobal(name)
+			if this:GetChecked() then
+				self.profile.blacklist[name] = nil
+				self:RegisterFrame(getglobal(name))
+			else
+				self.profile.blacklist[name] = true
+				self:UnregisterFrame(frame)
+			end
+		end
+        Clique:TextListScrollUpdate()
+    end
+
+    local onenter = function() this:SetBackdropBorderColor(1, 1, 1) end
+    local onleave = function()
+        local selected = FauxScrollFrame_GetOffset(CliqueTextListScroll) + this.id
+		this:SetBackdropBorderColor(0.3, 0.3, 0.3)
+    end
+
+	local frames = {}
+
+	for i=1,12 do
+		local entry = CreateFrame("CheckButton", "CliqueTextList"..i, frame)
+		entry.id = i
+		entry:SetHeight(22)
+		entry:SetWidth(240)
+        entry:SetBackdrop({
+--          bgFile="Interface\\Tooltips\\UI-Tooltip-Background",
+--          edgeFile = "Interface/Tooltips/UI-Tooltip-Border",
+--          tile = true, tileSize = 8, edgeSize = 16,
+          insets = {left = 2, right = 2, top = 2, bottom = 2}})
+
+        entry:SetBackdropBorderColor(0.3, 0.3, 0.3)
+        entry:SetBackdropColor(0.1, 0.1, 0.1, 0.3)
+        entry:SetScript("OnClick", onclick)
+        entry:SetScript("OnEnter", onenter)
+        entry:SetScript("OnLeave", onleave)
+
+		local texture = entry:CreateTexture("ARTWORK")
+		texture:SetTexture("Interface\\Buttons\\UI-CheckBox-Up")
+		texture:SetPoint("LEFT", 0, 0)
+		texture:SetHeight(20)
+		texture:SetWidth(20)
+		entry:SetNormalTexture(texture)
+
+		local texture = entry:CreateTexture("ARTWORK")
+		texture:SetTexture("Interface\\Buttons\\UI-CheckBox-Highlight")
+		texture:SetPoint("LEFT", 0, 0)
+		texture:SetHeight(20)
+		texture:SetWidth(20)
+		texture:SetBlendMode("ADD")
+		entry:SetHighlightTexture(texture)
+
+		local texture = entry:CreateTexture("ARTWORK")
+		texture:SetTexture("Interface\\Buttons\\UI-CheckBox-Check")
+		texture:SetPoint("LEFT", 0, 0)
+		texture:SetHeight(20)
+		texture:SetWidth(20)
+		entry:SetCheckedTexture(texture)
+
+		entry.name = entry:CreateFontString(nil, "ARTWORK", "GameFontHighlightSmall")
+		entry.name:SetPoint("LEFT", 25, 0)
+		entry.name:SetJustifyH("LEFT")
+		entry.name:SetText("Profile Name")
+		frames[i] = entry
+	end
+
+	frames[1]:SetPoint("TOPLEFT", 5, -25)
+	for i=2,12 do
+		frames[i]:SetPoint("TOP", frames[i-1], "BOTTOM", 0, 2)
+	end
+
+    local endButton = CliqueTextList12
+    CreateFrame("ScrollFrame", "CliqueTextListScroll", CliqueTextListFrame, "FauxScrollFrameTemplate")
+    CliqueTextListScroll:SetPoint("TOPLEFT", CliqueTextList1, "TOPLEFT", 0, 0)
+    CliqueTextListScroll:SetPoint("BOTTOMRIGHT", endButton, "BOTTOMRIGHT", 0, 0)
+
+    local texture = CliqueTextListScroll:CreateTexture(nil, "BACKGROUND")
+    texture:SetTexture("Interface\\ChatFrame\\ChatFrameBackground")
+    texture:SetPoint("TOPLEFT", CliqueTextListScroll, "TOPRIGHT", 14, 0)
+    texture:SetPoint("BOTTOMRIGHT", CliqueTextListScroll, "BOTTOMRIGHT", 23, 0)
+    texture:SetGradientAlpha("HORIZONTAL", 0.5, 0.25, 0.05, 0, 0.15, 0.15, 0.15, 1)
+
+    local texture = CliqueTextListScroll:CreateTexture(nil, "BACKGROUND")
+    texture:SetTexture("Interface\\ChatFrame\\ChatFrameBackground")
+    texture:SetPoint("TOPLEFT", CliqueTextListScroll, "TOPRIGHT", 4, 0)
+    texture:SetPoint("BOTTOMRIGHT", CliqueTextListScroll, "BOTTOMRIGHT", 14, 0)
+    texture:SetGradientAlpha("HORIZONTAL", 0.15, 0.15, 0.15, 0.15, 1, 0.5, 0.25, 0.05, 0)
+
+    local update = function()
+		Clique:TextListScrollUpdate()
+	end
+
+    CliqueTextListScroll:SetScript("OnVerticalScroll", function() FauxScrollFrame_OnVerticalScroll(22, update) end)
+    CliqueTextListFrame:SetScript("OnShow", update)
+	CliqueTextListFrame:Hide()
+
 	-- Dropdown Frame
 	CreateFrame("Frame", "CliqueDropDown", CliqueFrame, "UIDropDownMenuTemplate")
 	CliqueDropDown:SetID(1)
@@ -315,14 +473,6 @@ function Clique:CreateOptionsFrame()
 	local font = CliqueDropDown:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
 	font:SetText("Click Set:")
 	font:SetPoint("RIGHT", CliqueDropDown, "LEFT", 5, 3)
-
-	-- Profile Dropdown Frame
-	CreateFrame("Frame", "CliqueDropDownProfile", CliqueFrame, "UIDropDownMenuTemplate")
-	CliqueDropDownProfile:SetID(1)
-	CliqueDropDownProfile:SetPoint("TOPLEFT", CliqueFrame, "TOPLEFT", -10, -25)
-	CliqueDropDownProfile:SetScript("OnShow", function() Clique:DropDownProfile_OnShow() end)
-	UIDropDownMenu_SetWidth(150, CliqueDropDownProfile)
-
 	-- Button Creations
     local buttonFunc = function() Clique:ButtonOnClick() end

@@ -332,36 +482,78 @@ function Clique:CreateOptionsFrame()
 	button:SetPoint("TOPRIGHT", -5, 3)
 	button:SetScript("OnClick", buttonFunc)

+    local button = CreateFrame("Button", "CliqueButtonCustom", CliqueFrame, "UIPanelButtonGrayTemplate")
+    button:SetHeight(24)
+    button:SetWidth(60)
+    button:SetText("Custom")
+    button:SetPoint("BOTTOMLEFT", CliqueFrame, "BOTTOMLEFT", 10, 5)
+    button:SetScript("OnClick", buttonFunc)
+
+    local button = CreateFrame("Button", "CliqueButtonOptions", CliqueFrame, "UIPanelButtonGrayTemplate")
+    button:SetHeight(24)
+    button:SetWidth(60)
+    button:SetText("Options")
+    button:SetPoint("LEFT", CliqueButtonCustom, "RIGHT", 3, 0)
+    button:SetScript("OnClick", buttonFunc)
+
+    local button = CreateFrame("Button", "CliqueButtonProfiles", CliqueFrame, "UIPanelButtonGrayTemplate")
+    button:SetHeight(24)
+    button:SetWidth(60)
+    button:SetText("Profiles")
+    button:SetPoint("LEFT", CliqueButtonOptions, "RIGHT", 3, 0)
+    button:SetScript("OnClick", buttonFunc)
+
     local button = CreateFrame("Button", "CliqueButtonDelete", CliqueFrame, "UIPanelButtonGrayTemplate")
     button:SetHeight(24)
-    button:SetWidth(70)
+    button:SetWidth(60)
     button:SetText("Delete")
-    button:SetPoint("BOTTOM", -38, 4)
+    button:SetPoint("LEFT", CliqueButtonProfiles, "RIGHT", 3, 0)
     button:SetScript("OnClick", buttonFunc)

     local button = CreateFrame("Button", "CliqueButtonEdit", CliqueFrame, "UIPanelButtonGrayTemplate")
     button:SetHeight(24)
-    button:SetWidth(70)
+    button:SetWidth(60)
     button:SetText("Edit")
-    button:SetPoint("BOTTOM", 38, 4)
+    button:SetPoint("LEFT", CliqueButtonDelete, "RIGHT", 3, 0)
     button:SetScript("OnClick", buttonFunc)

     local button = CreateFrame("Button", "CliqueButtonMax", CliqueFrame, "UIPanelButtonGrayTemplate")
     button:SetHeight(24)
-    button:SetWidth(70)
-    button:SetText("Max Rank")
-    button:SetPoint("LEFT", CliqueButtonEdit, "RIGHT", 6, 0)
+    button:SetWidth(60)
+    button:SetText("Max")
+    button:SetPoint("LEFT", CliqueButtonEdit, "RIGHT", 3, 0)
     button:SetScript("OnClick", buttonFunc)

-	-- Create the custom edit screen
-    local button = CreateFrame("Button", "CliqueButtonCustom", CliqueFrame, "UIPanelButtonGrayTemplate")
+	-- Buttons for text list scroll frame
+
+	local button = CreateFrame("Button", "CliqueTextButtonClose", CliqueTextListFrame.titleBar, "UIPanelCloseButton")
+	button:SetHeight(25)
+	button:SetWidth(25)
+	button:SetPoint("TOPRIGHT", -5, 3)
+	button:SetScript("OnClick", buttonFunc)
+
+    local button = CreateFrame("Button", "CliqueButtonDeleteProfile", CliqueTextListFrame, "UIPanelButtonGrayTemplate")
     button:SetHeight(24)
-    button:SetWidth(70)
-    button:SetText("Custom")
-    button:SetPoint("RIGHT", CliqueButtonDelete, "LEFT", -6, 0)
+    button:SetWidth(60)
+    button:SetText("Delete")
+    button:SetPoint("BOTTOMLEFT", CliqueTextListFrame, "BOTTOMLEFT", 30, 5)
     button:SetScript("OnClick", buttonFunc)
-	self.customEntry = {}
-
+
+    local button = CreateFrame("Button", "CliqueButtonSetProfile", CliqueTextListFrame, "UIPanelButtonGrayTemplate")
+    button:SetHeight(24)
+    button:SetWidth(60)
+    button:SetText("Set")
+    button:SetPoint("LEFT", CliqueButtonDeleteProfile, "RIGHT", 3, 0)
+    button:SetScript("OnClick", buttonFunc)
+
+    local button = CreateFrame("Button", "CliqueButtonNewProfile", CliqueTextListFrame, "UIPanelButtonGrayTemplate")
+    button:SetHeight(24)
+    button:SetWidth(60)
+    button:SetText("New")
+    button:SetPoint("LEFT", CliqueButtonSetProfile, "RIGHT", 3, 0)
+    button:SetScript("OnClick", buttonFunc)
+
+	self.customEntry = {}
     local frame = CreateFrame("Frame", "CliqueCustomFrame", CliqueFrame)
     frame:SetHeight(375)
 	frame:SetWidth(450)
@@ -395,11 +587,51 @@ function Clique:CreateOptionsFrame()
 		{type = "click", name = "Click Button"},
 	}

+	for i=1,#buttons do
+		local entry = buttons[i]
+
+		local name = "CliqueRadioButton"..entry.type
+		local button = CreateFrame("CheckButton", name, CliqueCustomFrame)
+		button:SetHeight(20)
+		button:SetWidth(150)
+
+		local texture = button:CreateTexture("ARTWORK")
+		texture:SetTexture("Interface\\AddOns\\Clique\\images\\RadioEmpty")
+		texture:SetPoint("LEFT", 0, 0)
+		texture:SetHeight(20)
+		texture:SetWidth(20)
+		button:SetNormalTexture(texture)
+
+		local texture = button:CreateTexture("ARTWORK")
+		texture:SetTexture("Interface\\AddOns\\Clique\\images\\RadioChecked")
+		texture:SetPoint("LEFT", 0, 0)
+		texture:SetHeight(20)
+		texture:SetWidth(20)
+		texture:SetBlendMode("ADD")
+		button:SetHighlightTexture(texture)
+
+		local texture = button:CreateTexture("ARTWORK")
+		texture:SetTexture("Interface\\AddOns\\Clique\\images\\RadioChecked")
+		texture:SetPoint("LEFT", 0, 0)
+		texture:SetHeight(20)
+		texture:SetWidth(20)
+		button:SetCheckedTexture(texture)
+
+		button.name = button:CreateFontString(nil, "ARTWORK", "GameFontHighlightSmall")
+		button.name:SetPoint("LEFT", 25, 0)
+		button.name:SetJustifyH("LEFT")
+
+		local entry = buttons[1]
+		local name = "CliqueRadioButton"..entry.type
+		local button = CreateFrame("CheckButton", name, CliqueCustomFrame)
+		button:SetHeight(22)
+		button:SetWidth(150)
+	end
+
 	local entry = buttons[1]
-	local name = "CliqueRadioButton"..entry.type
-	local button = CreateFrame("CheckButton", name, CliqueCustomFrame, "UIRadioButtonTemplate")
+	local button = getglobal("CliqueRadioButton"..entry.type)
 	button.type = entry.type
-	getglobal(name.."Text"):SetText(entry.name)
+	button.name:SetText(entry.name)
 	button:SetPoint("TOPLEFT", 5, -30)
 	button:SetScript("OnClick", checkFunc)
 	self.radio[button] = true
@@ -409,18 +641,16 @@ function Clique:CreateOptionsFrame()
 	for i=2,#buttons do
 		local entry = buttons[i]
 		local name = "CliqueRadioButton"..entry.type
-		local button = CreateFrame("CheckButton", name, CliqueCustomFrame, "UIRadioButtonTemplate")
+		local button = getglobal(name)
+
 		button.type = entry.type
-		getglobal(name.."Text"):SetText(entry.name)
+		button.name:SetText(entry.name)
 		button:SetPoint("TOPLEFT", prev, "BOTTOMLEFT", 0, 0)
 		button:SetScript("OnClick", checkFunc)
 		self.radio[button] = true
 		prev = button
 	end

-	-- Disable the click button
-	CliqueRadioButtonclick:Disable()
-
 	-- Button to set the binding

     local button = CreateFrame("Button", "CliqueCustomButtonBinding", CliqueCustomFrame, "UIPanelButtonGrayTemplate")
@@ -458,6 +688,7 @@ function Clique:CreateOptionsFrame()
 	local edit = CreateFrame("EditBox", "CliqueCustomArg1", CliqueCustomFrame, "InputBoxTemplate")
 	edit:SetHeight(30)
 	edit:SetWidth(200)
+	edit:SetMultiLine(true)
 	edit:SetPoint("TOPRIGHT", CliqueCustomFrame, "TOPRIGHT", -10, -190)
 	edit:SetAutoFocus(nil)
 	edit:SetScript("OnTabPressed", function()
@@ -681,7 +912,8 @@ function Clique:CreateOptionsFrame()
 	-- Create the CliqueHelpText
 	CliqueFrame:CreateFontString("CliqueHelpText", "OVERLAY", "GameFontHighlight")
 	CliqueHelpText:SetText(L.HELP_TEXT)
-	CliqueHelpText:SetAllPoints()
+	CliqueHelpText:SetPoint("TOPLEFT", 10, -10)
+	CliqueHelpText:SetPoint("BOTTOMRIGHT", -10, 10)
 	CliqueHelpText:SetJustifyH("CENTER")
 	CliqueHelpText:SetJustifyV("CENTER")
 	CliqueHelpText:SetPoint("CENTER", 0, 0)
@@ -691,8 +923,9 @@ function Clique:CreateOptionsFrame()
 end

 function Clique:ListScrollUpdate()
-    local idx,button
+	if not CliqueListScroll then return end

+    local idx,button
     Clique:SortList()
     local clickCasts = self.sortList
     local offset = FauxScrollFrame_GetOffset(CliqueListScroll)
@@ -763,7 +996,7 @@ function Clique:ValidateButtons()

 	-- Disable the help text
 	Clique.inuse = nil
-	for k,v in pairs(self.profile) do
+	for k,v in pairs(self.clicksets) do
 		if next(v) then
 			Clique.inuse = true
 		end
@@ -784,20 +1017,34 @@ function Clique:FillListEntry(frame, idx)
     frame.icon:SetTexture(entry.texture or "Interface/Icons/Ability_Rogue_Sprint")
 	frame.binding:SetText(entry.modifier..self:GetButtonText(button))

+
 	if entry.type == "action" then
-		frame.name:SetText(string.format("Action Button %d", entry.arg1))
+		frame.name:SetText(string.format("Action Button %d%s", entry.arg1, entry.arg2 and " on "..entry.arg2 or ""))
+	elseif entry.type == "actionbar" then
+		frame.name:SetText(string.format("Action Bar: %s", entry.arg1))
 	elseif entry.type == "pet" then
-		frame.name:SetText(string.format("Pet Action %d", entry.arg1))
+		frame.name:SetText(string.format("Pet Action %d%s", entry.arg1, entry.arg2 and " on "..entry.arg2 or ""))
 	elseif entry.type == "spell" then
-		frame.name:SetText(string.format(entry.arg2 and "%s (%s %d)" or "%s", entry.arg1, L.RANK, entry.arg2))
+		if entry.arg2 then
+			frame.name:SetText(string.format("%s (%s %d)%s", entry.arg1, L.RANK, entry.arg2,
+				entry.arg5 and " on "..entry.arg5 or ""))
+		else
+			frame.name:SetText(string.format("%s%s", entry.arg1, entry.arg5 and " on "..entry.arg5 or ""))
+		end
 	elseif entry.type == "stop" then
 		frame.name:SetText("Stop Casting")
 	elseif entry.type == "target" then
-		frame.name:SetText("Target Unit")
+		frame.name:SetText("Target Unit: %s" .. entry.arg2 and entry.arg2 or "")
 	elseif entry.type == "focus" then
-		frame.name:SetText("Set Focus Unit")
+		frame.name:SetText("Set Focus Unit: %s" .. entry.arg2 and entry.arg2 or "")
 	elseif entry.type == "assist" then
-		frame.name:SetText("Assist Unit")
+		frame.name:SetText("Assist Unit: %s" .. entry.arg2 and entry.arg2 or "")
+	elseif entry.type == "item" then
+		if entry.arg1 then
+			frame.name:SetText(string.format("Item: %d,%d", entry.arg1, entry.arg2))
+		elseif entry.arg3 then
+			frame.name:SetText(string.format("Item: %s", entry.arg3))
+		end
 	end

     frame:Show()
@@ -826,6 +1073,8 @@ function Clique:ButtonOnClick(button)
         self:ListScrollUpdate()
 	elseif this == CliqueButtonClose then
 		self:Toggle()
+	elseif this == CliqueTextButtonClose then
+		CliqueTextListFrame:Hide()
     elseif this == CliqueButtonMax then
         entry.arg2 = nil
 		self:DeleteAction(entry)
@@ -836,6 +1085,47 @@ function Clique:ButtonOnClick(button)
         else
             CliqueCustomFrame:Show()
 		end
+	elseif this == CliqueButtonOptions then
+		if CliqueTextListFrame:IsVisible() and self.textlist == "FRAMES" then
+			CliqueTextListFrame:Hide()
+		else
+			CliqueTextListFrame:Show()
+		end
+
+		self.textlist = "FRAMES"
+		CliqueButtonDeleteProfile:Hide()
+		CliqueButtonSetProfile:Hide()
+		CliqueButtonNewProfile:Hide()
+
+		self:TextListScrollUpdate()
+		CliqueTextListFrame.title:SetText("Clique Frame Editor")
+		self.textlistSelected = nil
+	elseif this == CliqueButtonProfiles then
+		if CliqueTextListFrame:IsVisible() and self.textlist == "PROFILES" then
+			CliqueTextListFrame:Hide()
+		else
+			CliqueTextListFrame:Show()
+		end
+		self.textlist = "PROFILES"
+		self:TextListScrollUpdate()
+		CliqueButtonDeleteProfile:Show()
+		CliqueButtonSetProfile:Show()
+		CliqueButtonNewProfile:Show()
+
+		--CliqueTextListFrame.title:SetText("Profile: " .. self.db.char.profileKey)
+		self.textlistSelected = nil
+	elseif this == CliqueButtonSetProfile then
+	    local offset = FauxScrollFrame_GetOffset(CliqueTextListScroll)
+		local selected = self.textlistSelected - offset
+		local button = getglobal("CliqueTextList"..selected)
+		self:SetProfile(button.name:GetText())
+	elseif this == CliqueButtonNewProfile then
+		StaticPopup_Show("CLIQUE_NEW_PROFILE")
+	elseif this == CliqueButtonDeleteProfile then
+	    local offset = FauxScrollFrame_GetOffset(CliqueTextListScroll)
+		local selected = self.textlistSelected - offset
+		local button = getglobal("CliqueTextList"..selected)
+		self:DeleteProfile(button.name:GetText())
 	elseif this == CliqueButtonEdit then
 		-- Make a copy of the entry
 		self.customEntry = {}
@@ -963,7 +1253,7 @@ function Clique:DropDown_Initialize()
     for k,v in pairs(work) do
         info = {}
         info.text = v
-        info.value = Clique.profile[v]
+        info.value = self.clicksets[v]
         info.func = click_func
         UIDropDownMenu_AddButton(info)
 	end
@@ -978,7 +1268,7 @@ end

 function Clique:DropDown_OnShow()
 	work = {}
-	for k,v in pairs(self.profile) do
+	for k,v in pairs(self.clicksets) do
 		table.insert(work, k)
 	end
 	table.sort(work)
@@ -988,65 +1278,6 @@ function Clique:DropDown_OnShow()
 	Clique:ListScrollUpdate()
 end

--- Profile dropdown
-
-local work
-local click_func = function() Clique:DropDownProfile_OnClick() end
-
-function Clique:DropDownProfile_Initialize()
-    local info = {}
-
-    for k,v in ipairs(work) do
-        info = {}
-        info.text = v
-		info.value = v
-        info.func = click_func
-        UIDropDownMenu_AddButton(info)
-	end
-
-	info = {
-		text = "New profile",
-		value = -1,
-		func = click_func
-	}
-	UIDropDownMenu_AddButton(info)
-
-	info = {
-		text = "Delete Profile",
-		value = -2,
-		func = click_func
-	}
-	UIDropDownMenu_AddButton(info)
-end
-
-function Clique:DropDownProfile_OnClick()
-	if this.value == -1 then
-		StaticPopup_Show("CLIQUE_NEW_PROFILE")
-		return
-	elseif this.value == -2 then
-		StaticPopup_Show("CLIQUE_DELETE_PROFILE")
-		return
-	end
-
-	UIDropDownMenu_SetSelectedValue(CliqueDropDownProfile, this.value)
-	self:SetProfile(this.value)
-	self.listSelected = 0
-	Clique:ListScrollUpdate()
-end
-
-function Clique:DropDownProfile_OnShow()
-	work = {}
-	for k,v in pairs(self.db.profiles) do
-		table.insert(work, k)
-	end
-	table.sort(work)
-
-	UIDropDownMenu_Initialize(this, function() Clique:DropDownProfile_Initialize() end);
-	UIDropDownMenu_SetSelectedValue(CliqueDropDownProfile, self.db.char.profileKey)
-	Clique:ListScrollUpdate()
-end
-
-
 function Clique:CustomBinding_OnClick()
 	-- This handles the binding click
 	local mod = self:GetModifierText()
@@ -1142,7 +1373,7 @@ function Clique:CustomRadio(button)
 		CliqueCustomButtonBinding:SetText("Set Click Binding")
 		return
 	end
-
+
 	-- Clear any open arguments
 	CliqueCustomArg1:SetText("")
 	CliqueCustomArg2:SetText("")
@@ -1330,3 +1561,95 @@ StaticPopupDialogs["CLIQUE_DELETE_PROFILE"] = {
 		ClearCursor();
 	end
 }
+
+local work = {}
+
+function Clique:TextListScrollUpdate()
+	if not CliqueTextListScroll then return end
+
+    local idx,button
+	for k,v in pairs(work) do work[k] = nil end
+
+	if not self.textlist then self.textlist = "FRAMES" end
+
+	if self.textlist == "PROFILES" then
+		for k,v in pairs(self.db.profiles) do table.insert(work, k) end
+		table.sort(work)
+		CliqueTextListFrame.title:SetText("Profile: " .. self.db.char.profileKey)
+
+	elseif self.textlist == "FRAMES" then
+		for k,v in pairs(self.ccframes) do
+			local name = k:GetName()
+			if name then
+				table.insert(work, name)
+			end
+		end
+		table.sort(work)
+	end
+
+    local offset = FauxScrollFrame_GetOffset(CliqueTextListScroll)
+    FauxScrollFrame_Update(CliqueTextListScroll, #work, 12, 22)
+
+    if not CliqueTextListScroll:IsShown() then
+        CliqueTextListFrame:SetWidth(250)
+    else
+        CliqueTextListFrame:SetWidth(275)
+    end
+
+    for i=1,12 do
+        idx = offset + i
+        button = getglobal("CliqueTextList"..i)
+        if idx <= #work then
+			button.name:SetText(work[idx])
+            button:Show()
+			-- Change texture
+			if self.textlist == "PROFILES" then
+				button:SetNormalTexture("Interface\\AddOns\\Clique\\images\\RadioEmpty")
+				button:SetCheckedTexture("Interface\\AddOns\\Clique\\images\\RadioChecked")
+				button:SetHighlightTexture("Interface\\AddOns\\Clique\\images\\RadioChecked")
+			else
+				button:SetNormalTexture("Interface\\Buttons\\UI-CheckBox-Up")
+				button:SetCheckedTexture("Interface\\Buttons\\UI-CheckBox-Check")
+				button:SetHighlightTexture("Interface\\Buttons\\UI-CheckBox-Highlight")
+			end
+
+			if self.textlistSelected == nil and self.textlist == "PROFILES" then
+				if work[idx] == self.db.char.profileKey then
+					button:SetChecked(true)
+					CliqueButtonSetProfile:Disable()
+					CliqueButtonDeleteProfile:Disable()
+				else
+					button:SetChecked(nil)
+				end
+			elseif idx == self.textlistSelected and self.textlist == "PROFILES" then
+				if work[idx] == self.db.char.profileKey then
+					CliqueButtonSetProfile:Disable()
+					CliqueButtonDeleteProfile:Disable()
+				else
+					CliqueButtonSetProfile:Enable()
+					CliqueButtonDeleteProfile:Enable()
+				end
+				button:SetChecked(true)
+			elseif self.textlist == "FRAMES" then
+				local name = work[idx]
+				local frame = getglobal(name)
+
+				if not self.profile.blacklist then
+					self.profile.blacklist = {}
+				end
+				local bl = self.profile.blacklist
+
+				if bl[name] then
+					button:SetChecked(nil)
+				else
+					button:SetChecked(true)
+				end
+            else
+                button:SetBackdropBorderColor(0.3, 0.3, 0.3)
+				button:SetChecked(nil)
+            end
+        else
+            button:Hide()
+        end
+    end
+end
diff --git a/Dongle.lua b/Dongle.lua
new file mode 100644
index 0000000..0d513fd
--- /dev/null
+++ b/Dongle.lua
@@ -0,0 +1,686 @@
+--[[-------------------------------------------------------------------------
+  Copyright (c) 2006, Dongle Development Team
+  All rights reserved.
+
+  Redistribution and use in source and binary forms, with or without
+  modification, are permitted provided that the following conditions are
+  met:
+
+      * Redistributions of source code must retain the above copyright
+        notice, this list of conditions and the following disclaimer.
+      * Redistributions in binary form must reproduce the above
+        copyright notice, this list of conditions and the following
+        disclaimer in the documentation and/or other materials provided
+        with the distribution.
+      * Neither the name of the Dongle Development Team nor the names of
+        its contributors may be used to endorse or promote products derived
+        from this software without specific prior written permission.
+
+  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+  A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+  OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+  SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+  LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+  DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+  THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+  OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+---------------------------------------------------------------------------]]
+
+local major,minor = "DongleStub", 1
+local g = getfenv(0)
+
+if not g.DongleStub or g.DongleStub:IsNewerVersion(major, minor) then
+	local lib = setmetatable({}, {
+		__call = function(t,k)
+			if type(t.versions == "table") and t.versions[k] then
+				return t.versions[k]
+			else
+				error("Cannot find a library with name '"..tostring(k).."'")
+			end
+		end
+	})
+
+	function lib:IsNewerVersion(major, minor)
+		local entry = self.versions and self.versions[major]
+
+		if not entry then return true end
+		local oldmajor,oldminor = entry:GetVersion()
+
+		return minor > oldminor
+	end
+
+	function lib:Register(new)
+		local major,minor = new:GetVersion()
+		if not self:IsNewerVersion(major, minor) then return false end
+		local old = self.versions and self.versions[major]
+		-- Run the new libraries activation
+		new:Activate(old)
+
+		-- Deactivate the old libary if necessary
+		if old and old.Deactivate then old:Deactivate(new) end
+
+		self.versions[major] = new
+	end
+
+	function lib:GetVersion() return major,minor end
+
+	function lib:Activate(old)
+		if old then
+			self.versions = old.versions
+		else
+			self.versions = {}
+		end
+		g.DongleStub = self
+	end
+
+	-- Actually trigger libary activation here
+	local stub = g.DongleStub or lib
+	stub:Register(lib)
+end
+
+--[[-------------------------------------------------------------------------
+Begin Library Implementation
+---------------------------------------------------------------------------]]
+
+local major = "Dongle"
+local minor = tonumber(select(3,string.find("$Revision: 67 $", "(%d+)")) or 1)
+
+assert(DongleStub, string.format("%s requires DongleStub.", major))
+if not DongleStub:IsNewerVersion(major, minor) then return end
+
+Dongle = {}
+local methods = {
+	"RegisterEvent", "UnregisterEvent", "UnregisterAllEvents", "TriggerEvent",
+	"EnableDebug", "Print", "Debug",
+	"InitializeDB", "RegisterDBDefaults", "ResetDB",
+	"SetProfile", "CopyProfile", "ResetProfile", "DeleteProfile",
+	"NewModule", "HasModule", "IterateModules",
+}
+
+local registry = {}
+local lookup = {}
+local loadqueue = {}
+local loadorder = {}
+local events = {}
+
+local function assert(level,condition,message)
+	if not condition then
+		error(message,level)
+	end
+end
+
+local function argcheck(value, num, ...)
+	assert(1, type(num) == "number",
+		"Bad argument #2 to 'argcheck' (number expected, got " .. type(level) .. ")")
+
+	for i=1,select("#", ...) do
+		if type(value) == select(i, ...) then return end
+	end
+
+	local types = strjoin(", ", ...)
+	local name = string.match(debugstack(), "`argcheck'.-[`<](.-)['>]") or "Unknown"
+	error(string.format("bad argument #%d to '%s' (%s expected, got %s)",
+		num, name, types, type(value)), 3)
+end
+
+local function safecall(func,...)
+	local success,err = pcall(func,...)
+	if not success then geterrorhandler(err) end
+end
+
+function Dongle:New(obj, name)
+	argcheck(obj, 2, "table", "string", "nil")
+	argcheck(name, 3, "string", "nil")
+
+	if not name and type(obj) == "string" then
+		name = obj
+		obj = {}
+	end
+
+	if registry[name] then
+		error("A Dongle with the name '"..name.."' is already registered.")
+	end
+
+	local reg = {["obj"] = obj, ["name"] = name}
+
+	registry[name] = reg
+	lookup[obj] = reg
+	lookup[name] = reg
+
+	for k,v in pairs(methods) do
+		obj[v] = self[v]
+	end
+
+	-- Add this Dongle to the end of the queue
+	table.insert(loadqueue, obj)
+	return obj,name
+end
+
+function Dongle:NewModule(obj, name)
+	local reg = lookup[self]
+	assert(3, reg, "You must call 'NewModule' from a registered Dongle.")
+	argcheck(obj, 2, "table", "string", "nil")
+	argcheck(name, 3, "string", "nil")
+
+	obj,name = Dongle:New(obj, name)
+
+	if not reg.modules then reg.modules = {} end
+	table.insert(reg.modules, name)
+	table.sort(reg.modules)
+
+	return obj,name
+end
+
+function Dongle:HasModule(name)
+	local reg = lookup[self]
+	assert(3, reg, "You must call 'HasModule' from a registered Dongle.")
+	argcheck(name, 2, "string")
+
+	return lookup[name]
+end
+
+local EMPTY_TABLE = {}
+
+function Dongle:IterateModules()
+	local reg = lookup[self]
+	assert(3, reg, "You must call 'IterateModules' from a registered Dongle.")
+
+	return ipairs(reg.modules or EMPTY_TABLE)
+end
+
+function Dongle:ADDON_LOADED(frame, event, ...)
+	for i=1, #loadqueue do
+		local obj = loadqueue[i]
+		table.insert(loadorder, obj)
+
+		if type(obj.Initialize) == "function" then
+			safecall(obj.Initialize, obj)
+		end
+
+		if self.initialized and type(obj.Enable) == "function" then
+			safecall(obj.Enable, obj)
+		end
+		loadqueue[i] = nil
+	end
+end
+
+function Dongle:PLAYER_LOGIN()
+	self.initialized = true
+	for i,obj in ipairs(loadorder) do
+		if type(obj.Enable) == "function" then
+			safecall(obj.Enable, obj)
+		end
+	end
+end
+
+function Dongle:TriggerEvent(event, ...)
+	argcheck(event, 2, "string")
+	local eventTbl = events[event]
+	if eventTbl then
+		for obj,func in pairs(eventTbl) do
+			if type(func) == "string" then
+				if type(obj[func]) then
+					safecall(obj[func], obj, event, ...)
+				end
+			else
+				safecall(func,event,...)
+			end
+		end
+	end
+end
+
+function Dongle:OnEvent(frame, event, ...)
+	local eventTbl = events[event]
+	if eventTbl then
+		for obj,func in pairs(eventTbl) do
+			if type(func) == "string" then
+				if type(obj[func]) then
+					obj[func](obj, event, ...)
+				end
+			else
+				func(event, ...)
+			end
+		end
+	end
+end
+
+function Dongle:RegisterEvent(event, func)
+	local reg = lookup[self]
+	assert(3, reg, "You must call 'RegisterEvent' from a registered Dongle.")
+	argcheck(event, 2, "string")
+	argcheck(func, 3, "string", "function", "nil")
+
+	-- Name the method the same as the event if necessary
+	if not func then func = event end
+
+	if not events[event] then
+		events[event] = {}
+		frame:RegisterEvent(event)
+	end
+	events[event][self] = func
+end
+
+function Dongle:UnregisterEvent(event)
+	local reg = lookup[self]
+	assert(3, reg, "You must call 'UnregisterEvent' from a registered Dongle.")
+	argcheck(event, 2, "string")
+
+	if events[event] then
+		events[event][self] = nil
+		if not next(events[event]) then
+			events[event] = nil
+			frame:UnregisterEvent(event)
+		end
+	end
+end
+
+function Dongle:UnregisterAllEvents()
+	assert(3, lookup[self], "You must call 'UnregisterAllEvents' from a registered Dongle.")
+
+	for event,tbl in pairs(events) do
+		tbl[self] = nil
+	end
+end
+
+function Dongle:AdminEvents(event)
+	local method
+	if event == "PLAYER_LOGOUT" then
+		Dongle:ClearDBDefaults()
+		method = "Disable"
+	elseif event == "PLAYER_REGEN_DISABLED" then
+		method = "CombatLockdown"
+	elseif event == "PLAYER_REGEN_ENABLED" then
+		method = "CombatUnlock"
+	end
+
+	if method then
+		for k,v in pairs(registry) do
+			local obj = v.obj
+			if obj[method] then obj[method](obj) end
+		end
+	end
+end
+
+function Dongle:EnableDebug(level)
+	local reg = lookup[self]
+	assert(3, reg, "You must call 'EnableDebug' from a registered Dongle.")
+	argcheck(level, 2, "number", "nil")
+
+	reg.debugLevel = level
+end
+
+do
+	local function printHelp(obj, method, msg, ...)
+		local reg = lookup[obj]
+		assert(4, reg, "You must call '"..method.."' from a registered Dongle.")
+
+		local name = reg.name
+		local msg = string.format("|cFF33FF99%s|r: %s", name, msg)
+
+		local success,txt = pcall(string.format, msg, ...)
+		if success then
+			ChatFrame1:AddMessage(string.format(txt, ...))
+		else
+			error(string.gsub(txt, "'%?'", string.format("'%s'", method)), 3)
+		end
+	end
+
+	function Dongle:Print(msg, ...)
+		return printHelp(self, "Print", msg, ...)
+	end
+
+	function Dongle:Debug(level, msg, ...)
+		local reg = lookup[self]
+		assert(3, reg, "You must call 'Debug' from a registered Dongle.")
+		argcheck(level, 2, "number")
+
+		if reg.debugLevel and level >= reg.debugLevel then
+			printHelp(self, "Debug", msg, ...)
+		end
+	end
+end
+
+function Dongle:InitializeDB(name, defaults)
+	local reg = lookup[self]
+	assert(3, reg, "You must call 'InitializeDB' from a registered Dongle.")
+	argcheck(name, 2, "string")
+	argcheck(defaults, 3, "table", "nil")
+
+	local sv = getglobal(name)
+
+	if not sv then
+		sv = {}
+		setglobal(name, sv)
+
+		-- Lets do the initial setup
+		sv.char = {}
+		sv.faction = {}
+		sv.realm = {}
+		sv.class = {}
+		sv.global = {}
+		sv.profiles = {}
+	end
+
+	-- Initialize the specific databases
+	local char = string.format("%s of %s", UnitName("player"), GetRealmName())
+	local realm = string.format("%s", GetRealmName())
+	local class = UnitClass("player")
+	local race = select(2, UnitRace("player"))
+	local faction = UnitFactionGroup("player")
+
+	-- Initialize the containers
+	if not sv.char then sv.char = {} end
+	if not sv.realm then sv.realm = {} end
+	if not sv.class then sv.class = {} end
+	if not sv.faction then sv.faction = {} end
+	if not sv.global then sv.global = {} end
+	if not sv.profiles then sv.profiles = {} end
+
+	-- Initialize this characters profiles
+	if not sv.char[char] then sv.char[char] = {} end
+	if not sv.realm[realm] then sv.realm[realm] = {} end
+	if not sv.class[class] then sv.class[class] = {} end
+	if not sv.faction[faction] then sv.faction[faction] = {} end
+
+	-- Try to get the profile selected from the char db
+	local profileKey = sv.char[char].profileKey or char
+	sv.char[char].profileKey = profileKey
+
+	if not sv.profiles[profileKey] then sv.profiles[profileKey] = {} end
+
+	local db = {
+		["char"] = sv.char[char],
+		["realm"] = sv.realm[realm],
+		["class"] = sv.class[class],
+		["faction"] = sv.faction[faction],
+		["profile"] = sv.profiles[profileKey],
+		["global"] = sv.global,
+		["profiles"] = sv.profiles,
+	}
+
+	local reg = lookup[self]
+	reg.sv = sv
+	reg.sv_name = name
+	reg.db = db
+	reg.db_char = char
+	reg.db_realm = realm
+	reg.db_class = class
+	reg.db_faction = faction
+	reg.db_profileKey = profileKey
+
+	if defaults then
+		self:RegisterDBDefaults(db, defaults)
+	end
+
+	return db
+end
+
+local function copyDefaults(dest, src)
+	for k,v in pairs(src) do
+		if type(v) == "table" then
+			if not dest[k] then dest[k] = {} end
+			copyDefaults(dest[k], v)
+		else
+			dest[k] = v
+		end
+	end
+end
+
+function Dongle:RegisterDBDefaults(db, defaults)
+	local reg = lookup[self]
+	assert(3, reg, "You must call 'RegisterDBDefaults' from a registered Dongle.")
+	argcheck(db, 2, "table")
+	argcheck(defaults, 3, "table")
+	assert(3, reg.db, "You cannot call \"RegisterDBDefaults\" before calling \"InitializeDB\".")
+
+	if defaults.char then copyDefaults(db.char, defaults.char) end
+	if defaults.realm then copyDefaults(db.realm, defaults.realm) end
+	if defaults.class then copyDefaults(db.class, defaults.class) end
+	if defaults.faction then copyDefaults(db.faction, defaults.faction) end
+	if defaults.global then copyDefaults(db.global, defaults.global) end
+	if defaults.profile then copyDefaults(db.profile, defaults.profile) end
+
+	reg.dbDefaults = defaults
+end
+
+local function removeDefaults(db, defaults)
+	if not db then return end
+	for k,v in pairs(defaults) do
+		if type(v) == "table" and db[k] then
+			removeDefaults(db[k], v)
+			if not next(db[k]) then
+				db[k] = nil
+			end
+		else
+			if db[k] == defaults[k] then
+				db[k] = nil
+			end
+		end
+	end
+end
+
+function Dongle:ClearDBDefaults()
+	for name,obj in pairs(registry) do
+		local db = obj.db
+		local defaults = obj.dbDefaults
+		local sv = obj.sv
+
+		if db and defaults then
+			if defaults.char then removeDefaults(db.char, defaults.char) end
+			if defaults.realm then removeDefaults(db.realm, defaults.realm) end
+			if defaults.class then removeDefaults(db.class, defaults.class) end
+			if defaults.faction then removeDefaults(db.faction, defaults.faction) end
+			if defaults.global then removeDefaults(db.global, defaults.global) end
+			if defaults.profile then
+				for k,v in pairs(sv.profiles) do
+					removeDefaults(sv.profiles[k], defaults.profile)
+				end
+			end
+
+			-- Remove any blank "profiles"
+			if not next(db.char) then sv.char[obj.db_char] = nil end
+			if not next(db.realm) then sv.realm[obj.db_realm] = nil end
+			if not next(db.class) then sv.class[obj.db_class] = nil end
+			if not next(db.faction) then sv.faction[obj.db_faction] = nil end
+			if not next(db.global) then sv.global = nil end
+		end
+	end
+end
+
+function Dongle:SetProfile(name)
+	local reg = lookup[self]
+	assert(3, reg, "You must call 'SetProfile' from a registered Dongle.")
+	argcheck(name, 2, "string")
+	assert(3, reg.db, "You cannot call \"SetProfile\" before calling \"InitializeDB\".")
+
+	local old = reg.sv.profiles[reg.db_profileKey]
+
+	local new = reg.sv.profiles[name]
+	if not new then
+		reg.sv.profiles[name] = {}
+		new = reg.sv.profiles[name]
+	end
+
+	if reg.dbDefaults and reg.dbDefaults.profile then
+		-- Remove the defaults from the old profile
+		removeDefaults(old, reg.dbDefaults.profile)
+
+		-- Inject the defaults into the new profile
+		copyDefaults(new, reg.dbDefaults.profile)
+	end
+
+	reg.db.profile = new
+
+	-- Save this new profile name in db.char
+	reg.db.char.profileKey = name
+
+	self:TriggerEvent("DONGLE_PROFILE_CHANGED", reg.name, name)
+end
+
+function Dongle:DeleteProfile(name)
+	local reg = lookup[self]
+	assert(3, reg, "You must call 'DeleteProfile' from a registered Dongle.")
+	argcheck(name, 2, "string")
+	assert(3, reg.db, "You cannot call \"DeleteProfile\" before calling \"InitializeDB\".")
+
+	if reg.db.char.profileKey == name then
+		error("You cannot delete your active profile.  Change profiles, then attempt to delete.", 2)
+	end
+
+	reg.sv.profiles[name] = nil
+	self:TriggerEvent("DONGLE_PROFILE_DELETED", reg.name, name)
+end
+
+function Dongle:CopyProfile(name)
+	local reg = lookup[self]
+	assert(3, reg, "You must call 'CopyProfile' from a registered Dongle.")
+	argcheck(name, 2, "string")
+	assert(3, reg.db, "You cannot call \"CopyProfile\" before calling \"InitializeDB\".")
+
+	assert(3, reg.db.char.profileKey ~= name, "Source/Destination profile cannot be the same profile")
+	assert(3, type(reg.sv.profiles[name]) == "table", "Profile \""..name.."\" doesn't exist.")
+
+	local profile = reg.db.profile
+	local source = reg.sv.profiles[name]
+
+	-- Don't do a destructive copy, just do what we're told
+	copyDefaults(profile, source)
+	self:TriggerEvent("DONGLE_PROFILE_COPIED", reg.name, name, reg.db.char.profileKey)
+end
+
+function Dongle:ResetProfile()
+	local reg = lookup[self]
+	assert(3, reg, "You must call 'ResetProfile' from a registered Dongle.")
+	assert(3, reg.db, "You cannot call \"ResetProfile\" before calling \"InitializeDB\".")
+
+	local profile = reg.db.profile
+
+	for k,v in pairs(profile) do
+		profile[k] = nil
+	end
+	if reg.dbDefaults and reg.dbDefaults.profile then
+		copyDefaults(profile, reg.dbDefaults.profile)
+	end
+	self:TriggerEvent("DONGLE_PROFILE_RESET", reg.name, name)
+end
+
+function Dongle:ResetDB()
+	local reg = lookup[self]
+	assert(3, reg, "You must call 'ResetDB' from a registered Dongle.")
+	assert(3, reg.db, "You cannot call \"ResetDB\" before calling \"InitializeDB\".")
+
+	local sv = reg.sv
+	for k,v in pairs(sv) do
+		sv[k] = nil
+	end
+
+	local db = self:InitializeDB(reg.sv_name, reg.dbDefaults)
+	self:SetProfile(reg.db.char.profileKey)
+	self:TriggerEvent("DONGLE_DATABASE_RESET", reg.name)
+	return db
+end
+
+-- Set up a basic slash command for /dongle and /reload
+SLASH_RELOAD1 = "/reload"
+SLASH_RELOAD2 = "/rl"
+SlashCmdList["RELOAD"] = ReloadUI
+
+SLASH_DONGLE1 = "/dongle"
+
+SlashCmdList["DONGLE"] = function(msg)
+	local s,e,cmd,args = string.find(msg, "([^%s]+)%s*(.*)")
+	if not cmd then return end
+
+	cmd = string.lower(cmd)
+
+	if cmd == "enable" then
+		local name,title,notes,enabled = GetAddOnInfo(args)
+		if enabled then
+			Dongle:Print("'%s' is already enabled.", args)
+		elseif not name then
+			Dongle:Print("'%s' is not a valid addon.", args)
+		elseif args then
+			EnableAddOn(args)
+			Dongle:Print("Enabled AddOn '%s'", args)
+		end
+	elseif cmd == "disable" then
+		local name,title,notes,enabled = GetAddOnInfo(args)
+		if not name then
+			Dongle:Print("'%s' is not a valid addon.", args)
+		elseif not enabled then
+			Dongle:Print("'%s' is already disabled.", args)
+		elseif args then
+			DisableAddOn(args)
+			Dongle:Print("Disabled AddOn '%s'", args)
+		end
+	end
+end
+
+--]]
+
+--[[-------------------------------------------------------------------------
+  Begin DongleStub required functions and registration
+---------------------------------------------------------------------------]]
+
+function Dongle:GetVersion() return major,minor end
+
+function Dongle:Activate(old)
+	if old then
+		self.registry = old.registry
+		self.lookup = old.lookup
+		self.loadqueue = old.loadqueue
+		self.loadorder = old.loadorder
+		self.events = old.events
+
+		registry = self.registry
+		lookup = self.lookup
+		loadqueue = self.loadqueue
+		loadorder = self.loadorder
+		events = self.events
+
+		frame = old.frame
+		self.registry[major].obj = self
+	else
+		self.registry = registry
+		self.lookup = lookup
+		self.loadqueue = loadqueue
+		self.loadorder = loadorder
+		self.events = events
+
+		local reg = {obj = self, name = "Dongle"}
+		registry[major] = reg
+		lookup[self] = reg
+		lookup[major] = reg
+	end
+
+	if not frame then
+		frame = CreateFrame("Frame")
+	end
+
+	self.frame = frame
+	frame:SetScript("OnEvent", function(...) self:OnEvent(...) end)
+
+	-- Register for events using Dongle itself
+	self:RegisterEvent("ADDON_LOADED")
+	self:RegisterEvent("PLAYER_LOGIN")
+	self:RegisterEvent("PLAYER_LOGOUT", "AdminEvents")
+	self:RegisterEvent("PLAYER_REGEN_ENABLED", "AdminEvents")
+	self:RegisterEvent("PLAYER_REGEN_DISABLED", "AdminEvents")
+
+	-- Convert all the modules handles
+	for name,obj in pairs(registry) do
+		for k,v in ipairs(methods) do
+			obj[k] = self[v]
+		end
+	end
+end
+
+function Dongle:Deactivate(new)
+	lookup[self] = nil
+	self:UnregisterAllEvents()
+end
+
+DongleStub:Register(Dongle)
diff --git a/images/RadioChecked.tga b/images/RadioChecked.tga
new file mode 100644
index 0000000..2895d73
Binary files /dev/null and b/images/RadioChecked.tga differ
diff --git a/images/RadioEmpty.tga b/images/RadioEmpty.tga
new file mode 100644
index 0000000..1fba516
Binary files /dev/null and b/images/RadioEmpty.tga differ
diff --git a/images/myborder.tga b/images/myborder.tga
new file mode 100644
index 0000000..700c1ba
Binary files /dev/null and b/images/myborder.tga differ