From c9d41028fd41abaca6758df656d6c220413faeeb Mon Sep 17 00:00:00 2001 From: James Whitehead II Date: Tue, 5 Dec 2006 03:31:32 +0000 Subject: [PATCH] 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 --- Clique.lua | 152 +++++++---- Clique.toc | 1 + CliqueOptions.lua | 531 +++++++++++++++++++++++++++++------- Dongle.lua | 686 +++++++++++++++++++++++++++++++++++++++++++++++ images/RadioChecked.tga | Bin 0 -> 1350 bytes images/RadioEmpty.tga | Bin 0 -> 628 bytes images/myborder.tga | Bin 0 -> 19842 bytes 7 files changed, 1221 insertions(+), 149 deletions(-) create mode 100644 Dongle.lua create mode 100644 images/RadioChecked.tga create mode 100644 images/RadioEmpty.tga create mode 100644 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 0000000000000000000000000000000000000000..2895d73d40256acf30f4078bbbf2613343e32796 GIT binary patch literal 1350 zcma)6J!@KF7`;j%rlpJiflhAiKhP}&p|sH2(#g%Oa}c*{+Qzg=h$f;Aehn_J85C3; zf-X9kK_Y&UMNrg46Ex$g=LueuQd94R%YEPTKIc8pr=mPp7SAhX{u*LjuKfHax(DFf z9{+yvqvLY^V0XTN%9WS<@{y%Nlm_q+{i&e>cnIiCCKIeyE1XUzRJYp=J^#2vM9wIh zF>hMltCC>TY&OH?av`73qucG`?(Pn=*-ZYqM~r9Ww8;8F77?SlAmB3|+uz^E<>e*D z<1wbwDR#)GCoz%7MBgqL`!%2o+QL8iaJ@;^G23B;4@( z{RoG{h{xk76beF8tJTnMx7Tr*!6wB$8C|BjSS+TIQCTLFStBj_MF~-zxVu4jALf1$ zzo*JaW%o%?Un~lng!GpAUZf?JN}*ILNjivWC9b1ATTd!dT5n0IwH>RnUa#Zu@DPDO z04)1-I{moqG%9I584wRf0NrK$>~=fU?d@&o9S(==5cz8ah_Oc-ETU*RAY?Gfo(v`J rUa^uPrqJJA+KZKpvz22{05&-v`^V2)Uu>UjAKt(I`quVt>y7dU1l^l7 literal 0 HcmV?d00001 diff --git a/images/RadioEmpty.tga b/images/RadioEmpty.tga new file mode 100644 index 0000000000000000000000000000000000000000..1fba516366e6aa2e483c30739e24567d7b7d23ea GIT binary patch literal 628 zcmdUt-3h`_429D-aS9jEEh-d5!9N_rC0f*4MT!&n-X-HbpP0Vb0Zbv>YcnkIbu_hq)VEHdo0adI4eQ!Byq{j+lCMs5LUDtUn dJt~$z_>%y%5XE-A%lE~mSS_cAd9lc6_6Bol%(egk literal 0 HcmV?d00001 diff --git a/images/myborder.tga b/images/myborder.tga new file mode 100644 index 0000000000000000000000000000000000000000..700c1baab44e614548eb24a4c688899c40b79ebd GIT binary patch literal 19842 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 literal 0 HcmV?d00001 -- 1.7.9.5