Mercurial > wow > reaction
changeset 30:0d95ce7a9ec2
- added Ace3 externs
- converted ReAction_ConfigUI to use blizzard interface addons panel via AceConfigDialog-3.0
- partially converted FuBar module to LibRock, deprecated it (going to remove it entirely later)
- cleaned up a couple other tidbits
line wrap: on
line diff
--- a/Bar.lua Wed Apr 02 18:22:02 2008 +0000 +++ b/Bar.lua Wed Apr 02 23:31:13 2008 +0000 @@ -21,8 +21,8 @@ local f = CreateFrame("Frame",nil,config.parent or UIParent,"SecureStateDriverTemplate") f:SetFrameStrata("MEDIUM") - config.width = config.width or 400 - config.height = config.height or 80 + config.width = config.width or 480 + config.height = config.height or 40 f:SetWidth(config.width) f:SetWidth(config.height)
--- a/Bindings.xml Wed Apr 02 18:22:02 2008 +0000 +++ b/Bindings.xml Wed Apr 02 23:31:13 2008 +0000 @@ -1,10 +1,10 @@ <Bindings> <!-- general keybindings --> <Binding name="REACTION_TOGGLELOCK" header="REACTION"> - ReActionAddOn:ToggleLocked() + </Binding> <Binding name="REACTION_TOGGLEKEYBIND"> - ReActionAddOn:ToggleKeybindMode() + </Binding> <!-- dummy keybinds -->
--- a/ReAction.lua Wed Apr 02 18:22:02 2008 +0000 +++ b/ReAction.lua Wed Apr 02 23:31:13 2008 +0000 @@ -1,13 +1,17 @@ -- ReAction.lua -- See modules/ReAction_ModuleTemplate for Module API listing -- See Bar.lua for Bar object listing +local MAJOR_VERSION = GetAddOnMetadata("ReAction","Version") ------ LIBRARIES ------ local L = LibStub("AceLocale-3.0"):GetLocale("ReAction") ------ CORE ------ -local ReAction = LibStub("AceAddon-3.0"):NewAddon( "ReAction" ) +local ReAction = LibStub("AceAddon-3.0"):NewAddon( "ReAction", + "AceConsole-3.0" +) ReAction.revision = tonumber(("$Revision: 1 $"):match("%d+")) +ReAction.version = MAJOR_VERSION ReAction.L = L ------ GLOBALS ------ @@ -25,7 +29,7 @@ end ------ PRIVATE ------ -local SelectBar, DestroyBar, InitializeBars, TearDownBars, DeepCopy, SafeCall, CheckMethod +local SelectBar, DestroyBar, InitializeBars, TearDownBars, DeepCopy, SafeCall, CheckMethod, SlashHandler do local pcall = pcall local geterrorhandler = geterrorhandler @@ -106,6 +110,34 @@ error("Invalid method") end end + + local function CallOptsModule(method,...) + local m = ReAction:GetModule("ConfigUI",true) + if not m then + LoadAddOn("ReAction_ConfigUI") + m = ReAction:GetModule("ConfigUI") + end + if m and type(m) == "table" and type(m[method]) == "function" then + m[method](m,...) + else + ReAction:Print("Options module not found") + end + end + + SlashHandler = function(option) + if option == "config" then + CallOptsModule("OpenConfig",true) + elseif option == "unlock" then + CallOptsModule("SetConfigMode",true) + elseif option == "lock" then + CallOptsModule("SetConfigMode",false) + else + ReAction:Print(ReAction.version) + ReAction:Print("/reaction config") + ReAction:Print("/reaction unlock") + ReAction:Print("/reaction lock") + end + end end @@ -121,6 +153,9 @@ -- default profile is character-specific ) self.db.RegisterCallback(self,"OnProfileChanged") + self.callbacks = LibStub("CallbackHandler-1.0"):New(self) + self:RegisterChatCommand("reaction", SlashHandler) + self:RegisterChatCommand("rxn", SlashHandler) self.bars = {} end @@ -193,6 +228,7 @@ local bar = self.Bar:new( name, profile.bars[name] ) -- ReAction.Bar defined in Bar.lua self:CallMethodOnAllModules("ApplyToBar", bar) self.bars[name] = bar + self.callbacks:Fire("OnCreateBar", bar) return bar end @@ -202,6 +238,7 @@ DestroyBar(bar) self.db.profile.bars[name] = nil self:CallMethodOnAllModules("EraseBarConfig", name) + self.callbacks:Fire("OnEraseBar",name) end end @@ -221,7 +258,40 @@ local cfg = self.db.profile.bars cfg[newname], cfg[name] = cfg[name], nil self:CallMethodOnAllModules("RenameBarConfig", name, newname) + self.callbacks:Fire("OnRenameBar", name, newname) end end +ReAction.options = {} +-- See modules/ReAction_ConfigUI for valid options contexts. +function ReAction:RegisterOptions(context, module, opts) + if module == nil or context == nil then + error("ReAction:RegisterOptions requires a module object and context ID") + end + if not self.options[context] then + self.options[context] = {} + end + self.options[context][module] = opts + self.callbacks:Fire("OnOptionsRegistered", context, module, opts) +end +function ReAction:GetOptions(context) + if context then + if not self.options[context] then + self.options[context] = { } + end + return self.options[context] + end +end + +function ReAction:GetOptionContextList() + local c = {} + for k in self.options do + tinsert(c,k) + end + return c +end + +function ReAction:RefreshOptions() + self.callbacks:Fire("OnOptionsRefreshed") +end
--- a/ReAction.toc Wed Apr 02 18:22:02 2008 +0000 +++ b/ReAction.toc Wed Apr 02 23:31:13 2008 +0000 @@ -1,4 +1,4 @@ -## Interface: 20300 +## Interface: 20400 ## Title: ReAction ## Notes: Action button layout and configuration ## DefaultState: enabled
--- a/lib/embeds.xml Wed Apr 02 18:22:02 2008 +0000 +++ b/lib/embeds.xml Wed Apr 02 23:31:13 2008 +0000 @@ -7,6 +7,8 @@ <Include file="AceAddon-3.0\AceAddon-3.0.xml"/> <Include file="AceLocale-3.0\AceLocale-3.0.xml"/> <Include file="AceDB-3.0\AceDB-3.0.xml"/> + <Include file="AceDBOptions-3.0\AceDBOptions-3.0.xml"/> + <Include file="AceConsole-3.0\AceConsole-3.0.xml"/> <Include file="AceEvent-3.0\AceEvent-3.0.xml"/> </Ui>
--- a/locale/enUS.lua Wed Apr 02 18:22:02 2008 +0000 +++ b/locale/enUS.lua Wed Apr 02 23:31:13 2008 +0000 @@ -11,7 +11,7 @@ L["ReAction: name already in use"] = true -- modules/ReAction_HideBlizzard -L["Hide Default Action Bars"] = true +L["Hide Blizzard Action Bars"] = true L["Hide the default main bar and extra action bars"] = true -- modules/ReAction_Action @@ -19,6 +19,8 @@ L["Create a new bar of standard action buttons"] = true -- modules/ReAction_ConfigUI +L["Customizable replacement for Blizzard's Action Bars"] = true +L["Action Bars"] = true L["Delete Bar"] = true L["Remove the bar from the current profile"] = true L["Rename Bar"] = true
--- a/modules/FuBar_ReActionFu/FuBar_ReActionFu.lua Wed Apr 02 18:22:02 2008 +0000 +++ b/modules/FuBar_ReActionFu/FuBar_ReActionFu.lua Wed Apr 02 23:31:13 2008 +0000 @@ -5,27 +5,28 @@ -- local imports local ReAction = ReAction -local L = ReAction.L -local _G = _G -local Tablet = AceLibrary("Tablet-2.0") -local Dewdrop = AceLibrary("Dewdrop-2.0") -- module declaration local moduleID = "ReActionFu" -local rmodule = ReAction:NewModule( moduleID ) -local module = AceLibrary("AceAddon-2.0"):new("FuBarPlugin-2.0") -rmodule.fubar = module +local module = ReAction:NewModule( moduleID, + "FuBarPlugin-3.0" +) -module.hasIcon = "Interface\\Icons\\INV_Qiraj_JewelEncased" -module.hasNoColor = true -module.clickableTooltip = false -module.cannotDetachTooltip = true -module.hideMenuTitle = true -module.independentProfile = true -module.defaultPosition = "LEFT" -module.defaultMinimapPosition = 240 -- degrees +local fubarOptions = { + iconPath = "Interface\\Icons\\INV_Qiraj_JewelEncased", + hasNoColor = true, + tooltipType = "Tablet-2.0", + configType = "Dewdrop-2.0", + hasNoText = true, + defaultPosition = "LEFT", + defaultMinimapPosition = 240, -- degrees + clickableTooltip = false, + independentProfile = true + cannotDetachTooltip = true + hideMenuTitle = true +} -function rmodule:OnInitialize() +function module:OnInitialize() self.db = ReAction.db:RegisterNamespace(moduleID, { profile = { @@ -39,17 +40,35 @@ self:Hide() end + self.OnMenuRequest = { + type = "group", + handler = ReAction, + args = { + -- include profile management by default + profile = LibStub("AceDBOptions-3.0"):GetOptionsTable(ReAction.db) + } + } + + -- insert each module's registered global opts into the table top level + local opts = self.OnMenuRequest + for m, tbl in pairs(ReAction:GetOptions("global")) do + if tbl.args then + for k, v in pairs(tbl.args) do + opts.args[k] = v + end + end + end + + -- listen for new module adds + ReAction.RegisterCallback(self, "OnOptionsRegistered") + + -- configure FuBarPlugin + for k, v in pairs(fubarOptions) do + self:SetFuBarOption(k,v) + end end -function rmodule:OnEnable() - -end - -function rmodule:OnDisable() - -end - -function rmodule:OnProfileChanged() +function module:OnProfileChanged() if self.db.profile.requireFuBar == true then module:Hide() else @@ -57,32 +76,23 @@ end end -function module:OnTooltipUpdate() - -end - -function module:OnClick(button) - -end - -function module:OnMenuRequest( level, value, inTooltip, valueN_1, valueN_2, valueN_3, valueN_4 ) - if not self.aceOptionsTable then - local opts = { - type = "group", - handler = ReAction, - args = { - } - } - Dewdrop:InjectAceOptionsTable(ReAction,opts) - self.aceOptionsTable = opts - end - for _, m in ReAction:IterateModules() do - if m and type(m.GetGlobalOptions) == "function" then - for k,v in pairs(m:GetGlobalOptions()) do - self.aceOptionsTable.args[k] = v +function module:OnOptionsRegistered( evtName, context, module, opts ) + if context == "global" then + if opts.args then + for k, v in pairs(tbl.args) do + opts.args[k] = v end end end - Dewdrop:FeedAceOptionsTable(self.aceOptionsTable) end +function module:OnFuBarClick(button) + -- TODO: + -- alt, shift, ctrl clicks +end + +function module:OnUpdateFuBarTooltip() + -- TODO: + -- display status of config mode, keybind mode, etc +end +
--- a/modules/FuBar_ReActionFu/FuBar_ReActionFu.xml Wed Apr 02 18:22:02 2008 +0000 +++ b/modules/FuBar_ReActionFu/FuBar_ReActionFu.xml Wed Apr 02 23:31:13 2008 +0000 @@ -2,14 +2,7 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.blizzard.com/wow/ui/..\FrameXML\UI.xsd"> - <Script file="lib\AceLibrary\AceLibrary.lua"/> - <Script file="lib\AceOO-2.0\AceOO-2.0.lua"/> - <Script file="lib\AceEvent-2.0\AceEvent-2.0.lua"/> - <Script file="lib\AceAddon-2.0\AceAddon-2.0.lua"/> - <Script file="lib\FuBarPlugin-2.0\FuBarPlugin-2.0.lua"/> - <Script file="lib\Tablet-2.0\Tablet-2.0.lua"/> - <Script file="lib\Dewdrop-2.0\Dewdrop-2.0.lua"/> - + <Include file="lib\embeds.xml"/> <Script file="FuBar_ReActionFu.lua"/> </Ui> \ No newline at end of file
--- a/modules/FuBar_ReActionFu/lib/AceAddon-2.0/AceAddon-2.0.lua Wed Apr 02 18:22:02 2008 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1394 +0,0 @@ ---[[ -Name: AceAddon-2.0 -Revision: $Rev: 46764 $ -Developed by: The Ace Development Team (http://www.wowace.com/index.php/The_Ace_Development_Team) -Inspired By: Ace 1.x by Turan (turan@gryphon.com) -Website: http://www.wowace.com/ -Documentation: http://www.wowace.com/wiki/AceAddon-2.0 -SVN: http://svn.wowace.com/wowace/trunk/Ace2/AceAddon-2.0 -Description: Base for all Ace addons to inherit from. -Dependencies: AceLibrary, AceOO-2.0, AceEvent-2.0, (optional) AceConsole-2.0 -License: LGPL v2.1 -]] - -local MAJOR_VERSION = "AceAddon-2.0" -local MINOR_VERSION = "$Revision: 46764 $" - --- This ensures the code is only executed if the libary doesn't already exist, or is a newer version -if not AceLibrary then error(MAJOR_VERSION .. " requires AceLibrary.") end -if not AceLibrary:IsNewVersion(MAJOR_VERSION, MINOR_VERSION) then return end - -if not AceLibrary:HasInstance("AceOO-2.0") then error(MAJOR_VERSION .. " requires AceOO-2.0.") end - -local function safecall(func,...) - local success, err = pcall(func,...) - if not success then geterrorhandler()(err:find("%.lua:%d+:") and err or (debugstack():match("\n(.-: )in.-\n") or "") .. err) end -end --- Localization -local STANDBY, TITLE, NOTES, VERSION, AUTHOR, DATE, CATEGORY, EMAIL, CREDITS, WEBSITE, CATEGORIES, ABOUT, LICENSE, PRINT_ADDON_INFO, DONATE, DONATE_DESC, HOWTO_DONATE_WINDOWS, HOWTO_DONATE_MAC -if GetLocale() == "deDE" then - STANDBY = "|cffff5050(Standby)|r" -- capitalized - - TITLE = "Titel" - NOTES = "Anmerkung" - VERSION = "Version" - AUTHOR = "Autor" - DATE = "Datum" - CATEGORY = "Kategorie" - EMAIL = "E-Mail" - WEBSITE = "Webseite" - CREDITS = "Credits" -- fix - LICENSE = "License" -- fix - - ABOUT = "Über" - PRINT_ADDON_INFO = "Gibt Addondaten aus" - DONATE = "Donate" -- fix - DONATE_DESC = "Give a much-needed donation to the author of this addon." -- fix - HOWTO_DONATE_WINDOWS = "Press Ctrl-A to select the link, then Ctrl-C to copy, then Alt-Tab out of the game, open your favorite web browser, and paste the link into the address bar." -- fix - HOWTO_DONATE_MAC = "Press Cmd-A to select the link, then Cmd-C to copy, then Cmd-Tab out of the game, open your favorite web browser, and paste the link into the address bar." -- fix - - CATEGORIES = { - ["Action Bars"] = "Aktionsleisten", - ["Auction"] = "Auktion", - ["Audio"] = "Audio", - ["Battlegrounds/PvP"] = "Schlachtfeld/PvP", - ["Buffs"] = "Stärkungszauber", - ["Chat/Communication"] = "Chat/Kommunikation", - ["Druid"] = "Druide", - ["Hunter"] = "Jäger", - ["Mage"] = "Magier", - ["Paladin"] = "Paladin", - ["Priest"] = "Priester", - ["Rogue"] = "Schurke", - ["Shaman"] = "Schamane", - ["Warlock"] = "Hexenmeister", - ["Warrior"] = "Krieger", - ["Healer"] = "Heiler", - ["Tank"] = "Tank", - ["Caster"] = "Zauberer", - ["Combat"] = "Kampf", - ["Compilations"] = "Zusammenstellungen", - ["Data Export"] = "Datenexport", - ["Development Tools"] = "Entwicklungs Tools", - ["Guild"] = "Gilde", - ["Frame Modification"] = "Frame Veränderungen", - ["Interface Enhancements"] = "Interface Verbesserungen", - ["Inventory"] = "Inventar", - ["Library"] = "Bibliotheken", - ["Map"] = "Karte", - ["Mail"] = "Post", - ["Miscellaneous"] = "Diverses", - ["Quest"] = "Quest", - ["Raid"] = "Schlachtzug", - ["Tradeskill"] = "Beruf", - ["UnitFrame"] = "Einheiten-Fenster", - } -elseif GetLocale() == "frFR" then - STANDBY = "|cffff5050(attente)|r" - - TITLE = "Titre" - NOTES = "Notes" - VERSION = "Version" - AUTHOR = "Auteur" - DATE = "Date" - CATEGORY = "Catégorie" - EMAIL = "E-mail" - WEBSITE = "Site web" - CREDITS = "Credits" -- fix - LICENSE = "License" -- fix - - ABOUT = "A propos" - PRINT_ADDON_INFO = "Afficher les informations sur l'addon" - DONATE = "Donate" -- fix - DONATE_DESC = "Give a much-needed donation to the author of this addon." -- fix - HOWTO_DONATE_WINDOWS = "Press Ctrl-A to select the link, then Ctrl-C to copy, then Alt-Tab out of the game, open your favorite web browser, and paste the link into the address bar." -- fix - HOWTO_DONATE_MAC = "Press Cmd-A to select the link, then Cmd-C to copy, then Cmd-Tab out of the game, open your favorite web browser, and paste the link into the address bar." -- fix - - CATEGORIES = { - ["Action Bars"] = "Barres d'action", - ["Auction"] = "Hôtel des ventes", - ["Audio"] = "Audio", - ["Battlegrounds/PvP"] = "Champs de bataille/JcJ", - ["Buffs"] = "Buffs", - ["Chat/Communication"] = "Chat/Communication", - ["Druid"] = "Druide", - ["Hunter"] = "Chasseur", - ["Mage"] = "Mage", - ["Paladin"] = "Paladin", - ["Priest"] = "Prêtre", - ["Rogue"] = "Voleur", - ["Shaman"] = "Chaman", - ["Warlock"] = "Démoniste", - ["Warrior"] = "Guerrier", - ["Healer"] = "Soigneur", - ["Tank"] = "Tank", - ["Caster"] = "Casteur", - ["Combat"] = "Combat", - ["Compilations"] = "Compilations", - ["Data Export"] = "Exportation de données", - ["Development Tools"] = "Outils de développement", - ["Guild"] = "Guilde", - ["Frame Modification"] = "Modification des fenêtres", - ["Interface Enhancements"] = "Améliorations de l'interface", - ["Inventory"] = "Inventaire", - ["Library"] = "Bibliothèques", - ["Map"] = "Carte", - ["Mail"] = "Courrier", - ["Miscellaneous"] = "Divers", - ["Quest"] = "Quêtes", - ["Raid"] = "Raid", - ["Tradeskill"] = "Métiers", - ["UnitFrame"] = "Fenêtres d'unité", - } -elseif GetLocale() == "koKR" then - STANDBY = "|cffff5050(사용가능)|r" - - TITLE = "제목" - NOTES = "노트" - VERSION = "버전" - AUTHOR = "저작자" - DATE = "날짜" - CATEGORY = "분류" - EMAIL = "전자 우편" - WEBSITE = "웹 사이트" - CREDITS = "공로자" - LICENSE = "라이센스" - - ABOUT = "정보" - PRINT_ADDON_INFO = "애드온에 대한 정보를 출력합니다." - DONATE = "기부" - DONATE_DESC = "이 애드온의 저작자에게 기부를 합니다." - HOWTO_DONATE_WINDOWS = "Ctrl-A를 눌려 링크를 선택후, Ctrl-C로 복사합니다. Alt-Tab 눌려 게임으로 부터 나간후 웹 브라우저를 엽니다. 복사된 링크를 주소 창에 붙여넣기 합니다." - HOWTO_DONATE_MAC = "Cmd-A를 눌려 링크를 선택후, Cmd-C로 복사합니다. Cmd-Tab 눌려 게임으로 부터 나간후 웹 브라우저를 엽니다. 복사된 링크를 주소 창에 붙여넣기 합니다." - - CATEGORIES = { - ["Action Bars"] = "액션바", - ["Auction"] = "경매", - ["Audio"] = "음향", - ["Battlegrounds/PvP"] = "전장/PvP", - ["Buffs"] = "버프", - ["Chat/Communication"] = "대화/의사소통", - ["Druid"] = "드루이드", - ["Hunter"] = "사냥꾼", - ["Mage"] = "마법사", - ["Paladin"] = "성기사", - ["Priest"] = "사제", - ["Rogue"] = "도적", - ["Shaman"] = "주술사", - ["Warlock"] = "흑마법사", - ["Warrior"] = "전사", - ["Healer"] = "힐러", - ["Tank"] = "탱커", - ["Caster"] = "캐스터", - ["Combat"] = "전투", - ["Compilations"] = "복합", - ["Data Export"] = "자료 출력", - ["Development Tools"] = "개발 도구", - ["Guild"] = "길드", - ["Frame Modification"] = "구조 변경", - ["Interface Enhancements"] = "인터페이스 강화", - ["Inventory"] = "인벤토리", - ["Library"] = "라이브러리", - ["Map"] = "지도", - ["Mail"] = "우편", - ["Miscellaneous"] = "기타", - ["Quest"] = "퀘스트", - ["Raid"] = "공격대", - ["Tradeskill"] = "전문기술", - ["UnitFrame"] = "유닛 프레임", - } -elseif GetLocale() == "zhTW" then - STANDBY = "|cffff5050(待命)|r" - - TITLE = "標題" - NOTES = "註記" - VERSION = "版本" - AUTHOR = "作者" - DATE = "日期" - CATEGORY = "類別" - EMAIL = "電子郵件" - WEBSITE = "網站" - CREDITS = "特別感謝" - LICENSE = "版權" - - ABOUT = "關於" - PRINT_ADDON_INFO = "顯示插件資訊。" - DONATE = "捐贈" - DONATE_DESC = "捐贈金錢給插件作者。" - HOWTO_DONATE_WINDOWS = "請按Ctrl-A選擇網站連結,Ctrl-C複製網址,Alt-Tab切換到電腦桌面,打開瀏覽器,在網址列貼上網址。" - HOWTO_DONATE_MAC = "請按Cmd-A選擇網站連結,Cmd-C複製網址,Cmd-Tab切換到電腦桌面,打開瀏覽器,在網址列貼上網址。" - - CATEGORIES = { - ["Action Bars"] = "動作條", - ["Auction"] = "拍賣", - ["Audio"] = "音效", - ["Battlegrounds/PvP"] = "戰場/PvP", - ["Buffs"] = "增益", - ["Chat/Communication"] = "聊天/通訊", - ["Druid"] = "德魯伊", - ["Hunter"] = "獵人", - ["Mage"] = "法師", - ["Paladin"] = "聖騎士", - ["Priest"] = "牧師", - ["Rogue"] = "盜賊", - ["Shaman"] = "薩滿", - ["Warlock"] = "術士", - ["Warrior"] = "戰士", - ["Healer"] = "治療者", - ["Tank"] = "坦克", - ["Caster"] = "施法者", - ["Combat"] = "戰鬥", - ["Compilations"] = "整合", - ["Data Export"] = "資料匯出", - ["Development Tools"] = "開發工具", - ["Guild"] = "公會", - ["Frame Modification"] = "框架修改", - ["Interface Enhancements"] = "介面增強", - ["Inventory"] = "庫存", - ["Library"] = "程式庫", - ["Map"] = "地圖", - ["Mail"] = "郵件", - ["Miscellaneous"] = "雜項", - ["Quest"] = "任務", - ["Raid"] = "團隊", - ["Tradeskill"] = "交易技能", - ["UnitFrame"] = "單位框架", - } -elseif GetLocale() == "zhCN" then - STANDBY = "|cffff5050(暂挂)|r" - - TITLE = "标题" - NOTES = "附注" - VERSION = "版本" - AUTHOR = "作者" - DATE = "日期" - CATEGORY = "分类" - EMAIL = "电子邮件" - WEBSITE = "网站" - CREDITS = "Credits" -- fix - LICENSE = "License" -- fix - - ABOUT = "关于" - PRINT_ADDON_INFO = "印列出插件信息" - DONATE = "Donate" -- fix - DONATE_DESC = "Give a much-needed donation to the author of this addon." -- fix - HOWTO_DONATE_WINDOWS = "Press Ctrl-A to select the link, then Ctrl-C to copy, then Alt-Tab out of the game, open your favorite web browser, and paste the link into the address bar." -- fix - HOWTO_DONATE_MAC = "Press Cmd-A to select the link, then Cmd-C to copy, then Cmd-Tab out of the game, open your favorite web browser, and paste the link into the address bar." -- fix - - CATEGORIES = { - ["Action Bars"] = "动作条", - ["Auction"] = "拍卖", - ["Audio"] = "音频", - ["Battlegrounds/PvP"] = "战场/PvP", - ["Buffs"] = "增益魔法", - ["Chat/Communication"] = "聊天/交流", - ["Druid"] = "德鲁伊", - ["Hunter"] = "猎人", - ["Mage"] = "法师", - ["Paladin"] = "圣骑士", - ["Priest"] = "牧师", - ["Rogue"] = "盗贼", - ["Shaman"] = "萨满祭司", - ["Warlock"] = "术士", - ["Warrior"] = "战士", --- ["Healer"] = "治疗保障", --- ["Tank"] = "近战控制", --- ["Caster"] = "远程输出", - ["Combat"] = "战斗", - ["Compilations"] = "编译", - ["Data Export"] = "数据导出", - ["Development Tools"] = "开发工具", - ["Guild"] = "公会", - ["Frame Modification"] = "框架修改", - ["Interface Enhancements"] = "界面增强", - ["Inventory"] = "背包", - ["Library"] = "库", - ["Map"] = "地图", - ["Mail"] = "邮件", - ["Miscellaneous"] = "杂项", - ["Quest"] = "任务", - ["Raid"] = "团队", - ["Tradeskill"] = "商业技能", - ["UnitFrame"] = "头像框架", - } -elseif GetLocale() == "esES" then - STANDBY = "|cffff5050(espera)|r" - - TITLE = "Título" - NOTES = "Notas" - VERSION = "Versión" - AUTHOR = "Autor" - DATE = "Fecha" - CATEGORY = "Categoría" - EMAIL = "E-mail" - WEBSITE = "Web" - CREDITS = "Créditos" - LICENSE = "License" -- fix - - ABOUT = "Acerca de" - PRINT_ADDON_INFO = "Muestra información acerca del accesorio." - DONATE = "Donate" -- fix - DONATE_DESC = "Give a much-needed donation to the author of this addon." -- fix - HOWTO_DONATE_WINDOWS = "Press Ctrl-A to select the link, then Ctrl-C to copy, then Alt-Tab out of the game, open your favorite web browser, and paste the link into the address bar." -- fix - HOWTO_DONATE_MAC = "Press Cmd-A to select the link, then Cmd-C to copy, then Cmd-Tab out of the game, open your favorite web browser, and paste the link into the address bar." -- fix - - CATEGORIES = { - ["Action Bars"] = "Barras de Acción", - ["Auction"] = "Subasta", - ["Audio"] = "Audio", - ["Battlegrounds/PvP"] = "Campos de Batalla/JcJ", - ["Buffs"] = "Buffs", - ["Chat/Communication"] = "Chat/Comunicación", - ["Druid"] = "Druida", - ["Hunter"] = "Cazador", - ["Mage"] = "Mago", - ["Paladin"] = "Paladín", - ["Priest"] = "Sacerdote", - ["Rogue"] = "Pícaro", - ["Shaman"] = "Chamán", - ["Warlock"] = "Brujo", - ["Warrior"] = "Guerrero", - ["Healer"] = "Sanador", - ["Tank"] = "Tanque", - ["Caster"] = "Conjurador", - ["Combat"] = "Combate", - ["Compilations"] = "Compilaciones", - ["Data Export"] = "Exportar Datos", - ["Development Tools"] = "Herramientas de Desarrollo", - ["Guild"] = "Hermandad", - ["Frame Modification"] = "Modificación de Marcos", - ["Interface Enhancements"] = "Mejoras de la Interfaz", - ["Inventory"] = "Inventario", - ["Library"] = "Biblioteca", - ["Map"] = "Mapa", - ["Mail"] = "Correo", - ["Miscellaneous"] = "Misceláneo", - ["Quest"] = "Misión", - ["Raid"] = "Banda", - ["Tradeskill"] = "Habilidad de Comercio", - ["UnitFrame"] = "Marco de Unidades", - } -else -- enUS - STANDBY = "|cffff5050(standby)|r" - - TITLE = "Title" - NOTES = "Notes" - VERSION = "Version" - AUTHOR = "Author" - DATE = "Date" - CATEGORY = "Category" - EMAIL = "E-mail" - WEBSITE = "Website" - CREDITS = "Credits" - LICENSE = "License" - - ABOUT = "About" - PRINT_ADDON_INFO = "Show information about the addon." - DONATE = "Donate" - DONATE_DESC = "Give a much-needed donation to the author of this addon." - HOWTO_DONATE_WINDOWS = "Press Ctrl-A to select the link, then Ctrl-C to copy, then Alt-Tab out of the game, open your favorite web browser, and paste the link into the address bar." - HOWTO_DONATE_MAC = "Press Cmd-A to select the link, then Cmd-C to copy, then Cmd-Tab out of the game, open your favorite web browser, and paste the link into the address bar." - - CATEGORIES = { - ["Action Bars"] = "Action Bars", - ["Auction"] = "Auction", - ["Audio"] = "Audio", - ["Battlegrounds/PvP"] = "Battlegrounds/PvP", - ["Buffs"] = "Buffs", - ["Chat/Communication"] = "Chat/Communication", - ["Druid"] = "Druid", - ["Hunter"] = "Hunter", - ["Mage"] = "Mage", - ["Paladin"] = "Paladin", - ["Priest"] = "Priest", - ["Rogue"] = "Rogue", - ["Shaman"] = "Shaman", - ["Warlock"] = "Warlock", - ["Warrior"] = "Warrior", - ["Healer"] = "Healer", - ["Tank"] = "Tank", - ["Caster"] = "Caster", - ["Combat"] = "Combat", - ["Compilations"] = "Compilations", - ["Data Export"] = "Data Export", - ["Development Tools"] = "Development Tools", - ["Guild"] = "Guild", - ["Frame Modification"] = "Frame Modification", - ["Interface Enhancements"] = "Interface Enhancements", - ["Inventory"] = "Inventory", - ["Library"] = "Library", - ["Map"] = "Map", - ["Mail"] = "Mail", - ["Miscellaneous"] = "Miscellaneous", - ["Quest"] = "Quest", - ["Raid"] = "Raid", - ["Tradeskill"] = "Tradeskill", - ["UnitFrame"] = "UnitFrame", - } -end - -setmetatable(CATEGORIES, { __index = function(self, key) -- case-insensitive - local lowerKey = key:lower() - for k,v in pairs(CATEGORIES) do - if k:lower() == lowerKey then - return v - end - end -end }) - --- Create the library object - -local AceOO = AceLibrary("AceOO-2.0") -local AceAddon = AceOO.Class() -local AceEvent -local AceConsole -local AceModuleCore - -function AceAddon:GetLocalizedCategory(name) - self:argCheck(name, 2, "string") - return CATEGORIES[name] or UNKNOWN -end - -function AceAddon:ToString() - return "AceAddon" -end - -local function print(text) - DEFAULT_CHAT_FRAME:AddMessage(text) -end - -function AceAddon:ADDON_LOADED(name) - local unregister = true - local initAddon = {} - while #self.nextAddon > 0 do - local addon = table.remove(self.nextAddon, 1) - if addon.possibleNames[name] then - table.insert(initAddon, addon) - else - unregister = nil - table.insert(self.skipAddon, addon) - end - end - self.nextAddon, self.skipAddon = self.skipAddon, self.nextAddon - if unregister then - AceAddon:UnregisterEvent("ADDON_LOADED") - end - while #initAddon > 0 do - local addon = table.remove(initAddon, 1) - table.insert(self.addons, addon) - if not self.addons[name] then - self.addons[name] = addon - end - addon.possibleNames = nil - self:InitializeAddon(addon, name) - end -end - -local function RegisterOnEnable(self) - if DEFAULT_CHAT_FRAME and DEFAULT_CHAT_FRAME.defaultLanguage then -- HACK - AceAddon.playerLoginFired = true - end - if AceAddon.playerLoginFired then - AceAddon.addonsStarted[self] = true - if (type(self.IsActive) ~= "function" or self:IsActive()) and (not AceModuleCore or not AceModuleCore:IsModule(self) or AceModuleCore:IsModuleActive(self)) then - AceAddon:ManualEnable(self) - end - else - if not AceAddon.addonsToOnEnable then - AceAddon.addonsToOnEnable = {} - end - table.insert(AceAddon.addonsToOnEnable, self) - end -end - -function AceAddon:InitializeAddon(addon, name) - if addon.name == nil then - addon.name = name - end - if GetAddOnMetadata then - -- TOC checks - if addon.title == nil then - addon.title = GetAddOnMetadata(name, "Title") - end - if type(addon.title) == "string" then - local num = addon.title:find(" |cff7fff7f %-Ace2%-|r$") - if num then - addon.title = addon.title:sub(1, num - 1) - end - addon.title = addon.title:trim() - end - if addon.notes == nil then - addon.notes = GetAddOnMetadata(name, "Notes") - end - if type(addon.notes) == "string" then - addon.notes = addon.notes:trim() - end - if addon.version == nil then - addon.version = GetAddOnMetadata(name, "Version") - end - if type(addon.version) == "string" then - if addon.version:find("%$Revision: (%d+) %$") then - addon.version = addon.version:gsub("%$Revision: (%d+) %$", "%1") - elseif addon.version:find("%$Rev: (%d+) %$") then - addon.version = addon.version:gsub("%$Rev: (%d+) %$", "%1") - elseif addon.version:find("%$LastChangedRevision: (%d+) %$") then - addon.version = addon.version:gsub("%$LastChangedRevision: (%d+) %$", "%1") - end - addon.version = addon.version:trim() - end - if addon.author == nil then - addon.author = GetAddOnMetadata(name, "Author") - end - if type(addon.author) == "string" then - addon.author = addon.author:trim() - end - if addon.credits == nil then - addon.credits = GetAddOnMetadata(name, "X-Credits") - end - if type(addon.credits) == "string" then - addon.credits = addon.credits:trim() - end - if addon.donate == nil then - addon.donate = GetAddOnMetadata(name, "X-Donate") - end - if type(addon.donate) == "string" then - addon.donate = addon.donate:trim() - end - if addon.date == nil then - addon.date = GetAddOnMetadata(name, "X-Date") or GetAddOnMetadata(name, "X-ReleaseDate") - end - if type(addon.date) == "string" then - if addon.date:find("%$Date: (.-) %$") then - addon.date = addon.date:gsub("%$Date: (.-) %$", "%1") - elseif addon.date:find("%$LastChangedDate: (.-) %$") then - addon.date = addon.date:gsub("%$LastChangedDate: (.-) %$", "%1") - end - addon.date = addon.date:trim() - end - - if addon.category == nil then - addon.category = GetAddOnMetadata(name, "X-Category") - end - if type(addon.category) == "string" then - addon.category = addon.category:trim() - end - if addon.email == nil then - addon.email = GetAddOnMetadata(name, "X-eMail") or GetAddOnMetadata(name, "X-Email") - end - if type(addon.email) == "string" then - addon.email = addon.email:trim() - end - if addon.license == nil then - addon.license = GetAddOnMetadata(name, "X-License") - end - if type(addon.license) == "string" then - addon.license = addon.license:trim() - end - if addon.website == nil then - addon.website = GetAddOnMetadata(name, "X-Website") - end - if type(addon.website) == "string" then - addon.website = addon.website:trim() - end - end - local current = addon.class - while true do - if current == AceOO.Class or not current then - break - end - if current.mixins then - for mixin in pairs(current.mixins) do - if type(mixin.OnEmbedInitialize) == "function" then - mixin:OnEmbedInitialize(addon, name) - end - end - end - current = current.super - end - local n = AceAddon.addonsToOnEnable and #AceAddon.addonsToOnEnable or 0 - - if type(addon.OnInitialize) == "function" then - safecall(addon.OnInitialize, addon, name) - end - if AceEvent then - AceEvent:TriggerEvent("Ace2_AddonInitialized", addon) - end - RegisterOnEnable(addon) - local n2 = AceAddon.addonsToOnEnable and #AceAddon.addonsToOnEnable or 0 - if n2 - n > 1 then - local mine = table.remove(AceAddon.addonsToOnEnable) - table.insert(AceAddon.addonsToOnEnable, n+1, mine) - end -end - -local aboutFrame -local function createAboutFrame() - aboutFrame = CreateFrame("Frame", "AceAddon20AboutFrame", UIParent, "DialogBoxFrame") - aboutFrame:SetWidth(500) - aboutFrame:SetHeight(400) - aboutFrame:SetPoint("CENTER") - aboutFrame:SetBackdrop({ - bgFile = [[Interface\DialogFrame\UI-DialogBox-Background]], - edgeFile = [[Interface\Tooltips\UI-Tooltip-Border]], - tile = true, tileSize = 16, edgeSize = 16, - insets = { left = 5, right = 5, top = 5, bottom = 5 } - }) - aboutFrame:SetBackdropColor(0,0,0,1) - - local donateButton = CreateFrame("Button", "AceAddon20AboutFrameDonateButton", aboutFrame, "UIPanelButtonTemplate2") - aboutFrame.donateButton = donateButton - donateButton:SetPoint("BOTTOMRIGHT", -20, 20) - _G.AceAddon20AboutFrameDonateButtonText:SetText(DONATE) - donateButton:SetWidth(_G.AceAddon20AboutFrameDonateButtonText:GetWidth()+20) - donateButton:SetScript("OnClick", function() - aboutFrame.currentAddon:OpenDonationFrame() - end) - - local text = aboutFrame:CreateFontString(nil, "OVERLAY", "GameFontHighlightLarge") - aboutFrame.title = text - text:SetPoint("TOP", 0, -5) - - aboutFrame:Hide() - - aboutFrame.lefts = {} - aboutFrame.rights = {} - aboutFrame.textLefts = {} - aboutFrame.textRights = {} - function aboutFrame:Clear() - self.title:SetText("") - for i = 1, #self.lefts do - self.lefts[i] = nil - self.rights[i] = nil - end - end - - function aboutFrame:AddLine(left, right) - aboutFrame.lefts[#aboutFrame.lefts+1] = left - aboutFrame.rights[#aboutFrame.rights+1] = right - end - - local aboutFrame_Show = aboutFrame.Show - function aboutFrame:Show(...) - local maxLeftWidth = 0 - local maxRightWidth = 0 - local textHeight = 0 - for i = 1, #self.lefts do - if not self.textLefts[i] then - local left = aboutFrame:CreateFontString(nil, "OVERLAY", "GameFontNormal") - self.textLefts[i] = left - local right = aboutFrame:CreateFontString(nil, "OVERLAY", "GameFontHighlight") - self.textRights[i] = right - if i == 1 then - left:SetPoint("TOPRIGHT", aboutFrame, "TOPLEFT", 75, -35) - else - left:SetPoint("TOPRIGHT", self.textLefts[i-1], "BOTTOMRIGHT", 0, -5) - end - right:SetPoint("LEFT", left, "RIGHT", 5, 0) - end - self.textLefts[i]:SetText(self.lefts[i] .. ":") - self.textRights[i]:SetText(self.rights[i]) - local leftWidth = self.textLefts[i]:GetWidth() - local rightWidth = self.textRights[i]:GetWidth() - textHeight = self.textLefts[i]:GetHeight() - if maxLeftWidth < leftWidth then - maxLeftWidth = leftWidth - end - if maxRightWidth < rightWidth then - maxRightWidth = rightWidth - end - end - for i = #self.lefts+1, #self.textLefts do - self.textLefts[i]:SetText('') - self.textRights[i]:SetText('') - end - aboutFrame:SetWidth(75 + maxRightWidth + 20) - aboutFrame:SetHeight(#self.lefts * (textHeight + 5) + 100) - - aboutFrame_Show(self, ...) - end - aboutFrame:Hide() - - createAboutFrame = nil -end -local donateFrame - -local function unobfuscateEmail(email) - return email:gsub(" AT ", "@"):gsub(" DOT ", ".") -end - -local function isGoodVariable(var) - return type(var) == "string" or type(var) == "number" -end -function AceAddon.prototype:PrintAddonInfo() - if createAboutFrame then - createAboutFrame() - end - aboutFrame:Clear() - local x - if isGoodVariable(self.title) then - x = tostring(self.title) - elseif isGoodVariable(self.name) then - x = tostring(self.name) - else - x = "<" .. tostring(self.class) .. " instance>" - end - if type(self.IsActive) == "function" then - if not self:IsActive() then - x = x .. " " .. STANDBY - end - end - aboutFrame.title:SetText(x) - - if isGoodVariable(self.version) then - aboutFrame:AddLine(VERSION, tostring(self.version)) - end - if isGoodVariable(self.notes) then - aboutFrame:AddLine(NOTES, tostring(self.notes)) - end - if isGoodVariable(self.author) then - aboutFrame:AddLine(AUTHOR, tostring(self.author)) - end - if isGoodVariable(self.credits) then - aboutFrame:AddLine(CREDITS, tostring(self.credits)) - end - if isGoodVariable(self.date) then - aboutFrame:AddLine(DATE, tostring(self.date)) - end - if self.category then - local category = CATEGORIES[self.category] - if category then - aboutFrame:AddLine(CATEGORY, tostring(self.category)) - end - end - if isGoodVariable(self.email) then - aboutFrame:AddLine(EMAIL, unobfuscateEmail(tostring(self.email))) - end - if isGoodVariable(self.website) then - aboutFrame:AddLine(WEBSITE, tostring(self.website)) - end - if isGoodVariable(self.license) then - aboutFrame:AddLine(LICENSE, tostring(self.license)) - end - - if donateFrame and donateFrame:IsShown() then - donateFrame:Hide() - end - - aboutFrame.currentAddon = self - - aboutFrame:Show() - - if self.donate then - aboutFrame.donateButton:Show() - else - aboutFrame.donateButton:Hide() - end -end - -local function createDonateFrame() - donateFrame = CreateFrame("Frame", "AceAddon20Frame", UIParent, "DialogBoxFrame") - - donateFrame:SetWidth(500) - donateFrame:SetHeight(200) - donateFrame:SetPoint("CENTER") - donateFrame:SetBackdrop({ - bgFile = [[Interface\DialogFrame\UI-DialogBox-Background]], - edgeFile = [[Interface\Tooltips\UI-Tooltip-Border]], - tile = true, tileSize = 16, edgeSize = 16, - insets = { left = 5, right = 5, top = 5, bottom = 5 } - }) - donateFrame:SetBackdropColor(0,0,0,1) - - local text = donateFrame:CreateFontString(nil, "OVERLAY", "GameFontHighlightLarge") - text:SetPoint("TOP", 0, -5) - text:SetText(DONATE) - - local howto = donateFrame:CreateFontString(nil, "OVERLAY", "GameFontHighlight") - howto:SetPoint("TOP", text, "BOTTOM", 0, -5) - howto:SetPoint("LEFT", 16, 0) - howto:SetPoint("RIGHT", -16, 0) - if not IsMacClient() then - -- Windows or Linux - howto:SetText(HOWTO_DONATE_WINDOWS) - else - howto:SetText(HOWTO_DONATE_MAC) - end - - local scrollFrame = CreateFrame("ScrollFrame", "AceAddon20FrameScrollFrame", donateFrame, "UIPanelScrollFrameTemplate") - scrollFrame:SetToplevel(true) - scrollFrame:SetPoint("TOP", -10, -76) - scrollFrame:SetWidth(455) - scrollFrame:SetHeight(70) - howto:SetPoint("BOTTOM", scrollFrame, "TOP") - - local editBox = CreateFrame("EditBox", nil, scrollFrame) - donateFrame.editBox = editBox - scrollFrame:SetScrollChild(editBox) - editBox:SetFontObject(ChatFontNormal) - editBox:SetMultiLine(true) - editBox:SetMaxLetters(99999) - editBox:SetWidth(450) - editBox:SetHeight(54) - editBox:SetPoint("BOTTOM", 5, 0) - editBox:SetJustifyH("LEFT") - editBox:SetJustifyV("TOP") - editBox:SetAutoFocus(false) - editBox:SetScript("OnTextChanged", function(this) - if this:GetText() ~= this.text then - this:SetText(this.text) - end - end) - editBox:SetScript("OnEscapePressed", function(this) - this:ClearFocus() - end) - createDonateFrame = nil -end - -local function fix(char) - return ("%%%02x"):format(char:byte()) -end - -local function urlencode(text) - return text:gsub("[^0-9A-Za-z]", fix) -end - -function AceAddon.prototype:OpenDonationFrame() - if createDonateFrame then - createDonateFrame() - end - local donate = self.donate - if type(donate) ~= "string" then - donate = "Wowace" - end - local style, data = (":"):split(donate, 2) - style = style:lower() - if style ~= "website" and style ~= "paypal" then - style = "wowace" - end - if style == "wowace" then - donateFrame.editBox.text = "http://www.wowace.com/wiki/Donations" - elseif style == "website" then - donateFrame.editBox.text = data - else -- PayPal - local text = "https://www.paypal.com/cgi-bin/webscr?cmd=_xclick&business=" .. urlencode(unobfuscateEmail(data)) - local name - if type(self.title) == "string" then - name = self.title - elseif type(self.name) == "string" then - name = self.name - end - if name then - name = name:gsub("|c%x%x%x%x%x%x%x%x", ""):gsub("|r", "") - text = text .. "&item_name=" .. urlencode(name) - end - donateFrame.editBox.text = text - end - donateFrame.editBox:SetText(donateFrame.editBox.text) - - if aboutFrame and aboutFrame:IsShown() then - aboutFrame:Hide() - end - - donateFrame:Show() - - donateFrame.editBox:SetFocus() -end - -local options -function AceAddon:GetAceOptionsDataTable(target) - return { - about = { - name = ABOUT, - desc = PRINT_ADDON_INFO, - type = "execute", - func = "PrintAddonInfo", - order = -1, - }, - donate = { - name = DONATE, - desc = DONATE_DESC, - type = "execute", - func = "OpenDonationFrame", - order = -1, - hidden = function() - return not target.donate - end - } - } -end - -function AceAddon:PLAYER_LOGIN() - self.playerLoginFired = true - if self.addonsToOnEnable then - while #self.addonsToOnEnable > 0 do - local addon = table.remove(self.addonsToOnEnable, 1) - self.addonsStarted[addon] = true - if (type(addon.IsActive) ~= "function" or addon:IsActive()) and (not AceModuleCore or not AceModuleCore:IsModule(addon) or AceModuleCore:IsModuleActive(addon)) then - AceAddon:ManualEnable(addon) - end - end - self.addonsToOnEnable = nil - end -end - -function AceAddon.prototype:Inject(t) - AceAddon:argCheck(t, 2, "table") - for k,v in pairs(t) do - self[k] = v - end -end - -function AceAddon.prototype:init() - if not AceEvent then - error(MAJOR_VERSION .. " requires AceEvent-2.0", 4) - end - AceAddon.super.prototype.init(self) - - self.super = self.class.prototype - - AceAddon:RegisterEvent("ADDON_LOADED", "ADDON_LOADED") - local names = {} - for i = 1, GetNumAddOns() do - if IsAddOnLoaded(i) then names[GetAddOnInfo(i)] = true end - end - self.possibleNames = names - table.insert(AceAddon.nextAddon, self) -end - -function AceAddon.prototype:ToString() - local x - if type(self.title) == "string" then - x = self.title - elseif type(self.name) == "string" then - x = self.name - else - x = "<" .. tostring(self.class) .. " instance>" - end - if (type(self.IsActive) == "function" and not self:IsActive()) or (AceModuleCore and AceModuleCore:IsModule(addon) and AceModuleCore:IsModuleActive(addon)) then - x = x .. " " .. STANDBY - end - return x -end - -AceAddon.new = function(self, ...) - local class = AceAddon:pcall(AceOO.Classpool, self, ...) - return class:new() -end - -function AceAddon:ManualEnable(addon) - AceAddon:argCheck(addon, 2, "table") - local first = nil - if AceOO.inherits(addon, "AceAddon-2.0") then - if AceAddon.addonsEnabled and not AceAddon.addonsEnabled[addon] then - first = true - AceAddon.addonsEnabled[addon] = true - end - end - local current = addon.class - while current and current ~= AceOO.Class do - if current.mixins then - for mixin in pairs(current.mixins) do - if type(mixin.OnEmbedEnable) == "function" then - safecall(mixin.OnEmbedEnable, mixin, addon, first) - end - end - end - current = current.super - end - if type(addon.OnEnable) == "function" then - safecall(addon.OnEnable, addon, first) - end - if AceEvent then - AceEvent:TriggerEvent("Ace2_AddonEnabled", addon, first) - end -end - -function AceAddon:ManualDisable(addon) - AceAddon:argCheck(addon, 2, "table") - local current = addon.class - while current and current ~= AceOO.Class do - if current.mixins then - for mixin in pairs(current.mixins) do - if type(mixin.OnEmbedDisable) == "function" then - safecall(mixin.OnEmbedDisable, mixin, addon) - end - end - end - current = current.super - end - if type(module.OnDisable) == "function" then - safecall(module.OnDisable, addon) - end - if AceEvent then - AceEvent:TriggerEvent("Ace2_AddonDisabled", addon) - end -end - -local function external(self, major, instance) - if major == "AceEvent-2.0" then - AceEvent = instance - - AceEvent:embed(self) - - self:RegisterEvent("PLAYER_LOGIN", "PLAYER_LOGIN", true) - elseif major == "AceConsole-2.0" then - AceConsole = instance - - local slashCommands = { "/ace2" } - local _,_,_,enabled,loadable = GetAddOnInfo("Ace") - if not enabled or not loadable then - table.insert(slashCommands, "/ace") - end - local function listAddon(addon, depth) - if not depth then - depth = 0 - end - - local s = (" "):rep(depth) .. " - " .. tostring(addon) - if rawget(addon, 'version') then - s = s .. " - |cffffff7f" .. tostring(addon.version) .. "|r" - end - if rawget(addon, 'slashCommand') then - s = s .. " |cffffff7f(" .. tostring(addon.slashCommand) .. ")|r" - end - print(s) - if type(rawget(addon, 'modules')) == "table" then - local i = 0 - for k,v in pairs(addon.modules) do - i = i + 1 - if i == 6 then - print((" "):rep(depth + 1) .. " - more...") - break - else - listAddon(v, depth + 1) - end - end - end - end - local function listNormalAddon(i) - local name,_,_,enabled,loadable = GetAddOnInfo(i) - if not loadable then - enabled = false - end - if self.addons[name] then - listAddon(self.addons[name]) - else - local s = " - " .. tostring(GetAddOnMetadata(i, "Title") or name) - local version = GetAddOnMetadata(i, "Version") - if version then - if version:find("%$Revision: (%d+) %$") then - version = version:gsub("%$Revision: (%d+) %$", "%1") - elseif version:find("%$Rev: (%d+) %$") then - version = version:gsub("%$Rev: (%d+) %$", "%1") - elseif version:find("%$LastChangedRevision: (%d+) %$") then - version = version:gsub("%$LastChangedRevision: (%d+) %$", "%1") - end - s = s .. " - |cffffff7f" .. version .. "|r" - end - if not enabled then - s = s .. " |cffff0000(disabled)|r" - end - if IsAddOnLoadOnDemand(i) then - s = s .. " |cff00ff00[LoD]|r" - end - print(s) - end - end - local function mySort(alpha, bravo) - return tostring(alpha) < tostring(bravo) - end - AceConsole.RegisterChatCommand(self, slashCommands, { - desc = "AddOn development framework", - name = "Ace2", - type = "group", - args = { - about = { - desc = "Get information about Ace2", - name = "About", - type = "execute", - func = function() - print("|cffffff7fAce2|r - |cffffff7f2.0." .. MINOR_VERSION:gsub("%$Revision: (%d+) %$", "%1") .. "|r - AddOn development framework") - print(" - |cffffff7f" .. AUTHOR .. ":|r Ace Development Team") - print(" - |cffffff7f" .. WEBSITE .. ":|r http://www.wowace.com/") - end - }, - list = { - desc = "List addons", - name = "List", - type = "group", - args = { - ace2 = { - desc = "List addons using Ace2", - name = "Ace2", - type = "execute", - func = function() - print("|cffffff7fAddon list:|r") - table.sort(self.addons, mySort) - for _,v in ipairs(self.addons) do - listAddon(v) - end - end - }, - all = { - desc = "List all addons", - name = "All", - type = "execute", - func = function() - print("|cffffff7fAddon list:|r") - local count = GetNumAddOns() - for i = 1, count do - listNormalAddon(i) - end - end - }, - enabled = { - desc = "List all enabled addons", - name = "Enabled", - type = "execute", - func = function() - print("|cffffff7fAddon list:|r") - local count = GetNumAddOns() - for i = 1, count do - local _,_,_,enabled,loadable = GetAddOnInfo(i) - if enabled and loadable then - listNormalAddon(i) - end - end - end - }, - disabled = { - desc = "List all disabled addons", - name = "Disabled", - type = "execute", - func = function() - print("|cffffff7fAddon list:|r") - local count = GetNumAddOns() - for i = 1, count do - local _,_,_,enabled,loadable = GetAddOnInfo(i) - if not enabled or not loadable then - listNormalAddon(i) - end - end - end - }, - lod = { - desc = "List all LoadOnDemand addons", - name = "LoadOnDemand", - type = "execute", - func = function() - print("|cffffff7fAddon list:|r") - local count = GetNumAddOns() - for i = 1, count do - if IsAddOnLoadOnDemand(i) then - listNormalAddon(i) - end - end - end - }, - ace1 = { - desc = "List all addons using Ace1", - name = "Ace 1.x", - type = "execute", - func = function() - print("|cffffff7fAddon list:|r") - local count = GetNumAddOns() - for i = 1, count do - local dep1, dep2, dep3, dep4 = GetAddOnDependencies(i) - if dep1 == "Ace" or dep2 == "Ace" or dep3 == "Ace" or dep4 == "Ace" then - listNormalAddon(i) - end - end - end - }, - libs = { - desc = "List all libraries using AceLibrary", - name = "Libraries", - type = "execute", - func = function() - if type(AceLibrary) == "table" and type(AceLibrary.libs) == "table" then - print("|cffffff7fLibrary list:|r") - for name, data in pairs(AceLibrary.libs) do - local s - if data.minor then - s = " - " .. tostring(name) .. "." .. tostring(data.minor) - else - s = " - " .. tostring(name) - end - if rawget(AceLibrary(name), 'slashCommand') then - s = s .. " |cffffff7f(" .. tostring(AceLibrary(name).slashCommand) .. "|cffffff7f)" - end - print(s) - end - end - end - }, - search = { - desc = "Search by name", - name = "Search", - type = "text", - usage = "<keyword>", - input = true, - get = false, - set = function(...) - local arg = { ... } - for i,v in ipairs(arg) do - arg[i] = v:gsub('%*', '.*'):gsub('%%', '%%%%'):lower() - end - local count = GetNumAddOns() - for i = 1, count do - local name = GetAddOnInfo(i) - local good = true - for _,v in ipairs(arg) do - if not name:lower():find(v) then - good = false - break - end - end - if good then - listNormalAddon(i) - end - end - end - } - }, - }, - enable = { - desc = "Enable addon(s).", - name = "Enable", - type = "text", - usage = "<addon 1> <addon 2> ...", - get = false, - input = true, - set = function(...) - for i = 1, select("#", ...) do - local addon = select(i, ...) - local name, title, _, enabled, _, reason = GetAddOnInfo(addon) - if reason == "MISSING" then - print(("|cffffff7fAce2:|r AddOn %q does not exist."):format(addon)) - elseif not enabled then - EnableAddOn(addon) - print(("|cffffff7fAce2:|r %s is now enabled."):format(addon or name)) - else - print(("|cffffff7fAce2:|r %s is already enabled."):format(addon or name)) - end - end - end, - }, - disable = { - desc = "Disable addon(s).", - name = "Disable", - type = "text", - usage = "<addon 1> <addon 2> ...", - get = false, - input = true, - set = function(...) - for i = 1, select("#", ...) do - local addon = select(i, ...) - local name, title, _, enabled, _, reason = GetAddOnInfo(addon) - if reason == "MISSING" then - print(("|cffffff7fAce2:|r AddOn %q does not exist."):format(addon)) - elseif enabled then - DisableAddOn(addon) - print(("|cffffff7fAce2:|r %s is now disabled."):format(addon or name)) - else - print(("|cffffff7fAce2:|r %s is already disabled."):format(addon or name)) - end - end - end, - }, - load = { - desc = "Load addon(s).", - name = "Load", - type = "text", - usage = "<addon 1> <addon 2> ...", - get = false, - input = true, - set = function(...) - for i = 1, select("#", ...) do - local addon = select(i, ...) - local name, title, _, _, loadable, reason = GetAddOnInfo(addon) - if reason == "MISSING" then - print(("|cffffff7fAce2:|r AddOn %q does not exist."):format(addon)) - elseif not loadable then - print(("|cffffff7fAce2:|r AddOn %q is not loadable. Reason: %s."):format(addon, reason)) - else - LoadAddOn(addon) - print(("|cffffff7fAce2:|r %s is now loaded."):format(addon or name)) - end - end - end - }, - info = { - desc = "Display information", - name = "Information", - type = "execute", - func = function() - local mem, threshold = gcinfo() - print((" - |cffffff7fMemory usage [|r%.3f MiB|cffffff7f]|r"):format(mem / 1024)) - if threshold then - print((" - |cffffff7fThreshold [|r%.3f MiB|cffffff7f]|r"):format(threshold / 1024)) - end - print((" - |cffffff7fFramerate [|r%.0f fps|cffffff7f]|r"):format(GetFramerate())) - local bandwidthIn, bandwidthOut, latency = GetNetStats() - bandwidthIn, bandwidthOut = floor(bandwidthIn * 1024), floor(bandwidthOut * 1024) - print((" - |cffffff7fLatency [|r%.0f ms|cffffff7f]|r"):format(latency)) - print((" - |cffffff7fBandwidth in [|r%.0f B/s|cffffff7f]|r"):format(bandwidthIn)) - print((" - |cffffff7fBandwidth out [|r%.0f B/s|cffffff7f]|r"):format(bandwidthOut)) - print((" - |cffffff7fTotal addons [|r%d|cffffff7f]|r"):format(GetNumAddOns())) - print((" - |cffffff7fAce2 addons [|r%d|cffffff7f]|r"):format(#self.addons)) - local ace = 0 - local enabled = 0 - local disabled = 0 - local lod = 0 - for i = 1, GetNumAddOns() do - local dep1, dep2, dep3, dep4 = GetAddOnDependencies(i) - if dep1 == "Ace" or dep2 == "Ace" or dep3 == "Ace" or dep4 == "Ace" then - ace = ace + 1 - end - if IsAddOnLoadOnDemand(i) then - lod = lod + 1 - end - local isActive, loadable = select(4, GetAddOnInfo(i)) - if not isActive or not loadable then - disabled = disabled + 1 - else - enabled = enabled + 1 - end - end - print((" - |cffffff7fAce 1.x addons [|r%d|cffffff7f]|r"):format(ace)) - print((" - |cffffff7fLoadOnDemand addons [|r%d|cffffff7f]|r"):format(lod)) - print((" - |cffffff7fenabled addons [|r%d|cffffff7f]|r"):format(enabled)) - print((" - |cffffff7fdisabled addons [|r%d|cffffff7f]|r"):format(disabled)) - local libs = 0 - if type(AceLibrary) == "table" and type(AceLibrary.libs) == "table" then - for _ in pairs(AceLibrary.libs) do - libs = libs + 1 - end - end - print((" - |cffffff7fAceLibrary instances [|r%d|cffffff7f]|r"):format(libs)) - end - } - } - }) - elseif major == "AceModuleCore-2.0" then - AceModuleCore = instance - end -end - -local function activate(self, oldLib, oldDeactivate) - AceAddon = self - - self.playerLoginFired = oldLib and oldLib.playerLoginFired or DEFAULT_CHAT_FRAME and DEFAULT_CHAT_FRAME.defaultLanguage - self.addonsToOnEnable = oldLib and oldLib.addonsToOnEnable - self.addons = oldLib and oldLib.addons or {} - self.nextAddon = oldLib and oldLib.nextAddon or {} - self.skipAddon = oldLib and oldLib.skipAddon or {} - self.addonsStarted = oldLib and oldLib.addonsStarted or {} - self.addonsEnabled = oldLib and oldLib.addonsEnabled or {} - - if oldDeactivate then - oldDeactivate(oldLib) - end -end - -AceLibrary:Register(AceAddon, MAJOR_VERSION, MINOR_VERSION, activate, nil, external)
--- a/modules/FuBar_ReActionFu/lib/AceAddon-2.0/AceAddon-2.0.toc Wed Apr 02 18:22:02 2008 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,12 +0,0 @@ -## Interface: 20200 - -## Title: Lib: AceAddon-2.0 -## Notes: AddOn development framework -## Author: Ace Development Team -## LoadOnDemand: 1 -## X-Website: http://www.wowace.com -## X-Category: Library -## X-License: LGPL v2.1 + MIT for AceOO-2.0 -## Dependencies: AceLibrary, AceOO-2.0 - -AceAddon-2.0.lua
--- a/modules/FuBar_ReActionFu/lib/AceEvent-2.0/AceEvent-2.0.lua Wed Apr 02 18:22:02 2008 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1082 +0,0 @@ ---[[ -Name: AceEvent-2.0 -Revision: $Rev: 49307 $ -Developed by: The Ace Development Team (http://www.wowace.com/index.php/The_Ace_Development_Team) -Inspired By: Ace 1.x by Turan (turan@gryphon.com) -Website: http://www.wowace.com/ -Documentation: http://www.wowace.com/index.php/AceEvent-2.0 -SVN: http://svn.wowace.com/root/trunk/Ace2/AceEvent-2.0 -Description: Mixin to allow for event handling, scheduling, and inter-addon - communication. -Dependencies: AceLibrary, AceOO-2.0 -License: LGPL v2.1 -]] - -local MAJOR_VERSION = "AceEvent-2.0" -local MINOR_VERSION = "$Revision: 49307 $" - -if not AceLibrary then error(MAJOR_VERSION .. " requires AceLibrary") end -if not AceLibrary:IsNewVersion(MAJOR_VERSION, MINOR_VERSION) then return end - -if not AceLibrary:HasInstance("AceOO-2.0") then error(MAJOR_VERSION .. " requires AceOO-2.0") end - -local AceOO = AceLibrary:GetInstance("AceOO-2.0") -local Mixin = AceOO.Mixin -local AceEvent = Mixin { - "RegisterEvent", - "RegisterAllEvents", - "UnregisterEvent", - "UnregisterAllEvents", - "TriggerEvent", - "ScheduleEvent", - "ScheduleRepeatingEvent", - "CancelScheduledEvent", - "CancelAllScheduledEvents", - "IsEventRegistered", - "IsEventScheduled", - "RegisterBucketEvent", - "UnregisterBucketEvent", - "UnregisterAllBucketEvents", - "IsBucketEventRegistered", - "ScheduleLeaveCombatAction", - "CancelAllCombatSchedules", -} - -local weakKey = {__mode="k"} - -local FAKE_NIL -local RATE - -local eventsWhichHappenOnce = { - PLAYER_LOGIN = true, - AceEvent_FullyInitialized = true, - VARIABLES_LOADED = true, - PLAYER_LOGOUT = true, -} -local next = next -local pairs = pairs -local pcall = pcall -local type = type -local GetTime = GetTime -local gcinfo = gcinfo -local unpack = unpack -local geterrorhandler = geterrorhandler - -local build = GetBuildInfo() -local useTablesAsIDs = build:find("^2%.0%.") or build:find("^2%.1%.") or build:find("^0%.1%.") - -local new, del -do - local cache = setmetatable({}, {__mode='k'}) - function new(...) - local t = next(cache) - if t then - cache[t] = nil - for i = 1, select('#', ...) do - t[i] = select(i, ...) - end - return t - else - return { ... } - end - end - function del(t) - for k in pairs(t) do - t[k] = nil - end - cache[t] = true - return nil - end -end - -local registeringFromAceEvent ---[[---------------------------------------------------------------------------------- -Notes: - * Registers the addon with a Blizzard event or a custom AceEvent, which will cause the given method to be called when that is triggered. -Arguments: - string - name of the event to register - [optional] string or function - name of the method or function to call. Default: same name as "event". - [optional] boolean - whether to have method called only once. Default: false -------------------------------------------------------------------------------------]] -function AceEvent:RegisterEvent(event, method, once) - AceEvent:argCheck(event, 2, "string") - if self == AceEvent and not registeringFromAceEvent then - AceEvent:argCheck(method, 3, "function") - self = method - else - AceEvent:argCheck(method, 3, "string", "function", "nil", "boolean", "number") - if type(method) == "boolean" or type(method) == "number" then - AceEvent:argCheck(once, 4, "nil") - once, method = method, event - end - end - AceEvent:argCheck(once, 4, "number", "boolean", "nil") - if eventsWhichHappenOnce[event] then - once = true - end - local throttleRate - if type(once) == "number" then - throttleRate, once = once - end - if not method then - method = event - end - if type(method) == "string" and type(self[method]) ~= "function" then - AceEvent:error("Cannot register event %q to method %q, it does not exist", event, method) - else - assert(type(method) == "function" or type(method) == "string") - end - - local AceEvent_registry = AceEvent.registry - if not AceEvent_registry[event] then - AceEvent_registry[event] = new() - AceEvent.frame:RegisterEvent(event) - end - - local remember = true - if AceEvent_registry[event][self] then - remember = false - end - AceEvent_registry[event][self] = method - - local AceEvent_onceRegistry = AceEvent.onceRegistry - if once then - if not AceEvent_onceRegistry then - AceEvent.onceRegistry = {} - AceEvent_onceRegistry = AceEvent.onceRegistry - end - if not AceEvent_onceRegistry[event] then - AceEvent_onceRegistry[event] = new() - end - AceEvent_onceRegistry[event][self] = true - else - if AceEvent_onceRegistry and AceEvent_onceRegistry[event] then - AceEvent_onceRegistry[event][self] = nil - if not next(AceEvent_onceRegistry[event]) then - AceEvent_onceRegistry[event] = del(AceEvent_onceRegistry[event]) - end - end - end - - local AceEvent_throttleRegistry = AceEvent.throttleRegistry - if throttleRate then - if not AceEvent_throttleRegistry then - AceEvent.throttleRegistry = {} - AceEvent_throttleRegistry = AceEvent.throttleRegistry - end - if not AceEvent_throttleRegistry[event] then - AceEvent_throttleRegistry[event] = new() - end - if AceEvent_throttleRegistry[event][self] then - AceEvent_throttleRegistry[event][self] = nil - end - AceEvent_throttleRegistry[event][self] = setmetatable(new(), weakKey) - local t = AceEvent_throttleRegistry[event][self] - t[RATE] = throttleRate - else - if AceEvent_throttleRegistry and AceEvent_throttleRegistry[event] then - if AceEvent_throttleRegistry[event][self] then - AceEvent_throttleRegistry[event][self] = nil - end - if not next(AceEvent_throttleRegistry[event]) then - AceEvent_throttleRegistry[event] = del(AceEvent_throttleRegistry[event]) - end - end - end - - if remember then - AceEvent:TriggerEvent("AceEvent_EventRegistered", self, event) - end -end - -local ALL_EVENTS - ---[[---------------------------------------------------------------------------------- -Notes: - * Registers all events to the given method - * To access the current event, check AceEvent.currentEvent - * To access the current event's unique identifier, check AceEvent.currentEventUID - * This is only for debugging purposes. -Arguments: - [optional] string or function - name of the method or function to call. Default: same name as "event". -------------------------------------------------------------------------------------]] -function AceEvent:RegisterAllEvents(method) - if self == AceEvent then - AceEvent:argCheck(method, 1, "function") - self = method - else - AceEvent:argCheck(method, 1, "string", "function") - if type(method) == "string" and type(self[method]) ~= "function" then - AceEvent:error("Cannot register all events to method %q, it does not exist", method) - end - end - - local AceEvent_registry = AceEvent.registry - if not AceEvent_registry[ALL_EVENTS] then - AceEvent_registry[ALL_EVENTS] = new() - AceEvent.frame:RegisterAllEvents() - end - - local remember = not AceEvent_registry[ALL_EVENTS][self] - AceEvent_registry[ALL_EVENTS][self] = method - if remember then - AceEvent:TriggerEvent("AceEvent_EventRegistered", self, "all") - end -end - ---[[---------------------------------------------------------------------------------- -Notes: - * Trigger a custom AceEvent. - * This should never be called to simulate fake Blizzard events. - * Custom events should be in the form of AddonName_SpecificEvent -Arguments: - string - name of the event - tuple - list of arguments to pass along -------------------------------------------------------------------------------------]] -function AceEvent:TriggerEvent(event, ...) - if type(event) ~= "string" then - DEFAULT_CHAT_FRAME:AddMessage(debugstack()) - end - AceEvent:argCheck(event, 2, "string") - local AceEvent_registry = AceEvent.registry - if (not AceEvent_registry[event] or not next(AceEvent_registry[event])) and (not AceEvent_registry[ALL_EVENTS] or not next(AceEvent_registry[ALL_EVENTS])) then - return - end - local lastEvent = AceEvent.currentEvent - AceEvent.currentEvent = event - local lastEventUID = AceEvent.currentEventUID - local uid = AceEvent.UID_NUM + 1 - AceEvent.UID_NUM = uid - AceEvent.currentEventUID = uid - - local tmp = new() - - local AceEvent_onceRegistry = AceEvent.onceRegistry - if AceEvent_onceRegistry and AceEvent_onceRegistry[event] then - for obj, method in pairs(AceEvent_onceRegistry[event]) do - tmp[obj] = AceEvent_registry[event] and AceEvent_registry[event][obj] or nil - end - local obj = next(tmp) - while obj do - local method = tmp[obj] - AceEvent.UnregisterEvent(obj, event) - if type(method) == "string" then - local obj_method = obj[method] - if obj_method then - local success, err = pcall(obj_method, obj, ...) - if not success then geterrorhandler()(err:find("%.lua:%d+:") and err or (debugstack():match("(.-: )in.-\n") or "") .. err) end - end - elseif method then -- function - local success, err = pcall(method, ...) - if not success then geterrorhandler()(err:find("%.lua:%d+:") and err or (debugstack():match("(.-: )in.-\n") or "") .. err) end - end - tmp[obj] = nil - obj = next(tmp) - end - end - - local AceEvent_throttleRegistry = AceEvent.throttleRegistry - local throttleTable = AceEvent_throttleRegistry and AceEvent_throttleRegistry[event] - if AceEvent_registry[event] then - for obj, method in pairs(AceEvent_registry[event]) do - tmp[obj] = method - end - local obj = next(tmp) - while obj do - local method = tmp[obj] - local continue = false - if throttleTable and throttleTable[obj] then - local a1 = ... - if a1 == nil then - a1 = FAKE_NIL - end - if not throttleTable[obj][a1] or GetTime() - throttleTable[obj][a1] >= throttleTable[obj][RATE] then - throttleTable[obj][a1] = GetTime() - else - continue = true - end - end - if not continue then - if type(method) == "string" then - local obj_method = obj[method] - if obj_method then - local success, err = pcall(obj_method, obj, ...) - if not success then geterrorhandler()(err:find("%.lua:%d+:") and err or (debugstack():match("(.-: )in.-\n") or "") .. err) end - end - elseif method then -- function - local success, err = pcall(method, ...) - if not success then geterrorhandler()(err:find("%.lua:%d+:") and err or (debugstack():match("(.-: )in.-\n") or "") .. err) end - end - end - tmp[obj] = nil - obj = next(tmp) - end - end - if AceEvent_registry[ALL_EVENTS] then - for obj, method in pairs(AceEvent_registry[ALL_EVENTS]) do - tmp[obj] = method - end - local obj = next(tmp) - while obj do - local method = tmp[obj] - if type(method) == "string" then - local obj_method = obj[method] - if obj_method then - local success, err = pcall(obj_method, obj, ...) - if not success then geterrorhandler()(err:find("%.lua:%d+:") and err or (debugstack():match("(.-: )in.-\n") or "") .. err) end - end - elseif method then -- function - local success, err = pcall(method, ...) - if not success then geterrorhandler()(err:find("%.lua:%d+:") and err or (debugstack():match("(.-: )in.-\n") or "") .. err) end - end - tmp[obj] = nil - obj = next(tmp) - end - end - tmp = del(tmp) - AceEvent.currentEvent = lastEvent - AceEvent.currentEventUID = lastEventUID -end - -local delayRegistry -local OnUpdate -do - local tmp = {} - OnUpdate = function() - local t = GetTime() - for k,v in pairs(delayRegistry) do - tmp[k] = true - end - for k in pairs(tmp) do - local v = delayRegistry[k] - if v then - local v_time = v.time - if not v_time then - delayRegistry[k] = nil - elseif v_time <= t then - local v_repeatDelay = v.repeatDelay - if v_repeatDelay then - -- use the event time, not the current time, else timing inaccuracies add up over time - v.time = v_time + v_repeatDelay - end - local event = v.event - if type(event) == "function" then - local uid = AceEvent.UID_NUM + 1 - AceEvent.UID_NUM = uid - AceEvent.currentEventUID = uid - local success, err = pcall(event, unpack(v, 1, v.n)) - if not success then geterrorhandler()(err:find("%.lua:%d+:") and err or (debugstack():match("(.-: )in.-\n") or "") .. err) end - AceEvent.currentEventUID = nil - else - AceEvent:TriggerEvent(event, unpack(v, 1, v.n)) - end - if not v_repeatDelay then - local x = delayRegistry[k] - if x and x.time == v_time then -- check if it was manually reset - if not useTablesAsIDs or type(k) == "string" then - del(delayRegistry[k]) - end - delayRegistry[k] = nil - end - end - end - end - end - for k in pairs(tmp) do - tmp[k] = nil - end - if not next(delayRegistry) then - AceEvent.frame:Hide() - end - end -end - -local function ScheduleEvent(self, repeating, event, delay, ...) - local id - if type(event) == "string" or (useTablesAsIDs and type(event) == "table") then - if useTablesAsIDs and type(event) == "table" then - if not delayRegistry or not delayRegistry[event] then - AceEvent:error("Bad argument #2 to `ScheduleEvent'. Improper id table fed in.") - end - end - if type(delay) ~= "number" then - id, event, delay = event, delay, ... - AceEvent:argCheck(event, 3, "string", "function", --[[ so message is right ]] "number") - AceEvent:argCheck(delay, 4, "number") - self:CancelScheduledEvent(id) - end - else - AceEvent:argCheck(event, 2, "string", "function") - AceEvent:argCheck(delay, 3, "number") - end - - if not delayRegistry then - AceEvent.delayRegistry = {} - delayRegistry = AceEvent.delayRegistry - AceEvent.frame:SetScript("OnUpdate", OnUpdate) - end - local t - if useTablesAsIDs and type(id) == "table" then - for k in pairs(id) do - id[k] = nil - end - t = id - for i = 2, select('#', ...) do - t[i-1] = select(i, ...) - end - t.n = select('#', ...) - 1 - elseif id then - t = new(select(2, ...)) - t.n = select('#', ...) - 1 - else - t = new(...) - t.n = select('#', ...) - end - t.event = event - t.time = GetTime() + delay - t.self = self - t.id = id or t - t.repeatDelay = repeating and delay - delayRegistry[t.id] = t - AceEvent.frame:Show() - if useTablesAsIDs then - return t.id - else - return - end -end - ---[[---------------------------------------------------------------------------------- -Notes: - * Schedule an event to fire. - * To fire on the next frame, specify a delay of 0. -Arguments: - string or function - name of the event to fire, or a function to call. - number - the amount of time to wait until calling. - tuple - a list of arguments to pass along. -------------------------------------------------------------------------------------]] -function AceEvent:ScheduleEvent(event, delay, ...) - if type(event) == "string" or (useTablesAsIDs and type(event) == "table") then - if useTablesAsIDs and type(event) == "table" then - if not delayRegistry or not delayRegistry[event] then - AceEvent:error("Bad argument #2 to `ScheduleEvent'. Improper id table fed in.") - end - end - if type(delay) ~= "number" then - AceEvent:argCheck(delay, 3, "string", "function", --[[ so message is right ]] "number") - AceEvent:argCheck(..., 4, "number") - end - else - AceEvent:argCheck(event, 2, "string", "function") - AceEvent:argCheck(delay, 3, "number") - end - - return ScheduleEvent(self, false, event, delay, ...) -end - -function AceEvent:ScheduleRepeatingEvent(event, delay, ...) - if type(event) == "string" or (useTablesAsIDs and type(event) == "table") then - if useTablesAsIDs and type(event) == "table" then - if not delayRegistry or not delayRegistry[event] then - AceEvent:error("Bad argument #2 to `ScheduleEvent'. Improper id table fed in.") - end - end - if type(delay) ~= "number" then - AceEvent:argCheck(delay, 3, "string", "function", --[[ so message is right ]] "number") - AceEvent:argCheck(..., 4, "number") - end - else - AceEvent:argCheck(event, 2, "string", "function") - AceEvent:argCheck(delay, 3, "number") - end - - return ScheduleEvent(self, true, event, delay, ...) -end - -function AceEvent:CancelScheduledEvent(t) - if useTablesAsIDs then - AceEvent:argCheck(t, 2, "string", "table") - else - AceEvent:argCheck(t, 2, "string") - end - if delayRegistry then - local v = delayRegistry[t] - if v then - if not useTablesAsIDs or type(t) == "string" then - del(delayRegistry[t]) - end - delayRegistry[t] = nil - if not next(delayRegistry) then - AceEvent.frame:Hide() - end - return true - end - end - return false -end - -function AceEvent:IsEventScheduled(t) - if useTablesAsIDs then - AceEvent:argCheck(t, 2, "string", "table") - else - AceEvent:argCheck(t, 2, "string") - end - if delayRegistry then - local v = delayRegistry[t] - if v then - return true, v.time - GetTime() - end - end - return false, nil -end - -function AceEvent:UnregisterEvent(event) - AceEvent:argCheck(event, 2, "string") - local AceEvent_registry = AceEvent.registry - if AceEvent_registry[event] and AceEvent_registry[event][self] then - AceEvent_registry[event][self] = nil - local AceEvent_onceRegistry = AceEvent.onceRegistry - if AceEvent_onceRegistry and AceEvent_onceRegistry[event] and AceEvent_onceRegistry[event][self] then - AceEvent_onceRegistry[event][self] = nil - if not next(AceEvent_onceRegistry[event]) then - AceEvent_onceRegistry[event] = del(AceEvent_onceRegistry[event]) - end - end - local AceEvent_throttleRegistry = AceEvent.throttleRegistry - if AceEvent_throttleRegistry and AceEvent_throttleRegistry[event] and AceEvent_throttleRegistry[event][self] then - AceEvent_throttleRegistry[event][self] = nil - if not next(AceEvent_throttleRegistry[event]) then - AceEvent_throttleRegistry[event] = del(AceEvent_throttleRegistry[event]) - end - end - if not next(AceEvent_registry[event]) then - AceEvent_registry[event] = del(AceEvent_registry[event]) - if not AceEvent_registry[ALL_EVENTS] or not next(AceEvent_registry[ALL_EVENTS]) then - AceEvent.frame:UnregisterEvent(event) - end - end - else - if self == AceEvent then - error(("Cannot unregister event %q. Improperly unregistering from AceEvent-2.0."):format(event), 2) - else - AceEvent:error("Cannot unregister event %q. %q is not registered with it.", event, self) - end - end - AceEvent:TriggerEvent("AceEvent_EventUnregistered", self, event) -end - -function AceEvent:UnregisterAllEvents() - local AceEvent_registry = AceEvent.registry - if AceEvent_registry[ALL_EVENTS] and AceEvent_registry[ALL_EVENTS][self] then - AceEvent_registry[ALL_EVENTS][self] = nil - if not next(AceEvent_registry[ALL_EVENTS]) then - AceEvent_registry[ALL_EVENTS] = del(AceEvent_registry[ALL_EVENTS]) - AceEvent.frame:UnregisterAllEvents() - for k,v in pairs(AceEvent_registry) do - AceEvent.frame:RegisterEvent(k) - end - end - end - if AceEvent_registry.AceEvent_EventUnregistered then - local event, data = "AceEvent_EventUnregistered", AceEvent_registry.AceEvent_EventUnregistered - local x = data[self] - data[self] = nil - if x then - if not next(data) then - if not AceEvent_registry[ALL_EVENTS] then - AceEvent.frame:UnregisterEvent(event) - end - AceEvent_registry[event] = del(AceEvent_registry[event]) - end - AceEvent:TriggerEvent("AceEvent_EventUnregistered", self, event) - end - end - for event, data in pairs(AceEvent_registry) do - local x = data[self] - data[self] = nil - if x and event ~= ALL_EVENTS then - if not next(data) then - if not AceEvent_registry[ALL_EVENTS] then - AceEvent.frame:UnregisterEvent(event) - end - AceEvent_registry[event] = del(AceEvent_registry[event]) - end - AceEvent:TriggerEvent("AceEvent_EventUnregistered", self, event) - end - end - if AceEvent.onceRegistry then - for event, data in pairs(AceEvent.onceRegistry) do - data[self] = nil - end - end -end - -function AceEvent:CancelAllScheduledEvents() - if delayRegistry then - for k,v in pairs(delayRegistry) do - if v.self == self then - if not useTablesAsIDs or type(k) == "string" then - del(delayRegistry[k]) - end - delayRegistry[k] = nil - end - end - if not next(delayRegistry) then - AceEvent.frame:Hide() - end - end -end - -function AceEvent:IsEventRegistered(event) - AceEvent:argCheck(event, 2, "string") - local AceEvent_registry = AceEvent.registry - if self == AceEvent then - return AceEvent_registry[event] and next(AceEvent_registry[event]) or AceEvent_registry[ALL_EVENTS] and next(AceEvent_registry[ALL_EVENTS]) and true or false - end - if AceEvent_registry[event] and AceEvent_registry[event][self] then - return true, AceEvent_registry[event][self] - end - if AceEvent_registry[ALL_EVENTS] and AceEvent_registry[ALL_EVENTS][self] then - return true, AceEvent_registry[ALL_EVENTS][self] - end - return false, nil -end - -local UnitExists = UnitExists -local bucketfunc -function AceEvent:RegisterBucketEvent(event, delay, method, ...) - AceEvent:argCheck(event, 2, "string", "table") - if type(event) == "table" then - for k,v in pairs(event) do - if type(k) ~= "number" then - AceEvent:error("All keys to argument #2 to `RegisterBucketEvent' must be numbers.") - elseif type(v) ~= "string" then - AceEvent:error("All values to argument #2 to `RegisterBucketEvent' must be strings.") - end - end - end - AceEvent:argCheck(delay, 3, "number") - if AceEvent == self then - AceEvent:argCheck(method, 4, "function") - self = method - else - if type(event) == "string" then - AceEvent:argCheck(method, 4, "string", "function", "nil") - if not method then - method = event - end - else - AceEvent:argCheck(method, 4, "string", "function") - end - - if type(method) == "string" and type(self[method]) ~= "function" then - AceEvent:error("Cannot register event %q to method %q, it does not exist", event, method) - end - end - local buckets = AceEvent.buckets - if not buckets[event] then - buckets[event] = new() - end - if not buckets[event][self] then - local t = new() - t.current = new() - t.self = self - buckets[event][self] = t - else - AceEvent.CancelScheduledEvent(self, buckets[event][self].id) - end - local bucket = buckets[event][self] - bucket.method = method - - local n = select('#', ...) - if n > 0 then - for i = 1, n do - bucket[i] = select(i, ...) - end - end - bucket.n = n - - local func = function(arg1) - bucket.run = true - if arg1 then - bucket.current[arg1] = true - end - end - buckets[event][self].func = func - local isUnitBucket = true - if type(event) == "string" then - AceEvent.RegisterEvent(self, event, func) - if not event:find("^UNIT_") then - isUnitBucket = false - end - else - for _,v in ipairs(event) do - AceEvent.RegisterEvent(self, v, func) - if isUnitBucket and not v:find("^UNIT_") then - isUnitBucket = false - end - end - end - bucket.unit = isUnitBucket - if not bucketfunc then - bucketfunc = function(bucket) - local current = bucket.current - local method = bucket.method - local self = bucket.self - if bucket.run then - if bucket.unit then - for unit in pairs(current) do - if not UnitExists(unit) then - current[unit] = nil - end - end - end - if type(method) == "string" then - self[method](self, current, unpack(bucket, 1, bucket.n)) - elseif method then -- function - method(current, unpack(bucket, 1, bucket.n)) - end - for k in pairs(current) do - current[k] = nil - k = nil - end - bucket.run = false - end - end - end - bucket.id = "AceEvent-Bucket-" .. tostring(bucket) - AceEvent.ScheduleRepeatingEvent(self, bucket.id, bucketfunc, delay, bucket) -end - -function AceEvent:IsBucketEventRegistered(event) - AceEvent:argCheck(event, 2, "string", "table") - return AceEvent.buckets and AceEvent.buckets[event] and AceEvent.buckets[event][self] -end - -function AceEvent:UnregisterBucketEvent(event) - AceEvent:argCheck(event, 2, "string", "table") - if not AceEvent.buckets or not AceEvent.buckets[event] or not AceEvent.buckets[event][self] then - AceEvent:error("Cannot unregister bucket event %q. %q is not registered with it.", event, self) - end - - local bucket = AceEvent.buckets[event][self] - - if type(event) == "string" then - AceEvent.UnregisterEvent(self, event) - else - for _,v in ipairs(event) do - AceEvent.UnregisterEvent(self, v) - end - end - AceEvent:CancelScheduledEvent(bucket.id) - - bucket.current = del(bucket.current) - AceEvent.buckets[event][self] = del(bucket) - if not next(AceEvent.buckets[event]) then - AceEvent.buckets[event] = del(AceEvent.buckets[event]) - end -end - -function AceEvent:UnregisterAllBucketEvents() - if not AceEvent.buckets or not next(AceEvent.buckets) then - return - end - for k,v in pairs(AceEvent.buckets) do - if v == self then - AceEvent.UnregisterBucketEvent(self, k) - k = nil - end - end -end - -local combatSchedules -function AceEvent:CancelAllCombatSchedules() - local i = 0 - while true do - i = i + 1 - if not combatSchedules[i] then - break - end - local v = combatSchedules[i] - if v.self == self then - v = del(v) - table.remove(combatSchedules, i) - i = i - 1 - end - end -end - -local inCombat = false - -function AceEvent:PLAYER_REGEN_DISABLED() - inCombat = true -end - -do - local tmp = {} - function AceEvent:PLAYER_REGEN_ENABLED() - inCombat = false - for i, v in ipairs(combatSchedules) do - tmp[i] = v - combatSchedules[i] = nil - end - for i, v in ipairs(tmp) do - local func = v.func - if func then - local success, err = pcall(func, unpack(v, 1, v.n)) - if not success then geterrorhandler()(err:find("%.lua:%d+:") and err or (debugstack():match("(.-: )in.-\n") or "") .. err) end - else - local obj = v.obj or v.self - local method = v.method - local obj_method = obj[method] - if obj_method then - local success, err = pcall(obj_method, obj, unpack(v, 1, v.n)) - if not success then geterrorhandler()(err:find("%.lua:%d+:") and err or (debugstack():match("(.-: )in.-\n") or "") .. err) end - end - end - tmp[i] = del(v) - end - end -end - -function AceEvent:ScheduleLeaveCombatAction(method, ...) - local style = type(method) - if self == AceEvent then - if style == "table" then - local func = (...) - AceEvent:argCheck(func, 3, "string") - if type(method[func]) ~= "function" then - AceEvent:error("Cannot schedule a combat action to method %q, it does not exist", func) - end - else - AceEvent:argCheck(method, 2, "function", --[[so message is right]] "table") - end - self = method - else - AceEvent:argCheck(method, 2, "function", "string", "table") - if style == "string" and type(self[method]) ~= "function" then - AceEvent:error("Cannot schedule a combat action to method %q, it does not exist", method) - elseif style == "table" then - local func = (...) - AceEvent:argCheck(func, 3, "string") - if type(method[func]) ~= "function" then - AceEvent:error("Cannot schedule a combat action to method %q, it does not exist", func) - end - end - end - - if not inCombat then - local success, err - if type(method) == "function" then - success, err = pcall(method, ...) - elseif type(method) == "table" then - local func = (...) - success, err = pcall(method[func], method, select(2, ...)) - else - success, err = pcall(self[method], self, ...) - end - if not success then geterrorhandler()(err:find("%.lua:%d+:") and err or (debugstack():match("(.-: )in.-\n") or "") .. err) end - return - end - local t - local n = select('#', ...) - if style == "table" then - t = new(select(2, ...)) - t.obj = method - t.method = (...) - t.n = n-1 - else - t = new(...) - t.n = n - if style == "function" then - t.func = method - else - t.method = method - end - end - t.self = self - table.insert(combatSchedules, t) -end - -function AceEvent:OnEmbedDisable(target) - self.UnregisterAllEvents(target) - - self.CancelAllScheduledEvents(target) - - self.UnregisterAllBucketEvents(target) - - self.CancelAllCombatSchedules(target) -end - -function AceEvent:IsFullyInitialized() - return self.postInit or false -end - -function AceEvent:IsPostPlayerLogin() - return IsLoggedIn() and true or false -end - -local function activate(self, oldLib, oldDeactivate) - AceEvent = self - - self.onceRegistry = oldLib and oldLib.onceRegistry or {} - self.throttleRegistry = oldLib and oldLib.throttleRegistry or {} - self.delayRegistry = oldLib and oldLib.delayRegistry or {} - self.buckets = oldLib and oldLib.buckets or {} - self.registry = oldLib and oldLib.registry or {} - self.frame = oldLib and oldLib.frame or CreateFrame("Frame", "AceEvent20Frame") - self.playerLogin = IsLoggedIn() and true - self.postInit = oldLib and oldLib.postInit or self.playerLogin and ChatTypeInfo and ChatTypeInfo.WHISPER and ChatTypeInfo.WHISPER.r and true - self.ALL_EVENTS = oldLib and oldLib.ALL_EVENTS or _G.newproxy() - self.FAKE_NIL = oldLib and oldLib.FAKE_NIL or _G.newproxy() - self.RATE = oldLib and oldLib.RATE or _G.newproxy() - self.combatSchedules = oldLib and oldLib.combatSchedules or {} - self.UID_NUM = oldLib and oldLib.UID_NUM or 0 - - -- Delete this down the road. Makes sure that the addonframes from revisions 33121 - 36174 get their events unregistered. - local addonframes = oldLib and oldLib.addonframes - if addonframes then - for _, v in pairs(addonframes) do - v:UnregisterAllEvents() - end - end - - combatSchedules = self.combatSchedules - ALL_EVENTS = self.ALL_EVENTS - FAKE_NIL = self.FAKE_NIL - RATE = self.RATE - local inPlw = false - local blacklist = { - UNIT_INVENTORY_CHANGED = true, - BAG_UPDATE = true, - ITEM_LOCK_CHANGED = true, - ACTIONBAR_SLOT_CHANGED = true, - } - self.frame:SetScript("OnEvent", function(_, event, ...) - if event == "PLAYER_ENTERING_WORLD" then - inPlw = false - elseif event == "PLAYER_LEAVING_WORLD" then - inPlw = true - end - if event and (not inPlw or not blacklist[event]) then - self:TriggerEvent(event, ...) - end - end) - if self.delayRegistry then - delayRegistry = self.delayRegistry - self.frame:SetScript("OnUpdate", OnUpdate) - end - - self:UnregisterAllEvents() - self:CancelAllScheduledEvents() - - registeringFromAceEvent = true - self:RegisterEvent("LOOT_OPENED", function() - SendAddonMessage("LOOT_OPENED", "", "RAID") - end) - registeringFromAceEvent = nil - - local function handleFullInit() - if not self.postInit then - local function func() - self.postInit = true - self:TriggerEvent("AceEvent_FullyInitialized") - if self.registry["CHAT_MSG_CHANNEL_NOTICE"] and self.registry["CHAT_MSG_CHANNEL_NOTICE"][self] then - self:UnregisterEvent("CHAT_MSG_CHANNEL_NOTICE") - end - if self.registry["MEETINGSTONE_CHANGED"] and self.registry["MEETINGSTONE_CHANGED"][self] then - self:UnregisterEvent("MEETINGSTONE_CHANGED") - end - if self.registry["MINIMAP_ZONE_CHANGED"] and self.registry["MINIMAP_ZONE_CHANGED"][self] then - self:UnregisterEvent("MINIMAP_ZONE_CHANGED") - end - if self.registry["LANGUAGE_LIST_CHANGED"] and self.registry["LANGUAGE_LIST_CHANGED"][self] then - self:UnregisterEvent("LANGUAGE_LIST_CHANGED") - end - collectgarbage('collect') - end - registeringFromAceEvent = true - local f = function() - self.playerLogin = true - self:ScheduleEvent("AceEvent_FullyInitialized", func, 1) - end - self:RegisterEvent("MEETINGSTONE_CHANGED", f, true) - self:RegisterEvent("CHAT_MSG_CHANNEL_NOTICE", function() - self:ScheduleEvent("AceEvent_FullyInitialized", func, 0.15) - end) - self:RegisterEvent("LANGUAGE_LIST_CHANGED", function() - if self.registry["MEETINGSTONE_CHANGED"] and self.registry["MEETINGSTONE_CHANGED"][self] then - registeringFromAceEvent = true - self:UnregisterEvent("MEETINGSTONE_CHANGED") - self:RegisterEvent("MINIMAP_ZONE_CHANGED", fd, true) - registeringFromAceEvent = nil - end - end) - self:ScheduleEvent("AceEvent_FullyInitialized", func, 10) - registeringFromAceEvent = nil - end - end - - if not self.playerLogin then - registeringFromAceEvent = true - self:RegisterEvent("PLAYER_LOGIN", function() - self.playerLogin = true - handleFullInit() - handleFullInit = nil - collectgarbage('collect') - end, true) - registeringFromAceEvent = nil - else - handleFullInit() - handleFullInit = nil - end - - if not AceEvent20EditBox then - CreateFrame("Editbox", "AceEvent20EditBox") - end - local editbox = AceEvent20EditBox - function editbox:Execute(line) - local defaulteditbox = DEFAULT_CHAT_FRAME.editBox - self:SetAttribute("chatType", defaulteditbox:GetAttribute("chatType")) - self:SetAttribute("tellTarget", defaulteditbox:GetAttribute("tellTarget")) - self:SetAttribute("channelTarget", defaulteditbox:GetAttribute("channelTarget")) - self:SetText(line) - ChatEdit_SendText(self) - end - editbox:Hide() - _G["SLASH_IN1"] = "/in" - SlashCmdList["IN"] = function(msg) - local seconds, command, rest = msg:match("^([^%s]+)%s+(/[^%s]+)(.*)$") - seconds = tonumber(seconds) - if not seconds then - DEFAULT_CHAT_FRAME:AddMessage("Error, bad arguments to /in. Must be in the form of `/in 5 /say hi'") - return - end - if IsSecureCmd(command) then - DEFAULT_CHAT_FRAME:AddMessage(("Error, /in cannot call secure command: %s"):format(command)) - return - end - self:ScheduleEvent("AceEventSlashIn-" .. math.random(1, 1000000000), editbox.Execute, seconds, editbox, command .. rest) - end - registeringFromAceEvent = true - self:RegisterEvent("PLAYER_REGEN_ENABLED") - self:RegisterEvent("PLAYER_REGEN_DISABLED") - inCombat = InCombatLockdown() - registeringFromAceEvent = nil - - -- another hack to make sure that we clean up properly from rev 33121 - 36174 - if self.registry[ALL_EVENTS] then - self.frame:RegisterAllEvents() - else - for event in pairs(self.registry) do - self.frame:RegisterEvent(event) - end - end - - self:activate(oldLib, oldDeactivate) - if oldLib then - oldDeactivate(oldLib) - end -end - -AceLibrary:Register(AceEvent, MAJOR_VERSION, MINOR_VERSION, activate)
--- a/modules/FuBar_ReActionFu/lib/AceEvent-2.0/AceEvent-2.0.toc Wed Apr 02 18:22:02 2008 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,12 +0,0 @@ -## Interface: 20200 - -## Title: Lib: AceEvent-2.0 -## Notes: AddOn development framework -## Author: Ace Development Team -## LoadOnDemand: 1 -## X-Website: http://www.wowace.com -## X-Category: Library -## X-License: LGPL v2.1 + MIT for AceOO-2.0 -## Dependencies: AceLibrary, AceOO-2.0 - -AceEvent-2.0.lua
--- a/modules/FuBar_ReActionFu/lib/AceOO-2.0/AceOO-2.0.lua Wed Apr 02 18:22:02 2008 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,980 +0,0 @@ ---[[ -Name: AceOO-2.0 -Revision: $Rev: 38641 $ -Developed by: The Ace Development Team (http://www.wowace.com/index.php/The_Ace_Development_Team) -Inspired By: Ace 1.x by Turan (turan@gryphon.com) -Website: http://www.wowace.com/ -Documentation: http://www.wowace.com/index.php/AceOO-2.0 -SVN: http://svn.wowace.com/root/trunk/Ace2/AceOO-2.0 -Description: Library to provide an object-orientation framework. -Dependencies: AceLibrary -License: MIT -]] - -local MAJOR_VERSION = "AceOO-2.0" -local MINOR_VERSION = "$Revision: 38641 $" - --- This ensures the code is only executed if the libary doesn't already exist, or is a newer version -if not AceLibrary then error(MAJOR_VERSION .. " requires AceLibrary.") end -if not AceLibrary:IsNewVersion(MAJOR_VERSION, MINOR_VERSION) then return end - -local AceOO = { - error = AceLibrary.error, - argCheck = AceLibrary.argCheck -} - --- @function getuid --- @brief Obtain a unique string identifier for the object in question. --- @param t The object to obtain the uid for. --- @return The uid string. -local function getuid(t) - local mt = getmetatable(t) - setmetatable(t, nil) - local str = tostring(t) - setmetatable(t, mt) - local cap = str:match("[^:]*: 0x(.*)$") or str:match("[^:]*: (.*)$") - if cap then - return ("0"):rep(8 - #cap) .. cap - end -end - -local function getlibrary(o) - if type(o) == "table" then - return o - elseif type(o) == "string" then - if not AceLibrary:HasInstance(o) then - AceOO:error("Library %q does not exist.", o) - end - return AceLibrary(o) - end -end - -local function deeprawget(self, k) - while true do - local v = rawget(self, k) - if v ~= nil then - return v - end - local mt = getmetatable(self) - if not mt or type(mt.__index) ~= "table" then - return nil - end - self = mt.__index - end -end - --- @function Factory --- @brief Construct a factory for the creation of objects. --- @param obj The object whose init method will be called on the new factory --- object. --- @param newobj The object whose init method will be called on the new --- objects that the Factory creates, to initialize them. --- @param (...) Arguments which will be passed to obj.init() in addition --- to the Factory object. --- @return The new factory which creates a newobj when its new method is called, --- or when it is called directly (__call metamethod). -local Factory -do - local function getlibraries(...) - if select('#', ...) == 0 then - return - end - return getlibrary((select(1, ...))), getlibraries(select(2, ...)) - end - local arg = {} - local function new(obj, ...) - local t = {} - local uid = getuid(t) - obj:init(t, getlibraries(...)) - t.uid = uid - return t - end - - local function createnew(self, ...) - local o = self.prototype - local x = new(o, getlibraries(...)) - return x - end - - function Factory(obj, newobj, ...) - local t = new(obj, ...) - t.prototype = newobj - t.new = createnew - getmetatable(t).__call = t.new - return t - end -end - - -local function objtostring(self) - if self.ToString then - return self:ToString() - elseif self.GetLibraryVersion then - return (self:GetLibraryVersion()) - elseif self.super then - local s = "Sub-" .. tostring(self.super) - local first = true - if self.interfaces then - for interface in pairs(self.interfaces) do - if first then - s = s .. "(" .. tostring(interface) - first = false - else - s = s .. ", " .. tostring(interface) - end - end - end - if self.mixins then - for mixin in pairs(self.mixins) do - if first then - s = s .. tostring(mixin) - first = false - else - s = s .. ", " .. tostring(mixin) - end - end - end - if first then - if self.uid then - return s .. ":" .. self.uid - else - return s - end - else - return s .. ")" - end - else - return self.uid and 'Subclass:' .. self.uid or 'Subclass' - end -end - --- @table Object --- @brief Base of all objects, including Class. --- --- @method init --- @brief Initialize a new object. --- @param newobject The object to initialize --- @param class The class to make newobject inherit from -local Object -do - Object = {} - function Object:init(newobject, class) - local parent = class or self - if not rawget(newobject, 'uid') then - newobject.uid = getuid(newobject) - end - local mt = { - __index = parent, - __tostring = objtostring, - } - setmetatable(newobject, mt) - end - Object.uid = getuid(Object) - setmetatable(Object, { __tostring = function() return 'Object' end }) -end - -local Interface - -local function validateInterface(object, interface) - if not object.class and object.prototype then - object = object.prototype - end - for k,v in pairs(interface.interface) do - if tostring(type(object[k])) ~= v then - return false - end - end - if interface.superinterfaces then - for superinterface in pairs(interface.superinterfaces) do - if not validateInterface(object, superinterface) then - return false - end - end - end - if type(object.class) == "table" and rawequal(object.class.prototype, object) then - if not object.class.interfaces then - rawset(object.class, 'interfaces', {}) - end - object.class.interfaces[interface] = true - elseif type(object.class) == "table" and type(object.class.prototype) == "table" then - validateInterface(object.class.prototype, interface) - -- check if class is proper, thus preventing future checks. - end - return true -end - --- @function inherits --- @brief Return whether an Object or Class inherits from a given --- parent. --- @param object Object or Class to check --- @param parent Parent to test inheritance from --- @return whether an Object or Class inherits from a given --- parent. -local function inherits(object, parent) - object = getlibrary(object) - if type(parent) == "string" then - if not AceLibrary:HasInstance(parent) then - return false - else - parent = AceLibrary(parent) - end - end - AceOO:argCheck(parent, 2, "table") - if type(object) ~= "table" then - return false - end - local current - local class = deeprawget(object, 'class') - if class then - current = class - else - current = object - end - if type(current) ~= "table" then - return false - end - if rawequal(current, parent) then - return true - end - if parent.class then - while true do - if rawequal(current, Object) then - break - end - if current.mixins then - for mixin in pairs(current.mixins) do - if rawequal(mixin, parent) then - return true - end - end - end - if current.interfaces then - for interface in pairs(current.interfaces) do - if rawequal(interface, parent) then - return true - end - end - end - current = deeprawget(current, 'super') - if type(current) ~= "table" then - break - end - end - - local isInterface = false - local curr = parent.class - while true do - if rawequal(curr, Object) then - break - elseif rawequal(curr, Interface) then - isInterface = true - break - end - curr = deeprawget(curr, 'super') - if type(curr) ~= "table" then - break - end - end - return isInterface and validateInterface(object, parent) - else - while true do - if rawequal(current, parent) then - return true - elseif rawequal(current, Object) then - return false - end - current = deeprawget(current, 'super') - if type(current) ~= "table" then - return false - end - end - end -end - --- @table Class --- @brief An object factory which sets up inheritence and supports --- 'mixins'. --- --- @metamethod Class call --- @brief Call ClassFactory:new() to create a new class. --- --- @method Class new --- @brief Construct a new object. --- @param (...) Arguments to pass to the object init function. --- @return The new object. --- --- @method Class init --- @brief Initialize a new class. --- @param parent Superclass. --- @param (...) Mixins. --- --- @method Class ToString --- @return A string representing the object, in this case 'Class'. -local initStatus -local Class -local Mixin -local autoEmbed = false -local function traverseInterfaces(bit, total) - if bit.superinterfaces then - for interface in pairs(bit.superinterfaces) do - if not total[interface] then - total[interface] = true - traverseInterfaces(interface, total) - end - end - end -end -local class_new -do - Class = Factory(Object, setmetatable({}, {__index = Object}), Object) - Class.super = Object - - local function protostring(t) - return '<' .. tostring(t.class) .. ' prototype>' - end - local function classobjectstring(t) - if t.ToString then - return t:ToString() - elseif t.GetLibraryVersion then - return (t:GetLibraryVersion()) - else - return '<' .. tostring(t.class) .. ' instance>' - end - end - local function classobjectequal(self, other) - if type(self) == "table" and self.Equals then - return self:Equals(other) - elseif type(other) == "table" and other.Equals then - return other:Equals(self) - elseif type(self) == "table" and self.CompareTo then - return self:CompareTo(other) == 0 - elseif type(other) == "table" and other.CompareTo then - return other:CompareTo(self) == 0 - else - return rawequal(self, other) - end - end - local function classobjectlessthan(self, other) - if type(self) == "table" and self.IsLessThan then - return self:IsLessThan(other) - elseif type(other) == "table" and other.IsLessThanOrEqualTo then - return not other:IsLessThanOrEqualTo(self) - elseif type(self) == "table" and self.CompareTo then - return self:CompareTo(other) < 0 - elseif type(other) == "table" and other.CompareTo then - return other:CompareTo(self) > 0 - elseif type(other) == "table" and other.IsLessThan and other.Equals then - return other:Equals(self) or other:IsLessThan(self) - else - AceOO:error("cannot compare two objects") - end - end - local function classobjectlessthanequal(self, other) - if type(self) == "table" and self.IsLessThanOrEqualTo then - return self:IsLessThanOrEqualTo(other) - elseif type(other) == "table" and other.IsLessThan then - return not other:IsLessThan(self) - elseif type(self) == "table" and self.CompareTo then - return self:CompareTo(other) <= 0 - elseif type(other) == "table" and other.CompareTo then - return other:CompareTo(self) >= 0 - elseif type(self) == "table" and self.IsLessThan and self.Equals then - return self:Equals(other) or self:IsLessThan(other) - else - AceOO:error("cannot compare two incompatible objects") - end - end - local function classobjectadd(self, other) - if type(self) == "table" and self.Add then - return self:Add(other) - else - AceOO:error("cannot add two incompatible objects") - end - end - local function classobjectsub(self, other) - if type(self) == "table" and self.Subtract then - return self:Subtract(other) - else - AceOO:error("cannot subtract two incompatible objects") - end - end - local function classobjectunm(self, other) - if type(self) == "table" and self.UnaryNegation then - return self:UnaryNegation(other) - else - AceOO:error("attempt to negate an incompatible object") - end - end - local function classobjectmul(self, other) - if type(self) == "table" and self.Multiply then - return self:Multiply(other) - else - AceOO:error("cannot multiply two incompatible objects") - end - end - local function classobjectdiv(self, other) - if type(self) == "table" and self.Divide then - return self:Divide(other) - else - AceOO:error("cannot divide two incompatible objects") - end - end - local function classobjectpow(self, other) - if type(self) == "table" and self.Exponent then - return self:Exponent(other) - else - AceOO:error("cannot exponentiate two incompatible objects") - end - end - local function classobjectconcat(self, other) - if type(self) == "table" and self.Concatenate then - return self:Concatenate(other) - else - AceOO:error("cannot concatenate two incompatible objects") - end - end - function class_new(self, ...) - if self.virtual then - AceOO:error("Cannot instantiate a virtual class.") - end - - local o = self.prototype - local newobj = {} - if o.class and o.class.instancemeta then - setmetatable(newobj, o.class.instancemeta) - else - Object:init(newobj, o) - end - - if self.interfaces and not self.interfacesVerified then - -- Verify the interfaces - - for interface in pairs(self.interfaces) do - for field,kind in pairs(interface.interface) do - if tostring(type(newobj[field])) ~= kind then - AceOO:error("Class did not satisfy all interfaces. %q is required to be a %s. It is a %s", field, kind, tostring(type(newobj[field]))) - end - end - end - self.interfacesVerified = true - end - local tmp = initStatus - initStatus = newobj - newobj:init(...) - if initStatus then - initStatus = tmp - AceOO:error("Initialization not completed, be sure to call the superclass's init method.") - return - end - initStatus = tmp - return newobj - end - local classmeta = { - __tostring = objtostring, - __call = function(self, ...) - return self:new(...) - end, - } - function Class:init(newclass, parent, ...) - parent = parent or self - - local total - - if parent.class then - total = { parent, ... } - parent = self - else - total = { ... } - end - if not inherits(parent, Class) then - AceOO:error("Classes must inherit from a proper class") - end - if parent.sealed then - AceOO:error("Cannot inherit from a sealed class") - end - for i,v in ipairs(total) do - if inherits(v, Mixin) and v.class then - if v.__deprecated then - AceOO:error(v.__deprecated) - end - if not newclass.mixins then - newclass.mixins = {} - end - if newclass.mixins[v] then - AceOO:error("Cannot explicitly inherit from the same mixin twice") - end - newclass.mixins[v] = true - elseif inherits(v, Interface) and v.class then - if not newclass.interfaces then - newclass.interfaces = {} - end - if newclass.interfaces[v] then - AceOO:error("Cannot explicitly inherit from the same interface twice") - end - newclass.interfaces[v] = true - else - AceOO:error("Classes can only inherit from one or zero classes and any number of mixins or interfaces") - end - end - if parent.interfaces then - if not newclass.interfaces then - newclass.interfaces = {} - end - for interface in pairs(parent.interfaces) do - newclass.interfaces[interface] = true - end - end - for k in pairs(total) do - total[k] = nil - end - - newclass.super = parent - - newclass.prototype = setmetatable(total, { - __index = parent.prototype, - __tostring = protostring, - }) - total = nil - - newclass.instancemeta = { - __index = newclass.prototype, - __tostring = classobjectstring, - __eq = classobjectequal, - __lt = classobjectlessthan, - __le = classobjectlessthanequal, - __add = classobjectadd, - __sub = classobjectsub, - __unm = classobjectunm, - __mul = classobjectmul, - __div = classobjectdiv, - __pow = classobjectpow, - __concat = classobjectconcat, - } - - setmetatable(newclass, classmeta) - - newclass.new = class_new - - if newclass.mixins then - -- Fold in the mixins - local err, msg - for mixin in pairs(newclass.mixins) do - local ret - autoEmbed = true - ret, msg = pcall(mixin.embed, mixin, newclass.prototype) - autoEmbed = false - if not ret then - err = true - break - end - end - - if err then - local pt = newclass.prototype - for k,v in pairs(pt) do - pt[k] = nil - end - - -- method conflict - AceOO:error(msg) - end - end - - newclass.prototype.class = newclass - - if newclass.interfaces then - for interface in pairs(newclass.interfaces) do - traverseInterfaces(interface, newclass.interfaces) - end - end - if newclass.mixins then - for mixin in pairs(newclass.mixins) do - if mixin.interfaces then - if not newclass.interfaces then - newclass.interfaces = {} - end - for interface in pairs(mixin.interfaces) do - newclass.interfaces[interface] = true - end - end - end - end - end - function Class:ToString() - if type(self.GetLibraryVersion) == "function" then - return (self:GetLibraryVersion()) - else - return "Class" - end - end - - local tmp - function Class.prototype:init() - if rawequal(self, initStatus) then - initStatus = nil - else - AceOO:error("Improper self passed to init. You must do MyClass.super.prototype.init(self, ...)", 2) - end - self.uid = getuid(self) - local current = self.class - while true do - if current == Class then - break - end - if current.mixins then - for mixin in pairs(current.mixins) do - if type(mixin.OnInstanceInit) == "function" then - mixin:OnInstanceInit(self) - end - end - end - current = current.super - end - end -end - - --- @object ClassFactory --- @brief A factory for creating classes. Rarely used directly. -local ClassFactory = Factory(Object, Class, Object) - -function Class:new(...) - local x = ClassFactory:new(...) - if AceOO.classes then - AceOO.classes[x] = true - end - return x -end -getmetatable(Class).__call = Class.new - --- @class Mixin --- @brief A class to create mixin objects, which contain methods that get --- "mixed in" to class prototypes. --- --- @object Mixin prototype --- @brief The prototype that mixin objects inherit their methods from. --- --- @method Mixin prototype embed --- @brief Mix in the methods of our object which are listed in our interface --- to the supplied target table. --- --- @method Mixin prototype init --- @brief Initialize the mixin object. --- @param newobj The new object we're initializing. --- @param interface The interface we implement (the list of methods our --- prototype provides which should be mixed into the target --- table by embed). -do - Mixin = Class() - function Mixin:ToString() - if self.GetLibraryVersion then - return (self:GetLibraryVersion()) - else - return 'Mixin' - end - end - local function _Embed(state, field, target) - field = next(state.export, field) - if field == nil then - return - end - - if rawget(target, field) or (target[field] and target[field] ~= state[field]) then - AceOO:error("Method conflict in attempt to mixin. Field %q", field) - end - - target[field] = state[field] - - local ret,msg = pcall(_Embed, state, field, target) - if not ret then - -- Mix in the next method according to the defined interface. If that - -- fails due to a conflict, re-raise to back out the previous mixed - -- methods. - - target[field] = nil - AceOO:error(msg) - end - end - function Mixin.prototype:embed(target) - if self.__deprecated then - AceOO:error(self.__deprecated) - end - local mt = getmetatable(target) - setmetatable(target, nil) - local err, msg = pcall(_Embed, self, nil, target) - if not err then - setmetatable(target, mt) - AceOO:error(msg) - return - end - if type(self.embedList) == "table" then - self.embedList[target] = true - end - if type(target.class) ~= "table" then - target[self] = true - end - if not autoEmbed and type(self.OnManualEmbed) == "function" then - self:OnManualEmbed(target) - end - setmetatable(target, mt) - end - - function Mixin.prototype:activate(oldLib, oldDeactivate) - if oldLib and oldLib.embedList then - for target in pairs(oldLib.embedList) do - local mt = getmetatable(target) - setmetatable(target, nil) - for field in pairs(oldLib.export) do - target[field] = nil - end - setmetatable(target, mt) - end - self.embedList = oldLib.embedList - for target in pairs(self.embedList) do - self:embed(target) - end - else - self.embedList = setmetatable({}, {__mode="k"}) - end - end - - function Mixin.prototype:init(export, ...) - AceOO:argCheck(export, 2, "table") - for k,v in pairs(export) do - if type(k) ~= "number" then - AceOO:error("All keys to argument #2 must be numbers.") - elseif type(v) ~= "string" then - AceOO:error("All values to argument #2 must be strings.") - end - end - local num = #export - for i = 1, num do - local v = export[i] - export[i] = nil - export[v] = true - end - - local interfaces - if select('#', ...) >= 1 then - interfaces = { ... } - for i,v in ipairs(interfaces) do - v = getlibrary(v) - interfaces[i] = v - if not v.class or not inherits(v, Interface) then - AceOO:error("Mixins can inherit only from interfaces") - end - end - local num = #interfaces - for i = 1, num do - local v = interfaces[i] - interfaces[i] = nil - interfaces[v] = true - end - for interface in pairs(interfaces) do - traverseInterfaces(interface, interfaces) - end - for interface in pairs(interfaces) do - for field,kind in pairs(interface.interface) do - if kind ~= "nil" then - local good = false - for bit in pairs(export) do - if bit == field then - good = true - break - end - end - if not good then - AceOO:error("Mixin does not fully accommodate field %q", field) - end - end - end - end - end - self.super = Mixin.prototype - Mixin.super.prototype.init(self) - self.export = export - self.interfaces = interfaces - end -end - --- @class Interface --- @brief A class to create interfaces, which contain contracts that classes --- which inherit from this must comply with. --- --- @object Interface prototype --- @brief The prototype that interface objects must adhere to. --- --- @method Interface prototype init --- @brief Initialize the mixin object. --- @param interface The interface we contract (the hash of fields forced). --- @param (...) Superinterfaces -do - Interface = Class() - function Interface:ToString() - if self.GetLibraryVersion then - return (self:GetLibraryVersion()) - else - return 'Instance' - end - end - function Interface.prototype:init(interface, ...) - Interface.super.prototype.init(self) - AceOO:argCheck(interface, 2, "table") - for k,v in pairs(interface) do - if type(k) ~= "string" then - AceOO:error("All keys to argument #2 must be numbers.") - elseif type(v) ~= "string" then - AceOO:error("All values to argument #2 must be strings.") - elseif v ~= "nil" and v ~= "string" and v ~= "number" and v ~= "table" and v ~= "function" then - AceOO:error('All values to argument #2 must either be "nil", "string", "number", "table", or "function".') - end - end - if select('#', ...) >= 1 then - self.superinterfaces = { ... } - for i,v in ipairs(self.superinterfaces) do - v = getlibrary(v) - self.superinterfaces[i] = v - if not inherits(v, Interface) or not v.class then - AceOO:error('Cannot provide a non-Interface to inherit from') - end - end - local num = #self.superinterfaces - for i = 1, num do - local v = self.superinterfaces[i] - self.superinterfaces[i] = nil - self.superinterfaces[v] = true - end - end - self.interface = interface - end -end - --- @function Classpool --- @brief Obtain a read only class from our pool of classes, indexed by the --- superclass and mixins. --- @param sc The superclass of the class we want. --- @param (m1..m20) Mixins of the class we want's objects. --- @return A read only class from the class pool. -local Classpool -do - local pool = setmetatable({}, {__mode = 'v'}) - local function newindex(k, v) - AceOO:error('Attempt to modify a read-only class.') - end - local function protonewindex(k, v) - AceOO:error('Attempt to modify a read-only class prototype.') - end - local function ts(bit) - if type(bit) ~= "table" then - return tostring(bit) - elseif getmetatable(bit) and bit.__tostring then - return tostring(bit) - elseif type(bit.GetLibraryVersion) == "function" then - return bit:GetLibraryVersion() - else - return tostring(bit) - end - end - local t = {} - local function getcomplexuid(sc, ...) - if sc then - if sc.uid then - table.insert(t, sc.uid) - else - AceOO:error("%s is not an appropriate class/mixin", ts(sc)) - end - end - for i = 1, select('#', ...) do - local m = select(i, ...) - if m.uid then - table.insert(t, m.uid) - else - AceOO:error("%s is not an appropriate mixin", ts(m)) - end - end - table.sort(t) - local uid = table.concat(t, '') - local num = #t - for i = 1, num do - t[i] = nil - end - return uid - end - local classmeta - local arg = {} - function Classpool(superclass, ...) - local l = getlibrary - superclass = getlibrary(superclass) - arg = { ... } - for i, v in ipairs(arg) do - arg[i] = getlibrary(v) - end - if superclass then - if superclass.class then -- mixin - table.insert(arg, 1, superclass) - superclass = Class - end - else - superclass = Class - end - local key = getcomplexuid(superclass, unpack(arg)) - if not pool[key] then - local class = Class(superclass, unpack(arg)) - if not classmeta then - classmeta = {} - local mt = getmetatable(class) - for k,v in pairs(mt) do - classmeta[k] = v - end - classmeta.__newindex = newindex - end - -- Prevent the user from adding methods to this class. - -- NOTE: I'm not preventing modifications of existing class members, - -- but it's likely that only a truly malicious user will be doing so. - class.sealed = true - setmetatable(class, classmeta) - getmetatable(class.prototype).__newindex = protonewindex - pool[key] = class - end - return pool[key] - end -end - -AceOO.Factory = Factory -AceOO.Object = Object -AceOO.Class = Class -AceOO.Mixin = Mixin -AceOO.Interface = Interface -AceOO.Classpool = Classpool -AceOO.inherits = inherits - --- Library handling bits - -local function activate(self, oldLib, oldDeactivate) - AceOO = self - Factory = self.Factory - Object = self.Object - Class = self.Class - ClassFactory.prototype = Class - Mixin = self.Mixin - Interface = self.Interface - Classpool = self.Classpool - - if oldLib then - self.classes = oldLib.classes - end - if not self.classes then - self.classes = setmetatable({}, {__mode="k"}) - else - for class in pairs(self.classes) do - class.new = class_new - end - end - - if oldDeactivate then - oldDeactivate(oldLib) - end -end - -AceLibrary:Register(AceOO, MAJOR_VERSION, MINOR_VERSION, activate) -AceOO = AceLibrary(MAJOR_VERSION)
--- a/modules/FuBar_ReActionFu/lib/AceOO-2.0/AceOO-2.0.toc Wed Apr 02 18:22:02 2008 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,12 +0,0 @@ -## Interface: 20200 - -## Title: Lib: AceOO-2.0 -## Notes: AddOn development framework -## Author: Ace Development Team -## LoadOnDemand: 1 -## X-Website: http://www.wowace.com -## X-Category: Library -## X-License: LGPL v2.1 + MIT for AceOO-2.0 -## Dependencies: AceLibrary - -AceOO-2.0.lua
--- a/modules/FuBar_ReActionFu/lib/Dewdrop-2.0/Dewdrop-2.0.lua Wed Apr 02 18:22:02 2008 +0000 +++ b/modules/FuBar_ReActionFu/lib/Dewdrop-2.0/Dewdrop-2.0.lua Wed Apr 02 23:31:13 2008 +0000 @@ -1,3487 +1,3487 @@ ---[[ -Name: Dewdrop-2.0 -Revision: $Rev: 48630 $ -Author(s): ckknight (ckknight@gmail.com) -Website: http://ckknight.wowinterface.com/ -Documentation: http://wiki.wowace.com/index.php/Dewdrop-2.0 -SVN: http://svn.wowace.com/root/trunk/DewdropLib/Dewdrop-2.0 -Description: A library to provide a clean dropdown menu interface. -Dependencies: AceLibrary -License: LGPL v2.1 -]] - -local MAJOR_VERSION = "Dewdrop-2.0" -local MINOR_VERSION = "$Revision: 48630 $" - -if not AceLibrary then error(MAJOR_VERSION .. " requires AceLibrary") end -if not AceLibrary:IsNewVersion(MAJOR_VERSION, MINOR_VERSION) then return end - -local Dewdrop = {} - -local SharedMedia - -local CLOSE = "Close" -local CLOSE_DESC = "Close the menu." -local VALIDATION_ERROR = "Validation error." -local USAGE_TOOLTIP = "Usage: %s." -local RANGE_TOOLTIP = "Note that you can scroll your mouse wheel while over the slider to step by one." -local RESET_KEYBINDING_DESC = "Hit escape to clear the keybinding." -local KEY_BUTTON1 = "Left Mouse" -local KEY_BUTTON2 = "Right Mouse" -local DISABLED = "Disabled" -local DEFAULT_CONFIRM_MESSAGE = "Are you sure you want to perform `%s'?" - -if GetLocale() == "deDE" then - CLOSE = "Schlie\195\159en" - CLOSE_DESC = "Men\195\188 schlie\195\159en." - VALIDATION_ERROR = "Validierungsfehler." - USAGE_TOOLTIP = "Benutzung: %s." - RANGE_TOOLTIP = "Beachte das du mit dem Mausrad scrollen kannst solange du \195\188ber dem Schieberegler bist, um 10er Spr\195\188nge zu machen." - RESET_KEYBINDING_DESC = "Escape dr\195\188cken, um die Tastenbelegung zu l\195\182schen." - KEY_BUTTON1 = "Linke Maustaste" - KEY_BUTTON2 = "Rechte Maustaste" - DISABLED = "Deaktiviert" - DEFAULT_CONFIRM_MESSAGE = "Bist du sicher das du `%s' machen willst?" -elseif GetLocale() == "koKR" then - CLOSE = "닫기" - CLOSE_DESC = "메뉴를 닫습니다." - VALIDATION_ERROR = "오류 확인." - USAGE_TOOLTIP = "사용법: %s." - RANGE_TOOLTIP = "알림 : 슬라이더 위에서 마우스 휠을 사용하면 한단계씩 조절할 수 있습니다." - RESET_KEYBINDING_DESC = "단축키를 해제하려면 ESC키를 누르세요." - KEY_BUTTON1 = "왼쪽 마우스" - KEY_BUTTON2 = "오른쪽 마우스" - DISABLED = "비활성화됨" - DEFAULT_CONFIRM_MESSAGE = "정말로 `%s' 실행을 하시겠습니까 ?" -elseif GetLocale() == "frFR" then - CLOSE = "Fermer" - CLOSE_DESC = "Ferme le menu." - VALIDATION_ERROR = "Erreur de validation." - USAGE_TOOLTIP = "Utilisation : %s." - RANGE_TOOLTIP = "Vous pouvez aussi utiliser la molette de la souris pour pour modifier progressivement." - RESET_KEYBINDING_DESC = "Appuyez sur la touche Echappement pour effacer le raccourci." - KEY_BUTTON1 = "Clic gauche" - KEY_BUTTON2 = "Clic droit" - DISABLED = "D\195\169sactiv\195\169" - DEFAULT_CONFIRM_MESSAGE = "\195\138tes-vous s\195\187r de vouloir effectuer '%s' ?" -elseif GetLocale() == "esES" then - CLOSE = "Cerrar" - CLOSE_DESC = "Cierra el menú." - VALIDATION_ERROR = "Error de validación." - USAGE_TOOLTIP = "Uso: %s." - RANGE_TOOLTIP = "Puedes desplazarte verticalmente con la rueda del ratón sobre el desplazador." - RESET_KEYBINDING_DESC = "Pulsa Escape para borrar la asignación de tecla." - KEY_BUTTON1 = "Clic Izquierdo" - KEY_BUTTON2 = "Clic Derecho" - DISABLED = "Desactivado" - DEFAULT_CONFIRM_MESSAGE = "¿Estás seguro de querer realizar `%s'?" -elseif GetLocale() == "zhTW" then - CLOSE = "關閉" - CLOSE_DESC = "關閉選單。" - VALIDATION_ERROR = "驗證錯誤。" - USAGE_TOOLTIP = "用法: %s。" - RANGE_TOOLTIP = "你可以在捲動條上使用滑鼠滾輪來捲動。" - RESET_KEYBINDING_DESC = "按Esc鍵清除快捷鍵。" - KEY_BUTTON1 = "滑鼠左鍵" - KEY_BUTTON2 = "滑鼠右鍵" - DISABLED = "停用" - DEFAULT_CONFIRM_MESSAGE = "是否執行「%s」?" -elseif GetLocale() == "zhCN" then - CLOSE = "关闭" - CLOSE_DESC = "关闭菜单" - VALIDATION_ERROR = "验证错误." - USAGE_TOOLTIP = "用法: %s." - RANGE_TOOLTIP = "你可以在滚动条上使用鼠标滚轮来翻页." - RESET_KEYBINDING_DESC = "按ESC键清除按键绑定" - KEY_BUTTON1 = "鼠标左键" - KEY_BUTTON2 = "鼠标右键" - DISABLED = "禁用" - DEFAULT_CONFIRM_MESSAGE = "是否执行'%s'?" -end - -Dewdrop.KEY_BUTTON1 = KEY_BUTTON1 -Dewdrop.KEY_BUTTON2 = KEY_BUTTON2 - -local function new(...) - local t = {} - for i = 1, select('#', ...), 2 do - local k = select(i, ...) - if k then - t[k] = select(i+1, ...) - else - break - end - end - return t -end - -local tmp -do - local t = {} - function tmp(...) - for k in pairs(t) do - t[k] = nil - end - for i = 1, select('#', ...), 2 do - local k = select(i, ...) - if k then - t[k] = select(i+1, ...) - else - break - end - end - return t - end -end -local tmp2 -do - local t = {} - function tmp2(...) - for k in pairs(t) do - t[k] = nil - end - for i = 1, select('#', ...), 2 do - local k = select(i, ...) - if k then - t[k] = select(i+1, ...) - else - break - end - end - return t - end -end -local levels -local buttons - - --- Secure frame handling: --- Rather than using secure buttons in the menu (has problems), we have one --- master secureframe that we pop onto menu items on mouseover. This requires --- some dark magic with OnLeave etc, but it's not too bad. - -local secureFrame = CreateFrame("Button", nil, nil, "SecureActionButtonTemplate") -secureFrame:Hide() - -local function secureFrame_Show(self) - local owner = self.owner - - if self.secure then -- Leftovers from previos owner, clean up! ("Shouldn't" happen but does..) - for k,v in pairs(self.secure) do - self:SetAttribute(k, nil) - end - end - self.secure = owner.secure; -- Grab hold of new secure data - - local scale = owner:GetEffectiveScale() - - self:SetPoint("TOPLEFT", nil, "BOTTOMLEFT", owner:GetLeft() * scale, owner:GetTop() * scale) - self:SetPoint("BOTTOMRIGHT", nil, "BOTTOMLEFT", owner:GetRight() * scale, owner:GetBottom() * scale) - self:EnableMouse(true) - for k,v in pairs(self.secure) do - self:SetAttribute(k, v) - end - - secureFrame:SetFrameStrata(owner:GetFrameStrata()) - secureFrame:SetFrameLevel(owner:GetFrameLevel()+1) - - self:Show() -end - -local function secureFrame_Hide(self) - self:Hide() - if self.secure then - for k,v in pairs(self.secure) do - self:SetAttribute(k, nil) - end - end - self.secure = nil -end - -secureFrame:SetScript("OnEvent", - function() - if event=="PLAYER_REGEN_ENABLED" then - this.combat = false - if not this:IsShown() and this.owner then - secureFrame_Show(this) - end - elseif event=="PLAYER_REGEN_DISABLED" then - this.combat = true - if this:IsShown() then - secureFrame_Hide(this) - end - end - end -) -secureFrame:RegisterEvent("PLAYER_REGEN_ENABLED") -secureFrame:RegisterEvent("PLAYER_REGEN_DISABLED") - -secureFrame:SetScript("OnLeave", - function() - local owner=this.owner - this:Deactivate() - owner:GetScript("OnLeave")() - end -) - -secureFrame:HookScript("OnClick", - function() - local realthis = this - this = this.owner - this:GetScript("OnClick")() - end -) - -function secureFrame:IsOwnedBy(frame) - return self.owner == frame -end - -function secureFrame:Activate(owner) - if self.owner then -- "Shouldn't" happen but apparently it does and I cba to troubleshoot... - if not self.combat then - secureFrame_Hide(self) - end - end - self.owner = owner - if not self.combat then - secureFrame_Show(self) - end -end - -function secureFrame:Deactivate() - if not self.combat then - secureFrame_Hide(self) - end - self.owner = nil -end - --- END secure frame utilities - - --- Underline on mouseover - use a single global underline that we move around, no point in creating lots of copies -local underlineFrame = CreateFrame("Frame", nil) -underlineFrame.tx = underlineFrame:CreateTexture() -underlineFrame.tx:SetTexture(1,1,0.5,0.75) -underlineFrame:SetScript("OnHide", function(this) this:Hide(); end) -underlineFrame:SetScript("OnShow", function(this) -- change sizing on the fly to catch runtime uiscale changes - underlineFrame.tx:SetPoint("TOPLEFT", -1, -2/this:GetEffectiveScale()) - underlineFrame.tx:SetPoint("RIGHT", 1,0) - underlineFrame.tx:SetHeight(0.6 / this:GetEffectiveScale()); -end) -underlineFrame:SetHeight(1) - --- END underline on mouseover - - -local function GetScaledCursorPosition() - local x, y = GetCursorPosition() - local scale = UIParent:GetEffectiveScale() - return x / scale, y / scale -end - -local function StartCounting(self, level) - for i = level, 1, -1 do - if levels[i] then - levels[i].count = 3 - end - end -end - -local function StopCounting(self, level) - for i = level, 1, -1 do - if levels[i] then - levels[i].count = nil - end - end -end - -local function OnUpdate(self, elapsed) - for _,level in ipairs(levels) do - local count = level.count - if count then - count = count - elapsed - if count < 0 then - level.count = nil - self:Close(level.num) - else - level.count = count - end - end - end -end - -local function CheckDualMonitor(self, frame) - local ratio = GetScreenWidth() / GetScreenHeight() - if ratio >= 2.4 and frame:GetRight() > GetScreenWidth() / 2 and frame:GetLeft() < GetScreenWidth() / 2 then - local offsetx - if GetCursorPosition() / GetScreenHeight() * 768 < GetScreenWidth() / 2 then - offsetx = GetScreenWidth() / 2 - frame:GetRight() - else - offsetx = GetScreenWidth() / 2 - frame:GetLeft() - end - local point, parent, relativePoint, x, y = frame:GetPoint(1) - frame:SetPoint(point, parent, relativePoint, (x or 0) + offsetx, y or 0) - end -end - -local function CheckSize(self, level) - if not level.buttons then - return - end - local height = 20 - for _, button in ipairs(level.buttons) do - height = height + button:GetHeight() - end - level:SetHeight(height) - local width = 160 - for _, button in ipairs(level.buttons) do - local extra = 1 - if button.hasArrow or button.hasColorSwatch then - extra = extra + 16 - end - if not button.notCheckable then - extra = extra + 24 - end - button.text:SetFont(STANDARD_TEXT_FONT, button.textHeight) - if button.text:GetWidth() + extra > width then - width = button.text:GetWidth() + extra - end - end - level:SetWidth(width + 20) - if level:GetLeft() and level:GetRight() and level:GetTop() and level:GetBottom() and (level:GetLeft() < 0 or level:GetRight() > GetScreenWidth() or level:GetTop() > GetScreenHeight() or level:GetBottom() < 0) then - level:ClearAllPoints() - local parent = level.parent or level:GetParent() - if type(parent) ~= "table" then - parent = UIParent - end - if level.lastDirection == "RIGHT" then - if level.lastVDirection == "DOWN" then - level:SetPoint("TOPLEFT", parent, "TOPRIGHT", 5, 10) - else - level:SetPoint("BOTTOMLEFT", parent, "BOTTOMRIGHT", 5, -10) - end - else - if level.lastVDirection == "DOWN" then - level:SetPoint("TOPRIGHT", parent, "TOPLEFT", -5, 10) - else - level:SetPoint("BOTTOMRIGHT", parent, "BOTTOMLEFT", -5, -10) - end - end - end - local dirty = false - if not level:GetRight() then - self:Close() - return - end - if level:GetRight() > GetScreenWidth() and level.lastDirection == "RIGHT" then - level.lastDirection = "LEFT" - dirty = true - elseif level:GetLeft() < 0 and level.lastDirection == "LEFT" then - level.lastDirection = "RIGHT" - dirty = true - end - if level:GetTop() > GetScreenHeight() and level.lastVDirection == "UP" then - level.lastVDirection = "DOWN" - dirty = true - elseif level:GetBottom() < 0 and level.lastVDirection == "DOWN" then - level.lastVDirection = "UP" - dirty = true - end - if dirty then - level:ClearAllPoints() - local parent = level.parent or level:GetParent() - if type(parent) ~= "table" then - parent = UIParent - end - if level.lastDirection == "RIGHT" then - if level.lastVDirection == "DOWN" then - level:SetPoint("TOPLEFT", parent, "TOPRIGHT", 5, 10) - else - level:SetPoint("BOTTOMLEFT", parent, "BOTTOMRIGHT", 5, -10) - end - else - if level.lastVDirection == "DOWN" then - level:SetPoint("TOPRIGHT", parent, "TOPLEFT", -5, 10) - else - level:SetPoint("BOTTOMRIGHT", parent, "BOTTOMLEFT", -5, -10) - end - end - end - if level:GetTop() > GetScreenHeight() then - local top = level:GetTop() - local point, parent, relativePoint, x, y = level:GetPoint(1) - level:ClearAllPoints() - level:SetPoint(point, parent, relativePoint, x or 0, (y or 0) + GetScreenHeight() - top) - elseif level:GetBottom() < 0 then - local bottom = level:GetBottom() - local point, parent, relativePoint, x, y = level:GetPoint(1) - level:ClearAllPoints() - level:SetPoint(point, parent, relativePoint, x or 0, (y or 0) - bottom) - end - CheckDualMonitor(self, level) - if mod(level.num, 5) == 0 then - local left, bottom = level:GetLeft(), level:GetBottom() - level:ClearAllPoints() - level:SetPoint("BOTTOMLEFT", UIParent, "BOTTOMLEFT", left, bottom) - end -end - -local Open -local OpenSlider -local OpenEditBox -local Refresh -local Clear -local function ReleaseButton(self, level, index) - if not level.buttons then - return - end - if not level.buttons[index] then - return - end - local button = level.buttons[index] - button:Hide() - if button.highlight then - button.highlight:Hide() - end --- button.arrow:SetVertexColor(1, 1, 1) --- button.arrow:SetHeight(16) --- button.arrow:SetWidth(16) - table.remove(level.buttons, index) - table.insert(buttons, button) - for k in pairs(button) do - if k ~= 0 and k ~= "text" and k ~= "check" and k ~= "arrow" and k ~= "colorSwatch" and k ~= "highlight" and k ~= "radioHighlight" then - button[k] = nil - end - end - return true -end - -local function Scroll(self, level, down) - if down then - if level:GetBottom() < 0 then - local point, parent, relativePoint, x, y = level:GetPoint(1) - level:SetPoint(point, parent, relativePoint, x, y + 50) - if level:GetBottom() > 0 then - level:SetPoint(point, parent, relativePoint, x, y + 50 - level:GetBottom()) - end - end - else - if level:GetTop() > GetScreenHeight() then - local point, parent, relativePoint, x, y = level:GetPoint(1) - level:SetPoint(point, parent, relativePoint, x, y - 50) - if level:GetTop() < GetScreenHeight() then - level:SetPoint(point, parent, relativePoint, x, y - 50 + GetScreenHeight() - level:GetTop()) - end - end - end -end - -local function getArgs(t, str, num, ...) - local x = t[str .. num] - if x == nil then - return ... - else - return x, getArgs(t, str, num + 1, ...) - end -end - -local sliderFrame -local editBoxFrame - -local normalFont -local lastSetFont -local justSetFont = false -local regionTmp = {} -local function fillRegionTmp(...) - for i = 1, select('#', ...) do - regionTmp[i] = select(i, ...) - end -end - -local function showGameTooltip(this) - if this.tooltipTitle or this.tooltipText then - GameTooltip_SetDefaultAnchor(GameTooltip, this) - local disabled = not this.isTitle and this.disabled - local font - if this.tooltipTitle then - if SharedMedia and SharedMedia:IsValid("font", this.tooltipTitle) then - font = SharedMedia:Fetch("font", this.tooltipTitle) - end - if disabled then - GameTooltip:SetText(this.tooltipTitle, 0.5, 0.5, 0.5, 1) - else - GameTooltip:SetText(this.tooltipTitle, 1, 1, 1, 1) - end - if this.tooltipText then - if not font and SharedMedia and SharedMedia:IsValid("font", this.tooltipText) then - font = SharedMedia:Fetch("font", this.tooltipText) - end - if disabled then - GameTooltip:AddLine(this.tooltipText, (NORMAL_FONT_COLOR.r + 0.5) / 2, (NORMAL_FONT_COLOR.g + 0.5) / 2, (NORMAL_FONT_COLOR.b + 0.5) / 2, 1) - else - GameTooltip:AddLine(this.tooltipText, NORMAL_FONT_COLOR.r, NORMAL_FONT_COLOR.g, NORMAL_FONT_COLOR.b, 1) - end - end - else - if SharedMedia and SharedMedia:IsValid("font", this.tooltipText) then - font = SharedMedia:Fetch("font", this.tooltipText) - end - if disabled then - GameTooltip:SetText(this.tooltipText, 0.5, 0.5, 0.5, 1) - else - GameTooltip:SetText(this.tooltipText, 1, 1, 1, 1) - end - end - if font then - fillRegionTmp(GameTooltip:GetRegions()) - lastSetFont = font - justSetFont = true - for i,v in ipairs(regionTmp) do - if v.SetFont then - local norm,size,outline = v:GetFont() - v:SetFont(font, size, outline) - if not normalFont then - normalFont = norm - end - end - regionTmp[i] = nil - end - elseif not normalFont then - fillRegionTmp(GameTooltip:GetRegions()) - for i,v in ipairs(regionTmp) do - if v.GetFont and not normalFont then - normalFont = v:GetFont() - end - regionTmp[i] = nil - end - end - GameTooltip:Show() - end - if this.tooltipFunc then - GameTooltip:SetOwner(this, "ANCHOR_NONE") - GameTooltip:SetPoint("TOPLEFT", this, "TOPRIGHT", 5, 0) - this.tooltipFunc(getArgs(this, 'tooltipArg', 1)) - GameTooltip:Show() - end -end - -local tmpt = setmetatable({}, {mode='v'}) -local numButtons = 0 -local function AcquireButton(self, level) - if not levels[level] then - return - end - level = levels[level] - if not level.buttons then - level.buttons = {} - end - local button - if #buttons == 0 then - numButtons = numButtons + 1 - button = CreateFrame("Button", "Dewdrop20Button" .. numButtons, nil) - button:SetFrameStrata("FULLSCREEN_DIALOG") - button:SetHeight(16) - local highlight = button:CreateTexture(nil, "BACKGROUND") - highlight:SetTexture("Interface\\QuestFrame\\UI-QuestTitleHighlight") - button.highlight = highlight - highlight:SetBlendMode("ADD") - highlight:SetAllPoints(button) - highlight:Hide() - local check = button:CreateTexture(nil, "ARTWORK") - button.check = check - check:SetTexture("Interface\\Buttons\\UI-CheckBox-Check") - check:SetPoint("CENTER", button, "LEFT", 12, 0) - check:SetWidth(24) - check:SetHeight(24) - local radioHighlight = button:CreateTexture(nil, "ARTWORK") - button.radioHighlight = radioHighlight - radioHighlight:SetTexture("Interface\\Buttons\\UI-RadioButton") - radioHighlight:SetAllPoints(check) - radioHighlight:SetBlendMode("ADD") - radioHighlight:SetTexCoord(0.5, 0.75, 0, 1) - radioHighlight:Hide() - button:SetScript("OnEnter", function() - if (sliderFrame and sliderFrame:IsShown() and sliderFrame.mouseDown and sliderFrame.level == this.level.num + 1) or (editBoxFrame and editBoxFrame:IsShown() and editBoxFrame.mouseDown and editBoxFrame.level == this.level.num + 1) then - for i = 1, this.level.num do - Refresh(self, levels[i]) - end - return - end - self:Close(this.level.num + 1) - if not this.disabled then - if this.secure then - secureFrame:Activate(this) - elseif this.hasSlider then - OpenSlider(self, this) - elseif this.hasEditBox then - OpenEditBox(self, this) - elseif this.hasArrow then - Open(self, this, nil, this.level.num + 1, this.value) - end - end - if not this.level then -- button reclaimed - return - end - StopCounting(self, this.level.num + 1) - if not this.disabled then - highlight:Show() - if this.isRadio then - button.radioHighlight:Show() - end - if this.mouseoverUnderline then - underlineFrame:SetParent(this) - underlineFrame:SetPoint("BOTTOMLEFT",this.text,0,0) - underlineFrame:SetWidth(this.text:GetWidth()) - underlineFrame:Show() - end - end - showGameTooltip(this) - end) - button:SetScript("OnHide", function() - if this.secure and secureFrame:IsOwnedBy(this) then - secureFrame:Deactivate() - end - end) - button:SetScript("OnLeave", function() - if this.secure and secureFrame:IsShown() then - return; -- it's ok, we didn't actually mouse out of the button, only onto the secure frame on top of it - end - underlineFrame:Hide() - if not this.selected then - highlight:Hide() - end - button.radioHighlight:Hide() - if this.level then - StartCounting(self, this.level.num) - end - GameTooltip:Hide() - end) - local first = true - button:SetScript("OnClick", function() - if not this.disabled then - if this.hasColorSwatch then - local func = button.colorFunc - local hasOpacity = this.hasOpacity - local this = this - for k in pairs(tmpt) do - tmpt[k] = nil - end - for i = 1, 1000 do - local x = this['colorArg'..i] - if x == nil then - break - else - tmpt[i] = x - end - end - ColorPickerFrame.func = function() - if func then - local r,g,b = ColorPickerFrame:GetColorRGB() - local a = hasOpacity and 1 - OpacitySliderFrame:GetValue() or nil - local n = #tmpt - tmpt[n+1] = r - tmpt[n+2] = g - tmpt[n+3] = b - tmpt[n+4] = a - func(unpack(tmpt)) - tmpt[n+1] = nil - tmpt[n+2] = nil - tmpt[n+3] = nil - tmpt[n+4] = nil - end - end - ColorPickerFrame.hasOpacity = this.hasOpacity - ColorPickerFrame.opacityFunc = ColorPickerFrame.func - ColorPickerFrame.opacity = 1 - this.opacity - ColorPickerFrame:SetColorRGB(this.r, this.g, this.b) - local r, g, b, a = this.r, this.g, this.b, this.opacity - ColorPickerFrame.cancelFunc = function() - if func then - local n = #tmpt - tmpt[n+1] = r - tmpt[n+2] = g - tmpt[n+3] = b - tmpt[n+4] = a - func(unpack(tmpt)) - for i = 1, n+4 do - tmpt[i] = nil - end - end - end - self:Close(1) - ShowUIPanel(ColorPickerFrame) - elseif this.func then - local level = this.level - if type(this.func) == "string" then - if type(this.arg1[this.func]) ~= "function" then - self:error("Cannot call method %q", this.func) - end - this.arg1[this.func](this.arg1, getArgs(this, 'arg', 2)) - else - this.func(getArgs(this, 'arg', 1)) - end - if this.closeWhenClicked then - self:Close() - elseif level:IsShown() then - for i = 1, level.num do - Refresh(self, levels[i]) - end - local value = levels[level.num].value - for i = level.num-1, 1, -1 do - local level = levels[i] - local good = false - for _,button in ipairs(level.buttons) do - if button.value == value then - good = true - break - end - end - if not good then - Dewdrop:Close(i+1) - end - value = levels[i].value - end - end - elseif this.closeWhenClicked then - self:Close() - end - end - end) - local text = button:CreateFontString(nil, "ARTWORK") - button.text = text - text:SetFontObject(GameFontHighlightSmall) - button.text:SetFont(STANDARD_TEXT_FONT, UIDROPDOWNMENU_DEFAULT_TEXT_HEIGHT) - button:SetScript("OnMouseDown", function() - if not this.disabled and (this.func or this.colorFunc or this.closeWhenClicked) then - text:SetPoint("LEFT", button, "LEFT", this.notCheckable and 1 or 25, -1) - end - end) - button:SetScript("OnMouseUp", function() - if not this.disabled and (this.func or this.colorFunc or this.closeWhenClicked) then - text:SetPoint("LEFT", button, "LEFT", this.notCheckable and 0 or 24, 0) - end - end) - local arrow = button:CreateTexture(nil, "ARTWORK") - button.arrow = arrow - arrow:SetPoint("LEFT", button, "RIGHT", -16, 0) - arrow:SetWidth(16) - arrow:SetHeight(16) - arrow:SetTexture("Interface\\ChatFrame\\ChatFrameExpandArrow") - local colorSwatch = button:CreateTexture(nil, "ARTWORK") - button.colorSwatch = colorSwatch - colorSwatch:SetWidth(20) - colorSwatch:SetHeight(20) - colorSwatch:SetTexture("Interface\\ChatFrame\\ChatFrameColorSwatch") - local texture = button:CreateTexture(nil, "OVERLAY") - colorSwatch.texture = texture - texture:SetTexture("Interface\\Buttons\\WHITE8X8") - texture:SetWidth(11.5) - texture:SetHeight(11.5) - texture:Show() - texture:SetPoint("CENTER", colorSwatch, "CENTER") - colorSwatch:SetPoint("RIGHT", button, "RIGHT", 0, 0) - else - button = table.remove(buttons) - end - button:ClearAllPoints() - button:SetParent(level) - button:SetFrameStrata(level:GetFrameStrata()) - button:SetFrameLevel(level:GetFrameLevel() + 1) - button:SetPoint("LEFT", level, "LEFT", 10, 0) - button:SetPoint("RIGHT", level, "RIGHT", -10, 0) - if #level.buttons == 0 then - button:SetPoint("TOP", level, "TOP", 0, -10) - else - button:SetPoint("TOP", level.buttons[#level.buttons], "BOTTOM", 0, 0) - end - button.text:SetPoint("LEFT", button, "LEFT", 24, 0) - button:Show() - button.level = level - table.insert(level.buttons, button) - if not level.parented then - level.parented = true - level:ClearAllPoints() - if level.num == 1 then - if level.parent ~= UIParent and type(level.parent) == "table" then - level:SetPoint("TOPRIGHT", level.parent, "TOPLEFT") - else - level:SetPoint("CENTER", UIParent, "CENTER") - end - else - if level.lastDirection == "RIGHT" then - if level.lastVDirection == "DOWN" then - level:SetPoint("TOPLEFT", level.parent, "TOPRIGHT", 5, 10) - else - level:SetPoint("BOTTOMLEFT", level.parent, "BOTTOMRIGHT", 5, -10) - end - else - if level.lastVDirection == "DOWN" then - level:SetPoint("TOPRIGHT", level.parent, "TOPLEFT", -5, 10) - else - level:SetPoint("BOTTOMRIGHT", level.parent, "BOTTOMLEFT", -5, -10) - end - end - end - level:SetFrameStrata("FULLSCREEN_DIALOG") - end - button:SetAlpha(1) - return button -end - -local numLevels = 0 -local function AcquireLevel(self, level) - if not levels[level] then - for i = #levels + 1, level, -1 do - local i = i - numLevels = numLevels + 1 - local frame = CreateFrame("Button", "Dewdrop20Level" .. numLevels, nil) - if i == 1 then - local old_CloseSpecialWindows = CloseSpecialWindows - function CloseSpecialWindows() - local found = old_CloseSpecialWindows() - if levels[1]:IsShown() then - self:Close() - return 1 - end - return found - end - end - levels[i] = frame - frame.num = i - frame:SetParent(UIParent) - frame:SetFrameStrata("FULLSCREEN_DIALOG") - frame:Hide() - frame:SetWidth(180) - frame:SetHeight(10) - frame:SetFrameLevel(i * 3) - frame:SetScript("OnHide", function() - self:Close(level + 1) - end) - if frame.SetTopLevel then - frame:SetTopLevel(true) - end - frame:EnableMouse(true) - frame:EnableMouseWheel(true) - local backdrop = CreateFrame("Frame", nil, frame) - backdrop:SetAllPoints(frame) - backdrop:SetBackdrop(tmp( - 'bgFile', "Interface\\Tooltips\\UI-Tooltip-Background", - 'edgeFile', "Interface\\Tooltips\\UI-Tooltip-Border", - 'tile', true, - 'insets', tmp2( - 'left', 5, - 'right', 5, - 'top', 5, - 'bottom', 5 - ), - 'tileSize', 16, - 'edgeSize', 16 - )) - backdrop:SetBackdropBorderColor(TOOLTIP_DEFAULT_COLOR.r, TOOLTIP_DEFAULT_COLOR.g, TOOLTIP_DEFAULT_COLOR.b) - backdrop:SetBackdropColor(TOOLTIP_DEFAULT_BACKGROUND_COLOR.r, TOOLTIP_DEFAULT_BACKGROUND_COLOR.g, TOOLTIP_DEFAULT_BACKGROUND_COLOR.b) - frame:SetScript("OnClick", function() - self:Close(i) - end) - frame:SetScript("OnEnter", function() - StopCounting(self, i) - end) - frame:SetScript("OnLeave", function() - StartCounting(self, i) - end) - frame:SetScript("OnMouseWheel", function() - Scroll(self, frame, arg1 < 0) - end) - if i == 1 then - frame:SetScript("OnUpdate", function(this, arg1) - OnUpdate(self, arg1) - end) - levels[1].lastDirection = "RIGHT" - levels[1].lastVDirection = "DOWN" - else - levels[i].lastDirection = levels[i - 1].lastDirection - levels[i].lastVDirection = levels[i - 1].lastVDirection - end - end - end - local fullscreenFrame = GetUIPanel("fullscreen") - local l = levels[level] - local strata, framelevel = l:GetFrameStrata(), l:GetFrameLevel() - if fullscreenFrame then - l:SetParent(fullscreenFrame) - else - l:SetParent(UIParent) - end - l:SetFrameStrata(strata) - l:SetFrameLevel(framelevel) - l:SetAlpha(1) - return l -end - -local function validateOptions(options, position, baseOptions, fromPass) - if not baseOptions then - baseOptions = options - end - if type(options) ~= "table" then - return "Options must be a table.", position - end - local kind = options.type - if type(kind) ~= "string" then - return '"type" must be a string.', position - elseif kind ~= "group" and kind ~= "range" and kind ~= "text" and kind ~= "execute" and kind ~= "toggle" and kind ~= "color" and kind ~= "dragLink" and kind ~= "header" then - return '"type" must either be "range", "text", "group", "toggle", "execute", "color", "dragLink", or "header".', position - end - if options.aliases then - if type(options.aliases) ~= "table" and type(options.aliases) ~= "string" then - return '"alias" must be a table or string', position - end - end - if not fromPass then - if kind == "execute" then - if type(options.func) ~= "string" and type(options.func) ~= "function" then - return '"func" must be a string or function', position - end - elseif kind == "range" or kind == "text" or kind == "toggle" then - if type(options.set) ~= "string" and type(options.set) ~= "function" then - return '"set" must be a string or function', position - end - if kind == "text" and options.get == false then - elseif type(options.get) ~= "string" and type(options.get) ~= "function" then - return '"get" must be a string or function', position - end - elseif kind == "group" and options.pass then - if options.pass ~= true then - return '"pass" must be either nil, true, or false', position - end - if not options.func then - if type(options.set) ~= "string" and type(options.set) ~= "function" then - return '"set" must be a string or function', position - end - if type(options.get) ~= "string" and type(options.get) ~= "function" then - return '"get" must be a string or function', position - end - elseif type(options.func) ~= "string" and type(options.func) ~= "function" then - return '"func" must be a string or function', position - end - end - end - if options ~= baseOptions then - if kind == "header" then - elseif type(options.desc) ~= "string" then - return '"desc" must be a string', position - elseif options.desc:len() == 0 then - return '"desc" cannot be a 0-length string', position - end - end - if options ~= baseOptions or kind == "range" or kind == "text" or kind == "toggle" or kind == "color" then - if options.type == "header" and not options.cmdName and not options.name then - elseif options.cmdName then - if type(options.cmdName) ~= "string" then - return '"cmdName" must be a string or nil', position - elseif options.cmdName:len() == 0 then - return '"cmdName" cannot be a 0-length string', position - end - if type(options.guiName) ~= "string" then - if not options.guiNameIsMap then - return '"guiName" must be a string or nil', position - end - elseif options.guiName:len() == 0 then - return '"guiName" cannot be a 0-length string', position - end - else - if type(options.name) ~= "string" then - return '"name" must be a string', position - elseif options.name:len() == 0 then - return '"name" cannot be a 0-length string', position - end - end - end - if options.guiNameIsMap then - if type(options.guiNameIsMap) ~= "boolean" then - return '"guiNameIsMap" must be a boolean or nil', position - elseif options.type ~= "toggle" then - return 'if "guiNameIsMap" is true, then "type" must be set to \'toggle\'', position - elseif type(options.map) ~= "table" then - return '"map" must be a table', position - end - end - if options.message and type(options.message) ~= "string" then - return '"message" must be a string or nil', position - end - if options.error and type(options.error) ~= "string" then - return '"error" must be a string or nil', position - end - if options.current and type(options.current) ~= "string" then - return '"current" must be a string or nil', position - end - if options.order then - if type(options.order) ~= "number" or (-1 < options.order and options.order < 0.999) then - return '"order" must be a non-zero number or nil', position - end - end - if options.disabled then - if type(options.disabled) ~= "function" and type(options.disabled) ~= "string" and options.disabled ~= true then - return '"disabled" must be a function, string, or boolean', position - end - end - if options.cmdHidden then - if type(options.cmdHidden) ~= "function" and type(options.cmdHidden) ~= "string" and options.cmdHidden ~= true then - return '"cmdHidden" must be a function, string, or boolean', position - end - end - if options.guiHidden then - if type(options.guiHidden) ~= "function" and type(options.guiHidden) ~= "string" and options.guiHidden ~= true then - return '"guiHidden" must be a function, string, or boolean', position - end - end - if options.hidden then - if type(options.hidden) ~= "function" and type(options.hidden) ~= "string" and options.hidden ~= true then - return '"hidden" must be a function, string, or boolean', position - end - end - if kind == "text" then - if type(options.validate) == "table" then - local t = options.validate - local iTable = nil - for k,v in pairs(t) do - if type(k) == "number" then - if iTable == nil then - iTable = true - elseif not iTable then - return '"validate" must either have all keys be indexed numbers or strings', position - elseif k < 1 or k > #t then - return '"validate" numeric keys must be indexed properly. >= 1 and <= #t', position - end - else - if iTable == nil then - iTable = false - elseif iTable then - return '"validate" must either have all keys be indexed numbers or strings', position - end - end - if type(v) ~= "string" then - return '"validate" values must all be strings', position - end - end - if options.multiToggle and options.multiToggle ~= true then - return '"multiToggle" must be a boolean or nil if "validate" is a table', position - end - elseif options.validate == "keybinding" then - -- no other checks - else - if type(options.usage) ~= "string" then - return '"usage" must be a string', position - elseif options.validate and type(options.validate) ~= "string" and type(options.validate) ~= "function" then - return '"validate" must be a string, function, or table', position - end - end - if options.multiToggle and type(options.validate) ~= "table" then - return '"validate" must be a table if "multiToggle" is true', position - end - elseif kind == "range" then - if options.min or options.max then - if type(options.min) ~= "number" then - return '"min" must be a number', position - elseif type(options.max) ~= "number" then - return '"max" must be a number', position - elseif options.min >= options.max then - return '"min" must be less than "max"', position - end - end - if options.step then - if type(options.step) ~= "number" then - return '"step" must be a number', position - elseif options.step < 0 then - return '"step" must be nonnegative', position - end - end - if options.bigStep then - if type(options.bigStep) ~= "number" then - return '"bigStep" must be a number', position - elseif options.bigStep < 0 then - return '"bigStep" must be nonnegative', position - end - end - if options.isPercent and options.isPercent ~= true then - return '"isPercent" must either be nil, true, or false', position - end - elseif kind == "toggle" then - if options.map then - if type(options.map) ~= "table" then - return '"map" must be a table', position - elseif type(options.map[true]) ~= "string" then - return '"map[true]" must be a string', position - elseif type(options.map[false]) ~= "string" then - return '"map[false]" must be a string', position - end - end - elseif kind == "color" then - if options.hasAlpha and options.hasAlpha ~= true then - return '"hasAlpha" must be nil, true, or false', position - end - elseif kind == "group" then - if options.pass and options.pass ~= true then - return '"pass" must be nil, true, or false', position - end - if type(options.args) ~= "table" then - return '"args" must be a table', position - end - for k,v in pairs(options.args) do - if type(k) ~= "number" then - if type(k) ~= "string" then - return '"args" keys must be strings or numbers', position - elseif k:len() == 0 then - return '"args" keys must not be 0-length strings.', position - end - end - if type(v) ~= "table" then - return '"args" values must be tables', position and position .. "." .. k or k - end - local newposition - if position then - newposition = position .. ".args." .. k - else - newposition = "args." .. k - end - local err, pos = validateOptions(v, newposition, baseOptions, options.pass) - if err then - return err, pos - end - end - elseif kind == "execute" then - if type(options.confirm) ~= "string" and type(options.confirm) ~= "boolean" and type(options.confirm) ~= "nil" then - return '"confirm" must be a string, boolean, or nil', position - end - end - if options.icon and type(options.icon) ~= "string" then - return'"icon" must be a string', position - end - if options.iconWidth or options.iconHeight then - if type(options.iconWidth) ~= "number" or type(options.iconHeight) ~= "number" then - return '"iconHeight" and "iconWidth" must be numbers', position - end - end - if options.iconCoordLeft or options.iconCoordRight or options.iconCoordTop or options.iconCoordBottom then - if type(options.iconCoordLeft) ~= "number" or type(options.iconCoordRight) ~= "number" or type(options.iconCoordTop) ~= "number" or type(options.iconCoordBottom) ~= "number" then - return '"iconCoordLeft", "iconCoordRight", "iconCoordTop", and "iconCoordBottom" must be numbers', position - end - end -end - -local validatedOptions - -local values -local mysort_args -local mysort -local othersort -local othersort_validate - -local baseFunc, currentLevel - -local function confirmPopup(message, func, ...) - if not StaticPopupDialogs["DEWDROP20_CONFIRM_DIALOG"] then - StaticPopupDialogs["DEWDROP20_CONFIRM_DIALOG"] = {} - end - local t = StaticPopupDialogs["DEWDROP20_CONFIRM_DIALOG"] - for k in pairs(t) do - t[k] = nil - end - t.text = message - t.button1 = ACCEPT or "Accept" - t.button2 = CANCEL or "Cancel" - t.OnAccept = function() - func(unpack(t)) - end - for i = 1, select('#', ...) do - t[i] = select(i, ...) - end - t.timeout = 0 - t.whileDead = 1 - t.hideOnEscape = 1 - - Dewdrop:Close() - StaticPopup_Show("DEWDROP20_CONFIRM_DIALOG") -end - - -local function getMethod(settingname, handler, v, methodName, ...) -- "..." is simply returned straight out cause you can't do "a,b,c = 111,f(),222" - assert(v and type(v)=="table") - assert(methodName and type(methodName)=="string") - - local method = v[methodName] - if type(method)=="function" then - return method, ... - elseif type(method)=="string" then - if not handler then - Dewdrop:error("[%s] 'handler' is required if providing a method name: %q", tostring(settingname), method) - elseif not handler[method] then - Dewdrop:error("[%s] 'handler' method %q not defined", tostring(settingname), method) - end - return handler[method], handler, ... - end - - Dewdrop:error("[%s] Missing %q directive", tostring(settingname), methodName) -end - -local function callMethod(settingname, handler, v, methodName, ...) - assert(v and type(v)=="table") - assert(methodName and type(methodName)=="string") - - local method = v[methodName] - if type(method)=="function" then - local success, ret,ret2,ret3,ret4 = pcall(v[methodName], ...) - if not success then - geterrorhandler()(ret) - return nil - end - return ret,ret2,ret3,ret4 - - elseif type(method)=="string" then - - local neg = method:match("^~(.-)$") - if neg then - method = neg - end - if not handler then - Dewdrop:error("[%s] 'handler' is required if providing a method name: %q", tostring(settingname), method) - elseif not handler[method] then - Dewdrop:error("[%s] 'handler' (%q) method %q not defined", tostring(settingname), handler.name or "(unnamed)", method) - end - local success, ret,ret2,ret3,ret4 = pcall(handler[method], handler, ...) - if not success then - geterrorhandler()(ret) - return nil - end - if neg then - return not ret - end - return ret,ret2,ret3,ret4 - elseif method == false then - return nil - end - - Dewdrop:error("[%s] Missing %q directive in %q", tostring(settingname), methodName, v.name or "(unnamed)") -end - -local function skip1Nil(...) - if select(1,...)==nil then - return select(2,...) - end - return ... -end - -function Dewdrop:FeedAceOptionsTable(options, difference) - self:argCheck(options, 2, "table") - self:argCheck(difference, 3, "nil", "number") - if not currentLevel then - self:error("Cannot call `FeedAceOptionsTable' outside of a Dewdrop declaration") - end - if not difference then - difference = 0 - end - if not validatedOptions then - validatedOptions = {} - end - if not validatedOptions[options] then - local err, position = validateOptions(options) - - if err then - if position then - Dewdrop:error(position .. ": " .. err) - else - Dewdrop:error(err) - end - end - - validatedOptions[options] = true - end - local level = levels[currentLevel] - if not level then - self:error("Improper level given") - end - if not values then - values = {} - else - for k,v in pairs(values) do - values[k] = nil - end - end - - local current = level - while current do -- this traverses from higher level numbers to lower, building "values" with leaf nodes first and trunk nodes later - if current.num == difference + 1 then - break - end - table.insert(values, current.value) - current = levels[current.num - 1] - end - - local realOptions = options - local handler = options.handler - local passTable - local passValue - while #values > 0 do -- This loop traverses values from the END (trunk nodes first, then onto leaf nodes) - if options.pass then - if options.get and options.set then - passTable = options - elseif not passTable then - passTable = options - end - else - passTable = nil - end - local value = table.remove(values) - options = options.args and options.args[value] - if not options then - return - end - handler = options.handler or handler - passValue = passTable and value or nil - end - - if options.type == "group" then - local hidden = options.hidden - if type(hidden) == "function" or type(hidden) == "string" then - hidden = callMethod(options.name or "(options root)", handler, options, "hidden", options.passValue) or false - end - if hidden then - return - end - local disabled = options.disabled - if type(disabled) == "function" or type(disabled) == "string" then - disabled = callMethod(options.name or "(options root)", handler, options, "disabled", options.passValue) or false - end - if disabled then - self:AddLine( - 'text', DISABLED, - 'disabled', true - ) - return - end - for k in pairs(options.args) do - table.insert(values, k) - end - if options.pass then - if options.get and options.set then - passTable = options - elseif not passTable then - passTable = options - end - else - passTable = nil - end - if not mysort then - mysort = function(a, b) - local alpha, bravo = mysort_args[a], mysort_args[b] - local alpha_order = alpha.order or 100 - local bravo_order = bravo.order or 100 - local alpha_name = alpha.guiName or alpha.name - local bravo_name = bravo.guiName or bravo.name - if alpha_order == bravo_order then - if not alpha_name then - return bravo_name - elseif not bravo_name then - return false - else - return alpha_name:gsub("|c%x%x%x%x%x%x%x%x", ""):gsub("|r", ""):upper() < bravo_name:gsub("|c%x%x%x%x%x%x%x%x", ""):gsub("|r", ""):upper() - end - else - if alpha_order < 0 then - if bravo_order > 0 then - return false - end - else - if bravo_order < 0 then - return true - end - end - return alpha_order < bravo_order - end - end - end - mysort_args = options.args - table.sort(values, mysort) - mysort_args = nil - local hasBoth = #values >= 1 and (options.args[values[1]].order or 100) > 0 and (options.args[values[#values]].order or 100) < 0 - local last_order = 1 - for _,k in ipairs(values) do - local v = options.args[k] - local handler = v.handler or handler - if hasBoth and last_order > 0 and (v.order or 100) < 0 then - hasBoth = false - self:AddLine() - end - local hidden, disabled = v.guiHidden or v.hidden, v.disabled - - if type(hidden) == "function" or type(hidden) == "string" then - hidden = callMethod(k, handler, v, "hidden", v.passValue) or false - end - if not hidden then - if type(disabled) == "function" or type(disabled) == "string" then - disabled = callMethod(k, handler, v, "disabled", v.passValue) or false - end - local name = (v.guiIconOnly and v.icon) and "" or (v.guiName or v.name) - local desc = v.guiDesc or v.desc - local iconHeight = v.iconHeight or 16 - local iconWidth = v.iconWidth or 16 - local iconCoordLeft = v.iconCoordLeft - local iconCoordRight = v.iconCoordRight - local iconCoordBottom = v.iconCoordBottom - local iconCoordTop = v.iconCoordTop - local tooltipTitle, tooltipText - tooltipTitle = name - if name ~= desc then - tooltipText = desc - end - if type(v.usage) == "string" and v.usage:trim():len() > 0 then - if tooltipText then - tooltipText = tooltipText .. "\n\n" .. USAGE_TOOLTIP:format(v.usage) - else - tooltipText = USAGE_TOOLTIP:format(v.usage) - end - end - local v_p = passTable - if not v_p or (v.type ~= "execute" and v.get and v.set) or (v.type == "execute" and v.func) then - v_p = v - end - local passValue = v.passValue or (v_p~=v and k) or nil - if v.type == "toggle" then - local checked = callMethod(name, handler, v_p, "get", passValue) or false - local checked_arg = checked - if type(v_p.get)=="string" and v_p.get:match("^~") then - checked_arg = not checked - end - local func, arg1, arg2, arg3 = getMethod(name, handler, v_p, "set", skip1Nil(passValue, not checked_arg)) - if v.guiNameIsMap then - checked = checked and true or false - name = tostring(v.map and v.map[checked]):gsub("|c%x%x%x%x%x%x%x%x(.-)|r", "%1") - tooltipTitle = name - checked = true--nil - end - self:AddLine( - 'text', name, - 'checked', checked, - 'isRadio', v.isRadio, - 'func', func, - 'arg1', arg1, - 'arg2', arg2, - 'arg3', arg3, - 'disabled', disabled, - 'tooltipTitle', tooltipTitle, - 'tooltipText', tooltipText - ) - elseif v.type == "execute" then - local func, arg1, arg2, arg3, arg4 - local confirm = v.confirm - if confirm == true then - confirm = DEFAULT_CONFIRM_MESSAGE:format(tooltipText or tooltipTitle) - func,arg1,arg2,arg3,arg4 = confirmPopup, confirm, getMethod(name, handler, v_p, "func", passValue) - elseif type(confirm) == "string" then - func,arg1,arg2,arg3,arg4 = confirmPopup, confirm, getMethod(name, handler, v_p, "func", passValue) - else - func,arg1,arg2 = getMethod(name, handler, v_p, "func", passValue) - end - self:AddLine( - 'text', name, - 'checked', checked, - 'func', func, - 'arg1', arg1, - 'arg2', arg2, - 'arg3', arg3, - 'arg4', arg4, - 'disabled', disabled, - 'tooltipTitle', tooltipTitle, - 'tooltipText', tooltipText, - 'icon', v.icon, - 'iconHeight', iconHeight, - 'iconWidth', iconWidth, - 'iconCoordLeft', iconCoordLeft, - 'iconCoordRight', iconCoordRight, - 'iconCoordTop', iconCoordTop, - 'iconCoordBottom', iconCoordBottom - ) - elseif v.type == "range" then - local sliderValue - sliderValue = callMethod(name, handler, v_p, "get", passValue) or 0 - local sliderFunc, sliderArg1, sliderArg2 = getMethod(name, handler, v_p, "set", passValue) - if tooltipText then - tooltipText = format("%s\n\n%s", tooltipText, RANGE_TOOLTIP) - else - tooltipText = RANGE_TOOLTIP - end - self:AddLine( - 'text', name, - 'hasArrow', true, - 'hasSlider', true, - 'sliderMin', v.min or 0, - 'sliderMax', v.max or 1, - 'sliderStep', v.step or 0, - 'sliderBigStep', v.bigStep or nil, - 'sliderIsPercent', v.isPercent or false, - 'sliderValue', sliderValue, - 'sliderFunc', sliderFunc, - 'sliderArg1', sliderArg1, - 'sliderArg2', sliderArg2, - 'fromAceOptions', true, - 'disabled', disabled, - 'tooltipTitle', tooltipTitle, - 'tooltipText', tooltipText, - 'icon', v.icon, - 'iconHeight', iconHeight, - 'iconWidth', iconWidth, - 'iconCoordLeft', iconCoordLeft, - 'iconCoordRight', iconCoordRight, - 'iconCoordTop', iconCoordTop, - 'iconCoordBottom', iconCoordBottom - ) - elseif v.type == "color" then - local r,g,b,a = callMethod(name, handler, v_p, "get", passValue) - if not r then - r,g,b,a = 0,0,0,0 - end - local colorFunc, colorArg1, colorArg2 = getMethod(name, handler, v_p, "set", passValue) - self:AddLine( - 'text', name, - 'hasArrow', true, - 'hasColorSwatch', true, - 'r', r, - 'g', g, - 'b', b, - 'opacity', v.hasAlpha and a or nil, - 'hasOpacity', v.hasAlpha, - 'colorFunc', colorFunc, - 'colorArg1', colorArg1, - 'colorArg2', colorArg2, - 'disabled', disabled, - 'tooltipTitle', tooltipTitle, - 'tooltipText', tooltipText - ) - elseif v.type == "text" then - if type(v.validate) == "table" then - local func,arg1,arg2 - if v.onClick then - func,arg1,arg2 = getMethod(name, handler, v, "onClick", passValue) - end - local checked - if v.isChecked then - checked = callMethod(name, handler, v, "isChecked", passValue) or false - end - self:AddLine( - 'text', name, - 'hasArrow', true, - 'value', k, - 'func', func, - 'arg1', arg1, - 'arg2', arg2, - 'mouseoverUnderline', func and true or nil, - 'disabled', disabled, - 'checked', checked, - 'tooltipTitle', tooltipTitle, - 'tooltipText', tooltipText, - 'icon', v.icon, - 'iconHeight', iconHeight, - 'iconWidth', iconWidth, - 'iconCoordLeft', iconCoordLeft, - 'iconCoordRight', iconCoordRight, - 'iconCoordTop', iconCoordTop, - 'iconCoordBottom', iconCoordBottom - ) - else - local editBoxText - editBoxText = callMethod(name, handler, v_p, "get", passValue) or "" - local editBoxFunc, editBoxArg1, editBoxArg2 = getMethod(name, handler, v_p, "set", passValue) - - local editBoxValidateFunc, editBoxValidateArg1 - - if v.validate and v.validate ~= "keybinding" then - if v.validate == "keybinding" then - if tooltipText then - tooltipText = format("%s\n\n%s", tooltipText, RESET_KEYBINDING_DESC) - else - tooltipText = RESET_KEYBINDING_DESC - end - else - editBoxValidateFunc, editBoxValidateArg1 = getMethod(name, handler, v, "validate") -- no passvalue! - end - end - - self:AddLine( - 'text', name, - 'hasArrow', true, - 'icon', v.icon, - 'iconHeight', iconHeight, - 'iconWidth', iconWidth, - 'iconCoordLeft', iconCoordLeft, - 'iconCoordRight', iconCoordRight, - 'iconCoordTop', iconCoordTop, - 'iconCoordBottom', iconCoordBottom, - 'hasEditBox', true, - 'editBoxText', editBoxText, - 'editBoxFunc', editBoxFunc, - 'editBoxArg1', editBoxArg1, - 'editBoxArg2', editBoxArg2, - 'editBoxValidateFunc', editBoxValidateFunc, - 'editBoxValidateArg1', editBoxValidateArg1, - 'editBoxIsKeybinding', v.validate == "keybinding", - 'editBoxKeybindingOnly', v.keybindingOnly, - 'editBoxKeybindingExcept', v.keybindingExcept, - 'disabled', disabled, - 'tooltipTitle', tooltipTitle, - 'tooltipText', tooltipText - ) - end - elseif v.type == "group" then - local func,arg1,arg2 - if v.onClick then - func,arg1,arg2 = getMethod(name, handler, v, "onClick", passValue) - end - local checked - if v.isChecked then - checked = callMethod(name, handler, v, "isChecked", passValue) or false - end - self:AddLine( - 'text', name, - 'hasArrow', true, - 'value', k, - 'func', func, - 'arg1', arg1, - 'arg2', arg2, - 'mouseoverUnderline', func and true or nil, - 'disabled', disabled, - 'checked', checked, - 'tooltipTitle', tooltipTitle, - 'tooltipText', tooltipText, - 'icon', v.icon, - 'iconHeight', iconHeight, - 'iconWidth', iconWidth, - 'iconCoordLeft', iconCoordLeft, - 'iconCoordRight', iconCoordRight, - 'iconCoordTop', iconCoordTop, - 'iconCoordBottom', iconCoordBottom - ) - elseif v.type == "header" then - if name == "" or not name then - self:AddLine( - 'isTitle', true, - 'icon', v.icon, - 'iconHeight', iconHeight, - 'iconWidth', iconWidth, - 'iconCoordLeft', iconCoordLeft, - 'iconCoordRight', iconCoordRight, - 'iconCoordTop', iconCoordTop, - 'iconCoordBottom', iconCoordBottom - ) - else - self:AddLine( - 'text', name, - 'isTitle', true, - 'icon', v.icon, - 'iconHeight', iconHeight, - 'iconWidth', iconWidth, - 'iconCoordLeft', iconCoordLeft, - 'iconCoordRight', iconCoordRight, - 'iconCoordTop', iconCoordTop, - 'iconCoordBottom', iconCoordBottom - ) - end - end - end - last_order = v.order or 100 - end - elseif options.type == "text" and type(options.validate) == "table" then - local current - local options_p = passTable - if not options_p or (options.get and options.set) then - options_p = options - passTable = nil - passValue = nil - end - local multiToggle = options.multiToggle - local passValue = options.passValue or passValue - if not multiToggle then - current = callMethod(k, handler, options_p, "get", passValue) - end - local indexed = true - for k,v in pairs(options.validate) do - if type(k) ~= "number" then - indexed = false - end - table.insert(values, k) - end - if not indexed then - if not othersort then - othersort = function(alpha, bravo) - return othersort_validate[alpha]:gsub("|c%x%x%x%x%x%x%x%x", ""):gsub("|r", ""):upper() < othersort_validate[bravo]:gsub("|c%x%x%x%x%x%x%x%x", ""):gsub("|r", ""):upper() - end - end - othersort_validate = options.validate - table.sort(values, othersort) - othersort_validate = nil - end - for _,k in ipairs(values) do - local v = options.validate[k] - if type(k) == "number" then - k = v - end - local func, arg1, arg2, arg3, arg4 = getMethod(k, handler, options_p, "set", skip1Nil(passValue, k)) - local checked - if multiToggle then - checked = callMethod(k, handler, options_p, "get", skip1Nil(passValue, k)) or false - if arg2 == nil then - arg2 = not checked - elseif arg3 == nil then - arg3 = not checked - else - arg4 = not checked - end - else - checked = (k == current or (type(k) == "string" and type(current) == "string" and k:lower() == current:lower())) - if checked then - func, arg1, arg2, arg3, arg4 = nil, nil, nil, nil, nil - end - end - local tooltipTitle - local tooltipText - if options.validateDesc then - tooltipTitle = v - tooltipText = options.validateDesc[k] - else - tooltipTitle = options.guiName or options.name - tooltipText = v - end - self:AddLine( - 'text', v, - 'func', func, - 'arg1', arg1, - 'arg2', arg2, - 'arg3', arg3, - 'arg4', arg4, - 'isRadio', not multiToggle, - 'checked', checked, - 'tooltipTitle', tooltipTitle, - 'tooltipText', tooltipText - ) - end - for k in pairs(values) do - values[k] = nil - end - else - return false - end - return true -end - -function Dewdrop:FeedTable(s, difference) - self:argCheck(s, 2, "table") - self:argCheck(difference, 3, "nil", "number") - if not currentLevel then - self:error("Cannot call `FeedTable' outside of a Dewdrop declaration") - end - if not difference then - difference = 0 - end - local level = levels[currentLevel] - if not level then - self:error("Improper level given") - end - if not values then - values = {} - else - for k,v in pairs(values) do - values[k] = nil - end - end - local t = s.subMenu and s or {subMenu = s} - local current = level - while current do - if current.num == difference + 1 then - break - end - table.insert(values, current.value) - current = levels[current.num - 1] - end - - while #values > 0 do - local value = table.remove(values) - t = t.subMenu and t.subMenu[value] - if not t then - return - end - end - - if t.subMenu or current.num == 1 then - for k in pairs(t.subMenu) do - table.insert(values, k) - end - table.sort(values) - for _,k in ipairs(values) do - local argTable = {"value", k} - for key, val in pairs(t.subMenu[k]) do - table.insert(argTable, key) - table.insert(argTable, val) - end - self:AddLine(unpack(argTable)) - end - for k in pairs(values) do - values[k] = nil - end - return false - end - return true -end - -function Refresh(self, level) - if type(level) == "number" then - level = levels[level] - end - if not level then - return - end - if baseFunc then - Clear(self, level) - currentLevel = level.num - if type(baseFunc) == "table" then - if currentLevel == 1 then - local handler = baseFunc.handler - if handler then - local name = tostring(handler) - if not name:find('^table:') and not handler.hideMenuTitle then - name = name:gsub("|c%x%x%x%x%x%x%x%x(.-)|r", "%1") - self:AddLine( - 'text', name, - 'isTitle', true - ) - end - end --- elseif level.parentText then --- self:AddLine( --- 'text', level.parentText, --- 'tooltipTitle', level.parentTooltipTitle, --- 'tooltipText', level.parentTooltipText, --- 'tooltipFunc', level.parentTooltipFunc, --- 'isTitle', true --- ) - end - self:FeedAceOptionsTable(baseFunc) - if currentLevel == 1 then - self:AddLine( - 'text', CLOSE, - 'tooltipTitle', CLOSE, - 'tooltipText', CLOSE_DESC, - 'closeWhenClicked', true - ) - end - else --- if level.parentText then --- self:AddLine( --- 'text', level.parentText, --- 'tooltipTitle', level.parentTooltipTitle, --- 'tooltipText', level.parentTooltipText, --- 'tooltipFunc', level.parentTooltipFunc, --- 'isTitle', true --- ) --- end - baseFunc(currentLevel, level.value, levels[level.num - 1] and levels[level.num - 1].value, levels[level.num - 2] and levels[level.num - 2].value, levels[level.num - 3] and levels[level.num - 3].value, levels[level.num - 4] and levels[level.num - 4].value) - end - currentLevel = nil - CheckSize(self, level) - end -end - -function Dewdrop:Refresh(level) - self:argCheck(level, 2, "number", "nil") - if not level then - for k,v in pairs(levels) do - Refresh(self, v) - end - else - Refresh(self, levels[level]) - end -end - -function OpenSlider(self, parent) - if not sliderFrame then - sliderFrame = CreateFrame("Frame", nil, nil) - sliderFrame:SetWidth(100) - sliderFrame:SetHeight(170) - sliderFrame:SetScale(UIParent:GetScale()) - sliderFrame:SetBackdrop(tmp( - 'bgFile', "Interface\\Tooltips\\UI-Tooltip-Background", - 'edgeFile', "Interface\\Tooltips\\UI-Tooltip-Border", - 'tile', true, - 'insets', tmp2( - 'left', 5, - 'right', 5, - 'top', 5, - 'bottom', 5 - ), - 'tileSize', 16, - 'edgeSize', 16 - )) - sliderFrame:SetFrameStrata("FULLSCREEN_DIALOG") - if sliderFrame.SetTopLevel then - sliderFrame:SetTopLevel(true) - end - sliderFrame:SetBackdropBorderColor(TOOLTIP_DEFAULT_COLOR.r, TOOLTIP_DEFAULT_COLOR.g, TOOLTIP_DEFAULT_COLOR.b) - sliderFrame:SetBackdropColor(TOOLTIP_DEFAULT_BACKGROUND_COLOR.r, TOOLTIP_DEFAULT_BACKGROUND_COLOR.g, TOOLTIP_DEFAULT_BACKGROUND_COLOR.b) - sliderFrame:EnableMouse(true) - sliderFrame:EnableMouseWheel(true) - sliderFrame:Hide() - sliderFrame:SetPoint("CENTER", UIParent, "CENTER") - local slider = CreateFrame("Slider", nil, sliderFrame) - sliderFrame.slider = slider - slider:SetOrientation("VERTICAL") - slider:SetMinMaxValues(0, 1) - slider:SetValueStep(0.000000001) - slider:SetValue(0.5) - slider:SetWidth(16) - slider:SetHeight(128) - slider:SetPoint("LEFT", sliderFrame, "LEFT", 15, 0) - slider:SetBackdrop(tmp( - 'bgFile', "Interface\\Buttons\\UI-SliderBar-Background", - 'edgeFile', "Interface\\Buttons\\UI-SliderBar-Border", - 'tile', true, - 'edgeSize', 8, - 'tileSize', 8, - 'insets', tmp2( - 'left', 3, - 'right', 3, - 'top', 3, - 'bottom', 3 - ) - )) - local texture = slider:CreateTexture() - slider:SetThumbTexture("Interface\\Buttons\\UI-SliderBar-Button-Vertical") - local text = slider:CreateFontString(nil, "ARTWORK") - sliderFrame.topText = text - text:SetFontObject(GameFontGreenSmall) - text:SetText("100%") - text:SetPoint("BOTTOM", slider, "TOP") - local text = slider:CreateFontString(nil, "ARTWORK") - sliderFrame.bottomText = text - text:SetFontObject(GameFontGreenSmall) - text:SetText("0%") - text:SetPoint("TOP", slider, "BOTTOM") - local editBox = CreateFrame("EditBox", nil, sliderFrame) - sliderFrame.currentText = editBox - editBox:SetFontObject(ChatFontNormal) - editBox:SetHeight(13) - editBox:SetPoint("RIGHT", sliderFrame, "RIGHT", -16, 0) - editBox:SetPoint("LEFT", slider, "RIGHT", 12, 0) - editBox:SetText("50%") - editBox:SetJustifyH("CENTER") - - local width = editBox:GetWidth()/2 + 10 - local left = editBox:CreateTexture(nil, "BACKGROUND") - left:SetTexture("Interface\\ChatFrame\\UI-ChatInputBorder-Left") - left:SetTexCoord(0, width / 256, 0, 1) - left:SetWidth(width) - left:SetHeight(32) - left:SetPoint("LEFT", editBox, "LEFT", -10, 0) - local right = editBox:CreateTexture(nil, "BACKGROUND") - right:SetTexture("Interface\\ChatFrame\\UI-ChatInputBorder-Right") - right:SetTexCoord(1 - width / 256, 1, 0, 1) - right:SetWidth(width) - right:SetHeight(32) - right:SetPoint("RIGHT", editBox, "RIGHT", 10, 0) - - local changed = false - local inside = false - slider:SetScript("OnValueChanged", function() - if sliderFrame.changing then - return - end - changed = true - local done = false - if sliderFrame.parent and sliderFrame.parent.sliderFunc then - local min = sliderFrame.parent.sliderMin or 0 - local max = sliderFrame.parent.sliderMax or 1 - local step - if sliderFrame.fineStep then - step = sliderFrame.parent.sliderStep or (max - min) / 100 - else - step = sliderFrame.parent.sliderBigStep or sliderFrame.parent.sliderStep or (max - min) / 100 - end - local value = (1 - slider:GetValue()) * (max - min) + min - if step > 0 then - value = math.floor((value - min) / step + 0.5) * step + min - if value > max then - value = max - elseif value < min then - value = min - end - end - if value == sliderFrame.lastValue then - return - end - sliderFrame.lastValue = value - local text = sliderFrame.parent.sliderFunc(getArgs(sliderFrame.parent, 'sliderArg', 1, value)) - if sliderFrame.parent.fromAceOptions then - text = nil - elseif type(text) == "string" or type(text) == "number" then - sliderFrame.currentText:SetText(text) - done = true - end - end - if not done then - local min = sliderFrame.parent.sliderMin or 0 - local max = sliderFrame.parent.sliderMax or 1 - local step - if sliderFrame.fineStep then - step = sliderFrame.parent.sliderStep or (max - min) / 100 - else - step = sliderFrame.parent.sliderBigStep or sliderFrame.parent.sliderStep or (max - min) / 100 - end - local value = (1 - slider:GetValue()) * (max - min) + min - if step > 0 then - value = math.floor((value - min) / step + 0.5) * step + min - if value > max then - value = max - elseif value < min then - value = min - end - end - if sliderFrame.parent.sliderIsPercent then - sliderFrame.currentText:SetText(string.format("%.0f%%", value * 100)) - else - if step < 0.1 then - sliderFrame.currentText:SetText(string.format("%.2f", value)) - elseif step < 1 then - sliderFrame.currentText:SetText(string.format("%.1f", value)) - else - sliderFrame.currentText:SetText(string.format("%.0f", value)) - end - end - end - end) - local function onEnter() - StopCounting(self, sliderFrame.level) - showGameTooltip(sliderFrame.parent) - end - local function onLeave() - GameTooltip:Hide() - end - sliderFrame:SetScript("OnEnter", onEnter) - sliderFrame:SetScript("OnLeave", function() - GameTooltip:Hide() - if changed then - local parent = sliderFrame.parent - local sliderFunc = parent.sliderFunc - for i = 1, sliderFrame.level - 1 do - Refresh(self, levels[i]) - end - local newParent - for _,button in ipairs(levels[sliderFrame.level-1].buttons) do - if button.sliderFunc == sliderFunc then - newParent = button - break - end - end - if newParent then - OpenSlider(self, newParent) - else - sliderFrame:Hide() - end - end - end) - editBox:SetScript("OnEnter", onEnter) - editBox:SetScript("OnLeave", onLeave) - slider:SetScript("OnMouseDown", function() - sliderFrame.mouseDown = true - GameTooltip:Hide() - end) - slider:SetScript("OnMouseUp", function() - sliderFrame.mouseDown = false - if changed--[[ and not inside]] then - local parent = sliderFrame.parent - local sliderFunc = parent.sliderFunc - for i = 1, sliderFrame.level - 1 do - Refresh(self, levels[i]) - end - local newParent - for _,button in ipairs(levels[sliderFrame.level-1].buttons) do - if button.sliderFunc == sliderFunc then - newParent = button - break - end - end - if newParent then - OpenSlider(self, newParent) - else - sliderFrame:Hide() - end - end - if inside then - showGameTooltip(sliderFrame.parent) - end - end) - slider:SetScript("OnEnter", function() - inside = true - StopCounting(self, sliderFrame.level) - showGameTooltip(sliderFrame.parent) - end) - slider:SetScript("OnLeave", function() - inside = false - GameTooltip:Hide() - if changed and not sliderFrame.mouseDown then - local parent = sliderFrame.parent - local sliderFunc = parent.sliderFunc - for i = 1, sliderFrame.level - 1 do - Refresh(self, levels[i]) - end - local newParent - for _,button in ipairs(levels[sliderFrame.level-1].buttons) do - if button.sliderFunc == sliderFunc then - newParent = button - break - end - end - if newParent then - OpenSlider(self, newParent) - else - sliderFrame:Hide() - end - - changed = false - end - end) - sliderFrame:SetScript("OnMouseWheel", function(t, a1) - local arg1 = a1 or arg1 - local up = arg1 > 0 - - local min = sliderFrame.parent.sliderMin or 0 - local max = sliderFrame.parent.sliderMax or 1 - local step = sliderFrame.parent.sliderStep or (max - min) / 100 - if step <= 0 then - step = (max - min) / 100 - end - - local value = (1 - slider:GetValue()) * (max - min) + min - if up then - value = value + step - else - value = value - step - end - if value > max then - value = max - elseif value < min then - value = min - end - sliderFrame.fineStep = true - if max<=min then - slider:SetValue(0) - else - slider:SetValue(1 - (value - min) / (max - min)) - end - sliderFrame.fineStep = nil - end) - slider:SetScript("OnMouseWheel", sliderFrame:GetScript("OnMouseWheel")) - editBox:SetScript("OnEnterPressed", function(t, a1) - local value = editBox:GetNumber() - - if sliderFrame.parent.sliderIsPercent then - value = value / 100 - end - - local min = sliderFrame.parent.sliderMin or 0 - local max = sliderFrame.parent.sliderMax or 1 - - if value > max then - value = max - elseif value < min then - value = min - end - sliderFrame.fineStep = true - if max <= min then - slider:SetValue(0) - else - slider:SetValue(1 - (value - min) / (max - min)) - end - sliderFrame.fineStep = nil - - StartCounting(self, sliderFrame.level) - end) - editBox:SetScript("OnEscapePressed", function() - self:Close(sliderFrame.level) - StartCounting(self, sliderFrame.level) - end) - editBox:SetAutoFocus(false) - end - sliderFrame.parent = parent - sliderFrame.level = parent.level.num + 1 - sliderFrame.parentValue = parent.level.value - sliderFrame:SetFrameLevel(parent.level:GetFrameLevel() + 3) - sliderFrame.slider:SetFrameLevel(sliderFrame:GetFrameLevel() + 1) - sliderFrame.currentText:SetFrameLevel(sliderFrame:GetFrameLevel() + 1) - sliderFrame.currentText:ClearFocus() - sliderFrame.changing = true - if not parent.sliderMin or not parent.sliderMax then - return - end - - if parent.arrow then --- parent.arrow:SetVertexColor(0.2, 0.6, 0) --- parent.arrow:SetHeight(24) --- parent.arrow:SetWidth(24) - parent.selected = true - parent.highlight:Show() - end - - sliderFrame:SetClampedToScreen(false) - if not parent.sliderValue then - parent.sliderValue = (parent.sliderMin + parent.sliderMax) / 2 - end - if parent.sliderMax <= parent.sliderMin then - sliderFrame.slider:SetValue(0) - else - sliderFrame.slider:SetValue(1 - (parent.sliderValue - parent.sliderMin) / (parent.sliderMax - parent.sliderMin)) - end - sliderFrame.changing = false - sliderFrame.bottomText:SetText(parent.sliderMinText or "0") - sliderFrame.topText:SetText(parent.sliderMaxText or "1") - local text - if parent.sliderFunc and not parent.fromAceOptions then - text = parent.sliderFunc(getArgs(parent, 'sliderArg', 1, parent.sliderValue)) - end - if type(text) == "number" or type(text) == "string" then - sliderFrame.currentText:SetText(text) - elseif parent.sliderIsPercent then - sliderFrame.currentText:SetText(string.format("%.0f%%", parent.sliderValue * 100)) - else - if parent.sliderStep < 0.1 then - sliderFrame.currentText:SetText(string.format("%.2f", parent.sliderValue)) - elseif parent.sliderStep < 1 then - sliderFrame.currentText:SetText(string.format("%.1f", parent.sliderValue)) - else - sliderFrame.currentText:SetText(string.format("%.0f", parent.sliderValue)) - end - end - - - sliderFrame.lastValue = parent.sliderValue - - local level = parent.level - sliderFrame:Show() - sliderFrame:ClearAllPoints() - if level.lastDirection == "RIGHT" then - if level.lastVDirection == "DOWN" then - sliderFrame:SetPoint("TOPLEFT", parent, "TOPRIGHT", 5, 10) - else - sliderFrame:SetPoint("BOTTOMLEFT", parent, "BOTTOMRIGHT", 5, -10) - end - else - if level.lastVDirection == "DOWN" then - sliderFrame:SetPoint("TOPRIGHT", parent, "TOPLEFT", -5, 10) - else - sliderFrame:SetPoint("BOTTOMRIGHT", parent, "BOTTOMLEFT", -5, -10) - end - end - local dirty - if level.lastDirection == "RIGHT" then - if sliderFrame:GetRight() > GetScreenWidth() then - level.lastDirection = "LEFT" - dirty = true - end - elseif sliderFrame:GetLeft() < 0 then - level.lastDirection = "RIGHT" - dirty = true - end - if level.lastVDirection == "DOWN" then - if sliderFrame:GetBottom() < 0 then - level.lastVDirection = "UP" - dirty = true - end - elseif sliderFrame:GetTop() > GetScreenWidth() then - level.lastVDirection = "DOWN" - dirty = true - end - if dirty then - sliderFrame:ClearAllPoints() - if level.lastDirection == "RIGHT" then - if level.lastVDirection == "DOWN" then - sliderFrame:SetPoint("TOPLEFT", parent, "TOPRIGHT", 5, 10) - else - sliderFrame:SetPoint("BOTTOMLEFT", parent, "BOTTOMRIGHT", 5, -10) - end - else - if level.lastVDirection == "DOWN" then - sliderFrame:SetPoint("TOPRIGHT", parent, "TOPLEFT", -5, 10) - else - sliderFrame:SetPoint("BOTTOMRIGHT", parent, "BOTTOMLEFT", -5, -10) - end - end - end - local left, bottom = sliderFrame:GetLeft(), sliderFrame:GetBottom() - sliderFrame:ClearAllPoints() - sliderFrame:SetPoint("BOTTOMLEFT", UIParent, "BOTTOMLEFT", left, bottom) - if mod(level.num, 5) == 0 then - local left, bottom = level:GetLeft(), level:GetBottom() - level:ClearAllPoints() - level:SetPoint("BOTTOMLEFT", UIParent, "BOTTOMLEFT", left, bottom) - end - sliderFrame:SetClampedToScreen(true) -end - -function OpenEditBox(self, parent) - if not editBoxFrame then - editBoxFrame = CreateFrame("Frame", nil, nil) - editBoxFrame:SetWidth(200) - editBoxFrame:SetHeight(40) - editBoxFrame:SetScale(UIParent:GetScale()) - editBoxFrame:SetBackdrop(tmp( - 'bgFile', "Interface\\Tooltips\\UI-Tooltip-Background", - 'edgeFile', "Interface\\Tooltips\\UI-Tooltip-Border", - 'tile', true, - 'insets', tmp2( - 'left', 5, - 'right', 5, - 'top', 5, - 'bottom', 5 - ), - 'tileSize', 16, - 'edgeSize', 16 - )) - editBoxFrame:SetFrameStrata("FULLSCREEN_DIALOG") - if editBoxFrame.SetTopLevel then - editBoxFrame:SetTopLevel(true) - end - editBoxFrame:SetBackdropBorderColor(TOOLTIP_DEFAULT_COLOR.r, TOOLTIP_DEFAULT_COLOR.g, TOOLTIP_DEFAULT_COLOR.b) - editBoxFrame:SetBackdropColor(TOOLTIP_DEFAULT_BACKGROUND_COLOR.r, TOOLTIP_DEFAULT_BACKGROUND_COLOR.g, TOOLTIP_DEFAULT_BACKGROUND_COLOR.b) - editBoxFrame:EnableMouse(true) - editBoxFrame:EnableMouseWheel(true) - editBoxFrame:Hide() - editBoxFrame:SetPoint("CENTER", UIParent, "CENTER") - - local editBox = CreateFrame("EditBox", nil, editBoxFrame) - editBoxFrame.editBox = editBox - editBox:SetFontObject(ChatFontNormal) - editBox:SetWidth(160) - editBox:SetHeight(13) - editBox:SetPoint("CENTER", editBoxFrame, "CENTER", 0, 0) - - local left = editBox:CreateTexture(nil, "BACKGROUND") - left:SetTexture("Interface\\ChatFrame\\UI-ChatInputBorder-Left") - left:SetTexCoord(0, 100 / 256, 0, 1) - left:SetWidth(100) - left:SetHeight(32) - left:SetPoint("LEFT", editBox, "LEFT", -10, 0) - local right = editBox:CreateTexture(nil, "BACKGROUND") - right:SetTexture("Interface\\ChatFrame\\UI-ChatInputBorder-Right") - right:SetTexCoord(156/256, 1, 0, 1) - right:SetWidth(100) - right:SetHeight(32) - right:SetPoint("RIGHT", editBox, "RIGHT", 10, 0) - - editBox:SetScript("OnEnterPressed", function() - if editBoxFrame.parent and editBoxFrame.parent.editBoxValidateFunc then - local t = editBox.realText or editBox:GetText() or "" - local result = editBoxFrame.parent.editBoxValidateFunc(getArgs(editBoxFrame.parent, 'editBoxValidateArg', 1, t)) - if not result then - UIErrorsFrame:AddMessage(VALIDATION_ERROR, 1, 0, 0) - return - end - end - if editBoxFrame.parent and editBoxFrame.parent.editBoxFunc then - local t - if editBox.realText ~= "NONE" then - t = editBox.realText or editBox:GetText() or "" - end - editBoxFrame.parent.editBoxFunc(getArgs(editBoxFrame.parent, 'editBoxArg', 1, t)) - end - self:Close(editBoxFrame.level) - for i = 1, editBoxFrame.level - 1 do - Refresh(self, levels[i]) - end - StartCounting(self, editBoxFrame.level-1) - end) - editBox:SetScript("OnEscapePressed", function() - self:Close(editBoxFrame.level) - StartCounting(self, editBoxFrame.level-1) - end) - editBox:SetScript("OnReceiveDrag", function(this) - if GetCursorInfo then - local type, alpha, bravo = GetCursorInfo() - local text - if type == "spell" then - text = GetSpellName(alpha, bravo) - elseif type == "item" then - text = bravo - end - if not text then - return - end - ClearCursor() - editBox:SetText(text) - end - end) - local changing = false - local skipNext = false - - function editBox:SpecialSetText(text) - local oldText = editBox:GetText() or "" - if not text then - text = "" - end - if text ~= oldText then - changing = true - self:SetText(tostring(text)) - changing = false - skipNext = true - end - end - - editBox:SetScript("OnTextChanged", function() - if skipNext then - skipNext = false - elseif not changing and editBoxFrame.parent and editBoxFrame.parent.editBoxChangeFunc then - local t - if editBox.realText ~= "NONE" then - t = editBox.realText or editBox:GetText() or "" - end - local text = editBoxFrame.parent.editBoxChangeFunc(getArgs(editBoxFrame.parent, 'editBoxChangeArg', 1, t)) - if text then - editBox:SpecialSetText(text) - end - end - end) - editBoxFrame:SetScript("OnEnter", function() - StopCounting(self, editBoxFrame.level) - showGameTooltip(editBoxFrame.parent) - end) - editBoxFrame:SetScript("OnLeave", function() - GameTooltip:Hide() - end) - editBox:SetScript("OnEnter", function() - StopCounting(self, editBoxFrame.level) - showGameTooltip(editBoxFrame.parent) - end) - editBox:SetScript("OnLeave", function() - GameTooltip:Hide() - end) - editBoxFrame:SetScript("OnKeyDown", function(this, a1) - if not editBox.keybinding then - return - end - local arg1 = a1 or arg1 - local screenshotKey = GetBindingKey("SCREENSHOT") - if screenshotKey and arg1 == screenshotKey then - Screenshot() - return - end - - if arg1 == "LeftButton" then - arg1 = "BUTTON1" - elseif arg1 == "RightButton" then - arg1 = "BUTTON2" - elseif arg1 == "MiddleButton" then - arg1 = "BUTTON3" - elseif arg1 == "Button4" then - arg1 = "BUTTON4" - elseif arg1 == "Button5" then - arg1 = "BUTTON5" - end - if arg1 == "UNKNOWN" then - return - elseif arg1 == "SHIFT" or arg1 == "CTRL" or arg1 == "ALT" then - return - elseif arg1 == "ENTER" then - if editBox.keybindingOnly and not editBox.keybindingOnly[editBox.realText] then - return editBox:GetScript("OnEscapePressed")() - elseif editBox.keybindingExcept and editBox.keybindingExcept[editBox.realText] then - return editBox:GetScript("OnEscapePressed")() - else - return editBox:GetScript("OnEnterPressed")() - end - elseif arg1 == "ESCAPE" then - if editBox.realText == "NONE" then - return editBox:GetScript("OnEscapePressed")() - else - editBox:SpecialSetText(NONE or "NONE") - editBox.realText = "NONE" - return - end - elseif editBox.keybindingOnly and not editBox.keybindingOnly[arg1] then - return - elseif editBox.keybindingExcept and editBox.keybindingExcept[arg1] then - return - end - local s = GetBindingText(arg1, "KEY_") - if s == "BUTTON1" then - s = KEY_BUTTON1 - elseif s == "BUTTON2" then - s = KEY_BUTTON2 - end - local real = arg1 - if IsShiftKeyDown() then - s = "Shift-" .. s - real = "SHIFT-" .. real - end - if IsControlKeyDown() then - s = "Ctrl-" .. s - real = "CTRL-" .. real - end - if IsAltKeyDown() then - s = "Alt-" .. s - real = "ALT-" .. real - end - if editBox:GetText() ~= s then - editBox:SpecialSetText("-") - editBox:SpecialSetText(s) - editBox.realText = real - return editBox:GetScript("OnTextChanged")() - end - end) - editBoxFrame:SetScript("OnMouseDown", editBoxFrame:GetScript("OnKeyDown")) - editBox:SetScript("OnMouseDown", function(this, ...) - if GetCursorInfo and (CursorHasItem() or CursorHasSpell()) then - return editBox:GetScript("OnReceiveDrag")(this, ...) - end - return editBoxFrame:GetScript("OnKeyDown")(this, ...) - end) - editBoxFrame:SetScript("OnMouseWheel", function(t, a1) - local arg1 = a1 or arg1 - local up = arg1 > 0 - arg1 = up and "MOUSEWHEELUP" or "MOUSEWHEELDOWN" - return editBoxFrame:GetScript("OnKeyDown")(t or this, arg1) - end) - editBox:SetScript("OnMouseWheel", editBoxFrame:GetScript("OnMouseWheel")) - end - editBoxFrame.parent = parent - editBoxFrame.level = parent.level.num + 1 - editBoxFrame.parentValue = parent.level.value - editBoxFrame:SetFrameLevel(parent.level:GetFrameLevel() + 3) - editBoxFrame.editBox:SetFrameLevel(editBoxFrame:GetFrameLevel() + 1) - editBoxFrame.editBox.realText = nil - editBoxFrame:SetClampedToScreen(false) - - editBoxFrame.editBox:SpecialSetText("") - if parent.editBoxIsKeybinding then - local s = parent.editBoxText - if s == "" then - s = "NONE" - end - editBoxFrame.editBox.realText = s - if s and s ~= "NONE" then - local alpha,bravo = s:match("^(.+)%-(.+)$") - if not bravo then - alpha = nil - bravo = s - end - bravo = GetBindingText(bravo, "KEY_") - if alpha then - editBoxFrame.editBox:SpecialSetText(alpha:upper() .. "-" .. bravo) - else - editBoxFrame.editBox:SpecialSetText(bravo) - end - else - editBoxFrame.editBox:SpecialSetText(NONE or "NONE") - end - else - editBoxFrame.editBox:SpecialSetText(parent.editBoxText) - end - - editBoxFrame.editBox.keybinding = parent.editBoxIsKeybinding - editBoxFrame.editBox.keybindingOnly = parent.editBoxKeybindingOnly - editBoxFrame.editBox.keybindingExcept = parent.editBoxKeybindingExcept - editBoxFrame.editBox:EnableKeyboard(not parent.editBoxIsKeybinding) - editBoxFrame:EnableKeyboard(parent.editBoxIsKeybinding) - - if parent.arrow then --- parent.arrow:SetVertexColor(0.2, 0.6, 0) --- parent.arrow:SetHeight(24) --- parent.arrow:SetWidth(24) - parent.selected = true - parent.highlight:Show() - end - - local level = parent.level - editBoxFrame:Show() - editBoxFrame:ClearAllPoints() - if level.lastDirection == "RIGHT" then - if level.lastVDirection == "DOWN" then - editBoxFrame:SetPoint("TOPLEFT", parent, "TOPRIGHT", 5, 10) - else - editBoxFrame:SetPoint("BOTTOMLEFT", parent, "BOTTOMRIGHT", 5, -10) - end - else - if level.lastVDirection == "DOWN" then - editBoxFrame:SetPoint("TOPRIGHT", parent, "TOPLEFT", -5, 10) - else - editBoxFrame:SetPoint("BOTTOMRIGHT", parent, "BOTTOMLEFT", -5, -10) - end - end - local dirty - if level.lastDirection == "RIGHT" then - if editBoxFrame:GetRight() > GetScreenWidth() then - level.lastDirection = "LEFT" - dirty = true - end - elseif editBoxFrame:GetLeft() < 0 then - level.lastDirection = "RIGHT" - dirty = true - end - if level.lastVDirection == "DOWN" then - if editBoxFrame:GetBottom() < 0 then - level.lastVDirection = "UP" - dirty = true - end - elseif editBoxFrame:GetTop() > GetScreenWidth() then - level.lastVDirection = "DOWN" - dirty = true - end - if dirty then - editBoxFrame:ClearAllPoints() - if level.lastDirection == "RIGHT" then - if level.lastVDirection == "DOWN" then - editBoxFrame:SetPoint("TOPLEFT", parent, "TOPRIGHT", 5, 10) - else - editBoxFrame:SetPoint("BOTTOMLEFT", parent, "BOTTOMRIGHT", 5, -10) - end - else - if level.lastVDirection == "DOWN" then - editBoxFrame:SetPoint("TOPRIGHT", parent, "TOPLEFT", -5, 10) - else - editBoxFrame:SetPoint("BOTTOMRIGHT", parent, "BOTTOMLEFT", -5, -10) - end - end - end - local left, bottom = editBoxFrame:GetLeft(), editBoxFrame:GetBottom() - editBoxFrame:ClearAllPoints() - editBoxFrame:SetPoint("BOTTOMLEFT", UIParent, "BOTTOMLEFT", left, bottom) - if mod(level.num, 5) == 0 then - local left, bottom = level:GetLeft(), level:GetBottom() - level:ClearAllPoints() - level:SetPoint("BOTTOMLEFT", UIParent, "BOTTOMLEFT", left, bottom) - end - editBoxFrame:SetClampedToScreen(true) -end - -function Dewdrop:EncodeKeybinding(text) - if text == nil or text == "NONE" then - return nil - end - text = tostring(text):upper() - local shift, ctrl, alt - local modifier - while true do - if text == "-" then - break - end - modifier, text = strsplit('-', text, 2) - if text then - if modifier ~= "SHIFT" and modifier ~= "CTRL" and modifier ~= "ALT" then - return false - end - if modifier == "SHIFT" then - if shift then - return false - end - shift = true - end - if modifier == "CTRL" then - if ctrl then - return false - end - ctrl = true - end - if modifier == "ALT" then - if alt then - return false - end - alt = true - end - else - text = modifier - break - end - end - if not text:find("^F%d+$") and text ~= "CAPSLOCK" and text:len() ~= 1 and (text:len() == 0 or text:byte() < 128 or text:len() > 4) and not _G["KEY_" .. text] and text ~= "BUTTON1" and text ~= "BUTTON2" then - return false - end - local s = GetBindingText(text, "KEY_") - if s == "BUTTON1" then - s = KEY_BUTTON1 - elseif s == "BUTTON2" then - s = KEY_BUTTON2 - end - if shift then - s = "Shift-" .. s - end - if ctrl then - s = "Ctrl-" .. s - end - if alt then - s = "Alt-" .. s - end - return s -end - -function Dewdrop:IsOpen(parent) - self:argCheck(parent, 2, "table", "string", "nil") - return levels[1] and levels[1]:IsShown() and (not parent or parent == levels[1].parent or parent == levels[1]:GetParent()) -end - -function Dewdrop:GetOpenedParent() - return (levels[1] and levels[1]:IsShown()) and (levels[1].parent or levels[1]:GetParent()) -end - -function Open(self, parent, func, level, value, point, relativePoint, cursorX, cursorY) - self:Close(level) - if DewdropLib then - local d = DewdropLib:GetInstance('1.0') - local ret, val = pcall(d, IsOpen, d) - if ret and val then - DewdropLib:GetInstance('1.0'):Close() - end - end - if type(parent) == "table" then - parent:GetCenter() - end - local frame = AcquireLevel(self, level) - if level == 1 then - frame.lastDirection = "RIGHT" - frame.lastVDirection = "DOWN" - else - frame.lastDirection = levels[level - 1].lastDirection - frame.lastVDirection = levels[level - 1].lastVDirection - end - frame:SetFrameStrata("FULLSCREEN_DIALOG") - frame:ClearAllPoints() - frame.parent = parent - frame:SetPoint("LEFT", UIParent, "RIGHT", 10000, 0) - frame:Show() - if level == 1 then - baseFunc = func - end - levels[level].value = value --- levels[level].parentText = parent.text and parent.text:GetText() or nil --- levels[level].parentTooltipTitle = parent.tooltipTitle --- levels[level].parentTooltipText = parent.tooltipText --- levels[level].parentTooltipFunc = parent.tooltipFunc - if type(parent) == "table" and parent.arrow then --- parent.arrow:SetVertexColor(0.2, 0.6, 0) --- parent.arrow:SetHeight(24) --- parent.arrow:SetWidth(24) - parent.selected = true - parent.highlight:Show() - end - relativePoint = relativePoint or point - Refresh(self, levels[level]) - if point or (cursorX and cursorY) then - frame:ClearAllPoints() - if cursorX and cursorY then - local curX, curY = GetScaledCursorPosition() - if curY < GetScreenHeight() / 2 then - point, relativePoint = "BOTTOM", "BOTTOM" - else - point, relativePoint = "TOP", "TOP" - end - if curX < GetScreenWidth() / 2 then - point, relativePoint = point .. "LEFT", relativePoint .. "RIGHT" - else - point, relativePoint = point .. "RIGHT", relativePoint .. "LEFT" - end - end - frame:SetPoint(point, type(parent) == "table" and parent or UIParent, relativePoint) - if cursorX and cursorY then - local left = frame:GetLeft() - local width = frame:GetWidth() - local bottom = frame:GetBottom() - local height = frame:GetHeight() - local curX, curY = GetScaledCursorPosition() - frame:ClearAllPoints() - relativePoint = relativePoint or point - if point == "BOTTOM" or point == "TOP" then - if curX < GetScreenWidth() / 2 then - point = point .. "LEFT" - else - point = point .. "RIGHT" - end - elseif point == "CENTER" then - if curX < GetScreenWidth() / 2 then - point = "LEFT" - else - point = "RIGHT" - end - end - local xOffset, yOffset = 0, 0 - if curY > GetScreenHeight() / 2 then - yOffset = -height - end - if curX > GetScreenWidth() / 2 then - xOffset = -width - end - frame:SetPoint(point, type(parent) == "table" and parent or UIParent, relativePoint, curX - left + xOffset, curY - bottom + yOffset) - if level == 1 then - frame.lastDirection = "RIGHT" - end - elseif cursorX then - local left = frame:GetLeft() - local width = frame:GetWidth() - local curX, curY = GetScaledCursorPosition() - frame:ClearAllPoints() - relativePoint = relativePoint or point - if point == "BOTTOM" or point == "TOP" then - if curX < GetScreenWidth() / 2 then - point = point .. "LEFT" - else - point = point .. "RIGHT" - end - elseif point == "CENTER" then - if curX < GetScreenWidth() / 2 then - point = "LEFT" - else - point = "RIGHT" - end - end - frame:SetPoint(point, type(parent) == "table" and parent or UIParent, relativePoint, curX - left - width / 2, 0) - if level == 1 then - frame.lastDirection = "RIGHT" - end - elseif cursorY then - local bottom = frame:GetBottom() - local height = frame:GetHeight() - local curX, curY = GetScaledCursorPosition() - frame:ClearAllPoints() - relativePoint = relativePoint or point - if point == "LEFT" or point == "RIGHT" then - if curX < GetScreenHeight() / 2 then - point = point .. "BOTTOM" - else - point = point .. "TOP" - end - elseif point == "CENTER" then - if curX < GetScreenHeight() / 2 then - point = "BOTTOM" - else - point = "TOP" - end - end - frame:SetPoint(point, type(parent) == "table" and parent or UIParent, relativePoint, 0, curY - bottom - height / 2) - if level == 1 then - frame.lastDirection = "DOWN" - end - end - if (strsub(point, 1, 3) ~= strsub(relativePoint, 1, 3)) then - if frame:GetBottom() < 0 then - local point, parent, relativePoint, x, y = frame:GetPoint(1) - local change = GetScreenHeight() - frame:GetTop() - local otherChange = -frame:GetBottom() - if otherChange < change then - change = otherChange - end - frame:SetPoint(point, parent, relativePoint, x, y + change) - elseif frame:GetTop() > GetScreenHeight() then - local point, parent, relativePoint, x, y = frame:GetPoint(1) - local change = GetScreenHeight() - frame:GetTop() - local otherChange = -frame:GetBottom() - if otherChange < change then - change = otherChange - end - frame:SetPoint(point, parent, relativePoint, x, y + change) - end - end - end - CheckDualMonitor(self, frame) - frame:SetClampedToScreen(true) - frame:SetClampedToScreen(false) - StartCounting(self, level) -end - -function Dewdrop:IsRegistered(parent) - self:argCheck(parent, 2, "table", "string") - return not not self.registry[parent] -end - -function Dewdrop:Register(parent, ...) - self:argCheck(parent, 2, "table", "string") - if self.registry[parent] then - self:Unregister(parent) - end - local info = new(...) - if type(info.children) == "table" then - local err, position = validateOptions(info.children) - - if err then - if position then - Dewdrop:error(position .. ": " .. err) - else - Dewdrop:error(err) - end - end - end - self.registry[parent] = info - if not info.dontHook and not self.onceRegistered[parent] and type(parent) == "table" then - if parent:HasScript("OnMouseUp") then - local script = parent:GetScript("OnMouseUp") - parent:SetScript("OnMouseUp", function(this, ...) - if script then - script(this, ...) - end - if arg1 == "RightButton" and self.registry[parent] then - if self:IsOpen(parent) then - self:Close() - else - self:Open(parent) - end - end - end) - end - if parent:HasScript("OnMouseDown") then - local script = parent:GetScript("OnMouseDown") - parent:SetScript("OnMouseDown", function(this, ...) - if script then - script(this, ...) - end - if self.registry[parent] then - self:Close() - end - end) - end - end - self.onceRegistered[parent] = true -end - -function Dewdrop:Unregister(parent) - self:argCheck(parent, 2, "table", "string") - self.registry[parent] = nil -end - -function Dewdrop:Open(parent, ...) - self:argCheck(parent, 2, "table", "string") - local info - local k1 = ... - if type(k1) == "table" and k1[0] and k1.IsFrameType and self.registry[k1] then - info = tmp(select(2, ...)) - for k,v in pairs(self.registry[k1]) do - if info[k] == nil then - info[k] = v - end - end - else - info = tmp(...) - if self.registry[parent] then - for k,v in pairs(self.registry[parent]) do - if info[k] == nil then - info[k] = v - end - end - end - end - local point = info.point - local relativePoint = info.relativePoint - local cursorX = info.cursorX - local cursorY = info.cursorY - if type(point) == "function" then - local b - point, b = point(parent) - if b then - relativePoint = b - end - end - if type(relativePoint) == "function" then - relativePoint = relativePoint(parent) - end - Open(self, parent, info.children, 1, nil, point, relativePoint, cursorX, cursorY) -end - -function Clear(self, level) - if level then - if level.buttons then - for i = #level.buttons, 1, -1 do - ReleaseButton(self, level, i) - end - end - end -end - -function Dewdrop:Close(level) - if DropDownList1:IsShown() then - DropDownList1:Hide() - end - if DewdropLib then - local d = DewdropLib:GetInstance('1.0') - local ret, val = pcall(d, IsOpen, d) - if ret and val then - DewdropLib:GetInstance('1.0'):Close() - end - end - self:argCheck(level, 2, "number", "nil") - if not level then - level = 1 - end - if level == 1 and levels[level] then - levels[level].parented = false - end - if level > 1 and levels[level-1].buttons then - local buttons = levels[level-1].buttons - for _,button in ipairs(buttons) do --- button.arrow:SetWidth(16) --- button.arrow:SetHeight(16) - button.selected = nil - button.highlight:Hide() --- button.arrow:SetVertexColor(1, 1, 1) - end - end - if sliderFrame and sliderFrame.level >= level then - sliderFrame:Hide() - end - if editBoxFrame and editBoxFrame.level >= level then - editBoxFrame:Hide() - end - for i = level, #levels do - Clear(self, levels[level]) - levels[i]:Hide() - levels[i]:ClearAllPoints() - levels[i]:SetPoint("CENTER", UIParent, "CENTER") - levels[i].value = nil - end -end - -function Dewdrop:AddSeparator(level) - level = levels[level or currentLevel] - if not level or not level.buttons then return; end - - local prevbutton = level.buttons[#level.buttons] - if not prevbutton then return; end - - if prevbutton.disabled and prevbutton.text:GetText() == "" then - return - end - self:AddLine("text", "", "disabled", true) -end - -function Dewdrop:AddLine(...) - local info = tmp(...) - local level = info.level or currentLevel - info.level = nil - local button = AcquireButton(self, level) - if not next(info) then - info.disabled = true - end - button.disabled = info.isTitle or info.notClickable or info.disabled or (self.combat and info.secure) - button.isTitle = info.isTitle - button.notClickable = info.notClickable - if button.isTitle then - button.text:SetFontObject(GameFontNormalSmall) - elseif button.notClickable then - button.text:SetFontObject(GameFontHighlightSmall) - elseif button.disabled then - button.text:SetFontObject(GameFontDisableSmall) - else - button.text:SetFontObject(GameFontHighlightSmall) - end - if info.disabled then - button.arrow:SetDesaturated(true) - button.check:SetDesaturated(true) - else - button.arrow:SetDesaturated(false) - button.check:SetDesaturated(false) - end - if info.textR and info.textG and info.textB then - button.textR = info.textR - button.textG = info.textG - button.textB = info.textB - button.text:SetTextColor(button.textR, button.textG, button.textB) - else - button.text:SetTextColor(button.text:GetFontObject():GetTextColor()) - end - button.notCheckable = info.notCheckable - button.text:SetPoint("LEFT", button, "LEFT", button.notCheckable and 0 or 24, 0) - button.checked = not info.notCheckable and info.checked - button.mouseoverUnderline = info.mouseoverUnderline - button.isRadio = not info.notCheckable and info.isRadio - if info.isRadio then - button.check:Show() - button.check:SetTexture(info.checkIcon or "Interface\\Buttons\\UI-RadioButton") - if button.checked then - button.check:SetTexCoord(0.25, 0.5, 0, 1) - button.check:SetVertexColor(1, 1, 1, 1) - else - button.check:SetTexCoord(0, 0.25, 0, 1) - button.check:SetVertexColor(1, 1, 1, 0.5) - end - button.radioHighlight:SetTexture(info.checkIcon or "Interface\\Buttons\\UI-RadioButton") - button.check:SetWidth(16) - button.check:SetHeight(16) - elseif info.icon then - button.check:Show() - button.check:SetTexture(info.icon) - if info.iconWidth and info.iconHeight then - button.check:SetWidth(info.iconWidth) - button.check:SetHeight(info.iconHeight) - else - button.check:SetWidth(16) - button.check:SetHeight(16) - end - if info.iconCoordLeft and info.iconCoordRight and info.iconCoordTop and info.iconCoordBottom then - button.check:SetTexCoord(info.iconCoordLeft, info.iconCoordRight, info.iconCoordTop, info.iconCoordBottom) - elseif info.icon:find("^Interface\\Icons\\") then - button.check:SetTexCoord(0.05, 0.95, 0.05, 0.95) - else - button.check:SetTexCoord(0, 1, 0, 1) - end - button.check:SetVertexColor(1, 1, 1, 1) - else - if button.checked then - if info.checkIcon then - button.check:SetWidth(16) - button.check:SetHeight(16) - button.check:SetTexture(info.checkIcon) - if info.checkIcon:find("^Interface\\Icons\\") then - button.check:SetTexCoord(0.05, 0.95, 0.05, 0.95) - else - button.check:SetTexCoord(0, 1, 0, 1) - end - else - button.check:SetWidth(24) - button.check:SetHeight(24) - button.check:SetTexture("Interface\\Buttons\\UI-CheckBox-Check") - button.check:SetTexCoord(0, 1, 0, 1) - end - button.check:SetVertexColor(1, 1, 1, 1) - else - button.check:SetVertexColor(1, 1, 1, 0) - end - end - if not button.disabled then - button.func = info.func - button.secure = info.secure - end - button.hasColorSwatch = info.hasColorSwatch - if button.hasColorSwatch then - button.colorSwatch:Show() - button.colorSwatch.texture:Show() - button.r = info.r or 1 - button.g = info.g or 1 - button.b = info.b or 1 - button.colorSwatch.texture:SetVertexColor(button.r, button.g, button.b) - button.checked = false - button.func = nil - button.colorFunc = info.colorFunc - local i = 1 - while true do - local k = "colorArg" .. i - local x = info[k] - if x == nil then - break - end - button[k] = x - i = i + 1 - end - button.hasOpacity = info.hasOpacity - button.opacity = info.opacity or 1 - else - button.colorSwatch:Hide() - button.colorSwatch.texture:Hide() - end - button.hasArrow = not button.hasColorSwatch and (info.value or info.hasSlider or info.hasEditBox) and info.hasArrow - if button.hasArrow then - button.arrow:SetAlpha(1) - if info.hasSlider then - button.hasSlider = true - button.sliderMin = info.sliderMin or 0 - button.sliderMax = info.sliderMax or 1 - button.sliderStep = info.sliderStep or 0 - button.sliderBigStep = info.sliderBigStep or button.sliderStep - if button.sliderBigStep < button.sliderStep then - button.sliderBigStep = button.sliderStep - end - button.sliderIsPercent = info.sliderIsPercent and true or false - button.sliderMinText = info.sliderMinText or button.sliderIsPercent and string.format("%.0f%%", button.sliderMin * 100) or button.sliderMin - button.sliderMaxText = info.sliderMaxText or button.sliderIsPercent and string.format("%.0f%%", button.sliderMax * 100) or button.sliderMax - button.sliderFunc = info.sliderFunc - button.sliderValue = info.sliderValue - button.fromAceOptions = info.fromAceOptions - local i = 1 - while true do - local k = "sliderArg" .. i - local x = info[k] - if x == nil then - break - end - button[k] = x - i = i + 1 - end - elseif info.hasEditBox then - button.hasEditBox = true - button.editBoxText = info.editBoxText or "" - button.editBoxFunc = info.editBoxFunc - local i = 1 - while true do - local k = "editBoxArg" .. i - local x = info[k] - if x == nil then - break - end - button[k] = x - i = i + 1 - end - button.editBoxChangeFunc = info.editBoxChangeFunc - local i = 1 - while true do - local k = "editBoxChangeArg" .. i - local x = info[k] - if x == nil then - break - end - button[k] = x - i = i + 1 - end - button.editBoxValidateFunc = info.editBoxValidateFunc - local i = 1 - while true do - local k = "editBoxValidateArg" .. i - local x = info[k] - if x == nil then - break - end - button[k] = x - i = i + 1 - end - button.editBoxIsKeybinding = info.editBoxIsKeybinding - button.editBoxKeybindingOnly = info.editBoxKeybindingOnly - button.editBoxKeybindingExcept = info.editBoxKeybindingExcept - else - button.value = info.value - local l = levels[level+1] - if l and info.value == l.value then --- button.arrow:SetWidth(24) --- button.arrow:SetHeight(24) - button.selected = true - button.highlight:Show() - end - end - else - button.arrow:SetAlpha(0) - end - local i = 1 - while true do - local k = "arg" .. i - local x = info[k] - if x == nil then - break - end - button[k] = x - i = i + 1 - end - button.closeWhenClicked = info.closeWhenClicked - button.textHeight = info.textHeight or UIDROPDOWNMENU_DEFAULT_TEXT_HEIGHT or 10 - local font,_ = button.text:GetFont() - button.text:SetFont(STANDARD_TEXT_FONT or "Fonts\\FRIZQT__.TTF", button.textHeight) - button:SetHeight(button.textHeight + 6) - button.text:SetPoint("RIGHT", button.arrow, (button.hasColorSwatch or button.hasArrow) and "LEFT" or "RIGHT") - button.text:SetJustifyH(info.justifyH or "LEFT") - button.text:SetText(info.text) - button.tooltipTitle = info.tooltipTitle - button.tooltipText = info.tooltipText - button.tooltipFunc = info.tooltipFunc - local i = 1 - while true do - local k = "tooltipArg" .. i - local x = info[k] - if x == nil then - break - end - button[k] = x - i = i + 1 - end - if not button.tooltipTitle and not button.tooltipText and not button.tooltipFunc and not info.isTitle then - button.tooltipTitle = info.text - end - if type(button.func) == "string" then - if type(button.arg1) ~= "table" then - self:error("Cannot call method %q on a non-table", button.func) - end - if type(button.arg1[button.func]) ~= "function" then - self:error("Method %q nonexistant.", button.func) - end - end -end - -function Dewdrop:InjectAceOptionsTable(handler, options) - self:argCheck(handler, 2, "table") - self:argCheck(options, 3, "table") - if tostring(options.type):lower() ~= "group" then - self:error('Cannot inject into options table argument #3 if its type is not "group"') - end - if options.handler ~= nil and options.handler ~= handler then - self:error("Cannot inject into options table argument #3 if it has a different handler than argument #2") - end - options.handler = handler - local class = handler.class - if not AceLibrary:HasInstance("AceOO-2.0") or not class then - if Rock then - -- possible Rock object - for mixin in Rock:IterateObjectMixins(handler) do - if type(mixin.GetAceOptionsDataTable) == "function" then - local t = mixin:GetAceOptionsDataTable(handler) - for k,v in pairs(t) do - if type(options.args) ~= "table" then - options.args = {} - end - if options.args[k] == nil then - options.args[k] = v - end - end - end - end - end - else - -- Ace2 object - while class and class ~= AceLibrary("AceOO-2.0").Class do - if type(class.GetAceOptionsDataTable) == "function" then - local t = class:GetAceOptionsDataTable(handler) - for k,v in pairs(t) do - if type(options.args) ~= "table" then - options.args = {} - end - if options.args[k] == nil then - options.args[k] = v - end - end - end - local mixins = class.mixins - if mixins then - for mixin in pairs(mixins) do - if type(mixin.GetAceOptionsDataTable) == "function" then - local t = mixin:GetAceOptionsDataTable(handler) - for k,v in pairs(t) do - if type(options.args) ~= "table" then - options.args = {} - end - if options.args[k] == nil then - options.args[k] = v - end - end - end - end - end - class = class.super - end - end - return options -end - -function Dewdrop:OnTooltipHide() - if lastSetFont then - if lastSetFont == normalFont then - lastSetFont = nil - return - end - fillRegionTmp(GameTooltip:GetRegions()) - for i,v in ipairs(regionTmp) do - if v.GetFont then - local font,size,outline = v:GetFont() - if font == lastSetFont then - v:SetFont(normalFont, size, outline) - end - end - regionTmp[i] = nil - end - lastSetFont = nil - end -end - -local function activate(self, oldLib, oldDeactivate) - Dewdrop = self - if oldLib and oldLib.registry then - self.registry = oldLib.registry - self.onceRegistered = oldLib.onceRegistered - else - self.registry = {} - self.onceRegistered = {} - - local WorldFrame_OnMouseDown = WorldFrame:GetScript("OnMouseDown") - local WorldFrame_OnMouseUp = WorldFrame:GetScript("OnMouseUp") - local oldX, oldY, clickTime - WorldFrame:SetScript("OnMouseDown", function(this, ...) - oldX,oldY = GetCursorPosition() - clickTime = GetTime() - if WorldFrame_OnMouseDown then - WorldFrame_OnMouseDown(this, ...) - end - end) - - WorldFrame:SetScript("OnMouseUp", function(this, ...) - local x,y = GetCursorPosition() - if not oldX or not oldY or not x or not y or not clickTime then - self:Close() - if WorldFrame_OnMouseUp then - WorldFrame_OnMouseUp(this, ...) - end - return - end - local d = math.abs(x - oldX) + math.abs(y - oldY) - if d <= 5 and GetTime() - clickTime < 0.5 then - self:Close() - end - if WorldFrame_OnMouseUp then - WorldFrame_OnMouseUp(this, ...) - end - end) - - hooksecurefunc(DropDownList1, "Show", function() - if levels[1] and levels[1]:IsVisible() then - self:Close() - end - end) - - hooksecurefunc("HideDropDownMenu", function() - if levels[1] and levels[1]:IsVisible() then - self:Close() - end - end) - - hooksecurefunc("CloseDropDownMenus", function() - if levels[1] and levels[1]:IsVisible() then - local stack = debugstack() - if not stack:find("`TargetFrame_OnHide'") then - self:Close() - end - end - end) - end - self.frame = oldLib and oldLib.frame or CreateFrame("Frame") - self.frame:UnregisterAllEvents() - self.frame:RegisterEvent("PLAYER_REGEN_ENABLED") - self.frame:RegisterEvent("PLAYER_REGEN_DISABLED") - self.frame:Hide() - self.frame:SetScript("OnEvent", function(this, event) - this:Show() - if event=="PLAYER_REGEN_ENABLED" then -- track combat state for secure frame operations - self.combat = false - elseif event=="PLAYER_REGEN_DISABLED" then - self.combat = true - end - end) - self.frame:SetScript("OnUpdate", function(this) - this:Hide() - self:Refresh(1) - end) - self.hookedTooltip = true - if not oldLib or not oldLib.hookedTooltip then - local OnTooltipHide = GameTooltip:GetScript("OnHide") - GameTooltip:SetScript("OnHide", function(this, ...) - if OnTooltipHide then - OnTooltipHide(this, ...) - end - if type(self.OnTooltipHide) == "function" then - self:OnTooltipHide() - end - end) - end - levels = {} - buttons = {} - - if oldDeactivate then - oldDeactivate(oldLib) - end -end - -local function external(lib, major, instance) - if major == "SharedMedia-1.0" then - SharedMedia = instance - end -end - -AceLibrary:Register(Dewdrop, MAJOR_VERSION, MINOR_VERSION, activate, nil, external) +--[[ +Name: Dewdrop-2.0 +Revision: $Rev: 56529 $ +Author(s): ckknight (ckknight@gmail.com) +Website: http://ckknight.wowinterface.com/ +Documentation: http://wiki.wowace.com/index.php/Dewdrop-2.0 +SVN: http://svn.wowace.com/root/trunk/DewdropLib/Dewdrop-2.0 +Description: A library to provide a clean dropdown menu interface. +Dependencies: AceLibrary +License: LGPL v2.1 +]] + +local MAJOR_VERSION = "Dewdrop-2.0" +local MINOR_VERSION = "$Revision: 56529 $" + +if not AceLibrary then error(MAJOR_VERSION .. " requires AceLibrary") end +if not AceLibrary:IsNewVersion(MAJOR_VERSION, MINOR_VERSION) then return end + +local Dewdrop = {} + +local SharedMedia + +local CLOSE = "Close" +local CLOSE_DESC = "Close the menu." +local VALIDATION_ERROR = "Validation error." +local USAGE_TOOLTIP = "Usage: %s." +local RANGE_TOOLTIP = "Note that you can scroll your mouse wheel while over the slider to step by one." +local RESET_KEYBINDING_DESC = "Hit escape to clear the keybinding." +local KEY_BUTTON1 = "Left Mouse" +local KEY_BUTTON2 = "Right Mouse" +local DISABLED = "Disabled" +local DEFAULT_CONFIRM_MESSAGE = "Are you sure you want to perform `%s'?" + +if GetLocale() == "deDE" then + CLOSE = "Schlie\195\159en" + CLOSE_DESC = "Men\195\188 schlie\195\159en." + VALIDATION_ERROR = "Validierungsfehler." + USAGE_TOOLTIP = "Benutzung: %s." + RANGE_TOOLTIP = "Beachte das du mit dem Mausrad scrollen kannst solange du mit dem Mauszeiger \195\188ber dem Schieberegler bist, um feinere Spr\195\188nge zu machen." + RESET_KEYBINDING_DESC = "Escape dr\195\188cken, um die Tastenbelegung zu l\195\182schen." + KEY_BUTTON1 = "Linke Maustaste" + KEY_BUTTON2 = "Rechte Maustaste" + DISABLED = "Deaktiviert" + DEFAULT_CONFIRM_MESSAGE = "Bist du sicher das du `%s' machen willst?" +elseif GetLocale() == "koKR" then + CLOSE = "닫기" + CLOSE_DESC = "메뉴를 닫습니다." + VALIDATION_ERROR = "오류 확인." + USAGE_TOOLTIP = "사용법: %s." + RANGE_TOOLTIP = "알림 : 슬라이더 위에서 마우스 휠을 사용하면 한단계씩 조절할 수 있습니다." + RESET_KEYBINDING_DESC = "단축키를 해제하려면 ESC키를 누르세요." + KEY_BUTTON1 = "왼쪽 마우스" + KEY_BUTTON2 = "오른쪽 마우스" + DISABLED = "비활성화됨" + DEFAULT_CONFIRM_MESSAGE = "정말로 `%s' 실행을 하시겠습니까 ?" +elseif GetLocale() == "frFR" then + CLOSE = "Fermer" + CLOSE_DESC = "Ferme le menu." + VALIDATION_ERROR = "Erreur de validation." + USAGE_TOOLTIP = "Utilisation : %s." + RANGE_TOOLTIP = "Vous pouvez aussi utiliser la molette de la souris pour pour modifier progressivement." + RESET_KEYBINDING_DESC = "Appuyez sur la touche Echappement pour effacer le raccourci." + KEY_BUTTON1 = "Clic gauche" + KEY_BUTTON2 = "Clic droit" + DISABLED = "D\195\169sactiv\195\169" + DEFAULT_CONFIRM_MESSAGE = "\195\138tes-vous s\195\187r de vouloir effectuer '%s' ?" +elseif GetLocale() == "esES" then + CLOSE = "Cerrar" + CLOSE_DESC = "Cierra el menú." + VALIDATION_ERROR = "Error de validación." + USAGE_TOOLTIP = "Uso: %s." + RANGE_TOOLTIP = "Puedes desplazarte verticalmente con la rueda del ratón sobre el desplazador." + RESET_KEYBINDING_DESC = "Pulsa Escape para borrar la asignación de tecla." + KEY_BUTTON1 = "Clic Izquierdo" + KEY_BUTTON2 = "Clic Derecho" + DISABLED = "Desactivado" + DEFAULT_CONFIRM_MESSAGE = "¿Estás seguro de querer realizar `%s'?" +elseif GetLocale() == "zhTW" then + CLOSE = "關閉" + CLOSE_DESC = "關閉選單。" + VALIDATION_ERROR = "驗證錯誤。" + USAGE_TOOLTIP = "用法: %s。" + RANGE_TOOLTIP = "你可以在捲動條上使用滑鼠滾輪來捲動。" + RESET_KEYBINDING_DESC = "按Esc鍵清除快捷鍵。" + KEY_BUTTON1 = "滑鼠左鍵" + KEY_BUTTON2 = "滑鼠右鍵" + DISABLED = "停用" + DEFAULT_CONFIRM_MESSAGE = "是否執行「%s」?" +elseif GetLocale() == "zhCN" then + CLOSE = "关闭" + CLOSE_DESC = "关闭菜单" + VALIDATION_ERROR = "验证错误." + USAGE_TOOLTIP = "用法: %s." + RANGE_TOOLTIP = "你可以在滚动条上使用鼠标滚轮来翻页." + RESET_KEYBINDING_DESC = "按ESC键清除按键绑定" + KEY_BUTTON1 = "鼠标左键" + KEY_BUTTON2 = "鼠标右键" + DISABLED = "禁用" + DEFAULT_CONFIRM_MESSAGE = "是否执行'%s'?" +end + +Dewdrop.KEY_BUTTON1 = KEY_BUTTON1 +Dewdrop.KEY_BUTTON2 = KEY_BUTTON2 + +local function new(...) + local t = {} + for i = 1, select('#', ...), 2 do + local k = select(i, ...) + if k then + t[k] = select(i+1, ...) + else + break + end + end + return t +end + +local tmp +do + local t = {} + function tmp(...) + for k in pairs(t) do + t[k] = nil + end + for i = 1, select('#', ...), 2 do + local k = select(i, ...) + if k then + t[k] = select(i+1, ...) + else + break + end + end + return t + end +end +local tmp2 +do + local t = {} + function tmp2(...) + for k in pairs(t) do + t[k] = nil + end + for i = 1, select('#', ...), 2 do + local k = select(i, ...) + if k then + t[k] = select(i+1, ...) + else + break + end + end + return t + end +end +local levels +local buttons + + +-- Secure frame handling: +-- Rather than using secure buttons in the menu (has problems), we have one +-- master secureframe that we pop onto menu items on mouseover. This requires +-- some dark magic with OnLeave etc, but it's not too bad. + +local secureFrame = CreateFrame("Button", nil, nil, "SecureActionButtonTemplate") +secureFrame:Hide() + +local function secureFrame_Show(self) + local owner = self.owner + + if self.secure then -- Leftovers from previos owner, clean up! ("Shouldn't" happen but does..) + for k,v in pairs(self.secure) do + self:SetAttribute(k, nil) + end + end + self.secure = owner.secure; -- Grab hold of new secure data + + local scale = owner:GetEffectiveScale() + + self:SetPoint("TOPLEFT", nil, "BOTTOMLEFT", owner:GetLeft() * scale, owner:GetTop() * scale) + self:SetPoint("BOTTOMRIGHT", nil, "BOTTOMLEFT", owner:GetRight() * scale, owner:GetBottom() * scale) + self:EnableMouse(true) + for k,v in pairs(self.secure) do + self:SetAttribute(k, v) + end + + secureFrame:SetFrameStrata(owner:GetFrameStrata()) + secureFrame:SetFrameLevel(owner:GetFrameLevel()+1) + + self:Show() +end + +local function secureFrame_Hide(self) + self:Hide() + if self.secure then + for k,v in pairs(self.secure) do + self:SetAttribute(k, nil) + end + end + self.secure = nil +end + +secureFrame:SetScript("OnEvent", + function() + if event=="PLAYER_REGEN_ENABLED" then + this.combat = false + if not this:IsShown() and this.owner then + secureFrame_Show(this) + end + elseif event=="PLAYER_REGEN_DISABLED" then + this.combat = true + if this:IsShown() then + secureFrame_Hide(this) + end + end + end +) +secureFrame:RegisterEvent("PLAYER_REGEN_ENABLED") +secureFrame:RegisterEvent("PLAYER_REGEN_DISABLED") + +secureFrame:SetScript("OnLeave", + function() + local owner=this.owner + this:Deactivate() + owner:GetScript("OnLeave")() + end +) + +secureFrame:HookScript("OnClick", + function() + local realthis = this + this = this.owner + this:GetScript("OnClick")() + end +) + +function secureFrame:IsOwnedBy(frame) + return self.owner == frame +end + +function secureFrame:Activate(owner) + if self.owner then -- "Shouldn't" happen but apparently it does and I cba to troubleshoot... + if not self.combat then + secureFrame_Hide(self) + end + end + self.owner = owner + if not self.combat then + secureFrame_Show(self) + end +end + +function secureFrame:Deactivate() + if not self.combat then + secureFrame_Hide(self) + end + self.owner = nil +end + +-- END secure frame utilities + + +-- Underline on mouseover - use a single global underline that we move around, no point in creating lots of copies +local underlineFrame = CreateFrame("Frame", nil) +underlineFrame.tx = underlineFrame:CreateTexture() +underlineFrame.tx:SetTexture(1,1,0.5,0.75) +underlineFrame:SetScript("OnHide", function(this) this:Hide(); end) +underlineFrame:SetScript("OnShow", function(this) -- change sizing on the fly to catch runtime uiscale changes + underlineFrame.tx:SetPoint("TOPLEFT", -1, -2/this:GetEffectiveScale()) + underlineFrame.tx:SetPoint("RIGHT", 1,0) + underlineFrame.tx:SetHeight(0.6 / this:GetEffectiveScale()); +end) +underlineFrame:SetHeight(1) + +-- END underline on mouseover + + +local function GetScaledCursorPosition() + local x, y = GetCursorPosition() + local scale = UIParent:GetEffectiveScale() + return x / scale, y / scale +end + +local function StartCounting(self, level) + for i = level, 1, -1 do + if levels[i] then + levels[i].count = 3 + end + end +end + +local function StopCounting(self, level) + for i = level, 1, -1 do + if levels[i] then + levels[i].count = nil + end + end +end + +local function OnUpdate(self, elapsed) + for _,level in ipairs(levels) do + local count = level.count + if count then + count = count - elapsed + if count < 0 then + level.count = nil + self:Close(level.num) + else + level.count = count + end + end + end +end + +local function CheckDualMonitor(self, frame) + local ratio = GetScreenWidth() / GetScreenHeight() + if ratio >= 2.4 and frame:GetRight() > GetScreenWidth() / 2 and frame:GetLeft() < GetScreenWidth() / 2 then + local offsetx + if GetCursorPosition() / GetScreenHeight() * 768 < GetScreenWidth() / 2 then + offsetx = GetScreenWidth() / 2 - frame:GetRight() + else + offsetx = GetScreenWidth() / 2 - frame:GetLeft() + end + local point, parent, relativePoint, x, y = frame:GetPoint(1) + frame:SetPoint(point, parent, relativePoint, (x or 0) + offsetx, y or 0) + end +end + +local function CheckSize(self, level) + if not level.buttons then + return + end + local height = 20 + for _, button in ipairs(level.buttons) do + height = height + button:GetHeight() + end + level:SetHeight(height) + local width = 160 + for _, button in ipairs(level.buttons) do + local extra = 1 + if button.hasArrow or button.hasColorSwatch then + extra = extra + 16 + end + if not button.notCheckable then + extra = extra + 24 + end + button.text:SetFont(STANDARD_TEXT_FONT, button.textHeight) + if button.text:GetWidth() + extra > width then + width = button.text:GetWidth() + extra + end + end + level:SetWidth(width + 20) + if level:GetLeft() and level:GetRight() and level:GetTop() and level:GetBottom() and (level:GetLeft() < 0 or level:GetRight() > GetScreenWidth() or level:GetTop() > GetScreenHeight() or level:GetBottom() < 0) then + level:ClearAllPoints() + local parent = level.parent or level:GetParent() + if type(parent) ~= "table" then + parent = UIParent + end + if level.lastDirection == "RIGHT" then + if level.lastVDirection == "DOWN" then + level:SetPoint("TOPLEFT", parent, "TOPRIGHT", 5, 10) + else + level:SetPoint("BOTTOMLEFT", parent, "BOTTOMRIGHT", 5, -10) + end + else + if level.lastVDirection == "DOWN" then + level:SetPoint("TOPRIGHT", parent, "TOPLEFT", -5, 10) + else + level:SetPoint("BOTTOMRIGHT", parent, "BOTTOMLEFT", -5, -10) + end + end + end + local dirty = false + if not level:GetRight() then + self:Close() + return + end + if level:GetRight() > GetScreenWidth() and level.lastDirection == "RIGHT" then + level.lastDirection = "LEFT" + dirty = true + elseif level:GetLeft() < 0 and level.lastDirection == "LEFT" then + level.lastDirection = "RIGHT" + dirty = true + end + if level:GetTop() > GetScreenHeight() and level.lastVDirection == "UP" then + level.lastVDirection = "DOWN" + dirty = true + elseif level:GetBottom() < 0 and level.lastVDirection == "DOWN" then + level.lastVDirection = "UP" + dirty = true + end + if dirty then + level:ClearAllPoints() + local parent = level.parent or level:GetParent() + if type(parent) ~= "table" then + parent = UIParent + end + if level.lastDirection == "RIGHT" then + if level.lastVDirection == "DOWN" then + level:SetPoint("TOPLEFT", parent, "TOPRIGHT", 5, 10) + else + level:SetPoint("BOTTOMLEFT", parent, "BOTTOMRIGHT", 5, -10) + end + else + if level.lastVDirection == "DOWN" then + level:SetPoint("TOPRIGHT", parent, "TOPLEFT", -5, 10) + else + level:SetPoint("BOTTOMRIGHT", parent, "BOTTOMLEFT", -5, -10) + end + end + end + if level:GetTop() > GetScreenHeight() then + local top = level:GetTop() + local point, parent, relativePoint, x, y = level:GetPoint(1) + level:ClearAllPoints() + level:SetPoint(point, parent, relativePoint, x or 0, (y or 0) + GetScreenHeight() - top) + elseif level:GetBottom() < 0 then + local bottom = level:GetBottom() + local point, parent, relativePoint, x, y = level:GetPoint(1) + level:ClearAllPoints() + level:SetPoint(point, parent, relativePoint, x or 0, (y or 0) - bottom) + end + CheckDualMonitor(self, level) + if mod(level.num, 5) == 0 then + local left, bottom = level:GetLeft(), level:GetBottom() + level:ClearAllPoints() + level:SetPoint("BOTTOMLEFT", UIParent, "BOTTOMLEFT", left, bottom) + end +end + +local Open +local OpenSlider +local OpenEditBox +local Refresh +local Clear +local function ReleaseButton(self, level, index) + if not level.buttons then + return + end + if not level.buttons[index] then + return + end + local button = level.buttons[index] + button:Hide() + if button.highlight then + button.highlight:Hide() + end +-- button.arrow:SetVertexColor(1, 1, 1) +-- button.arrow:SetHeight(16) +-- button.arrow:SetWidth(16) + table.remove(level.buttons, index) + table.insert(buttons, button) + for k in pairs(button) do + if k ~= 0 and k ~= "text" and k ~= "check" and k ~= "arrow" and k ~= "colorSwatch" and k ~= "highlight" and k ~= "radioHighlight" then + button[k] = nil + end + end + return true +end + +local function Scroll(self, level, down) + if down then + if level:GetBottom() < 0 then + local point, parent, relativePoint, x, y = level:GetPoint(1) + level:SetPoint(point, parent, relativePoint, x, y + 50) + if level:GetBottom() > 0 then + level:SetPoint(point, parent, relativePoint, x, y + 50 - level:GetBottom()) + end + end + else + if level:GetTop() > GetScreenHeight() then + local point, parent, relativePoint, x, y = level:GetPoint(1) + level:SetPoint(point, parent, relativePoint, x, y - 50) + if level:GetTop() < GetScreenHeight() then + level:SetPoint(point, parent, relativePoint, x, y - 50 + GetScreenHeight() - level:GetTop()) + end + end + end +end + +local function getArgs(t, str, num, ...) + local x = t[str .. num] + if x == nil then + return ... + else + return x, getArgs(t, str, num + 1, ...) + end +end + +local sliderFrame +local editBoxFrame + +local normalFont +local lastSetFont +local justSetFont = false +local regionTmp = {} +local function fillRegionTmp(...) + for i = 1, select('#', ...) do + regionTmp[i] = select(i, ...) + end +end + +local function showGameTooltip(this) + if this.tooltipTitle or this.tooltipText then + GameTooltip_SetDefaultAnchor(GameTooltip, this) + local disabled = not this.isTitle and this.disabled + local font + if this.tooltipTitle then + if SharedMedia and SharedMedia:IsValid("font", this.tooltipTitle) then + font = SharedMedia:Fetch("font", this.tooltipTitle) + end + if disabled then + GameTooltip:SetText(this.tooltipTitle, 0.5, 0.5, 0.5, 1) + else + GameTooltip:SetText(this.tooltipTitle, 1, 1, 1, 1) + end + if this.tooltipText then + if not font and SharedMedia and SharedMedia:IsValid("font", this.tooltipText) then + font = SharedMedia:Fetch("font", this.tooltipText) + end + if disabled then + GameTooltip:AddLine(this.tooltipText, (NORMAL_FONT_COLOR.r + 0.5) / 2, (NORMAL_FONT_COLOR.g + 0.5) / 2, (NORMAL_FONT_COLOR.b + 0.5) / 2, 1) + else + GameTooltip:AddLine(this.tooltipText, NORMAL_FONT_COLOR.r, NORMAL_FONT_COLOR.g, NORMAL_FONT_COLOR.b, 1) + end + end + else + if SharedMedia and SharedMedia:IsValid("font", this.tooltipText) then + font = SharedMedia:Fetch("font", this.tooltipText) + end + if disabled then + GameTooltip:SetText(this.tooltipText, 0.5, 0.5, 0.5, 1) + else + GameTooltip:SetText(this.tooltipText, 1, 1, 1, 1) + end + end + if font then + fillRegionTmp(GameTooltip:GetRegions()) + lastSetFont = font + justSetFont = true + for i,v in ipairs(regionTmp) do + if v.SetFont then + local norm,size,outline = v:GetFont() + v:SetFont(font, size, outline) + if not normalFont then + normalFont = norm + end + end + regionTmp[i] = nil + end + elseif not normalFont then + fillRegionTmp(GameTooltip:GetRegions()) + for i,v in ipairs(regionTmp) do + if v.GetFont and not normalFont then + normalFont = v:GetFont() + end + regionTmp[i] = nil + end + end + GameTooltip:Show() + end + if this.tooltipFunc then + GameTooltip:SetOwner(this, "ANCHOR_NONE") + GameTooltip:SetPoint("TOPLEFT", this, "TOPRIGHT", 5, 0) + this.tooltipFunc(getArgs(this, 'tooltipArg', 1)) + GameTooltip:Show() + end +end + +local tmpt = setmetatable({}, {mode='v'}) +local numButtons = 0 +local function AcquireButton(self, level) + if not levels[level] then + return + end + level = levels[level] + if not level.buttons then + level.buttons = {} + end + local button + if #buttons == 0 then + numButtons = numButtons + 1 + button = CreateFrame("Button", "Dewdrop20Button" .. numButtons, nil) + button:SetFrameStrata("FULLSCREEN_DIALOG") + button:SetHeight(16) + local highlight = button:CreateTexture(nil, "BACKGROUND") + highlight:SetTexture("Interface\\QuestFrame\\UI-QuestTitleHighlight") + button.highlight = highlight + highlight:SetBlendMode("ADD") + highlight:SetAllPoints(button) + highlight:Hide() + local check = button:CreateTexture(nil, "ARTWORK") + button.check = check + check:SetTexture("Interface\\Buttons\\UI-CheckBox-Check") + check:SetPoint("CENTER", button, "LEFT", 12, 0) + check:SetWidth(24) + check:SetHeight(24) + local radioHighlight = button:CreateTexture(nil, "ARTWORK") + button.radioHighlight = radioHighlight + radioHighlight:SetTexture("Interface\\Buttons\\UI-RadioButton") + radioHighlight:SetAllPoints(check) + radioHighlight:SetBlendMode("ADD") + radioHighlight:SetTexCoord(0.5, 0.75, 0, 1) + radioHighlight:Hide() + button:SetScript("OnEnter", function() + if (sliderFrame and sliderFrame:IsShown() and sliderFrame.mouseDown and sliderFrame.level == this.level.num + 1) or (editBoxFrame and editBoxFrame:IsShown() and editBoxFrame.mouseDown and editBoxFrame.level == this.level.num + 1) then + for i = 1, this.level.num do + Refresh(self, levels[i]) + end + return + end + self:Close(this.level.num + 1) + if not this.disabled then + if this.secure then + secureFrame:Activate(this) + elseif this.hasSlider then + OpenSlider(self, this) + elseif this.hasEditBox then + OpenEditBox(self, this) + elseif this.hasArrow then + Open(self, this, nil, this.level.num + 1, this.value) + end + end + if not this.level then -- button reclaimed + return + end + StopCounting(self, this.level.num + 1) + if not this.disabled then + highlight:Show() + if this.isRadio then + button.radioHighlight:Show() + end + if this.mouseoverUnderline then + underlineFrame:SetParent(this) + underlineFrame:SetPoint("BOTTOMLEFT",this.text,0,0) + underlineFrame:SetWidth(this.text:GetWidth()) + underlineFrame:Show() + end + end + showGameTooltip(this) + end) + button:SetScript("OnHide", function() + if this.secure and secureFrame:IsOwnedBy(this) then + secureFrame:Deactivate() + end + end) + button:SetScript("OnLeave", function() + if this.secure and secureFrame:IsShown() then + return; -- it's ok, we didn't actually mouse out of the button, only onto the secure frame on top of it + end + underlineFrame:Hide() + if not this.selected then + highlight:Hide() + end + button.radioHighlight:Hide() + if this.level then + StartCounting(self, this.level.num) + end + GameTooltip:Hide() + end) + local first = true + button:SetScript("OnClick", function() + if not this.disabled then + if this.hasColorSwatch then + local func = button.colorFunc + local hasOpacity = this.hasOpacity + local this = this + for k in pairs(tmpt) do + tmpt[k] = nil + end + for i = 1, 1000 do + local x = this['colorArg'..i] + if x == nil then + break + else + tmpt[i] = x + end + end + ColorPickerFrame.func = function() + if func then + local r,g,b = ColorPickerFrame:GetColorRGB() + local a = hasOpacity and 1 - OpacitySliderFrame:GetValue() or nil + local n = #tmpt + tmpt[n+1] = r + tmpt[n+2] = g + tmpt[n+3] = b + tmpt[n+4] = a + func(unpack(tmpt)) + tmpt[n+1] = nil + tmpt[n+2] = nil + tmpt[n+3] = nil + tmpt[n+4] = nil + end + end + ColorPickerFrame.hasOpacity = this.hasOpacity + ColorPickerFrame.opacityFunc = ColorPickerFrame.func + ColorPickerFrame.opacity = 1 - this.opacity + ColorPickerFrame:SetColorRGB(this.r, this.g, this.b) + local r, g, b, a = this.r, this.g, this.b, this.opacity + ColorPickerFrame.cancelFunc = function() + if func then + local n = #tmpt + tmpt[n+1] = r + tmpt[n+2] = g + tmpt[n+3] = b + tmpt[n+4] = a + func(unpack(tmpt)) + for i = 1, n+4 do + tmpt[i] = nil + end + end + end + self:Close(1) + ShowUIPanel(ColorPickerFrame) + elseif this.func then + local level = this.level + if type(this.func) == "string" then + if type(this.arg1[this.func]) ~= "function" then + self:error("Cannot call method %q", this.func) + end + this.arg1[this.func](this.arg1, getArgs(this, 'arg', 2)) + else + this.func(getArgs(this, 'arg', 1)) + end + if this.closeWhenClicked then + self:Close() + elseif level:IsShown() then + for i = 1, level.num do + Refresh(self, levels[i]) + end + local value = levels[level.num].value + for i = level.num-1, 1, -1 do + local level = levels[i] + local good = false + for _,button in ipairs(level.buttons) do + if button.value == value then + good = true + break + end + end + if not good then + Dewdrop:Close(i+1) + end + value = levels[i].value + end + end + elseif this.closeWhenClicked then + self:Close() + end + end + end) + local text = button:CreateFontString(nil, "ARTWORK") + button.text = text + text:SetFontObject(GameFontHighlightSmall) + button.text:SetFont(STANDARD_TEXT_FONT, UIDROPDOWNMENU_DEFAULT_TEXT_HEIGHT) + button:SetScript("OnMouseDown", function() + if not this.disabled and (this.func or this.colorFunc or this.closeWhenClicked) then + text:SetPoint("LEFT", button, "LEFT", this.notCheckable and 1 or 25, -1) + end + end) + button:SetScript("OnMouseUp", function() + if not this.disabled and (this.func or this.colorFunc or this.closeWhenClicked) then + text:SetPoint("LEFT", button, "LEFT", this.notCheckable and 0 or 24, 0) + end + end) + local arrow = button:CreateTexture(nil, "ARTWORK") + button.arrow = arrow + arrow:SetPoint("LEFT", button, "RIGHT", -16, 0) + arrow:SetWidth(16) + arrow:SetHeight(16) + arrow:SetTexture("Interface\\ChatFrame\\ChatFrameExpandArrow") + local colorSwatch = button:CreateTexture(nil, "ARTWORK") + button.colorSwatch = colorSwatch + colorSwatch:SetWidth(20) + colorSwatch:SetHeight(20) + colorSwatch:SetTexture("Interface\\ChatFrame\\ChatFrameColorSwatch") + local texture = button:CreateTexture(nil, "OVERLAY") + colorSwatch.texture = texture + texture:SetTexture("Interface\\Buttons\\WHITE8X8") + texture:SetWidth(11.5) + texture:SetHeight(11.5) + texture:Show() + texture:SetPoint("CENTER", colorSwatch, "CENTER") + colorSwatch:SetPoint("RIGHT", button, "RIGHT", 0, 0) + else + button = table.remove(buttons) + end + button:ClearAllPoints() + button:SetParent(level) + button:SetFrameStrata(level:GetFrameStrata()) + button:SetFrameLevel(level:GetFrameLevel() + 1) + button:SetPoint("LEFT", level, "LEFT", 10, 0) + button:SetPoint("RIGHT", level, "RIGHT", -10, 0) + if #level.buttons == 0 then + button:SetPoint("TOP", level, "TOP", 0, -10) + else + button:SetPoint("TOP", level.buttons[#level.buttons], "BOTTOM", 0, 0) + end + button.text:SetPoint("LEFT", button, "LEFT", 24, 0) + button:Show() + button.level = level + table.insert(level.buttons, button) + if not level.parented then + level.parented = true + level:ClearAllPoints() + if level.num == 1 then + if level.parent ~= UIParent and type(level.parent) == "table" then + level:SetPoint("TOPRIGHT", level.parent, "TOPLEFT") + else + level:SetPoint("CENTER", UIParent, "CENTER") + end + else + if level.lastDirection == "RIGHT" then + if level.lastVDirection == "DOWN" then + level:SetPoint("TOPLEFT", level.parent, "TOPRIGHT", 5, 10) + else + level:SetPoint("BOTTOMLEFT", level.parent, "BOTTOMRIGHT", 5, -10) + end + else + if level.lastVDirection == "DOWN" then + level:SetPoint("TOPRIGHT", level.parent, "TOPLEFT", -5, 10) + else + level:SetPoint("BOTTOMRIGHT", level.parent, "BOTTOMLEFT", -5, -10) + end + end + end + level:SetFrameStrata("FULLSCREEN_DIALOG") + end + button:SetAlpha(1) + return button +end + +local numLevels = 0 +local function AcquireLevel(self, level) + if not levels[level] then + for i = #levels + 1, level, -1 do + local i = i + numLevels = numLevels + 1 + local frame = CreateFrame("Button", "Dewdrop20Level" .. numLevels, nil) + if i == 1 then + local old_CloseSpecialWindows = CloseSpecialWindows + function CloseSpecialWindows() + local found = old_CloseSpecialWindows() + if levels[1]:IsShown() then + self:Close() + return 1 + end + return found + end + end + levels[i] = frame + frame.num = i + frame:SetParent(UIParent) + frame:SetFrameStrata("FULLSCREEN_DIALOG") + frame:Hide() + frame:SetWidth(180) + frame:SetHeight(10) + frame:SetFrameLevel(i * 3) + frame:SetScript("OnHide", function() + self:Close(level + 1) + end) + if frame.SetTopLevel then + frame:SetTopLevel(true) + end + frame:EnableMouse(true) + frame:EnableMouseWheel(true) + local backdrop = CreateFrame("Frame", nil, frame) + backdrop:SetAllPoints(frame) + backdrop:SetBackdrop(tmp( + 'bgFile', "Interface\\Tooltips\\UI-Tooltip-Background", + 'edgeFile', "Interface\\Tooltips\\UI-Tooltip-Border", + 'tile', true, + 'insets', tmp2( + 'left', 5, + 'right', 5, + 'top', 5, + 'bottom', 5 + ), + 'tileSize', 16, + 'edgeSize', 16 + )) + backdrop:SetBackdropBorderColor(TOOLTIP_DEFAULT_COLOR.r, TOOLTIP_DEFAULT_COLOR.g, TOOLTIP_DEFAULT_COLOR.b) + backdrop:SetBackdropColor(TOOLTIP_DEFAULT_BACKGROUND_COLOR.r, TOOLTIP_DEFAULT_BACKGROUND_COLOR.g, TOOLTIP_DEFAULT_BACKGROUND_COLOR.b) + frame:SetScript("OnClick", function() + self:Close(i) + end) + frame:SetScript("OnEnter", function() + StopCounting(self, i) + end) + frame:SetScript("OnLeave", function() + StartCounting(self, i) + end) + frame:SetScript("OnMouseWheel", function() + Scroll(self, frame, arg1 < 0) + end) + if i == 1 then + frame:SetScript("OnUpdate", function(this, arg1) + OnUpdate(self, arg1) + end) + levels[1].lastDirection = "RIGHT" + levels[1].lastVDirection = "DOWN" + else + levels[i].lastDirection = levels[i - 1].lastDirection + levels[i].lastVDirection = levels[i - 1].lastVDirection + end + end + end + local fullscreenFrame = GetUIPanel("fullscreen") + local l = levels[level] + local strata, framelevel = l:GetFrameStrata(), l:GetFrameLevel() + if fullscreenFrame then + l:SetParent(fullscreenFrame) + else + l:SetParent(UIParent) + end + l:SetFrameStrata(strata) + l:SetFrameLevel(framelevel) + l:SetAlpha(1) + return l +end + +local function validateOptions(options, position, baseOptions, fromPass) + if not baseOptions then + baseOptions = options + end + if type(options) ~= "table" then + return "Options must be a table.", position + end + local kind = options.type + if type(kind) ~= "string" then + return '"type" must be a string.', position + elseif kind ~= "group" and kind ~= "range" and kind ~= "text" and kind ~= "execute" and kind ~= "toggle" and kind ~= "color" and kind ~= "dragLink" and kind ~= "header" then + return '"type" must either be "range", "text", "group", "toggle", "execute", "color", "dragLink", or "header".', position + end + if options.aliases then + if type(options.aliases) ~= "table" and type(options.aliases) ~= "string" then + return '"alias" must be a table or string', position + end + end + if not fromPass then + if kind == "execute" then + if type(options.func) ~= "string" and type(options.func) ~= "function" then + return '"func" must be a string or function', position + end + elseif kind == "range" or kind == "text" or kind == "toggle" then + if type(options.set) ~= "string" and type(options.set) ~= "function" then + return '"set" must be a string or function', position + end + if kind == "text" and options.get == false then + elseif type(options.get) ~= "string" and type(options.get) ~= "function" then + return '"get" must be a string or function', position + end + elseif kind == "group" and options.pass then + if options.pass ~= true then + return '"pass" must be either nil, true, or false', position + end + if not options.func then + if type(options.set) ~= "string" and type(options.set) ~= "function" then + return '"set" must be a string or function', position + end + if type(options.get) ~= "string" and type(options.get) ~= "function" then + return '"get" must be a string or function', position + end + elseif type(options.func) ~= "string" and type(options.func) ~= "function" then + return '"func" must be a string or function', position + end + end + end + if options ~= baseOptions then + if kind == "header" then + elseif type(options.desc) ~= "string" then + return '"desc" must be a string', position + elseif options.desc:len() == 0 then + return '"desc" cannot be a 0-length string', position + end + end + if options ~= baseOptions or kind == "range" or kind == "text" or kind == "toggle" or kind == "color" then + if options.type == "header" and not options.cmdName and not options.name then + elseif options.cmdName then + if type(options.cmdName) ~= "string" then + return '"cmdName" must be a string or nil', position + elseif options.cmdName:len() == 0 then + return '"cmdName" cannot be a 0-length string', position + end + if type(options.guiName) ~= "string" then + if not options.guiNameIsMap then + return '"guiName" must be a string or nil', position + end + elseif options.guiName:len() == 0 then + return '"guiName" cannot be a 0-length string', position + end + else + if type(options.name) ~= "string" then + return '"name" must be a string', position + elseif options.name:len() == 0 then + return '"name" cannot be a 0-length string', position + end + end + end + if options.guiNameIsMap then + if type(options.guiNameIsMap) ~= "boolean" then + return '"guiNameIsMap" must be a boolean or nil', position + elseif options.type ~= "toggle" then + return 'if "guiNameIsMap" is true, then "type" must be set to \'toggle\'', position + elseif type(options.map) ~= "table" then + return '"map" must be a table', position + end + end + if options.message and type(options.message) ~= "string" then + return '"message" must be a string or nil', position + end + if options.error and type(options.error) ~= "string" then + return '"error" must be a string or nil', position + end + if options.current and type(options.current) ~= "string" then + return '"current" must be a string or nil', position + end + if options.order then + if type(options.order) ~= "number" or (-1 < options.order and options.order < 0.999) then + return '"order" must be a non-zero number or nil', position + end + end + if options.disabled then + if type(options.disabled) ~= "function" and type(options.disabled) ~= "string" and options.disabled ~= true then + return '"disabled" must be a function, string, or boolean', position + end + end + if options.cmdHidden then + if type(options.cmdHidden) ~= "function" and type(options.cmdHidden) ~= "string" and options.cmdHidden ~= true then + return '"cmdHidden" must be a function, string, or boolean', position + end + end + if options.guiHidden then + if type(options.guiHidden) ~= "function" and type(options.guiHidden) ~= "string" and options.guiHidden ~= true then + return '"guiHidden" must be a function, string, or boolean', position + end + end + if options.hidden then + if type(options.hidden) ~= "function" and type(options.hidden) ~= "string" and options.hidden ~= true then + return '"hidden" must be a function, string, or boolean', position + end + end + if kind == "text" then + if type(options.validate) == "table" then + local t = options.validate + local iTable = nil + for k,v in pairs(t) do + if type(k) == "number" then + if iTable == nil then + iTable = true + elseif not iTable then + return '"validate" must either have all keys be indexed numbers or strings', position + elseif k < 1 or k > #t then + return '"validate" numeric keys must be indexed properly. >= 1 and <= #t', position + end + else + if iTable == nil then + iTable = false + elseif iTable then + return '"validate" must either have all keys be indexed numbers or strings', position + end + end + if type(v) ~= "string" then + return '"validate" values must all be strings', position + end + end + if options.multiToggle and options.multiToggle ~= true then + return '"multiToggle" must be a boolean or nil if "validate" is a table', position + end + elseif options.validate == "keybinding" then + -- no other checks + else + if type(options.usage) ~= "string" then + return '"usage" must be a string', position + elseif options.validate and type(options.validate) ~= "string" and type(options.validate) ~= "function" then + return '"validate" must be a string, function, or table', position + end + end + if options.multiToggle and type(options.validate) ~= "table" then + return '"validate" must be a table if "multiToggle" is true', position + end + elseif kind == "range" then + if options.min or options.max then + if type(options.min) ~= "number" then + return '"min" must be a number', position + elseif type(options.max) ~= "number" then + return '"max" must be a number', position + elseif options.min >= options.max then + return '"min" must be less than "max"', position + end + end + if options.step then + if type(options.step) ~= "number" then + return '"step" must be a number', position + elseif options.step < 0 then + return '"step" must be nonnegative', position + end + end + if options.bigStep then + if type(options.bigStep) ~= "number" then + return '"bigStep" must be a number', position + elseif options.bigStep < 0 then + return '"bigStep" must be nonnegative', position + end + end + if options.isPercent and options.isPercent ~= true then + return '"isPercent" must either be nil, true, or false', position + end + elseif kind == "toggle" then + if options.map then + if type(options.map) ~= "table" then + return '"map" must be a table', position + elseif type(options.map[true]) ~= "string" then + return '"map[true]" must be a string', position + elseif type(options.map[false]) ~= "string" then + return '"map[false]" must be a string', position + end + end + elseif kind == "color" then + if options.hasAlpha and options.hasAlpha ~= true then + return '"hasAlpha" must be nil, true, or false', position + end + elseif kind == "group" then + if options.pass and options.pass ~= true then + return '"pass" must be nil, true, or false', position + end + if type(options.args) ~= "table" then + return '"args" must be a table', position + end + for k,v in pairs(options.args) do + if type(k) ~= "number" then + if type(k) ~= "string" then + return '"args" keys must be strings or numbers', position + elseif k:len() == 0 then + return '"args" keys must not be 0-length strings.', position + end + end + if type(v) ~= "table" then + return '"args" values must be tables', position and position .. "." .. k or k + end + local newposition + if position then + newposition = position .. ".args." .. k + else + newposition = "args." .. k + end + local err, pos = validateOptions(v, newposition, baseOptions, options.pass) + if err then + return err, pos + end + end + elseif kind == "execute" then + if type(options.confirm) ~= "string" and type(options.confirm) ~= "boolean" and type(options.confirm) ~= "nil" then + return '"confirm" must be a string, boolean, or nil', position + end + end + if options.icon and type(options.icon) ~= "string" then + return'"icon" must be a string', position + end + if options.iconWidth or options.iconHeight then + if type(options.iconWidth) ~= "number" or type(options.iconHeight) ~= "number" then + return '"iconHeight" and "iconWidth" must be numbers', position + end + end + if options.iconCoordLeft or options.iconCoordRight or options.iconCoordTop or options.iconCoordBottom then + if type(options.iconCoordLeft) ~= "number" or type(options.iconCoordRight) ~= "number" or type(options.iconCoordTop) ~= "number" or type(options.iconCoordBottom) ~= "number" then + return '"iconCoordLeft", "iconCoordRight", "iconCoordTop", and "iconCoordBottom" must be numbers', position + end + end +end + +local validatedOptions + +local values +local mysort_args +local mysort +local othersort +local othersort_validate + +local baseFunc, currentLevel + +local function confirmPopup(message, func, ...) + if not StaticPopupDialogs["DEWDROP20_CONFIRM_DIALOG"] then + StaticPopupDialogs["DEWDROP20_CONFIRM_DIALOG"] = {} + end + local t = StaticPopupDialogs["DEWDROP20_CONFIRM_DIALOG"] + for k in pairs(t) do + t[k] = nil + end + t.text = message + t.button1 = ACCEPT or "Accept" + t.button2 = CANCEL or "Cancel" + t.OnAccept = function() + func(unpack(t)) + end + for i = 1, select('#', ...) do + t[i] = select(i, ...) + end + t.timeout = 0 + t.whileDead = 1 + t.hideOnEscape = 1 + + Dewdrop:Close() + StaticPopup_Show("DEWDROP20_CONFIRM_DIALOG") +end + + +local function getMethod(settingname, handler, v, methodName, ...) -- "..." is simply returned straight out cause you can't do "a,b,c = 111,f(),222" + assert(v and type(v)=="table") + assert(methodName and type(methodName)=="string") + + local method = v[methodName] + if type(method)=="function" then + return method, ... + elseif type(method)=="string" then + if not handler then + Dewdrop:error("[%s] 'handler' is required if providing a method name: %q", tostring(settingname), method) + elseif not handler[method] then + Dewdrop:error("[%s] 'handler' method %q not defined", tostring(settingname), method) + end + return handler[method], handler, ... + end + + Dewdrop:error("[%s] Missing %q directive", tostring(settingname), methodName) +end + +local function callMethod(settingname, handler, v, methodName, ...) + assert(v and type(v)=="table") + assert(methodName and type(methodName)=="string") + + local method = v[methodName] + if type(method)=="function" then + local success, ret,ret2,ret3,ret4 = pcall(v[methodName], ...) + if not success then + geterrorhandler()(ret) + return nil + end + return ret,ret2,ret3,ret4 + + elseif type(method)=="string" then + + local neg = method:match("^~(.-)$") + if neg then + method = neg + end + if not handler then + Dewdrop:error("[%s] 'handler' is required if providing a method name: %q", tostring(settingname), method) + elseif not handler[method] then + Dewdrop:error("[%s] 'handler' (%q) method %q not defined", tostring(settingname), handler.name or "(unnamed)", method) + end + local success, ret,ret2,ret3,ret4 = pcall(handler[method], handler, ...) + if not success then + geterrorhandler()(ret) + return nil + end + if neg then + return not ret + end + return ret,ret2,ret3,ret4 + elseif method == false then + return nil + end + + Dewdrop:error("[%s] Missing %q directive in %q", tostring(settingname), methodName, v.name or "(unnamed)") +end + +local function skip1Nil(...) + if select(1,...)==nil then + return select(2,...) + end + return ... +end + +function Dewdrop:FeedAceOptionsTable(options, difference) + self:argCheck(options, 2, "table") + self:argCheck(difference, 3, "nil", "number") + if not currentLevel then + self:error("Cannot call `FeedAceOptionsTable' outside of a Dewdrop declaration") + end + if not difference then + difference = 0 + end + if not validatedOptions then + validatedOptions = {} + end + if not validatedOptions[options] then + local err, position = validateOptions(options) + + if err then + if position then + Dewdrop:error(position .. ": " .. err) + else + Dewdrop:error(err) + end + end + + validatedOptions[options] = true + end + local level = levels[currentLevel] + if not level then + self:error("Improper level given") + end + if not values then + values = {} + else + for k,v in pairs(values) do + values[k] = nil + end + end + + local current = level + while current do -- this traverses from higher level numbers to lower, building "values" with leaf nodes first and trunk nodes later + if current.num == difference + 1 then + break + end + table.insert(values, current.value) + current = levels[current.num - 1] + end + + local realOptions = options + local handler = options.handler + local passTable + local passValue + while #values > 0 do -- This loop traverses values from the END (trunk nodes first, then onto leaf nodes) + if options.pass then + if options.get and options.set then + passTable = options + elseif not passTable then + passTable = options + end + else + passTable = nil + end + local value = table.remove(values) + options = options.args and options.args[value] + if not options then + return + end + handler = options.handler or handler + passValue = passTable and value or nil + end + + if options.type == "group" then + local hidden = options.hidden + if type(hidden) == "function" or type(hidden) == "string" then + hidden = callMethod(options.name or "(options root)", handler, options, "hidden", options.passValue) or false + end + if hidden then + return + end + local disabled = options.disabled + if type(disabled) == "function" or type(disabled) == "string" then + disabled = callMethod(options.name or "(options root)", handler, options, "disabled", options.passValue) or false + end + if disabled then + self:AddLine( + 'text', DISABLED, + 'disabled', true + ) + return + end + for k in pairs(options.args) do + table.insert(values, k) + end + if options.pass then + if options.get and options.set then + passTable = options + elseif not passTable then + passTable = options + end + else + passTable = nil + end + if not mysort then + mysort = function(a, b) + local alpha, bravo = mysort_args[a], mysort_args[b] + local alpha_order = alpha.order or 100 + local bravo_order = bravo.order or 100 + local alpha_name = alpha.guiName or alpha.name + local bravo_name = bravo.guiName or bravo.name + if alpha_order == bravo_order then + if not alpha_name then + return bravo_name + elseif not bravo_name then + return false + else + return alpha_name:gsub("|c%x%x%x%x%x%x%x%x", ""):gsub("|r", ""):upper() < bravo_name:gsub("|c%x%x%x%x%x%x%x%x", ""):gsub("|r", ""):upper() + end + else + if alpha_order < 0 then + if bravo_order > 0 then + return false + end + else + if bravo_order < 0 then + return true + end + end + return alpha_order < bravo_order + end + end + end + mysort_args = options.args + table.sort(values, mysort) + mysort_args = nil + local hasBoth = #values >= 1 and (options.args[values[1]].order or 100) > 0 and (options.args[values[#values]].order or 100) < 0 + local last_order = 1 + for _,k in ipairs(values) do + local v = options.args[k] + local handler = v.handler or handler + if hasBoth and last_order > 0 and (v.order or 100) < 0 then + hasBoth = false + self:AddLine() + end + local hidden, disabled = v.guiHidden or v.hidden, v.disabled + + if type(hidden) == "function" or type(hidden) == "string" then + hidden = callMethod(k, handler, v, "hidden", v.passValue) or false + end + if not hidden then + if type(disabled) == "function" or type(disabled) == "string" then + disabled = callMethod(k, handler, v, "disabled", v.passValue) or false + end + local name = (v.guiIconOnly and v.icon) and "" or (v.guiName or v.name) + local desc = v.guiDesc or v.desc + local iconHeight = v.iconHeight or 16 + local iconWidth = v.iconWidth or 16 + local iconCoordLeft = v.iconCoordLeft + local iconCoordRight = v.iconCoordRight + local iconCoordBottom = v.iconCoordBottom + local iconCoordTop = v.iconCoordTop + local tooltipTitle, tooltipText + tooltipTitle = name + if name ~= desc then + tooltipText = desc + end + if type(v.usage) == "string" and v.usage:trim():len() > 0 then + if tooltipText then + tooltipText = tooltipText .. "\n\n" .. USAGE_TOOLTIP:format(v.usage) + else + tooltipText = USAGE_TOOLTIP:format(v.usage) + end + end + local v_p = passTable + if not v_p or (v.type ~= "execute" and v.get and v.set) or (v.type == "execute" and v.func) then + v_p = v + end + local passValue = v.passValue or (v_p~=v and k) or nil + if v.type == "toggle" then + local checked = callMethod(name, handler, v_p, "get", passValue) or false + local checked_arg = checked + if type(v_p.get)=="string" and v_p.get:match("^~") then + checked_arg = not checked + end + local func, arg1, arg2, arg3 = getMethod(name, handler, v_p, "set", skip1Nil(passValue, not checked_arg)) + if v.guiNameIsMap then + checked = checked and true or false + name = tostring(v.map and v.map[checked]):gsub("|c%x%x%x%x%x%x%x%x(.-)|r", "%1") + tooltipTitle = name + checked = true--nil + end + self:AddLine( + 'text', name, + 'checked', checked, + 'isRadio', v.isRadio, + 'func', func, + 'arg1', arg1, + 'arg2', arg2, + 'arg3', arg3, + 'disabled', disabled, + 'tooltipTitle', tooltipTitle, + 'tooltipText', tooltipText + ) + elseif v.type == "execute" then + local func, arg1, arg2, arg3, arg4 + local confirm = v.confirm + if confirm == true then + confirm = DEFAULT_CONFIRM_MESSAGE:format(tooltipText or tooltipTitle) + func,arg1,arg2,arg3,arg4 = confirmPopup, confirm, getMethod(name, handler, v_p, "func", passValue) + elseif type(confirm) == "string" then + func,arg1,arg2,arg3,arg4 = confirmPopup, confirm, getMethod(name, handler, v_p, "func", passValue) + else + func,arg1,arg2 = getMethod(name, handler, v_p, "func", passValue) + end + self:AddLine( + 'text', name, + 'checked', checked, + 'func', func, + 'arg1', arg1, + 'arg2', arg2, + 'arg3', arg3, + 'arg4', arg4, + 'disabled', disabled, + 'tooltipTitle', tooltipTitle, + 'tooltipText', tooltipText, + 'icon', v.icon, + 'iconHeight', iconHeight, + 'iconWidth', iconWidth, + 'iconCoordLeft', iconCoordLeft, + 'iconCoordRight', iconCoordRight, + 'iconCoordTop', iconCoordTop, + 'iconCoordBottom', iconCoordBottom + ) + elseif v.type == "range" then + local sliderValue + sliderValue = callMethod(name, handler, v_p, "get", passValue) or 0 + local sliderFunc, sliderArg1, sliderArg2 = getMethod(name, handler, v_p, "set", passValue) + if tooltipText then + tooltipText = format("%s\n\n%s", tooltipText, RANGE_TOOLTIP) + else + tooltipText = RANGE_TOOLTIP + end + self:AddLine( + 'text', name, + 'hasArrow', true, + 'hasSlider', true, + 'sliderMin', v.min or 0, + 'sliderMax', v.max or 1, + 'sliderStep', v.step or 0, + 'sliderBigStep', v.bigStep or nil, + 'sliderIsPercent', v.isPercent or false, + 'sliderValue', sliderValue, + 'sliderFunc', sliderFunc, + 'sliderArg1', sliderArg1, + 'sliderArg2', sliderArg2, + 'fromAceOptions', true, + 'disabled', disabled, + 'tooltipTitle', tooltipTitle, + 'tooltipText', tooltipText, + 'icon', v.icon, + 'iconHeight', iconHeight, + 'iconWidth', iconWidth, + 'iconCoordLeft', iconCoordLeft, + 'iconCoordRight', iconCoordRight, + 'iconCoordTop', iconCoordTop, + 'iconCoordBottom', iconCoordBottom + ) + elseif v.type == "color" then + local r,g,b,a = callMethod(name, handler, v_p, "get", passValue) + if not r then + r,g,b,a = 0,0,0,0 + end + local colorFunc, colorArg1, colorArg2 = getMethod(name, handler, v_p, "set", passValue) + self:AddLine( + 'text', name, + 'hasArrow', true, + 'hasColorSwatch', true, + 'r', r, + 'g', g, + 'b', b, + 'opacity', v.hasAlpha and a or nil, + 'hasOpacity', v.hasAlpha, + 'colorFunc', colorFunc, + 'colorArg1', colorArg1, + 'colorArg2', colorArg2, + 'disabled', disabled, + 'tooltipTitle', tooltipTitle, + 'tooltipText', tooltipText + ) + elseif v.type == "text" then + if type(v.validate) == "table" then + local func,arg1,arg2 + if v.onClick then + func,arg1,arg2 = getMethod(name, handler, v, "onClick", passValue) + end + local checked + if v.isChecked then + checked = callMethod(name, handler, v, "isChecked", passValue) or false + end + self:AddLine( + 'text', name, + 'hasArrow', true, + 'value', k, + 'func', func, + 'arg1', arg1, + 'arg2', arg2, + 'mouseoverUnderline', func and true or nil, + 'disabled', disabled, + 'checked', checked, + 'tooltipTitle', tooltipTitle, + 'tooltipText', tooltipText, + 'icon', v.icon, + 'iconHeight', iconHeight, + 'iconWidth', iconWidth, + 'iconCoordLeft', iconCoordLeft, + 'iconCoordRight', iconCoordRight, + 'iconCoordTop', iconCoordTop, + 'iconCoordBottom', iconCoordBottom + ) + else + local editBoxText + editBoxText = callMethod(name, handler, v_p, "get", passValue) or "" + local editBoxFunc, editBoxArg1, editBoxArg2 = getMethod(name, handler, v_p, "set", passValue) + + local editBoxValidateFunc, editBoxValidateArg1 + + if v.validate and v.validate ~= "keybinding" then + if v.validate == "keybinding" then + if tooltipText then + tooltipText = format("%s\n\n%s", tooltipText, RESET_KEYBINDING_DESC) + else + tooltipText = RESET_KEYBINDING_DESC + end + else + editBoxValidateFunc, editBoxValidateArg1 = getMethod(name, handler, v, "validate") -- no passvalue! + end + end + + self:AddLine( + 'text', name, + 'hasArrow', true, + 'icon', v.icon, + 'iconHeight', iconHeight, + 'iconWidth', iconWidth, + 'iconCoordLeft', iconCoordLeft, + 'iconCoordRight', iconCoordRight, + 'iconCoordTop', iconCoordTop, + 'iconCoordBottom', iconCoordBottom, + 'hasEditBox', true, + 'editBoxText', editBoxText, + 'editBoxFunc', editBoxFunc, + 'editBoxArg1', editBoxArg1, + 'editBoxArg2', editBoxArg2, + 'editBoxValidateFunc', editBoxValidateFunc, + 'editBoxValidateArg1', editBoxValidateArg1, + 'editBoxIsKeybinding', v.validate == "keybinding", + 'editBoxKeybindingOnly', v.keybindingOnly, + 'editBoxKeybindingExcept', v.keybindingExcept, + 'disabled', disabled, + 'tooltipTitle', tooltipTitle, + 'tooltipText', tooltipText + ) + end + elseif v.type == "group" then + local func,arg1,arg2 + if v.onClick then + func,arg1,arg2 = getMethod(name, handler, v, "onClick", passValue) + end + local checked + if v.isChecked then + checked = callMethod(name, handler, v, "isChecked", passValue) or false + end + self:AddLine( + 'text', name, + 'hasArrow', true, + 'value', k, + 'func', func, + 'arg1', arg1, + 'arg2', arg2, + 'mouseoverUnderline', func and true or nil, + 'disabled', disabled, + 'checked', checked, + 'tooltipTitle', tooltipTitle, + 'tooltipText', tooltipText, + 'icon', v.icon, + 'iconHeight', iconHeight, + 'iconWidth', iconWidth, + 'iconCoordLeft', iconCoordLeft, + 'iconCoordRight', iconCoordRight, + 'iconCoordTop', iconCoordTop, + 'iconCoordBottom', iconCoordBottom + ) + elseif v.type == "header" then + if name == "" or not name then + self:AddLine( + 'isTitle', true, + 'icon', v.icon, + 'iconHeight', iconHeight, + 'iconWidth', iconWidth, + 'iconCoordLeft', iconCoordLeft, + 'iconCoordRight', iconCoordRight, + 'iconCoordTop', iconCoordTop, + 'iconCoordBottom', iconCoordBottom + ) + else + self:AddLine( + 'text', name, + 'isTitle', true, + 'icon', v.icon, + 'iconHeight', iconHeight, + 'iconWidth', iconWidth, + 'iconCoordLeft', iconCoordLeft, + 'iconCoordRight', iconCoordRight, + 'iconCoordTop', iconCoordTop, + 'iconCoordBottom', iconCoordBottom + ) + end + end + end + last_order = v.order or 100 + end + elseif options.type == "text" and type(options.validate) == "table" then + local current + local options_p = passTable + if not options_p or (options.get and options.set) then + options_p = options + passTable = nil + passValue = nil + end + local multiToggle = options.multiToggle + local passValue = options.passValue or passValue + if not multiToggle then + current = callMethod(k, handler, options_p, "get", passValue) + end + local indexed = true + for k,v in pairs(options.validate) do + if type(k) ~= "number" then + indexed = false + end + table.insert(values, k) + end + if not indexed then + if not othersort then + othersort = function(alpha, bravo) + return othersort_validate[alpha]:gsub("|c%x%x%x%x%x%x%x%x", ""):gsub("|r", ""):upper() < othersort_validate[bravo]:gsub("|c%x%x%x%x%x%x%x%x", ""):gsub("|r", ""):upper() + end + end + othersort_validate = options.validate + table.sort(values, othersort) + othersort_validate = nil + end + for _,k in ipairs(values) do + local v = options.validate[k] + if type(k) == "number" then + k = v + end + local func, arg1, arg2, arg3, arg4 = getMethod(k, handler, options_p, "set", skip1Nil(passValue, k)) + local checked + if multiToggle then + checked = callMethod(k, handler, options_p, "get", skip1Nil(passValue, k)) or false + if arg2 == nil then + arg2 = not checked + elseif arg3 == nil then + arg3 = not checked + else + arg4 = not checked + end + else + checked = (k == current or (type(k) == "string" and type(current) == "string" and k:lower() == current:lower())) + if checked then + func, arg1, arg2, arg3, arg4 = nil, nil, nil, nil, nil + end + end + local tooltipTitle + local tooltipText + if options.validateDesc then + tooltipTitle = v + tooltipText = options.validateDesc[k] + else + tooltipTitle = options.guiName or options.name + tooltipText = v + end + self:AddLine( + 'text', v, + 'func', func, + 'arg1', arg1, + 'arg2', arg2, + 'arg3', arg3, + 'arg4', arg4, + 'isRadio', not multiToggle, + 'checked', checked, + 'tooltipTitle', tooltipTitle, + 'tooltipText', tooltipText + ) + end + for k in pairs(values) do + values[k] = nil + end + else + return false + end + return true +end + +function Dewdrop:FeedTable(s, difference) + self:argCheck(s, 2, "table") + self:argCheck(difference, 3, "nil", "number") + if not currentLevel then + self:error("Cannot call `FeedTable' outside of a Dewdrop declaration") + end + if not difference then + difference = 0 + end + local level = levels[currentLevel] + if not level then + self:error("Improper level given") + end + if not values then + values = {} + else + for k,v in pairs(values) do + values[k] = nil + end + end + local t = s.subMenu and s or {subMenu = s} + local current = level + while current do + if current.num == difference + 1 then + break + end + table.insert(values, current.value) + current = levels[current.num - 1] + end + + while #values > 0 do + local value = table.remove(values) + t = t.subMenu and t.subMenu[value] + if not t then + return + end + end + + if t.subMenu or current.num == 1 then + for k in pairs(t.subMenu) do + table.insert(values, k) + end + table.sort(values) + for _,k in ipairs(values) do + local argTable = {"value", k} + for key, val in pairs(t.subMenu[k]) do + table.insert(argTable, key) + table.insert(argTable, val) + end + self:AddLine(unpack(argTable)) + end + for k in pairs(values) do + values[k] = nil + end + return false + end + return true +end + +function Refresh(self, level) + if type(level) == "number" then + level = levels[level] + end + if not level then + return + end + if baseFunc then + Clear(self, level) + currentLevel = level.num + if type(baseFunc) == "table" then + if currentLevel == 1 then + local handler = baseFunc.handler + if handler then + local name = tostring(handler) + if not name:find('^table:') and not handler.hideMenuTitle then + name = name:gsub("|c%x%x%x%x%x%x%x%x(.-)|r", "%1") + self:AddLine( + 'text', name, + 'isTitle', true + ) + end + end +-- elseif level.parentText then +-- self:AddLine( +-- 'text', level.parentText, +-- 'tooltipTitle', level.parentTooltipTitle, +-- 'tooltipText', level.parentTooltipText, +-- 'tooltipFunc', level.parentTooltipFunc, +-- 'isTitle', true +-- ) + end + self:FeedAceOptionsTable(baseFunc) + if currentLevel == 1 then + self:AddLine( + 'text', CLOSE, + 'tooltipTitle', CLOSE, + 'tooltipText', CLOSE_DESC, + 'closeWhenClicked', true + ) + end + else +-- if level.parentText then +-- self:AddLine( +-- 'text', level.parentText, +-- 'tooltipTitle', level.parentTooltipTitle, +-- 'tooltipText', level.parentTooltipText, +-- 'tooltipFunc', level.parentTooltipFunc, +-- 'isTitle', true +-- ) +-- end + baseFunc(currentLevel, level.value, levels[level.num - 1] and levels[level.num - 1].value, levels[level.num - 2] and levels[level.num - 2].value, levels[level.num - 3] and levels[level.num - 3].value, levels[level.num - 4] and levels[level.num - 4].value) + end + currentLevel = nil + CheckSize(self, level) + end +end + +function Dewdrop:Refresh(level) + self:argCheck(level, 2, "number", "nil") + if not level then + for k,v in pairs(levels) do + Refresh(self, v) + end + else + Refresh(self, levels[level]) + end +end + +function OpenSlider(self, parent) + if not sliderFrame then + sliderFrame = CreateFrame("Frame", nil, nil) + sliderFrame:SetWidth(100) + sliderFrame:SetHeight(170) + sliderFrame:SetScale(UIParent:GetScale()) + sliderFrame:SetBackdrop(tmp( + 'bgFile', "Interface\\Tooltips\\UI-Tooltip-Background", + 'edgeFile', "Interface\\Tooltips\\UI-Tooltip-Border", + 'tile', true, + 'insets', tmp2( + 'left', 5, + 'right', 5, + 'top', 5, + 'bottom', 5 + ), + 'tileSize', 16, + 'edgeSize', 16 + )) + sliderFrame:SetFrameStrata("FULLSCREEN_DIALOG") + if sliderFrame.SetTopLevel then + sliderFrame:SetTopLevel(true) + end + sliderFrame:SetBackdropBorderColor(TOOLTIP_DEFAULT_COLOR.r, TOOLTIP_DEFAULT_COLOR.g, TOOLTIP_DEFAULT_COLOR.b) + sliderFrame:SetBackdropColor(TOOLTIP_DEFAULT_BACKGROUND_COLOR.r, TOOLTIP_DEFAULT_BACKGROUND_COLOR.g, TOOLTIP_DEFAULT_BACKGROUND_COLOR.b) + sliderFrame:EnableMouse(true) + sliderFrame:EnableMouseWheel(true) + sliderFrame:Hide() + sliderFrame:SetPoint("CENTER", UIParent, "CENTER") + local slider = CreateFrame("Slider", nil, sliderFrame) + sliderFrame.slider = slider + slider:SetOrientation("VERTICAL") + slider:SetMinMaxValues(0, 1) + slider:SetValueStep(0.000000001) + slider:SetValue(0.5) + slider:SetWidth(16) + slider:SetHeight(128) + slider:SetPoint("LEFT", sliderFrame, "LEFT", 15, 0) + slider:SetBackdrop(tmp( + 'bgFile', "Interface\\Buttons\\UI-SliderBar-Background", + 'edgeFile', "Interface\\Buttons\\UI-SliderBar-Border", + 'tile', true, + 'edgeSize', 8, + 'tileSize', 8, + 'insets', tmp2( + 'left', 3, + 'right', 3, + 'top', 3, + 'bottom', 3 + ) + )) + local texture = slider:CreateTexture() + slider:SetThumbTexture("Interface\\Buttons\\UI-SliderBar-Button-Vertical") + local text = slider:CreateFontString(nil, "ARTWORK") + sliderFrame.topText = text + text:SetFontObject(GameFontGreenSmall) + text:SetText("100%") + text:SetPoint("BOTTOM", slider, "TOP") + local text = slider:CreateFontString(nil, "ARTWORK") + sliderFrame.bottomText = text + text:SetFontObject(GameFontGreenSmall) + text:SetText("0%") + text:SetPoint("TOP", slider, "BOTTOM") + local editBox = CreateFrame("EditBox", nil, sliderFrame) + sliderFrame.currentText = editBox + editBox:SetFontObject(ChatFontNormal) + editBox:SetHeight(13) + editBox:SetPoint("RIGHT", sliderFrame, "RIGHT", -16, 0) + editBox:SetPoint("LEFT", slider, "RIGHT", 12, 0) + editBox:SetText("50%") + editBox:SetJustifyH("CENTER") + + local width = editBox:GetWidth()/2 + 10 + local left = editBox:CreateTexture(nil, "BACKGROUND") + left:SetTexture("Interface\\ChatFrame\\UI-ChatInputBorder-Left") + left:SetTexCoord(0, width / 256, 0, 1) + left:SetWidth(width) + left:SetHeight(32) + left:SetPoint("LEFT", editBox, "LEFT", -10, 0) + local right = editBox:CreateTexture(nil, "BACKGROUND") + right:SetTexture("Interface\\ChatFrame\\UI-ChatInputBorder-Right") + right:SetTexCoord(1 - width / 256, 1, 0, 1) + right:SetWidth(width) + right:SetHeight(32) + right:SetPoint("RIGHT", editBox, "RIGHT", 10, 0) + + local changed = false + local inside = false + slider:SetScript("OnValueChanged", function() + if sliderFrame.changing then + return + end + changed = true + local done = false + if sliderFrame.parent and sliderFrame.parent.sliderFunc then + local min = sliderFrame.parent.sliderMin or 0 + local max = sliderFrame.parent.sliderMax or 1 + local step + if sliderFrame.fineStep then + step = sliderFrame.parent.sliderStep or (max - min) / 100 + else + step = sliderFrame.parent.sliderBigStep or sliderFrame.parent.sliderStep or (max - min) / 100 + end + local value = (1 - slider:GetValue()) * (max - min) + min + if step > 0 then + value = math.floor((value - min) / step + 0.5) * step + min + if value > max then + value = max + elseif value < min then + value = min + end + end + if value == sliderFrame.lastValue then + return + end + sliderFrame.lastValue = value + local text = sliderFrame.parent.sliderFunc(getArgs(sliderFrame.parent, 'sliderArg', 1, value)) + if sliderFrame.parent.fromAceOptions then + text = nil + elseif type(text) == "string" or type(text) == "number" then + sliderFrame.currentText:SetText(text) + done = true + end + end + if not done then + local min = sliderFrame.parent.sliderMin or 0 + local max = sliderFrame.parent.sliderMax or 1 + local step + if sliderFrame.fineStep then + step = sliderFrame.parent.sliderStep or (max - min) / 100 + else + step = sliderFrame.parent.sliderBigStep or sliderFrame.parent.sliderStep or (max - min) / 100 + end + local value = (1 - slider:GetValue()) * (max - min) + min + if step > 0 then + value = math.floor((value - min) / step + 0.5) * step + min + if value > max then + value = max + elseif value < min then + value = min + end + end + if sliderFrame.parent.sliderIsPercent then + sliderFrame.currentText:SetText(string.format("%.0f%%", value * 100)) + else + if step < 0.1 then + sliderFrame.currentText:SetText(string.format("%.2f", value)) + elseif step < 1 then + sliderFrame.currentText:SetText(string.format("%.1f", value)) + else + sliderFrame.currentText:SetText(string.format("%.0f", value)) + end + end + end + end) + local function onEnter() + StopCounting(self, sliderFrame.level) + showGameTooltip(sliderFrame.parent) + end + local function onLeave() + GameTooltip:Hide() + end + sliderFrame:SetScript("OnEnter", onEnter) + sliderFrame:SetScript("OnLeave", function() + GameTooltip:Hide() + if changed then + local parent = sliderFrame.parent + local sliderFunc = parent.sliderFunc + for i = 1, sliderFrame.level - 1 do + Refresh(self, levels[i]) + end + local newParent + for _,button in ipairs(levels[sliderFrame.level-1].buttons) do + if button.sliderFunc == sliderFunc then + newParent = button + break + end + end + if newParent then + OpenSlider(self, newParent) + else + sliderFrame:Hide() + end + end + end) + editBox:SetScript("OnEnter", onEnter) + editBox:SetScript("OnLeave", onLeave) + slider:SetScript("OnMouseDown", function() + sliderFrame.mouseDown = true + GameTooltip:Hide() + end) + slider:SetScript("OnMouseUp", function() + sliderFrame.mouseDown = false + if changed--[[ and not inside]] then + local parent = sliderFrame.parent + local sliderFunc = parent.sliderFunc + for i = 1, sliderFrame.level - 1 do + Refresh(self, levels[i]) + end + local newParent + for _,button in ipairs(levels[sliderFrame.level-1].buttons) do + if button.sliderFunc == sliderFunc then + newParent = button + break + end + end + if newParent then + OpenSlider(self, newParent) + else + sliderFrame:Hide() + end + end + if inside then + showGameTooltip(sliderFrame.parent) + end + end) + slider:SetScript("OnEnter", function() + inside = true + StopCounting(self, sliderFrame.level) + showGameTooltip(sliderFrame.parent) + end) + slider:SetScript("OnLeave", function() + inside = false + GameTooltip:Hide() + if changed and not sliderFrame.mouseDown then + local parent = sliderFrame.parent + local sliderFunc = parent.sliderFunc + for i = 1, sliderFrame.level - 1 do + Refresh(self, levels[i]) + end + local newParent + for _,button in ipairs(levels[sliderFrame.level-1].buttons) do + if button.sliderFunc == sliderFunc then + newParent = button + break + end + end + if newParent then + OpenSlider(self, newParent) + else + sliderFrame:Hide() + end + + changed = false + end + end) + sliderFrame:SetScript("OnMouseWheel", function(t, a1) + local arg1 = a1 or arg1 + local up = arg1 > 0 + + local min = sliderFrame.parent.sliderMin or 0 + local max = sliderFrame.parent.sliderMax or 1 + local step = sliderFrame.parent.sliderStep or (max - min) / 100 + if step <= 0 then + step = (max - min) / 100 + end + + local value = (1 - slider:GetValue()) * (max - min) + min + if up then + value = value + step + else + value = value - step + end + if value > max then + value = max + elseif value < min then + value = min + end + sliderFrame.fineStep = true + if max<=min then + slider:SetValue(0) + else + slider:SetValue(1 - (value - min) / (max - min)) + end + sliderFrame.fineStep = nil + end) + slider:SetScript("OnMouseWheel", sliderFrame:GetScript("OnMouseWheel")) + editBox:SetScript("OnEnterPressed", function(t, a1) + local value = editBox:GetNumber() + + if sliderFrame.parent.sliderIsPercent then + value = value / 100 + end + + local min = sliderFrame.parent.sliderMin or 0 + local max = sliderFrame.parent.sliderMax or 1 + + if value > max then + value = max + elseif value < min then + value = min + end + sliderFrame.fineStep = true + if max <= min then + slider:SetValue(0) + else + slider:SetValue(1 - (value - min) / (max - min)) + end + sliderFrame.fineStep = nil + + StartCounting(self, sliderFrame.level) + end) + editBox:SetScript("OnEscapePressed", function() + self:Close(sliderFrame.level) + StartCounting(self, sliderFrame.level) + end) + editBox:SetAutoFocus(false) + end + sliderFrame.parent = parent + sliderFrame.level = parent.level.num + 1 + sliderFrame.parentValue = parent.level.value + sliderFrame:SetFrameLevel(parent.level:GetFrameLevel() + 3) + sliderFrame.slider:SetFrameLevel(sliderFrame:GetFrameLevel() + 1) + sliderFrame.currentText:SetFrameLevel(sliderFrame:GetFrameLevel() + 1) + sliderFrame.currentText:ClearFocus() + sliderFrame.changing = true + if not parent.sliderMin or not parent.sliderMax then + return + end + + if parent.arrow then +-- parent.arrow:SetVertexColor(0.2, 0.6, 0) +-- parent.arrow:SetHeight(24) +-- parent.arrow:SetWidth(24) + parent.selected = true + parent.highlight:Show() + end + + sliderFrame:SetClampedToScreen(false) + if not parent.sliderValue then + parent.sliderValue = (parent.sliderMin + parent.sliderMax) / 2 + end + if parent.sliderMax <= parent.sliderMin then + sliderFrame.slider:SetValue(0) + else + sliderFrame.slider:SetValue(1 - (parent.sliderValue - parent.sliderMin) / (parent.sliderMax - parent.sliderMin)) + end + sliderFrame.changing = false + sliderFrame.bottomText:SetText(parent.sliderMinText or "0") + sliderFrame.topText:SetText(parent.sliderMaxText or "1") + local text + if parent.sliderFunc and not parent.fromAceOptions then + text = parent.sliderFunc(getArgs(parent, 'sliderArg', 1, parent.sliderValue)) + end + if type(text) == "number" or type(text) == "string" then + sliderFrame.currentText:SetText(text) + elseif parent.sliderIsPercent then + sliderFrame.currentText:SetText(string.format("%.0f%%", parent.sliderValue * 100)) + else + if parent.sliderStep < 0.1 then + sliderFrame.currentText:SetText(string.format("%.2f", parent.sliderValue)) + elseif parent.sliderStep < 1 then + sliderFrame.currentText:SetText(string.format("%.1f", parent.sliderValue)) + else + sliderFrame.currentText:SetText(string.format("%.0f", parent.sliderValue)) + end + end + + + sliderFrame.lastValue = parent.sliderValue + + local level = parent.level + sliderFrame:Show() + sliderFrame:ClearAllPoints() + if level.lastDirection == "RIGHT" then + if level.lastVDirection == "DOWN" then + sliderFrame:SetPoint("TOPLEFT", parent, "TOPRIGHT", 5, 10) + else + sliderFrame:SetPoint("BOTTOMLEFT", parent, "BOTTOMRIGHT", 5, -10) + end + else + if level.lastVDirection == "DOWN" then + sliderFrame:SetPoint("TOPRIGHT", parent, "TOPLEFT", -5, 10) + else + sliderFrame:SetPoint("BOTTOMRIGHT", parent, "BOTTOMLEFT", -5, -10) + end + end + local dirty + if level.lastDirection == "RIGHT" then + if sliderFrame:GetRight() > GetScreenWidth() then + level.lastDirection = "LEFT" + dirty = true + end + elseif sliderFrame:GetLeft() < 0 then + level.lastDirection = "RIGHT" + dirty = true + end + if level.lastVDirection == "DOWN" then + if sliderFrame:GetBottom() < 0 then + level.lastVDirection = "UP" + dirty = true + end + elseif sliderFrame:GetTop() > GetScreenWidth() then + level.lastVDirection = "DOWN" + dirty = true + end + if dirty then + sliderFrame:ClearAllPoints() + if level.lastDirection == "RIGHT" then + if level.lastVDirection == "DOWN" then + sliderFrame:SetPoint("TOPLEFT", parent, "TOPRIGHT", 5, 10) + else + sliderFrame:SetPoint("BOTTOMLEFT", parent, "BOTTOMRIGHT", 5, -10) + end + else + if level.lastVDirection == "DOWN" then + sliderFrame:SetPoint("TOPRIGHT", parent, "TOPLEFT", -5, 10) + else + sliderFrame:SetPoint("BOTTOMRIGHT", parent, "BOTTOMLEFT", -5, -10) + end + end + end + local left, bottom = sliderFrame:GetLeft(), sliderFrame:GetBottom() + sliderFrame:ClearAllPoints() + sliderFrame:SetPoint("BOTTOMLEFT", UIParent, "BOTTOMLEFT", left, bottom) + if mod(level.num, 5) == 0 then + local left, bottom = level:GetLeft(), level:GetBottom() + level:ClearAllPoints() + level:SetPoint("BOTTOMLEFT", UIParent, "BOTTOMLEFT", left, bottom) + end + sliderFrame:SetClampedToScreen(true) +end + +function OpenEditBox(self, parent) + if not editBoxFrame then + editBoxFrame = CreateFrame("Frame", nil, nil) + editBoxFrame:SetWidth(200) + editBoxFrame:SetHeight(40) + editBoxFrame:SetScale(UIParent:GetScale()) + editBoxFrame:SetBackdrop(tmp( + 'bgFile', "Interface\\Tooltips\\UI-Tooltip-Background", + 'edgeFile', "Interface\\Tooltips\\UI-Tooltip-Border", + 'tile', true, + 'insets', tmp2( + 'left', 5, + 'right', 5, + 'top', 5, + 'bottom', 5 + ), + 'tileSize', 16, + 'edgeSize', 16 + )) + editBoxFrame:SetFrameStrata("FULLSCREEN_DIALOG") + if editBoxFrame.SetTopLevel then + editBoxFrame:SetTopLevel(true) + end + editBoxFrame:SetBackdropBorderColor(TOOLTIP_DEFAULT_COLOR.r, TOOLTIP_DEFAULT_COLOR.g, TOOLTIP_DEFAULT_COLOR.b) + editBoxFrame:SetBackdropColor(TOOLTIP_DEFAULT_BACKGROUND_COLOR.r, TOOLTIP_DEFAULT_BACKGROUND_COLOR.g, TOOLTIP_DEFAULT_BACKGROUND_COLOR.b) + editBoxFrame:EnableMouse(true) + editBoxFrame:EnableMouseWheel(true) + editBoxFrame:Hide() + editBoxFrame:SetPoint("CENTER", UIParent, "CENTER") + + local editBox = CreateFrame("EditBox", nil, editBoxFrame) + editBoxFrame.editBox = editBox + editBox:SetFontObject(ChatFontNormal) + editBox:SetWidth(160) + editBox:SetHeight(13) + editBox:SetPoint("CENTER", editBoxFrame, "CENTER", 0, 0) + + local left = editBox:CreateTexture(nil, "BACKGROUND") + left:SetTexture("Interface\\ChatFrame\\UI-ChatInputBorder-Left") + left:SetTexCoord(0, 100 / 256, 0, 1) + left:SetWidth(100) + left:SetHeight(32) + left:SetPoint("LEFT", editBox, "LEFT", -10, 0) + local right = editBox:CreateTexture(nil, "BACKGROUND") + right:SetTexture("Interface\\ChatFrame\\UI-ChatInputBorder-Right") + right:SetTexCoord(156/256, 1, 0, 1) + right:SetWidth(100) + right:SetHeight(32) + right:SetPoint("RIGHT", editBox, "RIGHT", 10, 0) + + editBox:SetScript("OnEnterPressed", function() + if editBoxFrame.parent and editBoxFrame.parent.editBoxValidateFunc then + local t = editBox.realText or editBox:GetText() or "" + local result = editBoxFrame.parent.editBoxValidateFunc(getArgs(editBoxFrame.parent, 'editBoxValidateArg', 1, t)) + if not result then + UIErrorsFrame:AddMessage(VALIDATION_ERROR, 1, 0, 0) + return + end + end + if editBoxFrame.parent and editBoxFrame.parent.editBoxFunc then + local t + if editBox.realText ~= "NONE" then + t = editBox.realText or editBox:GetText() or "" + end + editBoxFrame.parent.editBoxFunc(getArgs(editBoxFrame.parent, 'editBoxArg', 1, t)) + end + self:Close(editBoxFrame.level) + for i = 1, editBoxFrame.level - 1 do + Refresh(self, levels[i]) + end + StartCounting(self, editBoxFrame.level-1) + end) + editBox:SetScript("OnEscapePressed", function() + self:Close(editBoxFrame.level) + StartCounting(self, editBoxFrame.level-1) + end) + editBox:SetScript("OnReceiveDrag", function(this) + if GetCursorInfo then + local type, alpha, bravo = GetCursorInfo() + local text + if type == "spell" then + text = GetSpellName(alpha, bravo) + elseif type == "item" then + text = bravo + end + if not text then + return + end + ClearCursor() + editBox:SetText(text) + end + end) + local changing = false + local skipNext = false + + function editBox:SpecialSetText(text) + local oldText = editBox:GetText() or "" + if not text then + text = "" + end + if text ~= oldText then + changing = true + self:SetText(tostring(text)) + changing = false + skipNext = true + end + end + + editBox:SetScript("OnTextChanged", function() + if skipNext then + skipNext = false + elseif not changing and editBoxFrame.parent and editBoxFrame.parent.editBoxChangeFunc then + local t + if editBox.realText ~= "NONE" then + t = editBox.realText or editBox:GetText() or "" + end + local text = editBoxFrame.parent.editBoxChangeFunc(getArgs(editBoxFrame.parent, 'editBoxChangeArg', 1, t)) + if text then + editBox:SpecialSetText(text) + end + end + end) + editBoxFrame:SetScript("OnEnter", function() + StopCounting(self, editBoxFrame.level) + showGameTooltip(editBoxFrame.parent) + end) + editBoxFrame:SetScript("OnLeave", function() + GameTooltip:Hide() + end) + editBox:SetScript("OnEnter", function() + StopCounting(self, editBoxFrame.level) + showGameTooltip(editBoxFrame.parent) + end) + editBox:SetScript("OnLeave", function() + GameTooltip:Hide() + end) + editBoxFrame:SetScript("OnKeyDown", function(this, a1) + if not editBox.keybinding then + return + end + local arg1 = a1 or arg1 + local screenshotKey = GetBindingKey("SCREENSHOT") + if screenshotKey and arg1 == screenshotKey then + Screenshot() + return + end + + if arg1 == "LeftButton" then + arg1 = "BUTTON1" + elseif arg1 == "RightButton" then + arg1 = "BUTTON2" + elseif arg1 == "MiddleButton" then + arg1 = "BUTTON3" + elseif arg1 == "Button4" then + arg1 = "BUTTON4" + elseif arg1 == "Button5" then + arg1 = "BUTTON5" + end + if arg1 == "UNKNOWN" then + return + elseif arg1 == "SHIFT" or arg1 == "CTRL" or arg1 == "ALT" then + return + elseif arg1 == "ENTER" then + if editBox.keybindingOnly and not editBox.keybindingOnly[editBox.realText] then + return editBox:GetScript("OnEscapePressed")() + elseif editBox.keybindingExcept and editBox.keybindingExcept[editBox.realText] then + return editBox:GetScript("OnEscapePressed")() + else + return editBox:GetScript("OnEnterPressed")() + end + elseif arg1 == "ESCAPE" then + if editBox.realText == "NONE" then + return editBox:GetScript("OnEscapePressed")() + else + editBox:SpecialSetText(NONE or "NONE") + editBox.realText = "NONE" + return + end + elseif editBox.keybindingOnly and not editBox.keybindingOnly[arg1] then + return + elseif editBox.keybindingExcept and editBox.keybindingExcept[arg1] then + return + end + local s = GetBindingText(arg1, "KEY_") + if s == "BUTTON1" then + s = KEY_BUTTON1 + elseif s == "BUTTON2" then + s = KEY_BUTTON2 + end + local real = arg1 + if IsShiftKeyDown() then + s = "Shift-" .. s + real = "SHIFT-" .. real + end + if IsControlKeyDown() then + s = "Ctrl-" .. s + real = "CTRL-" .. real + end + if IsAltKeyDown() then + s = "Alt-" .. s + real = "ALT-" .. real + end + if editBox:GetText() ~= s then + editBox:SpecialSetText("-") + editBox:SpecialSetText(s) + editBox.realText = real + return editBox:GetScript("OnTextChanged")() + end + end) + editBoxFrame:SetScript("OnMouseDown", editBoxFrame:GetScript("OnKeyDown")) + editBox:SetScript("OnMouseDown", function(this, ...) + if GetCursorInfo and (CursorHasItem() or CursorHasSpell()) then + return editBox:GetScript("OnReceiveDrag")(this, ...) + end + return editBoxFrame:GetScript("OnKeyDown")(this, ...) + end) + editBoxFrame:SetScript("OnMouseWheel", function(t, a1) + local arg1 = a1 or arg1 + local up = arg1 > 0 + arg1 = up and "MOUSEWHEELUP" or "MOUSEWHEELDOWN" + return editBoxFrame:GetScript("OnKeyDown")(t or this, arg1) + end) + editBox:SetScript("OnMouseWheel", editBoxFrame:GetScript("OnMouseWheel")) + end + editBoxFrame.parent = parent + editBoxFrame.level = parent.level.num + 1 + editBoxFrame.parentValue = parent.level.value + editBoxFrame:SetFrameLevel(parent.level:GetFrameLevel() + 3) + editBoxFrame.editBox:SetFrameLevel(editBoxFrame:GetFrameLevel() + 1) + editBoxFrame.editBox.realText = nil + editBoxFrame:SetClampedToScreen(false) + + editBoxFrame.editBox:SpecialSetText("") + if parent.editBoxIsKeybinding then + local s = parent.editBoxText + if s == "" then + s = "NONE" + end + editBoxFrame.editBox.realText = s + if s and s ~= "NONE" then + local alpha,bravo = s:match("^(.+)%-(.+)$") + if not bravo then + alpha = nil + bravo = s + end + bravo = GetBindingText(bravo, "KEY_") + if alpha then + editBoxFrame.editBox:SpecialSetText(alpha:upper() .. "-" .. bravo) + else + editBoxFrame.editBox:SpecialSetText(bravo) + end + else + editBoxFrame.editBox:SpecialSetText(NONE or "NONE") + end + else + editBoxFrame.editBox:SpecialSetText(parent.editBoxText) + end + + editBoxFrame.editBox.keybinding = parent.editBoxIsKeybinding + editBoxFrame.editBox.keybindingOnly = parent.editBoxKeybindingOnly + editBoxFrame.editBox.keybindingExcept = parent.editBoxKeybindingExcept + editBoxFrame.editBox:EnableKeyboard(not parent.editBoxIsKeybinding) + editBoxFrame:EnableKeyboard(parent.editBoxIsKeybinding) + + if parent.arrow then +-- parent.arrow:SetVertexColor(0.2, 0.6, 0) +-- parent.arrow:SetHeight(24) +-- parent.arrow:SetWidth(24) + parent.selected = true + parent.highlight:Show() + end + + local level = parent.level + editBoxFrame:Show() + editBoxFrame:ClearAllPoints() + if level.lastDirection == "RIGHT" then + if level.lastVDirection == "DOWN" then + editBoxFrame:SetPoint("TOPLEFT", parent, "TOPRIGHT", 5, 10) + else + editBoxFrame:SetPoint("BOTTOMLEFT", parent, "BOTTOMRIGHT", 5, -10) + end + else + if level.lastVDirection == "DOWN" then + editBoxFrame:SetPoint("TOPRIGHT", parent, "TOPLEFT", -5, 10) + else + editBoxFrame:SetPoint("BOTTOMRIGHT", parent, "BOTTOMLEFT", -5, -10) + end + end + local dirty + if level.lastDirection == "RIGHT" then + if editBoxFrame:GetRight() > GetScreenWidth() then + level.lastDirection = "LEFT" + dirty = true + end + elseif editBoxFrame:GetLeft() < 0 then + level.lastDirection = "RIGHT" + dirty = true + end + if level.lastVDirection == "DOWN" then + if editBoxFrame:GetBottom() < 0 then + level.lastVDirection = "UP" + dirty = true + end + elseif editBoxFrame:GetTop() > GetScreenWidth() then + level.lastVDirection = "DOWN" + dirty = true + end + if dirty then + editBoxFrame:ClearAllPoints() + if level.lastDirection == "RIGHT" then + if level.lastVDirection == "DOWN" then + editBoxFrame:SetPoint("TOPLEFT", parent, "TOPRIGHT", 5, 10) + else + editBoxFrame:SetPoint("BOTTOMLEFT", parent, "BOTTOMRIGHT", 5, -10) + end + else + if level.lastVDirection == "DOWN" then + editBoxFrame:SetPoint("TOPRIGHT", parent, "TOPLEFT", -5, 10) + else + editBoxFrame:SetPoint("BOTTOMRIGHT", parent, "BOTTOMLEFT", -5, -10) + end + end + end + local left, bottom = editBoxFrame:GetLeft(), editBoxFrame:GetBottom() + editBoxFrame:ClearAllPoints() + editBoxFrame:SetPoint("BOTTOMLEFT", UIParent, "BOTTOMLEFT", left, bottom) + if mod(level.num, 5) == 0 then + local left, bottom = level:GetLeft(), level:GetBottom() + level:ClearAllPoints() + level:SetPoint("BOTTOMLEFT", UIParent, "BOTTOMLEFT", left, bottom) + end + editBoxFrame:SetClampedToScreen(true) +end + +function Dewdrop:EncodeKeybinding(text) + if text == nil or text == "NONE" then + return nil + end + text = tostring(text):upper() + local shift, ctrl, alt + local modifier + while true do + if text == "-" then + break + end + modifier, text = strsplit('-', text, 2) + if text then + if modifier ~= "SHIFT" and modifier ~= "CTRL" and modifier ~= "ALT" then + return false + end + if modifier == "SHIFT" then + if shift then + return false + end + shift = true + end + if modifier == "CTRL" then + if ctrl then + return false + end + ctrl = true + end + if modifier == "ALT" then + if alt then + return false + end + alt = true + end + else + text = modifier + break + end + end + if not text:find("^F%d+$") and text ~= "CAPSLOCK" and text:len() ~= 1 and (text:len() == 0 or text:byte() < 128 or text:len() > 4) and not _G["KEY_" .. text] and text ~= "BUTTON1" and text ~= "BUTTON2" then + return false + end + local s = GetBindingText(text, "KEY_") + if s == "BUTTON1" then + s = KEY_BUTTON1 + elseif s == "BUTTON2" then + s = KEY_BUTTON2 + end + if shift then + s = "Shift-" .. s + end + if ctrl then + s = "Ctrl-" .. s + end + if alt then + s = "Alt-" .. s + end + return s +end + +function Dewdrop:IsOpen(parent) + self:argCheck(parent, 2, "table", "string", "nil") + return levels[1] and levels[1]:IsShown() and (not parent or parent == levels[1].parent or parent == levels[1]:GetParent()) +end + +function Dewdrop:GetOpenedParent() + return (levels[1] and levels[1]:IsShown()) and (levels[1].parent or levels[1]:GetParent()) +end + +function Open(self, parent, func, level, value, point, relativePoint, cursorX, cursorY) + self:Close(level) + if DewdropLib then + local d = DewdropLib:GetInstance('1.0') + local ret, val = pcall(d, IsOpen, d) + if ret and val then + DewdropLib:GetInstance('1.0'):Close() + end + end + if type(parent) == "table" then + parent:GetCenter() + end + local frame = AcquireLevel(self, level) + if level == 1 then + frame.lastDirection = "RIGHT" + frame.lastVDirection = "DOWN" + else + frame.lastDirection = levels[level - 1].lastDirection + frame.lastVDirection = levels[level - 1].lastVDirection + end + frame:SetFrameStrata("FULLSCREEN_DIALOG") + frame:ClearAllPoints() + frame.parent = parent + frame:SetPoint("LEFT", UIParent, "RIGHT", 10000, 0) + frame:Show() + if level == 1 then + baseFunc = func + end + levels[level].value = value +-- levels[level].parentText = parent.text and parent.text:GetText() or nil +-- levels[level].parentTooltipTitle = parent.tooltipTitle +-- levels[level].parentTooltipText = parent.tooltipText +-- levels[level].parentTooltipFunc = parent.tooltipFunc + if type(parent) == "table" and parent.arrow then +-- parent.arrow:SetVertexColor(0.2, 0.6, 0) +-- parent.arrow:SetHeight(24) +-- parent.arrow:SetWidth(24) + parent.selected = true + parent.highlight:Show() + end + relativePoint = relativePoint or point + Refresh(self, levels[level]) + if point or (cursorX and cursorY) then + frame:ClearAllPoints() + if cursorX and cursorY then + local curX, curY = GetScaledCursorPosition() + if curY < GetScreenHeight() / 2 then + point, relativePoint = "BOTTOM", "BOTTOM" + else + point, relativePoint = "TOP", "TOP" + end + if curX < GetScreenWidth() / 2 then + point, relativePoint = point .. "LEFT", relativePoint .. "RIGHT" + else + point, relativePoint = point .. "RIGHT", relativePoint .. "LEFT" + end + end + frame:SetPoint(point, type(parent) == "table" and parent or UIParent, relativePoint) + if cursorX and cursorY then + local left = frame:GetLeft() + local width = frame:GetWidth() + local bottom = frame:GetBottom() + local height = frame:GetHeight() + local curX, curY = GetScaledCursorPosition() + frame:ClearAllPoints() + relativePoint = relativePoint or point + if point == "BOTTOM" or point == "TOP" then + if curX < GetScreenWidth() / 2 then + point = point .. "LEFT" + else + point = point .. "RIGHT" + end + elseif point == "CENTER" then + if curX < GetScreenWidth() / 2 then + point = "LEFT" + else + point = "RIGHT" + end + end + local xOffset, yOffset = 0, 0 + if curY > GetScreenHeight() / 2 then + yOffset = -height + end + if curX > GetScreenWidth() / 2 then + xOffset = -width + end + frame:SetPoint(point, type(parent) == "table" and parent or UIParent, relativePoint, curX - left + xOffset, curY - bottom + yOffset) + if level == 1 then + frame.lastDirection = "RIGHT" + end + elseif cursorX then + local left = frame:GetLeft() + local width = frame:GetWidth() + local curX, curY = GetScaledCursorPosition() + frame:ClearAllPoints() + relativePoint = relativePoint or point + if point == "BOTTOM" or point == "TOP" then + if curX < GetScreenWidth() / 2 then + point = point .. "LEFT" + else + point = point .. "RIGHT" + end + elseif point == "CENTER" then + if curX < GetScreenWidth() / 2 then + point = "LEFT" + else + point = "RIGHT" + end + end + frame:SetPoint(point, type(parent) == "table" and parent or UIParent, relativePoint, curX - left - width / 2, 0) + if level == 1 then + frame.lastDirection = "RIGHT" + end + elseif cursorY then + local bottom = frame:GetBottom() + local height = frame:GetHeight() + local curX, curY = GetScaledCursorPosition() + frame:ClearAllPoints() + relativePoint = relativePoint or point + if point == "LEFT" or point == "RIGHT" then + if curX < GetScreenHeight() / 2 then + point = point .. "BOTTOM" + else + point = point .. "TOP" + end + elseif point == "CENTER" then + if curX < GetScreenHeight() / 2 then + point = "BOTTOM" + else + point = "TOP" + end + end + frame:SetPoint(point, type(parent) == "table" and parent or UIParent, relativePoint, 0, curY - bottom - height / 2) + if level == 1 then + frame.lastDirection = "DOWN" + end + end + if (strsub(point, 1, 3) ~= strsub(relativePoint, 1, 3)) then + if frame:GetBottom() < 0 then + local point, parent, relativePoint, x, y = frame:GetPoint(1) + local change = GetScreenHeight() - frame:GetTop() + local otherChange = -frame:GetBottom() + if otherChange < change then + change = otherChange + end + frame:SetPoint(point, parent, relativePoint, x, y + change) + elseif frame:GetTop() > GetScreenHeight() then + local point, parent, relativePoint, x, y = frame:GetPoint(1) + local change = GetScreenHeight() - frame:GetTop() + local otherChange = -frame:GetBottom() + if otherChange < change then + change = otherChange + end + frame:SetPoint(point, parent, relativePoint, x, y + change) + end + end + end + CheckDualMonitor(self, frame) + frame:SetClampedToScreen(true) + frame:SetClampedToScreen(false) + StartCounting(self, level) +end + +function Dewdrop:IsRegistered(parent) + self:argCheck(parent, 2, "table", "string") + return not not self.registry[parent] +end + +function Dewdrop:Register(parent, ...) + self:argCheck(parent, 2, "table", "string") + if self.registry[parent] then + self:Unregister(parent) + end + local info = new(...) + if type(info.children) == "table" then + local err, position = validateOptions(info.children) + + if err then + if position then + Dewdrop:error(position .. ": " .. err) + else + Dewdrop:error(err) + end + end + end + self.registry[parent] = info + if not info.dontHook and not self.onceRegistered[parent] and type(parent) == "table" then + if parent:HasScript("OnMouseUp") then + local script = parent:GetScript("OnMouseUp") + parent:SetScript("OnMouseUp", function(this, ...) + if script then + script(this, ...) + end + if arg1 == "RightButton" and self.registry[parent] then + if self:IsOpen(parent) then + self:Close() + else + self:Open(parent) + end + end + end) + end + if parent:HasScript("OnMouseDown") then + local script = parent:GetScript("OnMouseDown") + parent:SetScript("OnMouseDown", function(this, ...) + if script then + script(this, ...) + end + if self.registry[parent] then + self:Close() + end + end) + end + end + self.onceRegistered[parent] = true +end + +function Dewdrop:Unregister(parent) + self:argCheck(parent, 2, "table", "string") + self.registry[parent] = nil +end + +function Dewdrop:Open(parent, ...) + self:argCheck(parent, 2, "table", "string") + local info + local k1 = ... + if type(k1) == "table" and k1[0] and k1.IsFrameType and self.registry[k1] then + info = tmp(select(2, ...)) + for k,v in pairs(self.registry[k1]) do + if info[k] == nil then + info[k] = v + end + end + else + info = tmp(...) + if self.registry[parent] then + for k,v in pairs(self.registry[parent]) do + if info[k] == nil then + info[k] = v + end + end + end + end + local point = info.point + local relativePoint = info.relativePoint + local cursorX = info.cursorX + local cursorY = info.cursorY + if type(point) == "function" then + local b + point, b = point(parent) + if b then + relativePoint = b + end + end + if type(relativePoint) == "function" then + relativePoint = relativePoint(parent) + end + Open(self, parent, info.children, 1, nil, point, relativePoint, cursorX, cursorY) +end + +function Clear(self, level) + if level then + if level.buttons then + for i = #level.buttons, 1, -1 do + ReleaseButton(self, level, i) + end + end + end +end + +function Dewdrop:Close(level) + if DropDownList1:IsShown() then + DropDownList1:Hide() + end + if DewdropLib then + local d = DewdropLib:GetInstance('1.0') + local ret, val = pcall(d, IsOpen, d) + if ret and val then + DewdropLib:GetInstance('1.0'):Close() + end + end + self:argCheck(level, 2, "number", "nil") + if not level then + level = 1 + end + if level == 1 and levels[level] then + levels[level].parented = false + end + if level > 1 and levels[level-1].buttons then + local buttons = levels[level-1].buttons + for _,button in ipairs(buttons) do +-- button.arrow:SetWidth(16) +-- button.arrow:SetHeight(16) + button.selected = nil + button.highlight:Hide() +-- button.arrow:SetVertexColor(1, 1, 1) + end + end + if sliderFrame and sliderFrame.level >= level then + sliderFrame:Hide() + end + if editBoxFrame and editBoxFrame.level >= level then + editBoxFrame:Hide() + end + for i = level, #levels do + Clear(self, levels[level]) + levels[i]:Hide() + levels[i]:ClearAllPoints() + levels[i]:SetPoint("CENTER", UIParent, "CENTER") + levels[i].value = nil + end +end + +function Dewdrop:AddSeparator(level) + level = levels[level or currentLevel] + if not level or not level.buttons then return; end + + local prevbutton = level.buttons[#level.buttons] + if not prevbutton then return; end + + if prevbutton.disabled and prevbutton.text:GetText() == "" then + return + end + self:AddLine("text", "", "disabled", true) +end + +function Dewdrop:AddLine(...) + local info = tmp(...) + local level = info.level or currentLevel + info.level = nil + local button = AcquireButton(self, level) + if not next(info) then + info.disabled = true + end + button.disabled = info.isTitle or info.notClickable or info.disabled or (self.combat and info.secure) + button.isTitle = info.isTitle + button.notClickable = info.notClickable + if button.isTitle then + button.text:SetFontObject(GameFontNormalSmall) + elseif button.notClickable then + button.text:SetFontObject(GameFontHighlightSmall) + elseif button.disabled then + button.text:SetFontObject(GameFontDisableSmall) + else + button.text:SetFontObject(GameFontHighlightSmall) + end + if info.disabled then + button.arrow:SetDesaturated(true) + button.check:SetDesaturated(true) + else + button.arrow:SetDesaturated(false) + button.check:SetDesaturated(false) + end + if info.textR and info.textG and info.textB then + button.textR = info.textR + button.textG = info.textG + button.textB = info.textB + button.text:SetTextColor(button.textR, button.textG, button.textB) + else + button.text:SetTextColor(button.text:GetFontObject():GetTextColor()) + end + button.notCheckable = info.notCheckable + button.text:SetPoint("LEFT", button, "LEFT", button.notCheckable and 0 or 24, 0) + button.checked = not info.notCheckable and info.checked + button.mouseoverUnderline = info.mouseoverUnderline + button.isRadio = not info.notCheckable and info.isRadio + if info.isRadio then + button.check:Show() + button.check:SetTexture(info.checkIcon or "Interface\\Buttons\\UI-RadioButton") + if button.checked then + button.check:SetTexCoord(0.25, 0.5, 0, 1) + button.check:SetVertexColor(1, 1, 1, 1) + else + button.check:SetTexCoord(0, 0.25, 0, 1) + button.check:SetVertexColor(1, 1, 1, 0.5) + end + button.radioHighlight:SetTexture(info.checkIcon or "Interface\\Buttons\\UI-RadioButton") + button.check:SetWidth(16) + button.check:SetHeight(16) + elseif info.icon then + button.check:Show() + button.check:SetTexture(info.icon) + if info.iconWidth and info.iconHeight then + button.check:SetWidth(info.iconWidth) + button.check:SetHeight(info.iconHeight) + else + button.check:SetWidth(16) + button.check:SetHeight(16) + end + if info.iconCoordLeft and info.iconCoordRight and info.iconCoordTop and info.iconCoordBottom then + button.check:SetTexCoord(info.iconCoordLeft, info.iconCoordRight, info.iconCoordTop, info.iconCoordBottom) + elseif info.icon:find("^Interface\\Icons\\") then + button.check:SetTexCoord(0.05, 0.95, 0.05, 0.95) + else + button.check:SetTexCoord(0, 1, 0, 1) + end + button.check:SetVertexColor(1, 1, 1, 1) + else + if button.checked then + if info.checkIcon then + button.check:SetWidth(16) + button.check:SetHeight(16) + button.check:SetTexture(info.checkIcon) + if info.checkIcon:find("^Interface\\Icons\\") then + button.check:SetTexCoord(0.05, 0.95, 0.05, 0.95) + else + button.check:SetTexCoord(0, 1, 0, 1) + end + else + button.check:SetWidth(24) + button.check:SetHeight(24) + button.check:SetTexture("Interface\\Buttons\\UI-CheckBox-Check") + button.check:SetTexCoord(0, 1, 0, 1) + end + button.check:SetVertexColor(1, 1, 1, 1) + else + button.check:SetVertexColor(1, 1, 1, 0) + end + end + if not button.disabled then + button.func = info.func + button.secure = info.secure + end + button.hasColorSwatch = info.hasColorSwatch + if button.hasColorSwatch then + button.colorSwatch:Show() + button.colorSwatch.texture:Show() + button.r = info.r or 1 + button.g = info.g or 1 + button.b = info.b or 1 + button.colorSwatch.texture:SetVertexColor(button.r, button.g, button.b) + button.checked = false + button.func = nil + button.colorFunc = info.colorFunc + local i = 1 + while true do + local k = "colorArg" .. i + local x = info[k] + if x == nil then + break + end + button[k] = x + i = i + 1 + end + button.hasOpacity = info.hasOpacity + button.opacity = info.opacity or 1 + else + button.colorSwatch:Hide() + button.colorSwatch.texture:Hide() + end + button.hasArrow = not button.hasColorSwatch and (info.value or info.hasSlider or info.hasEditBox) and info.hasArrow + if button.hasArrow then + button.arrow:SetAlpha(1) + if info.hasSlider then + button.hasSlider = true + button.sliderMin = info.sliderMin or 0 + button.sliderMax = info.sliderMax or 1 + button.sliderStep = info.sliderStep or 0 + button.sliderBigStep = info.sliderBigStep or button.sliderStep + if button.sliderBigStep < button.sliderStep then + button.sliderBigStep = button.sliderStep + end + button.sliderIsPercent = info.sliderIsPercent and true or false + button.sliderMinText = info.sliderMinText or button.sliderIsPercent and string.format("%.0f%%", button.sliderMin * 100) or button.sliderMin + button.sliderMaxText = info.sliderMaxText or button.sliderIsPercent and string.format("%.0f%%", button.sliderMax * 100) or button.sliderMax + button.sliderFunc = info.sliderFunc + button.sliderValue = info.sliderValue + button.fromAceOptions = info.fromAceOptions + local i = 1 + while true do + local k = "sliderArg" .. i + local x = info[k] + if x == nil then + break + end + button[k] = x + i = i + 1 + end + elseif info.hasEditBox then + button.hasEditBox = true + button.editBoxText = info.editBoxText or "" + button.editBoxFunc = info.editBoxFunc + local i = 1 + while true do + local k = "editBoxArg" .. i + local x = info[k] + if x == nil then + break + end + button[k] = x + i = i + 1 + end + button.editBoxChangeFunc = info.editBoxChangeFunc + local i = 1 + while true do + local k = "editBoxChangeArg" .. i + local x = info[k] + if x == nil then + break + end + button[k] = x + i = i + 1 + end + button.editBoxValidateFunc = info.editBoxValidateFunc + local i = 1 + while true do + local k = "editBoxValidateArg" .. i + local x = info[k] + if x == nil then + break + end + button[k] = x + i = i + 1 + end + button.editBoxIsKeybinding = info.editBoxIsKeybinding + button.editBoxKeybindingOnly = info.editBoxKeybindingOnly + button.editBoxKeybindingExcept = info.editBoxKeybindingExcept + else + button.value = info.value + local l = levels[level+1] + if l and info.value == l.value then +-- button.arrow:SetWidth(24) +-- button.arrow:SetHeight(24) + button.selected = true + button.highlight:Show() + end + end + else + button.arrow:SetAlpha(0) + end + local i = 1 + while true do + local k = "arg" .. i + local x = info[k] + if x == nil then + break + end + button[k] = x + i = i + 1 + end + button.closeWhenClicked = info.closeWhenClicked + button.textHeight = info.textHeight or UIDROPDOWNMENU_DEFAULT_TEXT_HEIGHT or 10 + local font,_ = button.text:GetFont() + button.text:SetFont(STANDARD_TEXT_FONT or "Fonts\\FRIZQT__.TTF", button.textHeight) + button:SetHeight(button.textHeight + 6) + button.text:SetPoint("RIGHT", button.arrow, (button.hasColorSwatch or button.hasArrow) and "LEFT" or "RIGHT") + button.text:SetJustifyH(info.justifyH or "LEFT") + button.text:SetText(info.text) + button.tooltipTitle = info.tooltipTitle + button.tooltipText = info.tooltipText + button.tooltipFunc = info.tooltipFunc + local i = 1 + while true do + local k = "tooltipArg" .. i + local x = info[k] + if x == nil then + break + end + button[k] = x + i = i + 1 + end + if not button.tooltipTitle and not button.tooltipText and not button.tooltipFunc and not info.isTitle then + button.tooltipTitle = info.text + end + if type(button.func) == "string" then + if type(button.arg1) ~= "table" then + self:error("Cannot call method %q on a non-table", button.func) + end + if type(button.arg1[button.func]) ~= "function" then + self:error("Method %q nonexistant.", button.func) + end + end +end + +function Dewdrop:InjectAceOptionsTable(handler, options) + self:argCheck(handler, 2, "table") + self:argCheck(options, 3, "table") + if tostring(options.type):lower() ~= "group" then + self:error('Cannot inject into options table argument #3 if its type is not "group"') + end + if options.handler ~= nil and options.handler ~= handler then + self:error("Cannot inject into options table argument #3 if it has a different handler than argument #2") + end + options.handler = handler + local class = handler.class + if not AceLibrary:HasInstance("AceOO-2.0") or not class then + if Rock then + -- possible Rock object + for mixin in Rock:IterateObjectMixins(handler) do + if type(mixin.GetAceOptionsDataTable) == "function" then + local t = mixin:GetAceOptionsDataTable(handler) + for k,v in pairs(t) do + if type(options.args) ~= "table" then + options.args = {} + end + if options.args[k] == nil then + options.args[k] = v + end + end + end + end + end + else + -- Ace2 object + while class and class ~= AceLibrary("AceOO-2.0").Class do + if type(class.GetAceOptionsDataTable) == "function" then + local t = class:GetAceOptionsDataTable(handler) + for k,v in pairs(t) do + if type(options.args) ~= "table" then + options.args = {} + end + if options.args[k] == nil then + options.args[k] = v + end + end + end + local mixins = class.mixins + if mixins then + for mixin in pairs(mixins) do + if type(mixin.GetAceOptionsDataTable) == "function" then + local t = mixin:GetAceOptionsDataTable(handler) + for k,v in pairs(t) do + if type(options.args) ~= "table" then + options.args = {} + end + if options.args[k] == nil then + options.args[k] = v + end + end + end + end + end + class = class.super + end + end + return options +end + +function Dewdrop:OnTooltipHide() + if lastSetFont then + if lastSetFont == normalFont then + lastSetFont = nil + return + end + fillRegionTmp(GameTooltip:GetRegions()) + for i,v in ipairs(regionTmp) do + if v.GetFont then + local font,size,outline = v:GetFont() + if font == lastSetFont then + v:SetFont(normalFont, size, outline) + end + end + regionTmp[i] = nil + end + lastSetFont = nil + end +end + +local function activate(self, oldLib, oldDeactivate) + Dewdrop = self + if oldLib and oldLib.registry then + self.registry = oldLib.registry + self.onceRegistered = oldLib.onceRegistered + else + self.registry = {} + self.onceRegistered = {} + + local WorldFrame_OnMouseDown = WorldFrame:GetScript("OnMouseDown") + local WorldFrame_OnMouseUp = WorldFrame:GetScript("OnMouseUp") + local oldX, oldY, clickTime + WorldFrame:SetScript("OnMouseDown", function(this, ...) + oldX,oldY = GetCursorPosition() + clickTime = GetTime() + if WorldFrame_OnMouseDown then + WorldFrame_OnMouseDown(this, ...) + end + end) + + WorldFrame:SetScript("OnMouseUp", function(this, ...) + local x,y = GetCursorPosition() + if not oldX or not oldY or not x or not y or not clickTime then + self:Close() + if WorldFrame_OnMouseUp then + WorldFrame_OnMouseUp(this, ...) + end + return + end + local d = math.abs(x - oldX) + math.abs(y - oldY) + if d <= 5 and GetTime() - clickTime < 0.5 then + self:Close() + end + if WorldFrame_OnMouseUp then + WorldFrame_OnMouseUp(this, ...) + end + end) + + hooksecurefunc(DropDownList1, "Show", function() + if levels[1] and levels[1]:IsVisible() then + self:Close() + end + end) + + hooksecurefunc("HideDropDownMenu", function() + if levels[1] and levels[1]:IsVisible() then + self:Close() + end + end) + + hooksecurefunc("CloseDropDownMenus", function() + if levels[1] and levels[1]:IsVisible() then + local stack = debugstack() + if not stack:find("`TargetFrame_OnHide'") then + self:Close() + end + end + end) + end + self.frame = oldLib and oldLib.frame or CreateFrame("Frame") + self.frame:UnregisterAllEvents() + self.frame:RegisterEvent("PLAYER_REGEN_ENABLED") + self.frame:RegisterEvent("PLAYER_REGEN_DISABLED") + self.frame:Hide() + self.frame:SetScript("OnEvent", function(this, event) + this:Show() + if event=="PLAYER_REGEN_ENABLED" then -- track combat state for secure frame operations + self.combat = false + elseif event=="PLAYER_REGEN_DISABLED" then + self.combat = true + end + end) + self.frame:SetScript("OnUpdate", function(this) + this:Hide() + self:Refresh(1) + end) + self.hookedTooltip = true + if not oldLib or not oldLib.hookedTooltip then + local OnTooltipHide = GameTooltip:GetScript("OnHide") + GameTooltip:SetScript("OnHide", function(this, ...) + if OnTooltipHide then + OnTooltipHide(this, ...) + end + if type(self.OnTooltipHide) == "function" then + self:OnTooltipHide() + end + end) + end + levels = {} + buttons = {} + + if oldDeactivate then + oldDeactivate(oldLib) + end +end + +local function external(lib, major, instance) + if major == "SharedMedia-1.0" then + SharedMedia = instance + end +end + +AceLibrary:Register(Dewdrop, MAJOR_VERSION, MINOR_VERSION, activate, nil, external)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/modules/FuBar_ReActionFu/lib/LibFuBarPlugin-3.0/Changelog-LibFuBarPlugin-3.0-r63707.txt Wed Apr 02 23:31:13 2008 +0000 @@ -0,0 +1,25 @@ +------------------------------------------------------------------------ +r63707 | arrowmaster | 2008-03-05 19:54:23 -0500 (Wed, 05 Mar 2008) | 1 line +Changed paths: + M /trunk/LibFuBarPlugin-3.0/LibFuBarPlugin-3.0.lua + +LibFuBarPlugin-3.0: fix MINOR_VERSION to stop going negative +------------------------------------------------------------------------ +r63318 | ckknight | 2008-03-01 12:59:56 -0500 (Sat, 01 Mar 2008) | 1 line +Changed paths: + M /trunk/LibFuBarPlugin-3.0/LibFuBarPlugin-3.0.lua + +LibFuBarPlugin-3.0 - remove a pointless :OnEmbedProfileEnable +------------------------------------------------------------------------ +r62709 | ckknight | 2008-02-23 21:15:11 -0500 (Sat, 23 Feb 2008) | 1 line +Changed paths: + M /trunk/LibFuBarPlugin-3.0/LibFuBarPlugin-3.0.lua + +LibFuBarPlugin-3.0 - move around tricorner minimaps properly. +------------------------------------------------------------------------ +r61479 | ckknight | 2008-02-15 12:18:37 -0500 (Fri, 15 Feb 2008) | 1 line +Changed paths: + M /trunk/LibFuBarPlugin-3.0/LibFuBarPlugin-3.0.lua + +LibFuBarPlugin-3.0 - properly move around SIDE-TOP and SIDE-BOTTOM minimaps +------------------------------------------------------------------------
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/modules/FuBar_ReActionFu/lib/LibFuBarPlugin-3.0/LibFuBarPlugin-3.0.lua Wed Apr 02 23:31:13 2008 +0000 @@ -0,0 +1,2145 @@ +--[[ +Name: LibFuBarPlugin-3.0 +Revision: $Rev: 63707 $ +Developed by: ckknight (ckknight@gmail.com) +Website: http://www.wowace.com/ +Description: Plugin for FuBar. +Dependencies: LibRock-1.0 +License: LGPL v2.1 +]] + +local MAJOR_VERSION = "LibFuBarPlugin-3.0" +local MINOR_VERSION = tonumber(("$Revision: 63707 $"):match("(%d+)")) - 60000 + +if not Rock then error(MAJOR_VERSION .. " requires LibRock-1.0") end + +local FuBarPlugin, oldLib = Rock:NewLibrary(MAJOR_VERSION, MINOR_VERSION) +if not FuBarPlugin then + return +end + +local SHOW_FUBAR_ICON = "Show FuBar icon" +local SHOW_FUBAR_ICON_DESC = "Show the FuBar plugin's icon on the panel." +local SHOW_FUBAR_TEXT = "Show FuBar text" +local SHOW_FUBAR_TEXT_DESC = "Show the FuBar plugin's text on the panel." +local SHOW_COLORED_FUBAR_TEXT = "Show colored FuBar text" +local SHOW_COLORED_FUBAR_TEXT_DESC = "Allow the FuBar plugin to color its text on the panel." +local DETACH_FUBAR_TOOLTIP = "Detach FuBar tooltip" +local DETACH_FUBAR_TOOLTIP_DESC = "Detach the FuBar tooltip from the panel." +local LOCK_FUBAR_TOOLTIP = "Lock tooltip" +local LOCK_FUBAR_TOOLTIP_DESC = "Lock the tooltips position. When the tooltip is locked, you must use Alt to access it with your mouse." +local POSITION_ON_FUBAR = "Position on FuBar" +local POSITION_ON_FUBAR_DESC = "Position the FuBar plugin on the panel." +local POSITION_LEFT = "Left" +local POSITION_RIGHT = "Right" +local POSITION_CENTER = "Center" +local ATTACH_PLUGIN_TO_MINIMAP = "Attach FuBar plugin to minimap" +local ATTACH_PLUGIN_TO_MINIMAP_DESC = "Attach the FuBar plugin to the minimap instead of the panel." +local HIDE_FUBAR_PLUGIN = "Hide FuBar plugin" +local HIDE_MINIMAP_BUTTON = "Hide minimap button" +local HIDE_FUBAR_PLUGIN_DESC = "Hide the FuBar plugin from the panel or minimap, leaving the addon running." +local OTHER = "Other" +local CLOSE = "Close" +local CLOSE_DESC = "Close the menu." + +if GetLocale() == "zhCN" then + SHOW_FUBAR_ICON = "显示FuBar图标" + SHOW_FUBAR_ICON_DESC = "在面板上显示FuBar插件的图标." + SHOW_FUBAR_TEXT = "显示FuBar文字" + SHOW_FUBAR_TEXT_DESC = "在面板上显示Fubar插件文字标题" + SHOW_COLORED_FUBAR_TEXT = "显示彩色文字" + SHOW_COLORED_FUBAR_TEXT_DESC = "允许插件显示彩色文字." + DETACH_FUBAR_TOOLTIP = "独立提示信息" + DETACH_FUBAR_TOOLTIP_DESC = "从面板上独立显示信息" + LOCK_FUBAR_TOOLTIP = "锁定提示信息" + LOCK_FUBAR_TOOLTIP_DESC = "锁定提示信息位置.当提示信息被锁定时,你必须要按Alt-鼠标方可查看." + POSITION_ON_FUBAR = "位置" + POSITION_ON_FUBAR_DESC = "FuBar插件在面板上的位置." + POSITION_LEFT = "居左" + POSITION_RIGHT = "居右" + POSITION_CENTER = "居中" + ATTACH_PLUGIN_TO_MINIMAP = "依附在小地图" + ATTACH_PLUGIN_TO_MINIMAP_DESC = "插件图标依附在小地图而不显示在面板上." + HIDE_FUBAR_PLUGIN = "隐藏FuBar插件" + HIDE_MINIMAP_BUTTON = "隐藏小地图按钮" + HIDE_FUBAR_PLUGIN_DESC = "隐藏在面板或小地图上的FuBar插件,暂定插件工作." + OTHER = "其他" + CLOSE = "关闭" + LOSE_DESC = "关闭目录." +elseif GetLocale() == "zhTW" then + SHOW_FUBAR_ICON = "顯示圖示" + SHOW_FUBAR_ICON_DESC = "在面板上顯示插件圖示。" + SHOW_FUBAR_TEXT = "顯示文字" + SHOW_FUBAR_TEXT_DESC = "在面板上顯示插件文字。" + SHOW_COLORED_FUBAR_TEXT = "允許彩色文字" + SHOW_COLORED_FUBAR_TEXT_DESC = "允許插件在面板上使用彩色文字。" + DETACH_FUBAR_TOOLTIP = "獨立提示訊息" + DETACH_FUBAR_TOOLTIP_DESC = "從面板上獨立提示訊息。" + LOCK_FUBAR_TOOLTIP = "鎖定提示訊息" + LOCK_FUBAR_TOOLTIP_DESC = "鎖定提示訊息位置。當提示訊息鎖定時,需要用Alt鍵使用提示訊息的功能。" + POSITION_ON_FUBAR = "位置" + POSITION_ON_FUBAR_DESC = "插件在面板上的位置。" + POSITION_LEFT = "靠左" + POSITION_RIGHT = "靠右" + POSITION_CENTER = "置中" + ATTACH_PLUGIN_TO_MINIMAP = "依附在小地圖" + ATTACH_PLUGIN_TO_MINIMAP_DESC = "插件圖標依附在小地圖而不顯示在面板上。" + HIDE_FUBAR_PLUGIN = "隱藏插件" + HIDE_MINIMAP_BUTTON = "隱藏小地圖按鈕" + HIDE_FUBAR_PLUGIN_DESC = "在面板或小地圖上隱藏該插件,但保持執行狀態。" + OTHER = "其他" + CLOSE = "關閉" + CLOSE_DESC = "關閉選單。" +elseif GetLocale() == "koKR" then + SHOW_FUBAR_ICON = "FuBar 아이콘 표시" + SHOW_FUBAR_ICON_DESC = "FuBar 패널에 플러그인 아이콘을 표시합니다." + SHOW_FUBAR_TEXT = "FuBar 텍스트 표시" + SHOW_FUBAR_TEXT_DESC = "FuBar 페널에 플러그인 텍스트를 표시합니다." + SHOW_COLORED_FUBAR_TEXT = "색상화된 FuBar 텍스트 표시" + SHOW_COLORED_FUBAR_TEXT_DESC = "패널의 FuBar 플러그인의 텍스트 색상을 허용합니다." + DETACH_FUBAR_TOOLTIP = "FuBar 툴팁 분리" + DETACH_FUBAR_TOOLTIP_DESC = "패널에서 FuBar 툴팁을 분리합니다." + LOCK_FUBAR_TOOLTIP = "툴팁 고정" + LOCK_FUBAR_TOOLTIP_DESC = "툴팁 위치를 고정시킵니다. 툴팁이 고정되어 있을때, 마우스로 접근하기 위해 Alt키를 사용하여야 합니다." + POSITION_ON_FUBAR = "FuBar 위치" + POSITION_ON_FUBAR_DESC = "패널 위의 FuBar 플러그인의 위치를 설정합니다." + POSITION_LEFT = "좌측" + POSITION_RIGHT = "우측" + POSITION_CENTER = "중앙" + ATTACH_PLUGIN_TO_MINIMAP = "FuBar 플러그인 미니맵 표시" + ATTACH_PLUGIN_TO_MINIMAP_DESC = "FuBar 플러그인을 패널 대신 미니맵에 표시합니다." + HIDE_FUBAR_PLUGIN = "FuBar 플러그인 숨김" + HIDE_MINIMAP_BUTTON = "미니맵 버튼 숨김" + HIDE_FUBAR_PLUGIN_DESC = "FuBar 플러그인을 패널이나 미니맵으로 부터 숨김니다." + OTHER = "기타" + CLOSE = "닫기" + CLOSE_DESC = "메뉴를 닫습니다." +elseif GetLocale() == "frFR" then + SHOW_FUBAR_ICON = "Afficher l'icône FuBar" + SHOW_FUBAR_ICON_DESC = "Affiche l'icône du plugin FuBar sur le panneau." + SHOW_FUBAR_TEXT = "Afficher le texte FuBar" + SHOW_FUBAR_TEXT_DESC = "Affiche le texte du plugin FuBar sur le panneau." + SHOW_COLORED_FUBAR_TEXT = "Afficher le texte FuBar coloré" + SHOW_COLORED_FUBAR_TEXT_DESC = "Autorise le plugin FuBar à colorer son texte sur le panneau." + DETACH_FUBAR_TOOLTIP = "Détacher l'infobulle FuBar" + DETACH_FUBAR_TOOLTIP_DESC = "Détache l'infobulle FuBar du panneau." + LOCK_FUBAR_TOOLTIP = "Verrouiller l'infobulle" + LOCK_FUBAR_TOOLTIP_DESC = "Verrouille l'infobulle dans sa position actuelle. Quand l'infobulle est verrouillée, vous devez utiliser la touche Alt pour y interagir avec la souris." + POSITION_ON_FUBAR = "Position sur FuBar" + POSITION_ON_FUBAR_DESC = "Position du plugin FuBar sur le panneau." + POSITION_LEFT = "Gauche" + POSITION_RIGHT = "Droite" + POSITION_CENTER = "Centre" + ATTACH_PLUGIN_TO_MINIMAP = "Attacher le plugin FuBar sur la minicarte" + ATTACH_PLUGIN_TO_MINIMAP_DESC = "Attache le plugin FuBar sur la minicarte au lieu du panneau." + HIDE_FUBAR_PLUGIN = "Masquer le plugin FuBar" + HIDE_MINIMAP_BUTTON = "Masquer le bouton de la minicarte" + HIDE_FUBAR_PLUGIN_DESC = "Masque le plugin FuBar du panneau ou de la minicarte, laissant l'addon fonctionner." + OTHER = "Autre" + CLOSE = "Fermer" + CLOSE_DESC = "Ferme le menu." +end + +-- #AUTODOC_NAMESPACE FuBarPlugin + +local precondition, argCheck = Rock:GetContractFunctions(MAJOR_VERSION, "precondition", "argCheck") +local newList, del = Rock:GetRecyclingFunctions(MAJOR_VERSION, "newList", "del") + +FuBarPlugin.pluginToFrame = oldLib and oldLib.pluginToFrame or {} +local pluginToFrame = FuBarPlugin.pluginToFrame +FuBarPlugin.pluginToMinimapFrame = oldLib and oldLib.pluginToMinimapFrame or {} +local pluginToMinimapFrame = FuBarPlugin.pluginToMinimapFrame +FuBarPlugin.pluginToPanel = oldLib and oldLib.pluginToPanel or {} +local pluginToPanel = FuBarPlugin.pluginToPanel +FuBarPlugin.pluginToOptions = oldLib and oldLib.pluginToOptions or {} +local pluginToOptions = FuBarPlugin.pluginToOptions +FuBarPlugin.folderNames = oldLib and oldLib.folderNames or {} +local folderNames = FuBarPlugin.folderNames + +local Tablet20 +local Dewdrop20 + +FuBarPlugin.MinimapContainer = oldLib and oldLib.MinimapContainer or {} +local MinimapContainer = FuBarPlugin.MinimapContainer + +local epsilon = 1e-5 + +-- #AUTODOC_NAMESPACE FuBarPlugin + +--[[--------------------------------------------------------------------------- +Notes: + *Set metadata about a certain plugin. + ; tooltipType : string - + : "GameTooltip" + :: Use Blizzard's GameTooltip. (default if not given) + : "Tablet-2.0" + :: Use Tablet-2.0. + : "Custom" + :: LibFuBarPlugin-3.0 will not provide any extra mechanisms, all done manually. + ; configType : string - + : "LibRockConfig-1.0" + :: Use LibRockConfig-1.0 to show configuration. (default if not given) + : "Dewdrop-2.0" + :: Use Dewdrop-2.0. + ; hasNoText : boolean - If set to true, then it will be a text-less frame. + ; iconPath : string - the path of the icon to show. + ; hasNoColor : boolean - If set to true, then it is assumed that no color will be in the text (and thus not show the menu item) + ; cannotHideText : boolean - If set to true, then the menu item to hide text will not be shown. + ; overrideMenu : boolean - If set to true, then the menu will not show any of the standard menu items + ; hideMenuTitle : boolean - If set to true, the plugins name will not be added to the top of the menu as a header. + ; defaultPosition : string - + : "LEFT" + ::show on the left. (default if not given) + : "CENTER" + ::show in the center. + : "RIGHT" + ::show on the right. + : "MINIMAP" + ::show on the minimap. + ; defaultMinimapPosition : number - Angle on the minimap, in degrees. [0, 360) + ; clickableTooltip : boolean - Whether you can drag your mouse onto the tooltip and click a line + ; tooltipHiddenWhenEmpty : boolean - Whether the detached tooltip is hidden when it is empty. + ; cannotDetachTooltip : boolean - Whether the tooltip cannot be detached from the plugin text. + ::Normally, a tooltip can detach (if using Tablet-2.0). This should be set if there is no relevant data in the tooltip. + ; independentProfile : boolean - If set to true, then the profile setting will not be stripped from .OnMenuRequest, and FuBar will not set the plugin's profile when it changes. + ::non-FuBar-centric plugins should set this to true. +Arguments: + string - the key to set + value - the value to set said key to. +Example: + self:SetFuBarOption('tooltipType', "Tablet-2.0") +-----------------------------------------------------------------------------]] +function FuBarPlugin:SetFuBarOption(key, value) + local pluginToOptions_self = pluginToOptions[self] + if not pluginToOptions_self then + pluginToOptions_self = {} + pluginToOptions[self] = pluginToOptions_self + end + + pluginToOptions_self[key] = value + + if key == 'tooltipType' then + if value == "Tablet-2.0" then + Tablet20 = Rock("Tablet-2.0", false, true) + if not Tablet20 then + error(("Cannot specify %q = %q if %q is not loaded."):format(key, value, value), 2) + end + end + end + if key == 'configType' then + if value == "Dewdrop-2.0" then + Dewdrop20 = Rock("Dewdrop-2.0", false, true) + if not Dewdrop20 then + error(("Cannot specify %q = %q if %q is not loaded."):format(key, value, value), 2) + end + end + end +end +precondition(FuBarPlugin, 'SetFuBarOption', function(self, key, value) + argCheck(self, 1, "table") + argCheck(key, 2, "string") + argCheck(value, 3, "string", "number", "boolean") + + if pluginToOptions[self] and pluginToOptions[self][key] ~= nil then + error(("Bad argument #2 to `SetFuBarOption'. Cannot specify %q more than once."):format(key), 3) + end +end) + +local function getPluginOption(object, key, default) + local pluginToOptions_object = pluginToOptions[object] + if pluginToOptions_object == nil then + return default + end + local value = pluginToOptions_object[key] + if value == nil then + return default + end + return value +end + +local good = nil +local function CheckFuBar() + if not good then + if FuBar then + local version = FuBar.version + if type(version) == "string" then + local num = version:match("^(%d+%.?%d*)") + if num then + num = tonumber(num) + good = num >= 3 + end + end + end + end + return good +end + +--[[--------------------------------------------------------------------------- +Returns: + string - the localized name of the plugin, not including the "FuBar - " part. +Example + local title = self:GetTitle() +-----------------------------------------------------------------------------]] +function FuBarPlugin:GetTitle() + local name = self.title or self.name + if type(name) ~= "string" then + error("You must provide self.title or self.name", 2) + end + local title = name:match("[Ff][Uu][Bb][Aa][Rr]%s*%-%s*(.-)%s*$") or name + return title:gsub("|c%x%x%x%x%x%x%x%x", ""):gsub("|r", "") +end + +--[[--------------------------------------------------------------------------- +Returns: + string - name of the plugin. +Notes: + This is here for FuBar core to communicate properly. +Example: + local name = self:GetName() +-----------------------------------------------------------------------------]] +function FuBarPlugin:GetName() + return self.name +end + +--[[--------------------------------------------------------------------------- +Returns: + string - category of the plugin. +Notes: + This is here for FuBar core to communicate properly. +Example: + local category = self:GetCategory() +-----------------------------------------------------------------------------]] +function FuBarPlugin:GetCategory() + return self.category or OTHER +end + +--[[--------------------------------------------------------------------------- +Returns: + frame - frame for the plugin. +Notes: + This is here for FuBar core to communicate properly. +Example: + local frame = self:GetFrame() +-----------------------------------------------------------------------------]] +function FuBarPlugin:GetFrame() + return pluginToFrame[self] +end + +--[[--------------------------------------------------------------------------- +Returns: + object - panel for the plugin. +Notes: + This is here for FuBar core to communicate properly. +Example: + local panel = self:GetPanel() +-----------------------------------------------------------------------------]] +function FuBarPlugin:GetPanel() + return pluginToPanel[self] +end + +local function getLazyDatabaseValueDefault(object, value, ...) + local object_db = object.db + if type(object_db) ~= "table" then + return value + end + local current = object_db.profile + for i = 1, select('#', ...) do + -- traverse through, make sure tables exist. + if type(current) ~= "table" then + return value + end + current = current[(select(i, ...))] + end + if current == nil then + return value + else + return current + end +end + +local function getLazyDatabaseValue(object, ...) + return getLazyDatabaseValueDefault(object, nil, ...) +end + +local function setLazyDatabaseValue(object, value, ...) + local object_db = object.db + if type(object_db) ~= "table" then + return nil + end + local current = object_db.profile + if type(current) ~= "table" then + return nil + end + local n = select('#', ...) + for i = 1, n-1 do + -- traverse through, create tables if necessary. + local nextOne = current[(select(i, ...))] + if type(nextOne) ~= "table" then + if nextOne ~= nil then + return nil + end + nextOne = {} + current[(select(i, ...))] = nextOne + end + current = nextOne + end + current[select(n, ...)] = value + return true +end + +--[[--------------------------------------------------------------------------- +Returns: + boolean - whether the text has color applied. +Example: + local colored = self:IsFuBarTextColored() +-----------------------------------------------------------------------------]] +function FuBarPlugin:IsFuBarTextColored() + return not getLazyDatabaseValue(self, 'uncolored') +end + +--[[--------------------------------------------------------------------------- +Notes: + Toggles whether the text has color applied +Example: + self:ToggleTextColored() +-----------------------------------------------------------------------------]] +function FuBarPlugin:ToggleFuBarTextColored() + if not setLazyDatabaseValue(self, not getLazyDatabaseValue(self, 'uncolored') or nil, 'uncolored') then + error(("%s: Cannot change text color if self.db is not available."):format(self:GetTitle()), 2) + end + self:UpdateFuBarText() +end + +--[[--------------------------------------------------------------------------- +Returns: + boolean - whether the plugin is attached to the minimap. +Example: + local attached = self:IsMinimapAttached() +-----------------------------------------------------------------------------]] +function FuBarPlugin:IsFuBarMinimapAttached() + if not CheckFuBar() then + return true + end + return pluginToPanel[self] == MinimapContainer +end + +--[[--------------------------------------------------------------------------- +Notes: + Toggles whether the plugin is attached to the minimap. +Example: + self:ToggleMinimapAttached() +-----------------------------------------------------------------------------]] +function FuBarPlugin:ToggleFuBarMinimapAttached() + if CheckFuBar() and not getPluginOption(self, 'cannotAttachToMinimap', false) then + local panel = pluginToPanel[self] + local value = panel == MinimapContainer + if value then + panel:RemovePlugin(self) + local defaultPosition = getPluginOption(self, 'defaultPosition', "LEFT") + FuBar:GetPanel(1):AddPlugin(self, nil, defaultPosition == "MINIMAP" and "LEFT" or defaultPosition) + else + if panel then + panel:RemovePlugin(self) + end + MinimapContainer:AddPlugin(self) + end + end +end + +--[[--------------------------------------------------------------------------- +Notes: + Calls :UpdateFuBarText() and :UpdateFuBarTooltip(), in that order. +Example: + self:UpdateFuBarPlugin() +-----------------------------------------------------------------------------]] +function FuBarPlugin:UpdateFuBarPlugin() + self:UpdateFuBarText() + self:UpdateFuBarTooltip() +end + +--[[--------------------------------------------------------------------------- +Notes: + * Calls :OnUpdateFuBarText() if it is available and the plugin is not disabled. + * It is expected to update the icon in :OnUpdateFuBarText as well as text. +Example: + self:UpdateFuBarText() +-----------------------------------------------------------------------------]] +function FuBarPlugin:UpdateFuBarText() + if type(self.OnUpdateFuBarText) == "function" then + if not self:IsDisabled() then + self:OnUpdateFuBarText() + end + elseif self:IsFuBarTextShown() then + self:SetFuBarText(self:GetTitle()) + end +end + +local function Tablet20_point(frame) + if frame:GetTop() > GetScreenHeight() / 2 then + local x = frame:GetCenter() + if x < GetScreenWidth() / 3 then + return "TOPLEFT", "BOTTOMLEFT" + elseif x < GetScreenWidth() * 2 / 3 then + return "TOP", "BOTTOM" + else + return "TOPRIGHT", "BOTTOMRIGHT" + end + else + local x = frame:GetCenter() + if x < GetScreenWidth() / 3 then + return "BOTTOMLEFT", "TOPLEFT" + elseif x < GetScreenWidth() * 2 / 3 then + return "BOTTOM", "TOP" + else + return "BOTTOMRIGHT", "TOPRIGHT" + end + end +end + +local function RegisterTablet20(self) + local frame = pluginToFrame[self] + if not Tablet20:IsRegistered(frame) then + local db = getLazyDatabaseValue(self) + if db and not db.detachedTooltip then + db.detachedTooltip = {} + end + Tablet20:Register(frame, + 'children', function() + Tablet20:SetTitle(self:GetTitle()) + if type(self.OnUpdateFuBarTooltip) == "function" then + if not self:IsDisabled() then + self:OnUpdateFuBarTooltip() + end + end + end, + 'clickable', getPluginOption(self, 'clickableTooltip', false), + 'data', CheckFuBar() and FuBar.db.profile.tooltip or db and db.detachedTooltip or {}, + 'detachedData', db and db.detachedTooltip or {}, + 'point', Tablet20_point, + 'menu', self.OnMenuRequest and function(level, value, valueN_1, valueN_2, valueN_3, valueN_4) + if level == 1 then + local name = tostring(self) + if not name:find('^table:') then + name = name:gsub("|c%x%x%x%x%x%x%x%x(.-)|r", "%1") + Rock("Dewdrop-2.0"):AddLine( + 'text', name, + 'isTitle', true + ) + end + end + if type(self.OnMenuRequest) == "function" then + self:OnMenuRequest(level, value, true, valueN_1, valueN_2, valueN_3, valueN_4) + elseif type(self.OnMenuRequest) == "table" then + Rock("Dewdrop-2.0"):FeedAceOptionsTable(self.OnMenuRequest) + end + end, + 'hideWhenEmpty', getPluginOption(self, 'tooltipHiddenWhenEmpty', false) + ) + local func = pluginToFrame[self]:GetScript("OnEnter") + frame:SetScript("OnEnter", function(this, ...) + -- HACK + func(this, ...) + + if FuBar and FuBar.IsHidingTooltipsInCombat and FuBar:IsHidingTooltipsInCombat() and InCombatLockdown() then + if Tablet20:IsAttached(this) then + Tablet20:Close(this) + end + end + end) + end +end + +--[[--------------------------------------------------------------------------- +Notes: + Calls :OnUpdateFuBarTooltip() if it is available, the plugin is not disabled, and the tooltip is shown. +Example: + self:UpdateFuBarTooltip() +-----------------------------------------------------------------------------]] +function FuBarPlugin:UpdateFuBarTooltip() + local tooltipType = getPluginOption(self, 'tooltipType', "GameTooltip") + + if tooltipType == "GameTooltip" then + local frame = self:IsFuBarMinimapAttached() and pluginToMinimapFrame[self] or pluginToFrame[self] + if not GameTooltip:IsOwned(frame) then + return + end + GameTooltip:Hide() + + local anchor + if frame:GetTop() > GetScreenHeight() / 2 then + local x = frame:GetCenter() + if x < GetScreenWidth() / 2 then + anchor = "ANCHOR_BOTTOMRIGHT" + else + anchor = "ANCHOR_BOTTOMLEFT" + end + else + local x = frame:GetCenter() + if x < GetScreenWidth() / 2 then + anchor = "ANCHOR_TOPLEFT" + else + anchor = "ANCHOR_TOPRIGHT" + end + end + GameTooltip:SetOwner(frame, anchor) + if type(self.OnUpdateFuBarTooltip) == "function" and not self:IsDisabled() then + self:OnUpdateFuBarTooltip() + end + GameTooltip:Show() + return + elseif tooltipType == "Custom" then + if type(self.OnUpdateFuBarTooltip) == "function" and not self:IsDisabled() then + self:OnUpdateFuBarTooltip() + end + return + elseif tooltipType == "Tablet-2.0" then + RegisterTablet20(self) + if self:IsFuBarMinimapAttached() and not self:IsFuBarTooltipDetached() and pluginToMinimapFrame[self] then + Tablet20:Refresh(pluginToMinimapFrame[self]) + else + Tablet20:Refresh(pluginToFrame[self]) + end + elseif tooltipType == "None" then + return + else + error(("Unknown %s option for %q: %q"):format(MAJOR_VERSION, 'tooltipType', tostring(tooltipType)), 2) + end +end + +--[[--------------------------------------------------------------------------- +Notes: + Shows the plugin, enables the plugin if previously disabled, and calls :UpdateFuBarPlugin(). +Example: + self:Show() +-----------------------------------------------------------------------------]] +function FuBarPlugin:Show(panelId) + if pluginToFrame[self]:IsShown() or (pluginToMinimapFrame[self] and pluginToMinimapFrame[self]:IsShown()) then + return + end + if panelId ~= false then + setLazyDatabaseValue(self, nil, 'hidden') + end + if self.IsActive and not self:IsActive() then + self.panelIdTmp = panelId + self:ToggleActive() + self.panelIdTmp = nil + setLazyDatabaseValue(self, nil, 'disabled') + elseif not getLazyDatabaseValue(self, 'hidden') then + if panelId == 0 or not CheckFuBar() then + MinimapContainer:AddPlugin(self) + else + FuBar:ShowPlugin(self, panelId or self.panelIdTmp) + end + if not getPluginOption(self, 'userDefinedFrame', false) then + if not self:IsFuBarTextShown() then + local text = pluginToFrame[self].text + text:SetText("") + text:SetWidth(epsilon) + text:Hide() + end + if not self:IsFuBarIconShown() then + local icon = pluginToFrame[self].icon + icon:SetWidth(epsilon) + icon:Hide() + end + end + self:UpdateFuBarPlugin() + end +end + +--[[--------------------------------------------------------------------------- +Notes: + Hides the plugin, disables the plugin if cannot hide without standby. +Arguments: + [optional] boolean - internal variable. Do not set this. +Example: + self:Hide() +-----------------------------------------------------------------------------]] +function FuBarPlugin:Hide(check) + if not pluginToFrame[self]:IsShown() and (not pluginToMinimapFrame[self] or not pluginToMinimapFrame[self]:IsShown()) then + return + end + local hideWithoutStandby = getPluginOption(self, 'hideWithoutStandby', false) + if hideWithoutStandby and check ~= false then + setLazyDatabaseValue(self, true, 'hidden') + end + if not hideWithoutStandby then + if getPluginOption(self, 'tooltipType', "GameTooltip") == "Tablet-2.0" and not getPluginOption(self, 'cannotDetachTooltip', false) and self:IsFuBarTooltipDetached() and getLazyDatabaseValue(self, 'detachedTooltip', 'detached') then + self:ReattachTooltip() + setLazyDatabaseValue(self, true, 'detachedTooltip', 'detached') + end + if self.IsActive and self:IsActive() and self.ToggleActive and (not CheckFuBar() or not FuBar:IsChangingProfile()) then + self:ToggleActive() + end + end + if pluginToPanel[self] then + pluginToPanel[self]:RemovePlugin(self) + end + pluginToFrame[self]:Hide() + if pluginToMinimapFrame[self] then + pluginToMinimapFrame[self]:Hide() + end +end + +--[[--------------------------------------------------------------------------- +Notes: + Sets the path to the icon for the plugin. +Arguments: + string or nil - The path to the icon. If nil, then no icon. +Example: + self:SetFuBarIcon("Interface\\AddOns\\MyAddon\\otherIcon") +-----------------------------------------------------------------------------]] +function FuBarPlugin:SetFuBarIcon(path) + if not path then + return + end + if not pluginToFrame[self] or not pluginToFrame[self].icon then + return + end + if path:match([[^Interface\Icons\]]) then + pluginToFrame[self].icon:SetTexCoord(0.05, 0.95, 0.05, 0.95) + else + pluginToFrame[self].icon:SetTexCoord(0, 1, 0, 1) + end + pluginToFrame[self].icon:SetTexture(path) + if pluginToMinimapFrame[self] and pluginToMinimapFrame[self].icon then + if path:match([[^Interface\Icons\]]) then + pluginToMinimapFrame[self].icon:SetTexCoord(0.05, 0.95, 0.05, 0.95) + else + pluginToMinimapFrame[self].icon:SetTexCoord(0, 1, 0, 1) + end + pluginToMinimapFrame[self].icon:SetTexture(path) + end +end +precondition(FuBarPlugin, 'SetFuBarIcon', function(self, path) + if not path then + return + end + argCheck(path, 2, "string", "nil") + if not getPluginOption(self, 'iconPath', false) then + error(("%s: Cannot set icon unless 'iconPath' is set."):format(self:GetTitle()), 3) + end +end) + +--[[--------------------------------------------------------------------------- +Returns: + string or nil - The path to the icon for the plugin. If nil, then no icon. +Example: + local path = self:GetFuBarIcon() +-----------------------------------------------------------------------------]] +function FuBarPlugin:GetFuBarIcon() + if getPluginOption(self, 'iconPath', false) then + return pluginToFrame[self] and pluginToFrame[self].icon and pluginToFrame[self].icon:GetTexture() + end +end + +--[[--------------------------------------------------------------------------- +Notes: + Checks the current width of the icon and text, then updates frame to expand/shrink to it if necessary. +Arguments: + [optional] boolean - if true, Shrink/expand no matter what, otherwise if the width is less than 8 pixels smaller, don't shrink. +Example: + self:CheckWidth(true) +-----------------------------------------------------------------------------]] +function FuBarPlugin:CheckWidth(force) + local frame = pluginToFrame[self] + if not frame then + return + end + local icon = frame.icon + local text = frame.text + if (not icon or not icon:IsShown()) and (not text or not text:IsShown()) then + return + end + + local db = getLazyDatabaseValue(self) + + if (db and not self:IsFuBarIconShown()) or not getPluginOption(self, 'iconPath', false) then + icon:SetWidth(epsilon) + end + local width + if not getPluginOption(self, 'hasNoText', false) then + text:SetHeight(0) + text:SetWidth(500) + width = text:GetStringWidth() + 1 + text:SetWidth(width) + text:SetHeight(text:GetHeight()) + end + local panel = pluginToPanel[self] + if getPluginOption(self, 'hasNoText', false) or not text:IsShown() then + frame:SetWidth(icon:GetWidth()) + if panel and panel:GetPluginSide(self) == "CENTER" then + panel:UpdateCenteredPosition() + end + elseif force or not frame.textWidth or frame.textWidth < width or frame.textWidth - 8 > width then + frame.textWidth = width + text:SetWidth(width) + if icon and icon:IsShown() then + frame:SetWidth(width + icon:GetWidth()) + else + frame:SetWidth(width) + end + if panel and panel:GetPluginSide(self) == "CENTER" then + panel:UpdateCenteredPosition() + end + end +end +precondition(FuBarPlugin, 'CheckWidth', function(self, force) + argCheck(force, 2, "boolean", "nil") +end) + +--[[--------------------------------------------------------------------------- +Notes: + Sets the text of the plugin. Should only be called from within :OnFuBarUpdateText() +Arguments: + string - text to set the plugin to. If not given, set to title. +Example: + myAddon.OnFuBarUpdateText = function(self) + self:SetFuBarText("Hello") + fend +-----------------------------------------------------------------------------]] +function FuBarPlugin:SetFuBarText(text) + local frame = pluginToFrame[self] + if not frame or not frame.text then + return + end + if text == "" then + if getPluginOption(self, 'iconPath', false) then + self:ShowFuBarIcon() + else + text = self:GetTitle() + end + end + if not self:IsFuBarTextColored() then + text = text:gsub("|c%x%x%x%x%x%x%x%x", ""):gsub("|r", "") + end + frame.text:SetText(text) + self:CheckWidth() +end +precondition(FuBarPlugin, 'SetFuBarText', function(self, text) + local frame = pluginToFrame[self] + if not frame or not frame.text then + return + end + if getPluginOption(self, 'hasNoText', false) then + error(("%s: Cannot set text if 'hasNoText' has been set."):format(self:GetTitle()), 3) + end + argCheck(text, 2, "string", "number") +end) + +--[[--------------------------------------------------------------------------- +Returns: + string - The current text of the plugin. +Example: + local text = self:GetFuBarText() +-----------------------------------------------------------------------------]] +function FuBarPlugin:GetFuBarText() + local frame = pluginToFrame[self] + if not frame or not frame.text then + error(("%s: Cannot get text without a text frame."):format(self:GetTitle()), 2) + end + if not getPluginOption(self, 'hasNoText', false) then + return frame.text:GetText() or "" + end +end + +--[[--------------------------------------------------------------------------- +Returns: + boolean - whether the icon for the plugin is showing. +Example: + local isIconShowing = self:IsFuBarIconShown() +-----------------------------------------------------------------------------]] +function FuBarPlugin:IsFuBarIconShown() + if not getPluginOption(self, 'iconPath', false) then + return false + elseif getPluginOption(self, 'hasNoText', false) then + return true + end + return not not getLazyDatabaseValueDefault(self, true, 'showIcon') +end + +--[[--------------------------------------------------------------------------- +Notes: + Toggles whether the icon for the plugin is showing. +Example: + self:ToggleFuBarIconShown() +-----------------------------------------------------------------------------]] +function FuBarPlugin:ToggleFuBarIconShown() + local frame = pluginToFrame[self] + local icon = frame and frame.icon + local text = frame and frame.text + if not icon then + error(("%s: Cannot toggle icon without an icon frame."):format(self:GetTitle()), 2) + elseif not text then + error(("%s: Cannot toggle icon without a text frame."):format(self:GetTitle()), 2) + elseif not getPluginOption(self, 'iconPath', false) then + error(("%s: Cannot show icon unless 'iconPath' is set."):format(self:GetTitle()), 2) + elseif getPluginOption(self, 'hasNoText', false) then + error(("%s: Cannot show icon if 'hasNoText' is set."):format(self:GetTitle()), 2) + elseif not getLazyDatabaseValue(self) then + error(("%s: Cannot hide icon if self.db is not available."):format(self:GetTitle()), 2) + end + local value = not self:IsFuBarIconShown() + setLazyDatabaseValue(self, value, 'showIcon') + if value then + if not self:IsFuBarTextShown() and text:IsShown() and text:GetText() == self:GetTitle() then + text:Hide() + text:SetText("") + end + icon:Show() + icon:SetWidth(pluginToFrame[self].icon:GetHeight()) + self:UpdateFuBarText() + else + if not text:IsShown() or not text:GetText() or text:GetText() == "" then + text:Show() + text:SetText(self:GetTitle()) + end + icon:Hide() + icon:SetWidth(epsilon) + end + self:CheckWidth(true) + return value +end + +--[[--------------------------------------------------------------------------- +Notes: + Shows the icon of the plugin if hidden. +Example: + self:ShowFuBarIcon() +-----------------------------------------------------------------------------]] +function FuBarPlugin:ShowFuBarIcon() + if not self:IsFuBarIconShown() then + self:ToggleFuBarIconShown() + end +end + +--[[--------------------------------------------------------------------------- +Notes: + Hides the icon of the plugin if shown. +Example: + self:HideFuBarIcon() +-----------------------------------------------------------------------------]] +function FuBarPlugin:HideFuBarIcon() + if self:IsFuBarIconShown() then + self:ToggleFuBarIconShown() + end +end + +--[[--------------------------------------------------------------------------- +Returns: + boolean - whether the text for the plugin is showing. +Example: + local isTextShowing = self:IsFuBarTextShown() +-----------------------------------------------------------------------------]] +function FuBarPlugin:IsFuBarTextShown() + if getPluginOption(self, 'hasNoText', false) then + return false + elseif not getPluginOption(self, 'iconPath', false) then + return true + end + return not not getLazyDatabaseValueDefault(self, true, 'showText') +end + +--[[--------------------------------------------------------------------------- +Notes: + Toggles whether the text for the plugin is showing. +Example: + self:ToggleFuBarTextShown() +-----------------------------------------------------------------------------]] +function FuBarPlugin:ToggleFuBarTextShown() + local frame = pluginToFrame[self] + local icon = frame and frame.icon + local text = frame and frame.text + if not icon then + error(("%s: Cannot toggle text without an icon frame."):format(self:GetTitle()), 2) + elseif not text then + error(("%s: Cannot toggle text without a text frame."):format(self:GetTitle()), 2) + elseif getPluginOption(self, 'cannotHideText', false) then + error(("%s: Cannot toggle text if 'cannotHideText' is set."):format(self:GetTitle()), 2) + elseif not getPluginOption(self, 'iconPath', false) then + error(("%s: Cannot toggle text unless 'iconPath' is set."):format(self:GetTitle()), 2) + elseif getPluginOption(self, 'hasNoText', false) then + error(("%s: Cannot toggle text if 'hasNoText' is set."):format(self:GetTitle()), 2) + elseif not getLazyDatabaseValue(self) then + error(("%s: Cannot toggle text if self.db is not available."):format(self:GetTitle()), 2) + end + local value = not self:IsFuBarTextShown() + setLazyDatabaseValue(self, value, 'showText') + if value then + text:Show() + self:UpdateFuBarText() + else + text:SetText("") + text:SetWidth(epsilon) + text:Hide() + self:ShowFuBarIcon() + end + self:CheckWidth(true) + return value +end + +--[[--------------------------------------------------------------------------- +Notes: + Shows the text of the plugin if hidden. +Example: + self:ShowFuBarText() +-----------------------------------------------------------------------------]] +function FuBarPlugin:ShowFuBarText() + if not self:IsFuBarTextShown() then + self:ToggleFuBarTextShown() + end +end + +--[[--------------------------------------------------------------------------- +Notes: + Hides the text of the plugin if shown. +Example: + self:HideFuBarText() +-----------------------------------------------------------------------------]] +function FuBarPlugin:HideFuBarText() + if self:IsFuBarTextShown() then + self:ToggleFuBarTextShown() + end +end + +--[[--------------------------------------------------------------------------- +Returns: + string - default position of the plugin. +Notes: + This is here for FuBar core to communicate properly. +Example: + local pos = self:GetDefaultPosition() +-----------------------------------------------------------------------------]] +function FuBarPlugin:GetDefaultPosition() + return getPluginOption(self, 'defaultPosition', "LEFT") +end + +--[[--------------------------------------------------------------------------- +Returns: + boolean - Whether the tooltip is detached. +Example: + local detached = self:IsFuBarTooltipDetached() +-----------------------------------------------------------------------------]] +function FuBarPlugin:IsFuBarTooltipDetached() + local tooltipType = getPluginOption(self, 'tooltipType', "GameTooltip") + if tooltipType ~= "Tablet-2.0" then + return + end + + RegisterTablet20(self) + return not Tablet20:IsAttached(pluginToFrame[self]) +end + +--[[--------------------------------------------------------------------------- +Notes: + Toggles whether the tooltip is detached. +Example: + self:ToggleFuBarTooltipDetached() +-----------------------------------------------------------------------------]] +function FuBarPlugin:ToggleFuBarTooltipDetached() + local tooltipType = getPluginOption(self, 'tooltipType', "GameTooltip") + if tooltipType ~= "Tablet-2.0" then + return + end + + RegisterTablet20(self) + if Tablet20:IsAttached(pluginToFrame[self]) then + Tablet20:Detach(pluginToFrame[self]) + else + Tablet20:Attach(pluginToFrame[self]) + end +end + +--[[--------------------------------------------------------------------------- +Notes: + * Detaches the tooltip from the plugin. + * This does nothing if already detached. +Example: + self:DetachFuBarTooltip() +-----------------------------------------------------------------------------]] +function FuBarPlugin:DetachFuBarTooltip() + if not self:IsFuBarTooltipDetached() then + self:ToggleFuBarTooltipDetached() + end +end + +--[[--------------------------------------------------------------------------- +Notes: + Reattaches the tooltip to the plugin. + This does nothing if already attached. +Example: + self:ReattachFuBarTooltip() +-----------------------------------------------------------------------------]] +function FuBarPlugin:ReattachFuBarTooltip() + if self:IsFuBarTooltipDetached() then + self:ToggleFuBarTooltipDetached() + end +end + +local function IsCorrectPanel(panel) + if type(panel) ~= "table" then + return false + elseif type(panel.AddPlugin) ~= "function" then + return false + elseif type(panel.RemovePlugin) ~= "function" then + return false + elseif type(panel.GetNumPlugins) ~= "function" then + return false + elseif type(panel:GetNumPlugins()) ~= "number" then + return false + elseif type(panel.GetPlugin) ~= "function" then + return false + elseif type(panel.HasPlugin) ~= "function" then + return false + elseif type(panel.GetPluginSide) ~= "function" then + return false + end + return true +end + +-- #NODOC +-- this is used internally by FuBar +function FuBarPlugin:SetPanel(panel) + pluginToPanel[self] = panel +end +precondition(FuBarPlugin, 'SetPanel', function(self, panel) + argCheck(panel, 2, "table", "nil") + if panel and not IsCorrectPanel(panel) then + error("Bad argument #2 to `SetPanel'. Panel does not have the correct API.", 3) + end +end) + +-- #NODOC +-- this is used internally by FuBar +function FuBarPlugin:SetFontSize(size) + if getPluginOption(self, 'userDefinedFrame', false) then + error(("%sYou must provide a :SetFontSize(size) method if you have 'userDefinedFrame' set."):format(self.name and self.name .. ": " or ""), 2) + end + if getPluginOption(self, 'iconPath', false) then + local frame = pluginToFrame[self] + local icon = frame and frame.icon + if not icon then + error(("%sno icon frame found."):format(self.name and self.name .. ": " or ""), 2) + end + icon:SetWidth(size + 3) + icon:SetHeight(size + 3) + end + if not getPluginOption(self, 'hasNoText', false) then + local frame = pluginToFrame[self] + local text = frame and frame.text + if not text then + error(("%sno text frame found."):format(self.name and self.name .. ": " or ""), 2) + end + local font, _, flags = text:GetFont() + text:SetFont(font, size, flags) + end + self:CheckWidth() +end + +local function IsLoadOnDemand(plugin) + return IsAddOnLoadOnDemand(folderNames[plugin] or "") +end + +-- #NODOC +-- this is used internally by FuBar. +function FuBarPlugin:IsDisabled() + return type(self.IsActive) == "function" and not self:IsActive() or false +end + +function FuBarPlugin:OnEmbed(target) + local folder = Rock.addonToFolder[target] + if not folder then + for i = 6, 3, -1 do + folder = debugstack(i, 1, 0):match([[\AddOns\(.*)\]]) + if folder then + break + end + end + end + folderNames[target] = folder +end + +local frame_OnClick, frame_OnDoubleClick, frame_OnMouseDown, frame_OnMouseUp, frame_OnReceiveDrag, frame_OnEnter, frame_OnLeave +--[[--------------------------------------------------------------------------- +Arguments: + [optional] string - name of the frame +Returns: + frame - a frame with the basic scripts to be considered a plugin frame. +Example: + MyPlugin.frame = MyPlugin:CreateBasicPluginFrame("FuBar_MyPluginFrame") +-----------------------------------------------------------------------------]] +function FuBarPlugin:CreateBasicPluginFrame(name) + local frame = CreateFrame("Button", name, UIParent) + frame:SetFrameStrata("HIGH") + frame:SetFrameLevel(7) + frame:EnableMouse(true) + frame:EnableMouseWheel(true) + frame:SetMovable(true) + frame:SetWidth(150) + frame:SetHeight(24) + frame:SetPoint("CENTER", UIParent, "CENTER") + frame.self = self + if not frame_OnEnter then + function frame_OnEnter(this) + local self = this.self + local tooltipType = getPluginOption(self, 'tooltipType', "GameTooltip") + if tooltipType == "GameTooltip" then + GameTooltip:SetOwner(self:IsFuBarMinimapAttached() and pluginToMinimapFrame[self] or pluginToFrame[self], "ANCHOR_CURSOR") + self:UpdateFuBarTooltip() + end + if type(self.OnFuBarEnter) == "function" then + self:OnFuBarEnter() + end + end + end + frame:SetScript("OnEnter", frame_OnEnter) + if not frame_OnLeave then + function frame_OnLeave(this) + local self = this.self + if type(self.OnFuBarLeave) == "function" then + self:OnFuBarLeave() + end + local tooltipType = getPluginOption(self, 'tooltipType', "GameTooltip") + if tooltipType == "GameTooltip" and GameTooltip:IsOwned(self:IsFuBarMinimapAttached() and pluginToMinimapFrame[self] or pluginToFrame[self]) then + GameTooltip:Hide() + end + end + end + frame:SetScript("OnLeave", frame_OnLeave) + if not frame_OnClick then + function frame_OnClick(this, button) + local self = this.self + if self:IsFuBarMinimapAttached() and this.dragged then return end + if type(self.OnFuBarClick) == "function" then + self:OnFuBarClick(button) + end + end + end + frame:SetScript("OnClick", frame_OnClick) + if not frame_OnDoubleClick then + function frame_OnDoubleClick(this, button) + local self = this.self + if type(self.OnFuBarDoubleClick) == "function" then + self:OnFuBarDoubleClick(button) + end + end + end + frame:SetScript("OnDoubleClick", frame_OnDoubleClick) + if not frame_OnMouseDown then + function frame_OnMouseDown(this, button) + local self = this.self + if button == "RightButton" and not IsShiftKeyDown() and not IsControlKeyDown() and not IsAltKeyDown() then + self:OpenMenu() + return + else + if type(self.OnFuBarMouseDown) == "function" then + self:OnFuBarMouseDown(button) + end + end + end + end + frame:SetScript("OnMouseDown", frame_OnMouseDown) + if not frame_OnMouseUp then + function frame_OnMouseUp(this, button) + local self = this.self + if type(self.OnFuBarMouseUp) == "function" then + self:OnFuBarMouseUp(button) + end + end + end + frame:SetScript("OnMouseUp", frame_OnMouseUp) + if not frame_OnReceiveDrag then + function frame_OnReceiveDrag(this) + local self = this.self + if (self:IsFuBarMinimapAttached() and not this.dragged) and type(self.OnReceiveDrag) == "function" then + self:OnFuBarReceiveDrag() + end + end + end + frame:SetScript("OnReceiveDrag", frame_OnReceiveDrag) + return frame +end + +local child_OnEnter, child_OnLeave, child_OnClick, child_OnDoubleClick, child_OnMouseDown, child_OnMouseUp, child_OnReceiveDrag +--[[--------------------------------------------------------------------------- +Arguments: + string - type of the frame, e.g. "Frame", "Button", etc. + [optional] string - name of the frame + [optional] frame - parent frame +Returns: + frame - a child frame that can be manipulated and used +Example: + local child = self:CreatePluginChildFrame("Frame", nil, self.frame) +-----------------------------------------------------------------------------]] +function FuBarPlugin:CreatePluginChildFrame(frameType, name, parent) + local child = CreateFrame(frameType, name, parent) + if parent then + child:SetFrameLevel(parent:GetFrameLevel() + 2) + end + child.self = self + if not child_OnEnter then + function child_OnEnter(this, ...) + local self = this.self + local frame = pluginToFrame[self] + if frame:GetScript("OnEnter") then + frame:GetScript("OnEnter")(frame, ...) + end + end + end + child:SetScript("OnEnter", child_OnEnter) + if not child_OnLeave then + function child_OnLeave(this, ...) + local self = this.self + local frame = pluginToFrame[self] + if frame:GetScript("OnLeave") then + frame:GetScript("OnLeave")(frame, ...) + end + end + end + child:SetScript("OnLeave", child_OnLeave) + if child:HasScript("OnClick") then + if not child_OnClick then + function child_OnClick(this, ...) + local self = this.self + local frame = pluginToFrame[self] + if frame:HasScript("OnClick") and frame:GetScript("OnClick") then + frame:GetScript("OnClick")(frame, ...) + end + end + end + child:SetScript("OnClick", child_OnClick) + end + if child:HasScript("OnDoubleClick") then + if not child_OnDoubleClick then + function child_OnDoubleClick(this, ...) + local self = this.self + local frame = pluginToFrame[self] + if frame:HasScript("OnDoubleClick") and frame:GetScript("OnDoubleClick") then + frame:GetScript("OnDoubleClick")(frame, ...) + end + end + end + child:SetScript("OnDoubleClick", child_OnDoubleClick) + end + if not child_OnMouseDown then + function child_OnMouseDown(this, ...) + local self = this.self + local frame = pluginToFrame[self] + if frame:HasScript("OnMouseDown") and frame:GetScript("OnMouseDown") then + frame:GetScript("OnMouseDown")(frame, ...) + end + end + end + child:SetScript("OnMouseDown", child_OnMouseDown) + if not child_OnMouseUp then + function child_OnMouseUp(this, ...) + local self = this.self + local frame = pluginToFrame[self] + if frame:HasScript("OnMouseUp") and frame:GetScript("OnMouseUp") then + frame:GetScript("OnMouseUp")(frame, ...) + end + end + end + child:SetScript("OnMouseUp", child_OnMouseUp) + if not child_OnReceiveDrag then + function child_OnReceiveDrag(this, ...) + local self = this.self + local frame = pluginToFrame[self] + if frame:HasScript("OnReceiveDrag") and frame:GetScript("OnReceiveDrag") then + frame:GetScript("OnReceiveDrag")(frame, ...) + end + end + end + child:SetScript("OnReceiveDrag", child_OnReceiveDrag) + return child +end +precondition(FuBarPlugin, 'CreatePluginChildFrame', function(self, frameType, name, parent) + if not pluginToFrame[self] then + error(("%sYou must have self.frame declared in order to add child frames."):format(self.name and self.name .. ": " or ""), 3) + end + argCheck(frameType, 2, "string") +end) + +--[[--------------------------------------------------------------------------- +Notes: + Opens the configuration menu associated with this plugin. +Example: + self:OpenMenu() +-----------------------------------------------------------------------------]] +function FuBarPlugin:OpenMenu(frame) + if not frame then + frame = self:IsFuBarMinimapAttached() and pluginToMinimapFrame[self] or pluginToFrame[self] + end + if not frame:IsVisible() then + frame = UIParent + end + local configType = getPluginOption(self, 'configType', "LibRockConfig-1.0") + if configType == "Dewdrop-2.0" then + if not frame or not self:GetFrame() or Dewdrop20:IsOpen(frame) then + Dewdrop20:Close() + return + end + local tooltipType = getPluginOption(self, 'tooltipType', "GameTooltip") + if tooltipType == "GameTooltip" then + if GameTooltip:IsOwned(frame) then + GameTooltip:Hide() + end + elseif tooltipType == "Custom" and type(self.CloseTooltip) == "function" then + self:CloseTooltip() + elseif tooltipType == "Tablet-2.0" and Tablet20 then + Tablet20:Close() + end + + if not Dewdrop20:IsRegistered(self:GetFrame()) then + if type(self.OnMenuRequest) == "table" and (not self.OnMenuRequest.handler or self.OnMenuRequest.handler == self) and self.OnMenuRequest.type == "group" then + Dewdrop20:InjectAceOptionsTable(self, self.OnMenuRequest) + if self.OnMenuRequest.args and CheckFuBar() and not getPluginOption(self, 'independentProfile', false) then + self.OnMenuRequest.args.profile = nil + if self.OnMenuRequest.extraArgs then + self.OnMenuRequest.extraArgs.profile = nil + end + end + end + Dewdrop20:Register(self:GetFrame(), + 'children', type(self.OnMenuRequest) == "table" and self.OnMenuRequest or function(level, value, valueN_1, valueN_2, valueN_3, valueN_4) + if level == 1 then + if not getPluginOption(self, 'hideMenuTitle', false) then + Dewdrop20:AddLine( + 'text', self:GetTitle(), + 'isTitle', true + ) + end + + if self.OnMenuRequest then + self:OnMenuRequest(level, value, false, valueN_1, valueN_2, valueN_3, valueN_4) + end + + if not getPluginOption(self, 'overrideMenu', false) then + if self.MenuSettings and not getPluginOption(self, 'hideMenuTitle', false) then + Dewdrop20:AddLine() + end + self:AddImpliedMenuOptions() + end + else + if not getPluginOption(self, 'overrideMenu', false) and self:AddImpliedMenuOptions() then + else + if self.OnMenuRequest then + self:OnMenuRequest(level, value, false, valueN_1, valueN_2, valueN_3, valueN_4) + end + end + end + if level == 1 then + Dewdrop20:AddLine( + 'text', CLOSE, + 'tooltipTitle', CLOSE, + 'tooltipText', CLOSE_DESC, + 'func', Dewdrop.Close, + 'arg1', Dewdrop + ) + end + end, + 'point', function(frame) + local x, y = frame:GetCenter() + local leftRight + if x < GetScreenWidth() / 2 then + leftRight = "LEFT" + else + leftRight = "RIGHT" + end + if y < GetScreenHeight() / 2 then + return "BOTTOM" .. leftRight, "TOP" .. leftRight + else + return "TOP" .. leftRight, "BOTTOM" .. leftRight + end + end, + 'dontHook', true + ) + end + if frame == self:GetFrame() then + Dewdrop20:Open(self:GetFrame()) + elseif frame ~= UIParent then + Dewdrop20:Open(frame, self:GetFrame()) + else + Dewdrop20:Open(frame, self:GetFrame(), 'cursorX', true, 'cursorY', true) + end + elseif configType == "LibRockConfig-1.0" then + local RockConfig = Rock("LibRockConfig-1.0", false, true) + if RockConfig then + RockConfig.OpenConfigMenu(self) + end + else + -- TODO: add more possibilities + end +end + +function FuBarPlugin.OnEmbedInitialize(FuBarPlugin, self) + if not self.frame then + local name = MAJOR_VERSION .. "_" .. self:GetTitle() .. "_" .. "Frame" + local frame = _G[name] + if not frame or not _G[name .. "Text"] or not _G[name .. "Icon"] then + frame = FuBarPlugin.CreateBasicPluginFrame(self, name) + + local icon = frame:CreateTexture(name .. "Icon", "ARTWORK") + frame.icon = icon + icon:SetWidth(16) + icon:SetHeight(16) + icon:SetPoint("LEFT", frame, "LEFT") + + local text = frame:CreateFontString(name .. "Text", "ARTWORK") + frame.text = text + text:SetWidth(134) + text:SetHeight(24) + text:SetPoint("LEFT", icon, "RIGHT", 0, 1) + text:SetFontObject(GameFontNormal) + end + pluginToFrame[self] = frame + else + pluginToFrame[self] = self.frame + if not pluginToOptions[self] then + pluginToOptions[self] = {} + end + pluginToOptions[self].userDefinedFrame = true + end + + local frame = pluginToFrame[self] + frame.plugin = self + frame:SetParent(UIParent) + frame:SetPoint("RIGHT", UIParent, "LEFT", -5, 0) + frame:Hide() + + local iconPath = getPluginOption(self, 'iconPath', false) + if iconPath then + self:SetFuBarIcon(iconPath) + end + + if CheckFuBar() then + FuBar:RegisterPlugin(self) + end +end + + +local CheckShow = function(self, panelId) + if not pluginToFrame[self]:IsShown() and (not pluginToMinimapFrame[self] or not pluginToMinimapFrame[self]:IsShown()) then + self:Show(panelId) + end +end + +local schedules = {} +local f = CreateFrame("Frame") +f:SetScript("OnUpdate", function(this) + for i,v in ipairs(schedules) do + local success, ret = pcall(unpack(v)) + if not success then + geterrorhandler()(ret) + end + schedules[i] = del(v) + end + f:Hide() +end) + +local recheckPlugins +local AceConsole +function FuBarPlugin.OnEmbedEnable(FuBarPlugin, self, first) + if not getPluginOption(self, 'userDefinedFrame', false) then + local icon = pluginToFrame[self].icon + if self:IsFuBarIconShown() then + icon:Show() + else + icon:Hide() + end + end + self:CheckWidth(true) + + if not getPluginOption(self, 'hideWithoutStandby', false) or (getLazyDatabaseValue(self) and not getLazyDatabaseValue(self, 'hidden')) then + if not first then + CheckShow(self, self.panelIdTmp) + else + schedules[#schedules+1] = newList(CheckShow, self, self.panelIdTmp) + f:Show() + end + end + + local tooltipType = getPluginOption(self, 'tooltipType', "GameTooltip") + if tooltipType == "Tablet-2.0" and not getPluginOption(self, 'cannotDetachTooltip', false) and getLazyDatabaseValue(self, 'detachedTooltip', 'detached') then + schedules[#schedules+1] = newList(self.DetachFuBarTooltip, self) + f:Show() + end + + if IsLoadOnDemand(self) and CheckFuBar() then + if not FuBar.db.profile.loadOnDemand then + FuBar.db.profile.loadOnDemand = {} + end + if not FuBar.db.profile.loadOnDemand[folderNames[self]] then + FuBar.db.profile.loadOnDemand[folderNames[self]] = {} + end + FuBar.db.profile.loadOnDemand[folderNames[self]].disabled = nil + end + --[[ + if CheckFuBar() and AceLibrary:HasInstance("AceConsole-2.0") then + if not recheckPlugins then + if not AceConsole then + AceConsole = AceLibrary("AceConsole-2.0") + end + recheckPlugins = function() + for k,v in pairs(AceConsole.registry) do + if type(v) == "table" and v.args and AceOO.inherits(v.handler, FuBarPlugin) and not v.handler.independentProfile then + v.args.profile = nil + end + end + end + end + FuBarPlugin:ScheduleEvent("FuBarPlugin-recheckPlugins", recheckPlugins, 0) + end + ]] +end + +function FuBarPlugin.OnEmbedDisable(FuBarPlugin, self) + self:Hide(false) + + if IsLoadOnDemand(self) and CheckFuBar() then + if not FuBar.db.profile.loadOnDemand then + FuBar.db.profile.loadOnDemand = {} + end + if not FuBar.db.profile.loadOnDemand[folderNames[self]] then + FuBar.db.profile.loadOnDemand[folderNames[self]] = {} + end + FuBar.db.profile.loadOnDemand[folderNames[self]].disabled = true + end +end + +function FuBarPlugin.OnEmbedProfileEnable(FuBarPlugin, self) + self:UpdateFuBarPlugin() + if getLazyDatabaseValue(self) then + if not getLazyDatabaseValue(self, 'detachedTooltip') then + setLazyDatabaseValue(self, {}, 'detachedTooltip') + end + local tooltipType = getPluginOption(self, 'tooltipType', "GameTooltip") + if tooltipType == "Tablet-2.0" and Tablet20 then + if Tablet20.registry[pluginToFrame[self]] then + Tablet20:UpdateDetachedData(pluginToFrame[self], getLazyDatabaseValue(self, 'detachedTooltip')) + else + RegisterTablet20(self) + end + end + if MinimapContainer:HasPlugin(self) then + MinimapContainer:ReadjustLocation(self) + end + end +end + +-- #NODOC +function FuBarPlugin.GetEmbedRockConfigOptions(FuBarPlugin, self) + return 'icon', { + type = 'boolean', + name = SHOW_FUBAR_ICON, + desc = SHOW_FUBAR_ICON_DESC, + set = "ToggleFuBarIconShown", + get = "IsFuBarIconShown", + hidden = function() + return not getPluginOption(self, 'iconPath', false) or getPluginOption(self, 'hasNoText', false) or self:IsDisabled() or self:IsFuBarMinimapAttached() or not getLazyDatabaseValue(self) + end, + order = -13.7, + handler = self, + }, 'text', { + type = 'boolean', + name = SHOW_FUBAR_TEXT, + desc = SHOW_FUBAR_TEXT_DESC, + set = "ToggleFuBarTextShown", + get = "IsFuBarTextShown", + hidden = function() + return getPluginOption(self, 'cannotHideText', false) or not getPluginOption(self, 'iconPath', false) or getPluginOption(self, 'hasNoText') or self:IsDisabled() or self:IsFuBarMinimapAttached() or not getLazyDatabaseValue(self) + end, + order = -13.6, + handler = self, + }, 'colorText', { + type = 'boolean', + name = SHOW_COLORED_FUBAR_TEXT, + desc = SHOW_COLORED_FUBAR_TEXT_DESC, + set = "ToggleFuBarTextColored", + get = "IsFuBarTextColored", + hidden = function() + return getPluginOption(self, 'userDefinedFrame', false) or getPluginOption(self, 'hasNoText', false) or getPluginOption(self, 'hasNoColor', false) or self:IsDisabled() or self:IsFuBarMinimapAttached() or not getLazyDatabaseValue(self) + end, + order = -13.5, + handler = self, + }, 'detachTooltip', { + type = 'boolean', + name = DETACH_FUBAR_TOOLTIP, + desc = DETACH_FUBAR_TOOLTIP_DESC, + get = "IsFuBarTooltipDetached", + set = "ToggleFuBarTooltipDetached", + hidden = function() + return not Tablet20 or getPluginOption(self, 'tooltipType', "GameTooltip") ~= "Tablet-2.0" or self:IsDisabled() + end, + order = -13.4, + handler = self, + }, 'lockTooltip', { + type = 'boolean', + name = LOCK_FUBAR_TOOLTIP, + desc = LOCK_FUBAR_TOOLTIP_DESC, + get = function() + return Tablet20:IsLocked(pluginToFrame[self]) + end, + set = function() + return Tablet20:ToggleLocked(pluginToFrame[self]) + end, + disabled = function() + return not self:IsFuBarTooltipDetached() + end, + hidden = function() + return not Tablet20 or getPluginOption(self, 'tooltipType', "GameTooltip") ~= "Tablet-2.0" or getPluginOption(self, 'cannotDetachTooltip', false) or self:IsDisabled() + end, + order = -13.3, + handler = self, + }, 'position', { + type = 'choice', + name = POSITION_ON_FUBAR, + desc = POSITION_ON_FUBAR_DESC, + choices = { + LEFT = POSITION_LEFT, + CENTER = POSITION_CENTER, + RIGHT = POSITION_RIGHT + }, + choiceSort = { + "LEFT", + "CENTER", + "RIGHT", + }, + get = function() + return self:GetPanel() and self:GetPanel():GetPluginSide(self) + end, + set = function(value) + if self:GetPanel() then + self:GetPanel():SetPluginSide(self, value) + end + end, + hidden = function() + return self:IsFuBarMinimapAttached() or self:IsDisabled() or not pluginToPanel[self] + end, + order = -13.2, + handler = self, + }, 'minimapAttach', { + type = 'boolean', + name = ATTACH_PLUGIN_TO_MINIMAP, + desc = ATTACH_PLUGIN_TO_MINIMAP_DESC, + get = "IsFuBarMinimapAttached", + set = "ToggleFuBarMinimapAttached", + hidden = function() + return (getPluginOption(self, 'cannotAttachToMinimap', false) and not self:IsFuBarMinimapAttached()) or not CheckFuBar() or self:IsDisabled() + end, + order = -13.1, + handler = self, + }, 'hide', { + type = 'boolean', + name = function() + if self:IsFuBarMinimapAttached() then + return HIDE_MINIMAP_BUTTON + else + return HIDE_FUBAR_PLUGIN + end + end, + desc = HIDE_FUBAR_PLUGIN_DESC, + get = function() + return not pluginToFrame[self]:IsShown() and (not pluginToMinimapFrame[self] or not pluginToMinimapFrame[self]:IsShown()) + end, + set = function(value) + if not value then + self:Show() + else + self:Hide() + end + end, + hidden = function() + return not getPluginOption(self, 'hideWithoutStandby', false) or self:IsDisabled() + end, + order = -13, + handler = self, + } +end + +local plugins = MinimapContainer.plugins or {} +for k in pairs(MinimapContainer) do + MinimapContainer[k] = nil +end +MinimapContainer.plugins = plugins + +local minimap_OnMouseDown, minimap_OnMouseUp +function MinimapContainer:AddPlugin(plugin) + if CheckFuBar() and FuBar:IsChangingProfile() then + return + end + if pluginToPanel[plugin] then + pluginToPanel[plugin]:RemovePlugin(plugin) + end + pluginToPanel[plugin] = self + if not pluginToMinimapFrame[plugin] then + local frame = CreateFrame("Button", pluginToFrame[plugin]:GetName() .. "MinimapButton", Minimap) + pluginToMinimapFrame[plugin] = frame + plugin.minimapFrame = frame + frame.plugin = plugin + frame:SetWidth(31) + frame:SetHeight(31) + frame:SetFrameStrata("BACKGROUND") + frame:SetFrameLevel(4) + frame:SetHighlightTexture("Interface\\Minimap\\UI-Minimap-ZoomButton-Highlight") + local icon = frame:CreateTexture(frame:GetName() .. "Icon", "BACKGROUND") + plugin.minimapIcon = icon + local path = plugin:GetFuBarIcon() or (pluginToFrame[plugin].icon and pluginToFrame[plugin].icon:GetTexture()) or "Interface\\Icons\\INV_Misc_QuestionMark" + icon:SetTexture(path) + if path:sub(1, 16) == "Interface\\Icons\\" then + icon:SetTexCoord(0.07, 0.93, 0.07, 0.93) + else + icon:SetTexCoord(0, 1, 0, 1) + end + icon:SetWidth(20) + icon:SetHeight(20) + icon:SetPoint("TOPLEFT", frame, "TOPLEFT", 7, -5) + local overlay = frame:CreateTexture(frame:GetName() .. "Overlay","OVERLAY") + overlay:SetTexture("Interface\\Minimap\\MiniMap-TrackingBorder") + overlay:SetWidth(53) + overlay:SetHeight(53) + overlay:SetPoint("TOPLEFT",frame,"TOPLEFT") + frame:EnableMouse(true) + frame:RegisterForClicks("LeftButtonUp") + + frame.self = plugin + if not frame_OnEnter then + function frame_OnEnter(this) + if type(this.self.OnFuBarEnter) == "function" then + this.self:OnFuBarEnter() + end + end + end + frame:SetScript("OnEnter", frame_OnEnter) + if not frame_OnLeave then + function frame_OnLeave(this) + if type(this.self.OnFuBarLeave) == "function" then + this.self:OnFuBarLeave() + end + end + end + frame:SetScript("OnLeave", frame_OnLeave) + if not frame_OnClick then + function frame_OnClick(this, arg1) + if this.self:IsMinimapAttached() and this.dragged then return end + if type(this.self.OnFuBarClick) == "function" then + this.self:OnFuBarClick(arg1) + end + end + end + frame:SetScript("OnClick", frame_OnClick) + if not frame_OnDoubleClick then + function frame_OnDoubleClick(this, arg1) + if type(this.self.OnFuBarDoubleClick) == "function" then + this.self:OnFuBarDoubleClick(arg1) + end + end + end + frame:SetScript("OnDoubleClick", frame_OnDoubleClick) + if not frame_OnReceiveDrag then + function frame_OnReceiveDrag(this) + if this.self:IsMinimapAttached() and this.dragged then return end + if type(this.self.OnFuBarReceiveDrag) == "function" then + this.self:OnFuBarReceiveDrag() + end + end + end + frame:SetScript("OnReceiveDrag", frame_OnReceiveDrag) + if not minimap_OnMouseDown then + function minimap_OnMouseDown(this, arg1) + this.dragged = false + if arg1 == "LeftButton" and not IsShiftKeyDown() and not IsControlKeyDown() and not IsAltKeyDown() then + HideDropDownMenu(1) + if type(this.self.OnFuBarMouseDown) == "function" then + this.self:OnFuBarMouseDown(arg1) + end + elseif arg1 == "RightButton" and not IsShiftKeyDown() and not IsControlKeyDown() and not IsAltKeyDown() then + this.self:OpenMenu(this) + else + HideDropDownMenu(1) + if type(this.self.OnFuBarMouseDown) == "function" then + this.self:OnFuBarMouseDown(arg1) + end + end + if this.self.OnFuBarClick or this.self.OnFuBarMouseDown or this.self.OnFuBarMouseUp or this.self.OnFuBarDoubleClick then + if this.self.minimapIcon:GetTexture():sub(1, 16) == "Interface\\Icons\\" then + this.self.minimapIcon:SetTexCoord(0.14, 0.86, 0.14, 0.86) + else + this.self.minimapIcon:SetTexCoord(0.1, 0.9, 0.1, 0.9) + end + end + end + end + frame:SetScript("OnMouseDown", minimap_OnMouseDown) + if not minimap_OnMouseUp then + function minimap_OnMouseUp(this, arg1) + if not this.dragged and type(this.self.OnFuBarMouseUp) == "function" then + this.self:OnFuBarMouseUp(arg1) + end + if this.self.minimapIcon:GetTexture():sub(1, 16) == "Interface\\Icons\\" then + this.self.minimapIcon:SetTexCoord(0.05, 0.95, 0.05, 0.95) + else + this.self.minimapIcon:SetTexCoord(0, 1, 0, 1) + end + end + end + frame:SetScript("OnMouseUp", minimap_OnMouseUp) + frame:RegisterForDrag("LeftButton") + frame:SetScript("OnDragStart", self.OnDragStart) + frame:SetScript("OnDragStop", self.OnDragStop) + + if getPluginOption(plugin, 'tooltipType', "GameTooltip") == "Tablet-2.0" then + -- Note that we have to do this after :SetScript("OnEnter"), etc, + -- so that Tablet-2.0 can override it properly. + RegisterTablet20(plugin) + Tablet20:Register(frame, pluginToFrame[plugin]) + end + end + pluginToFrame[plugin]:Hide() + pluginToMinimapFrame[plugin]:Show() + self:ReadjustLocation(plugin) + table.insert(self.plugins, plugin) + local exists = false + return true +end + +function MinimapContainer:RemovePlugin(index) + if CheckFuBar() and FuBar:IsChangingProfile() then + return + end + if type(index) == "table" then + index = self:IndexOfPlugin(index) + if not index then + return + end + end + local t = self.plugins + local plugin = t[index] + assert(pluginToPanel[plugin] == self, "Plugin has improper panel field") + plugin:SetPanel(nil) + table.remove(t, index) + return true +end + +function MinimapContainer:ReadjustLocation(plugin) + local frame = pluginToMinimapFrame[plugin] + if plugin.db and plugin.db.profile.minimapPositionWild then + frame:SetPoint("CENTER", UIParent, "BOTTOMLEFT", plugin.db.profile.minimapPositionX, plugin.db.profile.minimapPositionY) + elseif not plugin.db and plugin.minimapPositionWild then + frame:SetPoint("CENTER", UIParent, "BOTTOMLEFT", plugin.minimapPositionX, plugin.minimapPositionY) + else + local position + if plugin.db then + position = plugin.db.profile.minimapPosition or getPluginOption(plugin, 'defaultMinimapPosition', nil) or math.random(1, 360) + else + position = plugin.minimapPosition or getPluginOption(plugin, 'defaultMinimapPosition', nil) or math.random(1, 360) + end + local angle = math.rad(position or 0) + local x,y + local minimapShape = GetMinimapShape and GetMinimapShape() or "ROUND" + local cos = math.cos(angle) + local sin = math.sin(angle) + + local round = true + if minimapShape == "ROUND" then + -- do nothing + elseif minimapShape == "SQUARE" then + round = false + elseif minimapShape == "CORNER-TOPRIGHT" then + if cos < 0 or sin < 0 then + round = false + end + elseif minimapShape == "CORNER-TOPLEFT" then + if cos > 0 or sin < 0 then + round = false + end + elseif minimapShape == "CORNER-BOTTOMRIGHT" then + if cos < 0 or sin > 0 then + round = false + end + elseif minimapShape == "CORNER-BOTTOMLEFT" then + if cos > 0 or sin > 0 then + round = false + end + elseif minimapShape == "SIDE-LEFT" then + if cos > 0 then + round = false + end + elseif minimapShape == "SIDE-RIGHT" then + if cos < 0 then + round = false + end + elseif minimapShape == "SIDE-TOP" then + if sin < 0 then + round = false + end + elseif minimapShape == "SIDE-BOTTOM" then + if sin > 0 then + round = false + end + elseif minimapShape == "TRICORNER-TOPRIGHT" then + if cos < 0 and sin < 0 then + round = false + end + elseif minimapShape == "TRICORNER-TOPLEFT" then + if cos > 0 and sin < 0 then + round = false + end + elseif minimapShape == "TRICORNER-BOTTOMRIGHT" then + if cos < 0 and sin > 0 then + round = false + end + elseif minimapShape == "TRICORNER-BOTTOMLEFT" then + if cos > 0 and sin > 0 then + round = false + end + end + + if round then + x = cos * 80 + y = sin * 80 + else + x = 80 * 2^0.5 * cos + y = 80 * 2^0.5 * sin + if x < -80 then + x = -80 + elseif x > 80 then + x = 80 + end + if y < -80 then + y = -80 + elseif y > 80 then + y = 80 + end + end + frame:SetPoint("CENTER", Minimap, "CENTER", x, y) + end +end + +function MinimapContainer:GetPlugin(index) + return self.plugins[index] +end + +function MinimapContainer:GetNumPlugins() + return #self.plugins +end + +function MinimapContainer:IndexOfPlugin(plugin) + for i,p in ipairs(self.plugins) do + if p == plugin then + return i, "MINIMAP" + end + end +end + +function MinimapContainer:HasPlugin(plugin) + return self:IndexOfPlugin(plugin) ~= nil +end + +function MinimapContainer:GetPluginSide(plugin) + local index = self:IndexOfPlugin(plugin) + assert(index, "Plugin not in panel") + return "MINIMAP" +end + +function MinimapContainer.OnDragStart(this) + this.dragged = true + this:LockHighlight() + this:SetScript("OnUpdate", MinimapContainer.OnUpdate) + if this.self.minimapIcon:GetTexture():sub(1, 16) == "Interface\\Icons\\" then + this.self.minimapIcon:SetTexCoord(0.05, 0.95, 0.05, 0.95) + else + this.self.minimapIcon:SetTexCoord(0, 1, 0, 1) + end +end + +function MinimapContainer.OnDragStop(this) + this:SetScript("OnUpdate", nil) + this:UnlockHighlight() +end + +function MinimapContainer.OnUpdate(this, elapsed) + if not IsAltKeyDown() then + local mx, my = Minimap:GetCenter() + local px, py = GetCursorPosition() + local scale = UIParent:GetEffectiveScale() + px, py = px / scale, py / scale + local position = math.deg(math.atan2(py - my, px - mx)) + if position <= 0 then + position = position + 360 + elseif position > 360 then + position = position - 360 + end + if this.self.db then + this.self.db.profile.minimapPosition = position + this.self.db.profile.minimapPositionX = nil + this.self.db.profile.minimapPositionY = nil + this.self.db.profile.minimapPositionWild = nil + else + this.self.minimapPosition = position + this.self.minimapPositionX = nil + this.self.minimapPositionY = nil + this.self.minimapPositionWild = nil + end + else + local px, py = GetCursorPosition() + local scale = UIParent:GetEffectiveScale() + px, py = px / scale, py / scale + if this.self.db then + this.self.db.profile.minimapPositionX = px + this.self.db.profile.minimapPositionY = py + this.self.db.profile.minimapPosition = nil + this.self.db.profile.minimapPositionWild = true + else + this.self.minimapPositionX = px + this.self.minimapPositionY = py + this.self.minimapPosition = nil + this.self.minimapPositionWild = true + end + end + MinimapContainer:ReadjustLocation(this.self) +end + +FuBarPlugin:SetExportedMethods( + "SetFuBarOption", + "GetTitle", + "GetName", + "GetCategory", + "SetFontSize", + "GetFrame", + "Show", + "Hide", + "GetPanel", + "IsFuBarTextColored", + "ToggleFuBarTextColored", + "IsFuBarMinimapAttached", + "ToggleFuBarMinimapAttached", + "UpdateFuBarPlugin", + "UpdateFuBarText", + "UpdateFuBarTooltip", + "SetFuBarIcon", + "GetFuBarIcon", + "CheckWidth", + "SetFuBarText", + "GetFuBarText", + "IsFuBarIconShown", + "ToggleFuBarIconShown", + "ShowFuBarIcon", + "HideFuBarIcon", + "IsFuBarTextShown", + "ToggleFuBarTextShown", + "ShowFuBarText", + "HideFuBarText", + "IsFuBarTooltipDetached", + "ToggleFuBarTooltipDetached", + "DetachFuBarTooltip", + "ReattachFuBarTooltip", + "GetDefaultPosition", + "SetPanel", + "IsDisabled", + "CreateBasicPluginFrame", + "CreatePluginChildFrame", + "OpenMenu" +) + +Rock:FinalizeLibrary(MAJOR_VERSION)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/modules/FuBar_ReActionFu/lib/LibFuBarPlugin-3.0/LibFuBarPlugin-3.0.toc Wed Apr 02 23:31:13 2008 +0000 @@ -0,0 +1,15 @@ +## Interface: 20300 +## LoadOnDemand: 1 +## Title: LibFuBarPlugin-3.0 +## Notes: A library to provide a means create a FuBar-compatible plugin. +## Notes-zhTW: 一個提供支援FuBar所需功能的插件。 +## Notes-esES: Una biblioteca para crear plugins compatibles con Fubar. +## Author: ckknight +## eMail: ckknight@gmail.com +## Version: 2.0 $Revision: 44269 $ +## X-Category: Library +## Dependencies: LibRock-1.0 +## OptionalDeps: FuBar +## X-License: LGPL v2.1 + +lib.xml \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/modules/FuBar_ReActionFu/lib/LibFuBarPlugin-3.0/lib.xml Wed Apr 02 23:31:13 2008 +0000 @@ -0,0 +1,4 @@ +<Ui xmlns="http://www.blizzard.com/wow/ui/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.blizzard.com/wow/ui/ +..\FrameXML\UI.xsd"> + <Script file="LibFuBarPlugin-3.0.lua" /> +</Ui> \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/modules/FuBar_ReActionFu/lib/LibRock-1.0/Changelog-LibRock-1.0-r63317.txt Wed Apr 02 23:31:13 2008 +0000 @@ -0,0 +1,44 @@ +------------------------------------------------------------------------ +r63317 | ckknight | 2008-03-01 12:57:51 -0500 (Sat, 01 Mar 2008) | 2 lines +Changed paths: + M /trunk/LibRock-1.0/LibRock-1.0.lua + +LibRock-1.0 - support LibStub-based mixins that have rawget(library, "Embed") which is a function on em. +ROCK-136 +------------------------------------------------------------------------ +r61418 | pb_ee1 | 2008-02-15 05:25:45 -0500 (Fri, 15 Feb 2008) | 2 lines +Changed paths: + M /trunk/LibRock-1.0 + +LibRock-1.0: +- Adding tsvn:logtemplate, i said -_-' +------------------------------------------------------------------------ +r61417 | pb_ee1 | 2008-02-15 05:24:03 -0500 (Fri, 15 Feb 2008) | 3 lines +Changed paths: + M /trunk/LibRock-1.0/LibRock-1.0.lua + +LibRock-1.0: +- coller -> collez +- Adding tsvn:logtemplate +------------------------------------------------------------------------ +r61415 | pettigrow | 2008-02-15 05:13:24 -0500 (Fri, 15 Feb 2008) | 1 line +Changed paths: + M /trunk/LibRock-1.0/LibRock-1.0.lua + +LibRock-1.0: frFR Update +------------------------------------------------------------------------ +r61118 | ckknight | 2008-02-13 01:32:54 -0500 (Wed, 13 Feb 2008) | 1 line +Changed paths: + M /trunk/LibRock-1.0/LibRock-1.0.lua + M /trunk/LibRockComm-1.0/LibRockComm-1.0.lua + M /trunk/LibRockConfig-1.0/LibRockConfig-1.0.lua + M /trunk/LibRockConsole-1.0/LibRockConsole-1.0.lua + M /trunk/LibRockDB-1.0/LibRockDB-1.0.lua + M /trunk/LibRockEvent-1.0/LibRockEvent-1.0.lua + M /trunk/LibRockHook-1.0/LibRockHook-1.0.lua + M /trunk/LibRockLocale-1.0/LibRockLocale-1.0.lua + M /trunk/LibRockModuleCore-1.0/LibRockModuleCore-1.0.lua + M /trunk/LibRockTimer-1.0/LibRockTimer-1.0.lua + +.LibRock-1.0 - don't use negative reversion numbers because the LibStub patch for supporting that was never accepted. +------------------------------------------------------------------------
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/modules/FuBar_ReActionFu/lib/LibRock-1.0/LibRock-1.0.lua Wed Apr 02 23:31:13 2008 +0000 @@ -0,0 +1,2968 @@ +--[[ +Name: LibRock-1.0 +Revision: $Rev: 63317 $ +Developed by: ckknight (ckknight@gmail.com) +Website: http://www.wowace.com/ +Description: Library to allow for library and addon creation and easy table recycling functions. +License: LGPL v2.1 +]] + +local MAJOR_VERSION = "LibRock-1.0" +local MINOR_VERSION = tonumber(("$Revision: 63317 $"):match("(%d+)")) - 60000 + +local _G = _G +local GetLocale = _G.GetLocale +local CATEGORIES +if GetLocale() == "deDE" then + CATEGORIES = { + ["Action Bars"] = "Aktionsleisten", + ["Auction"] = "Auktion", + ["Audio"] = "Audio", + ["Battlegrounds/PvP"] = "Schlachtfeld/PvP", + ["Buffs"] = "Stärkungszauber", + ["Chat/Communication"] = "Chat/Kommunikation", + ["Druid"] = "Druide", + ["Hunter"] = "Jäger", + ["Mage"] = "Magier", + ["Paladin"] = "Paladin", + ["Priest"] = "Priester", + ["Rogue"] = "Schurke", + ["Shaman"] = "Schamane", + ["Warlock"] = "Hexenmeister", + ["Warrior"] = "Krieger", + ["Healer"] = "Heiler", + ["Tank"] = "Tank", + ["Caster"] = "Zauberer", + ["Combat"] = "Kampf", + ["Compilations"] = "Zusammenstellungen", + ["Data Export"] = "Datenexport", + ["Development Tools"] = "Entwicklungstools", + ["Guild"] = "Gilde", + ["Frame Modification"] = "Frameveränderungen", + ["Interface Enhancements"] = "Interfaceverbesserungen", + ["Inventory"] = "Inventar", + ["Library"] = "Bibliotheken", + ["Map"] = "Karte", + ["Mail"] = "Post", + ["Miscellaneous"] = "Diverses", + ["Quest"] = "Quest", + ["Raid"] = "Schlachtzug", + ["Tradeskill"] = "Beruf", + ["UnitFrame"] = "Einheiten-Fenster", + } +elseif GetLocale() == "frFR" then + CATEGORIES = { + ["Action Bars"] = "Barres d'action", + ["Auction"] = "Hôtel des ventes", + ["Audio"] = "Audio", + ["Battlegrounds/PvP"] = "Champs de bataille/JcJ", + ["Buffs"] = "Buffs", + ["Chat/Communication"] = "Chat/Communication", + ["Druid"] = "Druide", + ["Hunter"] = "Chasseur", + ["Mage"] = "Mage", + ["Paladin"] = "Paladin", + ["Priest"] = "Prêtre", + ["Rogue"] = "Voleur", + ["Shaman"] = "Chaman", + ["Warlock"] = "Démoniste", + ["Warrior"] = "Guerrier", + ["Healer"] = "Soigneur", + ["Tank"] = "Tank", + ["Caster"] = "Casteur", + ["Combat"] = "Combat", + ["Compilations"] = "Compilations", + ["Data Export"] = "Exportation de données", + ["Development Tools"] = "Outils de développement", + ["Guild"] = "Guilde", + ["Frame Modification"] = "Modification des fenêtres", + ["Interface Enhancements"] = "Améliorations de l'interface", + ["Inventory"] = "Inventaire", + ["Library"] = "Bibliothèques", + ["Map"] = "Carte", + ["Mail"] = "Courrier", + ["Miscellaneous"] = "Divers", + ["Quest"] = "Quêtes", + ["Raid"] = "Raid", + ["Tradeskill"] = "Métiers", + ["UnitFrame"] = "Fenêtres d'unité", + } +elseif GetLocale() == "koKR" then + CATEGORIES = { + ["Action Bars"] = "액션바", + ["Auction"] = "경매", + ["Audio"] = "음향", + ["Battlegrounds/PvP"] = "전장/PvP", + ["Buffs"] = "버프", + ["Chat/Communication"] = "대화/의사소통", + ["Druid"] = "드루이드", + ["Hunter"] = "사냥꾼", + ["Mage"] = "마법사", + ["Paladin"] = "성기사", + ["Priest"] = "사제", + ["Rogue"] = "도적", + ["Shaman"] = "주술사", + ["Warlock"] = "흑마법사", + ["Warrior"] = "전사", + ["Healer"] = "힐러", + ["Tank"] = "탱커", + ["Caster"] = "캐스터", + ["Combat"] = "전투", + ["Compilations"] = "복합", + ["Data Export"] = "자료 출력", + ["Development Tools"] = "개발 도구", + ["Guild"] = "길드", + ["Frame Modification"] = "구조 변경", + ["Interface Enhancements"] = "인터페이스 강화", + ["Inventory"] = "인벤토리", + ["Library"] = "라이브러리", + ["Map"] = "지도", + ["Mail"] = "우편", + ["Miscellaneous"] = "기타", + ["Quest"] = "퀘스트", + ["Raid"] = "공격대", + ["Tradeskill"] = "전문기술", + ["UnitFrame"] = "유닛 프레임", + } +elseif GetLocale() == "zhTW" then + CATEGORIES = { + ["Action Bars"] = "動作列", + ["Auction"] = "拍賣", + ["Audio"] = "音效", + ["Battlegrounds/PvP"] = "戰場/PvP", + ["Buffs"] = "增益", + ["Chat/Communication"] = "聊天/通訊", + ["Druid"] = "德魯伊", + ["Hunter"] = "獵人", + ["Mage"] = "法師", + ["Paladin"] = "聖騎士", + ["Priest"] = "牧師", + ["Rogue"] = "盜賊", + ["Shaman"] = "薩滿", + ["Warlock"] = "術士", + ["Warrior"] = "戰士", + ["Healer"] = "治療者", + ["Tank"] = "坦克", + ["Caster"] = "施法者", + ["Combat"] = "戰鬥", + ["Compilations"] = "整合", + ["Data Export"] = "資料匯出", + ["Development Tools"] = "開發工具", + ["Guild"] = "公會", + ["Frame Modification"] = "框架修改", + ["Interface Enhancements"] = "介面增強", + ["Inventory"] = "庫存", + ["Library"] = "程式庫", + ["Map"] = "地圖", + ["Mail"] = "郵件", + ["Miscellaneous"] = "雜項", + ["Quest"] = "任務", + ["Raid"] = "團隊", + ["Tradeskill"] = "交易技能", + ["UnitFrame"] = "頭像框架", + } +elseif GetLocale() == "zhCN" then + CATEGORIES = { + ["Action Bars"] = "动作条", + ["Auction"] = "拍卖", + ["Audio"] = "音频", + ["Battlegrounds/PvP"] = "战场/PvP", + ["Buffs"] = "增益魔法", + ["Chat/Communication"] = "聊天/交流", + ["Druid"] = "德鲁伊", + ["Hunter"] = "猎人", + ["Mage"] = "法师", + ["Paladin"] = "圣骑士", + ["Priest"] = "牧师", + ["Rogue"] = "潜行者", + ["Shaman"] = "萨满祭司", + ["Warlock"] = "术士", + ["Warrior"] = "战士", + ["Healer"] = "治疗", + ["Tank"] = "坦克", + ["Caster"] = "远程输出", + ["Combat"] = "战斗", + ["Compilations"] = "编译", + ["Data Export"] = "数据导出", + ["Development Tools"] = "开发工具", + ["Guild"] = "公会", + ["Frame Modification"] = "框架修改", + ["Interface Enhancements"] = "界面增强", + ["Inventory"] = "背包", + ["Library"] = "库", + ["Map"] = "地图", + ["Mail"] = "邮件", + ["Miscellaneous"] = "杂项", + ["Quest"] = "任务", + ["Raid"] = "团队", + ["Tradeskill"] = "商业技能", + ["UnitFrame"] = "头像框架", + } +elseif GetLocale() == "esES" then + CATEGORIES = { + ["Action Bars"] = "Barras de Acción", + ["Auction"] = "Subasta", + ["Audio"] = "Audio", + ["Battlegrounds/PvP"] = "Campos de Batalla/JcJ", + ["Buffs"] = "Buffs", + ["Chat/Communication"] = "Chat/Comunicación", + ["Druid"] = "Druida", + ["Hunter"] = "Cazador", + ["Mage"] = "Mago", + ["Paladin"] = "Paladín", + ["Priest"] = "Sacerdote", + ["Rogue"] = "Pícaro", + ["Shaman"] = "Chamán", + ["Warlock"] = "Brujo", + ["Warrior"] = "Guerrero", + ["Healer"] = "Sanador", + ["Tank"] = "Tanque", + ["Caster"] = "Conjurador", + ["Combat"] = "Combate", + ["Compilations"] = "Compilaciones", + ["Data Export"] = "Exportar Datos", + ["Development Tools"] = "Herramientas de Desarrollo", + ["Guild"] = "Hermandad", + ["Frame Modification"] = "Modificación de Marcos", + ["Interface Enhancements"] = "Mejoras de la Interfaz", + ["Inventory"] = "Inventario", + ["Library"] = "Biblioteca", + ["Map"] = "Mapa", + ["Mail"] = "Correo", + ["Miscellaneous"] = "Misceláneo", + ["Quest"] = "Misión", + ["Raid"] = "Banda", + ["Tradeskill"] = "Habilidad de Comercio", + ["UnitFrame"] = "Marco de Unidades", + } +else -- enUS + CATEGORIES = { + ["Action Bars"] = "Action Bars", + ["Auction"] = "Auction", + ["Audio"] = "Audio", + ["Battlegrounds/PvP"] = "Battlegrounds/PvP", + ["Buffs"] = "Buffs", + ["Chat/Communication"] = "Chat/Communication", + ["Druid"] = "Druid", + ["Hunter"] = "Hunter", + ["Mage"] = "Mage", + ["Paladin"] = "Paladin", + ["Priest"] = "Priest", + ["Rogue"] = "Rogue", + ["Shaman"] = "Shaman", + ["Warlock"] = "Warlock", + ["Warrior"] = "Warrior", + ["Healer"] = "Healer", + ["Tank"] = "Tank", + ["Caster"] = "Caster", + ["Combat"] = "Combat", + ["Compilations"] = "Compilations", + ["Data Export"] = "Data Export", + ["Development Tools"] = "Development Tools", + ["Guild"] = "Guild", + ["Frame Modification"] = "Frame Modification", + ["Interface Enhancements"] = "Interface Enhancements", + ["Inventory"] = "Inventory", + ["Library"] = "Library", + ["Map"] = "Map", + ["Mail"] = "Mail", + ["Miscellaneous"] = "Miscellaneous", + ["Quest"] = "Quest", + ["Raid"] = "Raid", + ["Tradeskill"] = "Tradeskill", + ["UnitFrame"] = "UnitFrame", + } +end + +local select = _G.select +local tostring = _G.tostring +local pairs = _G.pairs +local ipairs = _G.ipairs +local error = _G.error +local setmetatable = _G.setmetatable +local getmetatable = _G.getmetatable +local type = _G.type +local pcall = _G.pcall +local next = _G.next +local tonumber = _G.tonumber +local strmatch = _G.strmatch +local table_remove = _G.table.remove +local debugstack = _G.debugstack +local LoadAddOn = _G.LoadAddOn +local GetAddOnInfo = _G.GetAddOnInfo +local GetAddOnMetadata = _G.GetAddOnMetadata +local GetNumAddOns = _G.GetNumAddOns +local DisableAddOn = _G.DisableAddOn +local EnableAddOn = _G.EnableAddOn +local IsAddOnLoadOnDemand = _G.IsAddOnLoadOnDemand +local IsLoggedIn = _G.IsLoggedIn +local geterrorhandler = _G.geterrorhandler +local assert = _G.assert +local collectgarbage = _G.collectgarbage +local table_sort = _G.table.sort +local table_concat = _G.table.concat + +-- #AUTODOC_NAMESPACE Rock + + +local LibStub = _G.LibStub + +local Rock = LibStub:GetLibrary(MAJOR_VERSION, true) or _G.Rock +local oldRock +if not Rock then + Rock = LibStub:NewLibrary(MAJOR_VERSION, MINOR_VERSION) + if not Rock then + return + end + Rock.name = MAJOR_VERSION +else + Rock, oldRock = Rock:NewLibrary(MAJOR_VERSION, MINOR_VERSION) + if not Rock then + return + end +end +_G.Rock = Rock + +local L = setmetatable({}, {__index=function(self,key) self[key] = key; return key end}) +if GetLocale() == "zhCN" then + L["Advanced options"] = "高级选项" + L["Advanced options for developers and power users."] = "开发者与高级用户的高级选项" + L["Unit tests"] = "框体测试" + L["Enable unit tests to be run. This is for developers only.\n\nYou must ReloadUI for changes to take effect."] = "开启框体测试,仅供开发者使用。\n\n需要重载用户界面。" + L["Contracts"] = "侦错协定" + L["Enable contracts to be run. This is for developers and anyone wanting to file a bug. Do not file a bug unless contracts are enabled. This will slightly slow down your addons if enabled."] = "启用侦错协定,这是给插件作者用来通报错误所使用。" + L["Reload UI"] = "重载UI" + L["Reload the User Interface for some changes to take effect."] = "部分功能更改需要重载用户界面才会生效。" + L["Reload"] = "重载" + L["Give donation"] = "捐赠" + L["Donate"] = "捐赠" + L["Give a much-needed donation to the author of this addon."] = "给插件作者捐赠支持插件开发。" + L["File issue"] = "通报错误" + L["Report"] = "报告" + L["File a bug or request a new feature or an improvement to this addon."] = "发送错误报告或请求新功能及要改进的部分。" + L["Press Ctrl-C to copy, then Alt-Tab out of the game, open your favorite web browser, and paste the link into the address bar."] = "Ctrl-C复制网址,Alt-Tab切换到桌面,打开浏览器,在地址栏贴上网址。" + L["Press Cmd-C to copy, then Cmd-Tab out of the game, open your favorite web browser, and paste the link into the address bar."] = "Cmd-C复制网址,Cmd-Tab切换到电脑桌面,打开浏览器,在地址栏贴上网址。" + L["Enabled"] = "开启" + L["Enable or disable this addon."] = "启用这个插件。" + +elseif GetLocale() == "zhTW" then + L["Advanced options"] = "進階選項" + L["Advanced options for developers and power users."] = "插件作者、進階用戶選項" + L["Unit tests"] = "單元測試" + L["Enable unit tests to be run. This is for developers only.\n\nYou must ReloadUI for changes to take effect."] = "啟用單元測試,這是給插件作者使用的功能。\n\n需要重載介面才能使用。" + L["Contracts"] = "偵錯協定" + L["Enable contracts to be run. This is for developers and anyone wanting to file a bug. Do not file a bug unless contracts are enabled. This will slightly slow down your addons if enabled."] = "啟用偵錯協定,這是給插件作者用來通報錯誤所使用。" + L["Reload UI"] = "重載介面" + L["Reload the User Interface for some changes to take effect."] = "重新載入使用者介面,部分功能才會生效。" + L["Reload"] = "重載" + L["Give donation"] = "捐贈" + L["Donate"] = "捐贈" + L["Give a much-needed donation to the author of this addon."] = "捐贈金錢給插件作者。" + L["File issue"] = "通報錯誤" + L["Report"] = "報告" + L["File a bug or request a new feature or an improvement to this addon."] = "發出錯誤報告或請求新功能及要改進的部分。" + L["Press Ctrl-C to copy, then Alt-Tab out of the game, open your favorite web browser, and paste the link into the address bar."] = "Ctrl-C複製網址,Alt-Tab切換到電腦桌面,打開瀏覽器,在網址列貼上網址。" + L["Press Cmd-C to copy, then Cmd-Tab out of the game, open your favorite web browser, and paste the link into the address bar."] = "Cmd-C複製網址,Cmd-Tab切換到電腦桌面,打開瀏覽器,在網址列貼上網址。" + L["Enabled"] = "啟用" + L["Enable or disable this addon."] = "啟用這個插件。" +elseif GetLocale() == "koKR" then + L["Advanced options"] = "상세 옵션" + L["Advanced options for developers and power users."] = "개발자와 파워 사용자를 위한 상세 옵션입니다." + L["Unit tests"] = "유닛 테스트" + L["Enable unit tests to be run. This is for developers only.\n\nYou must ReloadUI for changes to take effect."] = "유닛 테스트를 사용합니다. 이것은 개발자만을 위한 옵션입니다.\n\n변경된 결과를 적용하기 위해 당신의 UI를 재실행 합니다." + L["Contracts"] = "계약" + L["Enable contracts to be run. This is for developers and anyone wanting to file a bug. Do not file a bug unless contracts are enabled. This will slightly slow down your addons if enabled."] = "계약을 사용합니다. 이것은 개발자와 버그 파일을 알릴 분이면 누구나 사용 가능합니다. 계약이 가능하지 않으면 버그 파일을 보내지 마십시오. 이것은 당신의 애드온 속도를 약간 떨어뜨립니다." + L["Reload UI"] = "UI 재실행" + L["Reload the User Interface for some changes to take effect."] = "변경된 결과를 적용하기 위해 사용자 인터페이스를 재실행합니다." + L["Reload"] = "재실행" + L["Give donation"] = "기부" + L["Donate"] = "기부" + L["Give a much-needed donation to the author of this addon."] = "이 애드온의 제작자에게 필요한 기부를 합니다." + L["File issue"] = "파일 이슈" + L["Report"] = "보고" + L["File a bug or request a new feature or an improvement to this addon."] = "버그 파일을 알리거나 새로운 기능 또는 이 애드온에 대한 개선을 부탁합니다." + L["Press Ctrl-C to copy, then Alt-Tab out of the game, open your favorite web browser, and paste the link into the address bar."] = "Ctrl-C로 복사합니다. Alt-Tab 눌려 게임으로 부터 나간후 웹 브라우저를 엽니다. 복사된 링크를 주소 창에 붙여넣기 합니다." + L["Press Cmd-C to copy, then Cmd-Tab out of the game, open your favorite web browser, and paste the link into the address bar."] = "Cmd-C로 복사합니다. Cmd-Tab 눌려 게임으로 부터 나간후 웹 브라우저를 엽니다. 복사된 링크를 주소 창에 붙여넣기 합니다." + L["Enabled"] = "사용" + L["Enable or disable this addon."] = "이 애드온을 사용하거나 사용하지 않습니다." +elseif GetLocale() == "frFR" then + L["Advanced options"] = "Options avancées" + L["Advanced options for developers and power users."] = "Options avancées à l'attention des développeurs et des utilisateurs expérimentés." + L["Reload UI"] = "Recharger IU" + L["Reload the User Interface for some changes to take effect."] = "Recharge l'interface utilisateur afin que certains changements prennent effet." + L["Reload"] = "Recharger" + L["Give donation"] = "Faire un don" + L["Donate"] = "Don" + L["Give a much-needed donation to the author of this addon."] = "Permet de faire un don bien mérité à l'auteur de cet addon." + L["File issue"] = "Problème" + L["Report"] = "Signaler" + L["File a bug or request a new feature or an improvement to this addon."] = "Permet de signaler un bogue ou de demander une amélioration à cet addon." + L["Press Ctrl-C to copy, then Alt-Tab out of the game, open your favorite web browser, and paste the link into the address bar."] = "Ctrl-C pour copier, puis Alt-Tab pour sortir du jeu. Ouvrez votre navigateur internet et collez le lien dans la barre d'adresse." + L["Press Cmd-C to copy, then Cmd-Tab out of the game, open your favorite web browser, and paste the link into the address bar."] = "Cmd-C pour copier, puis Alt-Tab pour sortir du jeu. Ouvrez votre navigateur internet et collez le lien dans la barre d'adresse." + L["Enabled"] = "Activé" + L["Enable or disable this addon."] = "Active ou désactive cet addon." +end + +local isStandalone = debugstack():match("[Oo%.][Nn%.][Ss%.]\\([^\\]+)\\") == MAJOR_VERSION or nil +local unitTestDB, enableContracts + +local weakKey = { __mode = 'k' } + +-- frame to manage events from +Rock.frame = oldRock and oldRock.frame or _G.CreateFrame("Frame") +local frame = Rock.frame +-- dict of libraries in { ["major"] = object } form +Rock.libraries = oldRock and oldRock.libraries or { [MAJOR_VERSION] = Rock } +local libraries = Rock.libraries +-- set of libraries which have gone through the finalization process in { [object] = true } form +Rock.finalizedLibraries = setmetatable(oldRock and oldRock.finalizedLibraries or { }, weakKey) +local finalizedLibraries = Rock.finalizedLibraries +-- set of libraries which have been tried to be loaded. +Rock.scannedLibraries = oldRock and oldRock.scannedLibraries or {} +local scannedLibraries = Rock.scannedLibraries +-- exportedMethods[library] = { "method1", "method2" } +Rock.exportedMethods = setmetatable(oldRock and oldRock.exportedMethods or {}, weakKey) +local exportedMethods = Rock.exportedMethods +-- mixinToObject[mixin][object] = true +Rock.mixinToObject = setmetatable(oldRock and oldRock.mixinToObject or {}, weakKey) +local mixinToObject = Rock.mixinToObject +-- dict of addons in { ["name"] = object } form +Rock.addons = oldRock and oldRock.addons or {} +local addons = Rock.addons +-- set of libraries that should be finalized before ADDON_LOADED. +Rock.pendingLibraries = setmetatable(oldRock and oldRock.pendingLibraries or { }, weakKey) +local pendingLibraries = Rock.pendingLibraries +-- list of addons in order of created that need to be initialized by ADDON_LOADED. +Rock.pendingAddons = oldRock and oldRock.pendingAddons or {} +local pendingAddons = Rock.pendingAddons +-- dictionary of addons to their folder names +Rock.addonToFolder = oldRock and oldRock.addonToFolder or {} +local addonToFolder = Rock.addonToFolder +-- set of folders which have been loaded +Rock.foldersLoaded = oldRock and oldRock.foldersLoaded or {} +local foldersLoaded = Rock.foldersLoaded +-- list of addons in order of created that need to be enabled by PLAYER_LOGIN. +Rock.pendingAddonsEnable = oldRock and oldRock.pendingAddonsEnable or {} +local pendingAddonsEnable = Rock.pendingAddonsEnable +-- set of addons which have been enabled at least once. +Rock.addonsAlreadyEnabled = oldRock and oldRock.addonsAlreadyEnabled or {} +local addonsAlreadyEnabled = Rock.addonsAlreadyEnabled +-- set of addons which have no database and are set to be inactive. +Rock.inactiveAddons = oldRock and oldRock.inactiveAddons or {} +local inactiveAddons = Rock.inactiveAddons +-- set of addons which are currently enabled (not necessarily should be) +Rock.currentlyEnabledAddons = oldRock and oldRock.currentlyEnabledAddons or {} +local currentlyEnabledAddons = Rock.currentlyEnabledAddons +-- dictionary of namespace to list of functions which will be run. +Rock.unitTests = oldRock and oldRock.unitTests or {} +local unitTests = Rock.unitTests +-- metatable for addons +Rock.addon_mt = oldRock and oldRock.addon_mt or {} +local addon_mt = Rock.addon_mt +for k in pairs(addon_mt) do + addon_mt[k] = nil +end +function addon_mt:__tostring() + return tostring(self.name) +end + +local function better_tostring(self) + if type(self) == "table" and self.name then + return tostring(self.name) + end + return tostring(self) +end + +local function figureCurrentAddon(pos) + local stack = debugstack(pos+1, 1, 0) + local folder = stack:match("[Oo%.][Nn%.][Ss%.]\\([^\\]+)\\") + if folder then + return folder + end + + local partFolder = stack:match("...([^\\]+)\\") + if partFolder then + local partFolder_len = #partFolder + for i = 1, GetNumAddOns() do + local name = GetAddOnInfo(i) + if #name >= partFolder_len then + local partName = name:sub(-partFolder_len) + if partName == partFolder then + return name + end + end + end + end + return nil +end + +--[[--------------------------------------------------------------------------- +Returns: + string - the localized name of the given category. +Arguments: + string - the English name of the category. +Example: + local uf = Rock:GetLocalizedCategory("UnitFrame") +-----------------------------------------------------------------------------]] +function Rock:GetLocalizedCategory(name) + if type(name) ~= "string" then + error(("Bad argument #2 to `GetLocalizedCategory'. Expected %q, got %q."):format("string", type(name)), 2) + end + local cat = CATEGORIES[name] + if cat then + return cat + end + local name_lower = name:lower() + for k in pairs(CATEGORIES) do + if k:lower() == name_lower then + return k + end + end + return _G.UNKNOWN or "Unknown" +end + +local weak = {__mode = 'kv'} + +Rock.recycleData = oldRock and oldRock.recycleData or {} +local recycleData = Rock.recycleData +if recycleData.pools then + setmetatable(recycleData.pools, weak) +end +if recycleData.debugPools then + setmetatable(recycleData.debugPools, weak) +end +if recycleData.newList then + setmetatable(recycleData.newList, weak) +end +if recycleData.newDict then + setmetatable(recycleData.newDict, weak) +end +if recycleData.newSet then + setmetatable(recycleData.newSet, weak) +end +if recycleData.del then + setmetatable(recycleData.del, weak) +end + +local tmp = {} +local function myUnpack(t, start) + if not start then + start = 1 + end + local value = t[start] + if value == nil then + return + end + t[start] = nil + return value, myUnpack(t, start+1) +end + +--[[--------------------------------------------------------------------------- +Notes: + * Returns functions for the specified namespace based on what is provided. + * function types: + ; "newList" : to create a list + ; "newDict" : to create a dictionary + ; "newSet" : to create a set + ; "del" : to delete a table + ; "unpackListAndDel" : deletes a table and returns what its contents were as a list, in order. + ; "unpackSetAndDel" : deletes a table and returns what its contents were as a set, in no particular order. + ; "unpackDictAndDel" : deletes a table and returns what its contents were as a dictionary, in no particular order. + * If you provide "Debug" as the last argument, then the namespace can be debugged with ''':DebugRecycle''' + * It is '''not recommended''' to use table recycling with tables that have more than 128 keys, as it is typically faster to let lua's garbage collector handle it. +Arguments: + string - the namespace. ''Note: this doesn't necessarily have to be a string.'' +Example: + local newList, newDict, newSet, del, unpackListAndDel, unpackSetAndDel, unpackDictAndDel = Rock:GetRecyclingFunctions("MyNamespace", "newList", "newDict", "newSet", "del", "unpackListAndDel", "unpackSetAndDel", "unpackDictAndDel") + + local t = newList('alpha', 'bravo') -- same as t = {'alpha', 'bravo'} + local u = newDict('alpha', 'bravo') -- same as t = {['alpha'] = 'bravo'} + local v = newSet('alpha', 'bravo') -- same as t = {['alpha'] = true, ['bravo'] = true} + t = del(t) -- you want to clear your reference as well as deleting. + u = del(u) + v = del(v) + + -- for debugging + local newList = Rock:GetRecyclingFunctions("MyNamespace", "newList", "Debug") + local t = newList() + Rock:DebugRecycle("MyNamespace") + t = del(t) + + -- unpacking functions + unpackListAndDel(newList(...)) => ... + unpackSetAndDel(newSet(...)) => ... + unpackDictAndDel(newDict(...)) => ... + newList(unpackListAndDel(t)) => t + newSet(unpackSetAndDel(t)) => t + newDict(unpackDictAndDel(t)) => t + -- as you can see, they are inverses of each other. +-----------------------------------------------------------------------------]] +function Rock:GetRecyclingFunctions(namespace, ...) + local pools = recycleData.pools + if not pools then + pools = setmetatable({}, weak) + recycleData.pools = pools + end + if namespace == "newList" or namespace == "newSet" or namespace == "newDict" or namespace == "del" or namespace == "unpackListAndDel" or namespace == "unpackSetAndDel" or namespace == "unpackDictAndDel" then + error(("Bad argument #2 to `GetRecyclingFunctions'. Cannot be %q"):format(namespace), 2) + end + local pool = pools[namespace] + if not pool then + pool = setmetatable({}, weak) + pools[namespace] = pool + end + local n = select('#', ...) + local debug = select(n, ...) == "Debug" + if debug then + n = n - 1 + local debugPools = recycleData.debugPools + if not debugPools then + debugPools = setmetatable({}, weak) + recycleData.debugPools = debugPools + end + debug = debugPools[namespace] + if not debug then + debug = { num = 0 } + debugPools[namespace] = debug + end + elseif recycleData.debugPools and recycleData.debugPools[namespace] then + debug = recycleData.debugPools[namespace] + end + for i = 1, n do + local func = select(i, ...) + local recycleData_func = recycleData[func] + if not recycleData_func then + recycleData_func = setmetatable({}, weak) + recycleData[func] = recycleData_func + end + if func == "newList" then + local newList = recycleData_func[namespace] + if not newList then + function newList(...) + local t = next(pool) + local n = select('#', ...) + if t then + pool[t] = nil + for i = 1, n do + t[i] = select(i, ...) + end + else + t = { ... } + end + + if debug then + debug[t] = debugstack(2) + debug.num = debug.num + 1 + end + + return t, n + end + recycleData_func[namespace] = newList + end + tmp[i] = newList + elseif func == "newDict" then + local newDict = recycleData_func[namespace] + if not newDict then + function newDict(...) + local t = next(pool) + if t then + pool[t] = nil + else + t = {} + end + + for i = 1, select('#', ...), 2 do + t[select(i, ...)] = select(i+1, ...) + end + + if debug then + debug[t] = debugstack(2) + debug.num = debug.num + 1 + end + + return t + end + recycleData_func[namespace] = newDict + end + tmp[i] = newDict + elseif func == "newSet" then + local newSet = recycleData_func[namespace] + if not newSet then + function newSet(...) + local t = next(pool) + if t then + pool[t] = nil + else + t = {} + end + + for i = 1, select('#', ...) do + t[select(i, ...)] = true + end + + if debug then + debug[t] = debugstack(2) + debug.num = debug.num + 1 + end + + return t + end + recycleData_func[namespace] = newSet + end + tmp[i] = newSet + elseif func == "del" then + local del = recycleData_func[namespace] + if not del then + function del(t) + if not t then + error(("Bad argument #1 to `del'. Expected %q, got %q."):format("table", type(t)), 2) + end + if pool[t] then + local _, ret = pcall(error, "Error, double-free syndrome.", 3) + geterrorhandler()(ret) + end + setmetatable(t, nil) + for k in pairs(t) do + t[k] = nil + end + t[true] = true + t[true] = nil + pool[t] = true + + if debug then + debug[t] = nil + debug.num = debug.num - 1 + end + return nil + end + recycleData_func[namespace] = del + end + tmp[i] = del + elseif func == "unpackListAndDel" then + local unpackListAndDel = recycleData_func[namespace] + if not unpackListAndDel then + local function f(t, start, finish) + if start > finish then + for k in pairs(t) do + t[k] = nil + end + t[true] = true + t[true] = nil + pool[t] = true + return + end + return t[start], f(t, start+1, finish) + end + function unpackListAndDel(t, start, finish) + if not t then + error(("Bad argument #1 to `unpackListAndDel'. Expected %q, got %q."):format("table", type(t)), 2) + end + if not start then + start = 1 + end + if not finish then + finish = #t + end + setmetatable(t, nil) + if debug then + debug[t] = nil + debug.num = debug.num - 1 + end + return f(t, start, finish) + end + end + tmp[i] = unpackListAndDel + elseif func == "unpackSetAndDel" then + local unpackSetAndDel = recycleData_func[namespace] + if not unpackSetAndDel then + local function f(t, current) + current = next(t, current) + if current == nil then + for k in pairs(t) do + t[k] = nil + end + t[true] = true + t[true] = nil + pool[t] = true + return + end + return current, f(t, current) + end + function unpackSetAndDel(t) + if not t then + error(("Bad argument #1 to `unpackListAndDel'. Expected %q, got %q."):format("table", type(t)), 2) + end + setmetatable(t, nil) + if debug then + debug[t] = nil + debug.num = debug.num - 1 + end + return f(t, nil) + end + end + tmp[i] = unpackSetAndDel + elseif func == "unpackDictAndDel" then + local unpackDictAndDel = recycleData_func[namespace] + if not unpackDictAndDel then + local function f(t, current) + local value + current, value = next(t, current) + if current == nil then + for k in pairs(t) do + t[k] = nil + end + t[true] = true + t[true] = nil + pool[t] = true + return + end + return current, value, f(t, current) + end + function unpackDictAndDel(t) + if not t then + error(("Bad argument #1 to `unpackListAndDel'. Expected %q, got %q."):format("table", type(t)), 2) + end + setmetatable(t, nil) + if debug then + debug[t] = nil + debug.num = debug.num - 1 + end + return f(t, nil) + end + end + tmp[i] = unpackDictAndDel + else + error(("Bad argument #%d to `GetRecyclingFunctions': %q, %q, %q, %q, %q, %q, or %q expected, got %s"):format(i+2, "newList", "newDict", "newSet", "del", "unpackListAndDel", "unpackSetAndDel", "unpackDictAndDel", type(func) == "string" and ("%q"):format(func) or tostring(func)), 2) + end + end + return myUnpack(tmp) +end + +--[[--------------------------------------------------------------------------- +Notes: + * Prints information about the specified recycling namespace, including what tables are still in play and where they come from and how many there are. + * This goes in tandem with ''':GetRecyclingFunctions''' +Arguments: + string - the namespace. ''Note: this doesn't necessarily have to be a string.'' +Example: + local newList = Rock:GetRecyclingFunctions("MyNamespace", "newList", "Debug") + local t = newList() + Rock:DebugRecycle("MyNamespace") + t = del(t) +-----------------------------------------------------------------------------]] +function Rock:DebugRecycle(namespace) + local debug = recycleData.debugPools and recycleData.debugPools[namespace] + if not debug then + return + end + for k, v in pairs(debug) do + if k ~= "num" then + _G.DEFAULT_CHAT_FRAME:AddMessage(v) + _G.DEFAULT_CHAT_FRAME:AddMessage("------") + end + end + _G.DEFAULT_CHAT_FRAME:AddMessage(("%s: %d tables in action."):format(tostring(namespace), debug.num)) +end + +local newList, del, unpackListAndDel, unpackDictAndDel = Rock:GetRecyclingFunctions(MAJOR_VERSION, "newList", "del", "unpackListAndDel", "unpackDictAndDel") + +--[[--------------------------------------------------------------------------- +Notes: + * Adds a unit test for the specified namespace + * The function provided is called, and it should be where tests are performed, if a problem occurs, an error should fire. If no problems occur, it should return silently. + * You can have as many tests per namespace as you want. +Arguments: + string - the namespace. + function - the function to call. +Example: + Rock:AddUnitTest("LibMonkey-1.0", function() + local LibMonkey = Rock("LibMonkey-1.0") + assert(LibMonkey:Fling() == "Poo") + end) +-----------------------------------------------------------------------------]] +function Rock:AddUnitTest(namespace, func) + if not isStandalone then + return + end + if type(namespace) ~= "string" then + error(("Bad argument #2 to `AddUnitTest'. Expected %q, got %q."):format("string", type(namespace)), 2) + end + if namespace:find("^Lib[A-Z]") then + local addon = figureCurrentAddon(2) + if addon ~= namespace then + return + end + end + if type(func) ~= "function" then + error(("Bad argument #3 to `AddUnitTest'. Expected %q, got %q."):format("function", type(func)), 2) + end + local addon = figureCurrentAddon(2) + if libraries[namespace] and addon ~= namespace then + -- only work on standalone libraries. + return + end + local unitTests_namespace = unitTests[namespace] + if not unitTests_namespace then + unitTests_namespace = newList() + unitTests[namespace] = unitTests_namespace + end + if not unitTests_namespace.addon then + unitTests_namespace.addon = addon + end + if unitTestDB and not unitTestDB[namespace] then + return + end + unitTests_namespace[#unitTests_namespace+1] = func +end + +local LibRockEvent +local LibRockModuleCore +local OpenDonationFrame, OpenIssueFrame +function Rock:OnLibraryLoad(major, library) + if major == "LibRockEvent-1.0" then + LibRockEvent = library + LibRockEvent:Embed(Rock) + elseif major == "LibRockModuleCore-1.0" then + LibRockModuleCore = library + elseif major == "LibRockConfig-1.0" then + if isStandalone then + library.rockOptions.args.advanced = { + type = 'group', + groupType = 'inline', + name = L["Advanced options"], + desc = L["Advanced options for developers and power users."], + order = -1, + args = { + unitTests = { + type = 'multichoice', + name = L["Unit tests"], + desc = L["Enable unit tests to be run. This is for developers only.\n\nYou must ReloadUI for changes to take effect."], + get = function(key) + return unitTestDB[key] + end, + set = function(key, value) + unitTestDB[key] = value or nil + end, + choices = function() + local t = newList() + for k in pairs(unitTests) do + t[k] = k + end + return "@dict", unpackDictAndDel(t) + end + }, + contracts = { + type = 'boolean', + name = L["Contracts"], + desc = L["Enable contracts to be run. This is for developers and anyone wanting to file a bug. Do not file a bug unless contracts are enabled. This will slightly slow down your addons if enabled."], + get = function() + return enableContracts + end, + set = function(value) + _G.LibRock_1_0DB.contracts = value or nil + enableContracts = value + end, + } + } + } + end + library.rockOptions.args.reloadui = { + type = 'execute', + name = L["Reload UI"], + desc = L["Reload the User Interface for some changes to take effect."], + buttonText = L["Reload"], + func = function() + _G.ReloadUI() + end, + order = -2, + } + Rock.donate = "Paypal:ckknight AT gmail DOT com" + library.rockOptions.args.donate = { + type = 'execute', + name = L["Give donation"], + buttonText = L["Donate"], + desc = L["Give a much-needed donation to the author of this addon."], + func = OpenDonationFrame, + passValue = Rock, + order = -3, + } + Rock.issueTracker = "Wowace:10027" + library.rockOptions.args.issue = { + type = 'execute', + name = L["File issue"], + buttonText = L["Report"], + desc = L["File a bug or request a new feature or an improvement to this addon."], + func = OpenIssueFrame, + passValue = Rock, + order = -4, + } + end +end + +addon_mt.__index = {} +local addon_mt___index = addon_mt.__index +--[[--------------------------------------------------------------------------- +#FORCE_DOC +Notes: + * This is exported to all addons. + * This information is retrieved from LibRockModuleCore-1.0 if it is a module, otherwise from LibRockDB-1.0 if it uses that as a mixin, otherwise it keeps a variable locally. +Returns: + boolean - whether the addon is in an active state or not. +Example: + local active = MyAddon:IsActive() +-----------------------------------------------------------------------------]] +function addon_mt___index:IsActive() + if LibRockModuleCore then + local core = LibRockModuleCore:HasModule(self) + if core then + return core:IsModuleActive(self) + end + end + + local self_db = self.db + if self_db then + local disabled + local self_db_raw = self_db.raw + if self_db_raw then + local self_db_raw_disabled = self_db_raw.disabled + if self_db_raw_disabled then + local profile = type(self.GetProfile) == "function" and select(2, self:GetProfile()) or false + disabled = self_db_raw_disabled[profile] + end + else + return false + end + return not disabled + end + + return not inactiveAddons[self] +end +--[[--------------------------------------------------------------------------- +#FORCE_DOC +Notes: + * This is exported to all addons. + * If it enables the addon, it will call :OnEnable(first) on the addon and :OnEmbedEnable(addon, first) on all its mixins. + * If it disables the addon, it will call :OnDisable(first) on the addon and :OnEmbedDisable(addon, first) on all its mixins. + * This information is stored by LibRockModuleCore-1.0 if it is a module, otherwise from LibRockDB-1.0 if it uses that as a mixin, otherwise it keeps a variable locally. +Arguments: + [optional] boolean - whether the addon should be in an active state or not. Default: not :IsActive() +Returns: + boolean - whether the addon is in an active state or not. +Example: + MyAddon:ToggleActive() -- switch + MyAddon:ToggleActive(true) -- force on + MyAddon:ToggleActive(false) -- force off +-----------------------------------------------------------------------------]] +function addon_mt___index:ToggleActive(state) + if state and state ~= true then + error(("Bad argument #2 to `ToggleActive'. Expected %q or %q, got %q."):format("boolean", "nil", type(state)), 2) + end + if LibRockModuleCore then + local core = LibRockModuleCore:HasModule(self) + if core then + return core:ToggleModuleActive(self, state) + end + end + + local self_db = self.db + if self_db then + local self_db_raw = self_db.raw + if not self_db_raw then + error("Error saving to database with `ToggleActive'. db.raw not available.", 2) + end + local self_db_raw_disabled = self_db_raw.disabled + if not self_db_raw_disabled then + self_db_raw_disabled = newList() + self_db_raw.disabled = self_db_raw_disabled + end + local profile = type(self.GetProfile) == "function" and select(2, self:GetProfile()) or false + if state == nil then + state = not not self_db_raw_disabled[profile] + elseif (not self_db_raw_disabled[profile]) == state then + return + end + self_db_raw_disabled[profile] = not state or nil + if next(self_db_raw_disabled) == nil then + self_db_raw.disabled = del(self_db_raw_disabled) + end + else + if state == nil then + state = not not inactiveAddons[self] + elseif (not inactiveAddons[self]) == state then + return + end + inactiveAddons[self] = not state or nil + end + + Rock:RecheckEnabledStates() + + return state +end + +local function noop() end + +do + local preconditions = setmetatable({}, weakKey) + local postconditions = setmetatable({}, weakKey) + local postconditionsOld = setmetatable({}, weakKey) + + local currentMethod = nil + + local function hook(object, method) + local object_method = object[method] + object[method] = function(...) + local pre = preconditions[object_method] + local post = postconditions[object_method] + if pre then + local old_currentMethod = currentMethod + currentMethod = method + pre(...) + currentMethod = old_currentMethod + end + if not post then + return object_method(...) + end + local oldFunc = postconditionsOld[object_method] + local old + if oldFunc then + old = newList() + oldFunc(old, ...) + end + + local old_currentMethod = currentMethod + currentMethod = nil + local ret, n = newList(object_method(...)) + + currentMethod = method + if old then + post(old, ret, ...) + old = del(old) + else + post(ret, ...) + end + currentMethod = old_currentMethod + return unpackListAndDel(ret, 1, n) + end + end + + local function precondition(object, method, func) + if type(object) ~= "table" then + error(("Bad argument #1 to `precondition'. Expected %q, got %q."):format("table", type(object)), 2) + end + if type(object[method]) ~= "function" then + error(("Method %q not found on object %s. Expected %q, got %q."):format(tostring(method), tostring(object), "function", type(object[method])), 2) + end + if type(func) ~= "function" then + error(("Bad argument #3 to `precondition'. Expected %q, got %q."):format("function", type(func)), 2) + end + + local object_method = object[method] + if preconditions[object_method] then + error("Cannot call `preconditon' on the same method twice.", 2) + end + preconditions[object_method] = func + + if not postconditions[object_method] then + hook(object, method) + end + end + + local function postcondition(object, method, func, fillOld) + if type(object) ~= "table" then + error(("Bad argument #1 to `postcondition'. Expected %q, got %q."):format("table", type(object)), 2) + end + if type(object[method]) ~= "function" then + error(("Method %q not found on object %s. Expected %q, got %q."):format(tostring(method), tostring(object), "function", type(object[method])), 2) + end + if type(func) ~= "function" then + error(("Bad argument #3 to `postcondition'. Expected %q, got %q."):format("function", type(func)), 2) + end + if fillOld and type(fillOld) ~= "function" then + error(("Bad argument #4 to `postcondition'. Expected %q or %q, got %q."):format("function", "nil", type(func)), 2) + end + + local object_method = object[method] + if postconditions[object_method] then + error("Cannot call `postcondition' on the same method twice.", 2) + end + postconditions[object_method] = func + postconditionsOld[object_method] = fillOld + + if not preconditions[object_method] then + hook(object, method) + end + end + + local function argCheck(value, position, ...) + if not currentMethod then + error("Cannot call `argCheck' outside of a pre/post-condition.", 2) + end + if type(position) ~= "number" then + error(("Bad argument #2 to `argCheck'. Expected %q, got %q"):format("number", type(position)), 2) + end + local type_value = type(value) + for i = 1, select('#', ...) do + local v = select(i, ...) + if type(v) ~= "string" then + error(("Bad argument #%d to `argCheck'. Expected %q, got %q"):format(i+1, "string", type(v)), 2) + end + if v == type_value then + return + end + end + local t = newList(...) + t[#t] = nil + for i,v in ipairs(t) do + t[i] = ("%q"):format(v) + end + local s + if #t == 0 then + s = ("%q"):format((...)) + elseif #t == 1 then + s = ("%q or %q"):format(...) + else + s = table_concat(t, ", ") .. ", or " .. ("%q"):format(select(#t+1, ...)) + end + t = del(t) + + error(("Bad argument #%d to `%s'. Expected %s, got %q."):format(position, tostring(currentMethod), s, type_value), 4) + end + + --[[--------------------------------------------------------------------------- + Notes: + * Returns functions for the specified namespace based on what is provided. + * function types: + ; "precondition" : to set the pre-condition for a method. + ; "postcondition" : to set the post-condition for a method. + ; "argCheck" : to check the type of an argument, to be executed within a pre-condition. + * preconditon is in the form of <tt>precondition(object, "methodName", func(self, ...))</tt> + * postcondition is in the form of either <tt>postcondition(object, "methodName", func(returnValues, self, ...))</tt> or <tt>postcondition(object, "methodName", func(oldValues, returnValues, self, ...), populateOld(oldValues, self, ...))</tt> + ** returnValues is the list of return values, empty if no return values were sent. + ** if the populateOld function is provided, then the empty oldValues table is provided and expected to be filled, and then given to the func. + * argCheck is in the form of <tt>argCheck(value, n, "type1" [, "type2", ...])</tt> + ** value is the value provided to the function you're checking. + ** n is the argument position. ''Note: 1 is the position of `self'. 2 would be the first "real" position.'' + ** the tuple of types can be any string, but specifically "nil", "boolean", "string", "number", "function", "userdata", "table", etc. + Arguments: + string - the namespace. ''Note: this doesn't necessarily have to be a string.'' + Example: + local precondition, postcondition, argCheck = Rock:GetRecyclingFunctions("Stack", "precondition", "postcondition", "argCheck") + + local stack = {} + stack.IsEmpty = function(self) + return self[1] == nil + end + stack.GetLength = function(self) + return #self + end + stack.Push = function(self, value) + self[#self+1] = value + end + precondition(stack, "Push", function(self, value) + argCheck(value, 2, "string") -- only accept strings, no other values + end) + postcondition(stack, "Push", function(old, ret, self, value) + assert(self:GetLength() == old.length+1) + assert(not self:IsEmpty()) + end, function(old, self) + old.length = self:GetLength() + end) + stack.Pop = function(self) + local value = self[#self] + self[#self] = nil + return value + end + precondition(stack, "Pop", function(self) + assert(self:GetLength() >= 1) + end) + postcondition(stack, "Pop", function(old, ret, self) + assert(self:GetLength() == old.length-1) + end, function(old, self) + old.length = self:GetLength() + end) + stack.Peek = function(self) + return self[#self] + end + precondition(stack, "Peek", function(self) + assert(self:GetLength() >= 1) + end) + postcondition(stack, "Peek", function(old, ret, self) + assert(self:GetLength() == old.length) + end, function(old, self) + old.length = self:GetLength() + end) + + local t = setmetatable({}, {__index=stack}) + t:Push("Alpha") + t:Push("Bravo") + t:Push(5) -- error, only strings + assert(t:Pop() == "Bravo") + assert(t:Pop() == "Alpha") + t:Pop() -- error, out of values + -----------------------------------------------------------------------------]] + function Rock:GetContractFunctions(namespace, ...) + if namespace == "precondition" or namespace == "postcondition" or namespace == "argCheck" then + error(("Bad argument #2 to `GetContractFunctions'. Cannot be %q."):format(namespace), 2) + end + local t = newList() + if enableContracts then + for i = 1, select('#', ...) do + local v = select(i, ...) + if v == "precondition" then + t[i] = precondition + elseif v == "postcondition" then + t[i] = postcondition + elseif v == "argCheck" then + t[i] = argCheck + else + error(("Bad argument #%d to `GetContractFunctions'. Expected %q, %q, or %q, got %q."):format(i+2, "precondition", "postcondition", "argCheck", tostring(v))) + end + end + else + for i = 1, select('#', ...) do + t[i] = noop + end + end + return unpackListAndDel(t) + end +end + +--[[--------------------------------------------------------------------------- +Notes: + * convert a revision string to a number +Arguments: + string - revision string +Returns: + string or number - the string given or the number retrieved from it. +-----------------------------------------------------------------------------]] +local function coerceRevisionToNumber(version) + if type(version) == "string" then + return tonumber(version:match("(%-?%d+)")) or version + else + return version + end +end + +--[[--------------------------------------------------------------------------- +Notes: + * try to enable the standalone library specified +Arguments: + string - name of the library. +Returns: + boolean - whether the library is properly enabled and loadable. +-----------------------------------------------------------------------------]] +local function TryToEnable(addon) + local islod = IsAddOnLoadOnDemand(addon) + if islod then + local _, _, _, enabled = GetAddOnInfo(addon) + EnableAddOn(addon) + local _, _, _, _, loadable = GetAddOnInfo(addon) + if not loadable and not enabled then + DisableAddOn(addon) + end + + return loadable + end +end + +--[[--------------------------------------------------------------------------- +Notes: + * try to load the standalone library specified +Arguments: + string - name of the library. +Returns: + boolean - whether the library is loaded. +-----------------------------------------------------------------------------]] +local function TryToLoadStandalone(major) + major = major:lower() + if scannedLibraries[major] then + return + end + scannedLibraries[major] = true + local name, _, _, enabled, loadable, state = GetAddOnInfo(major) + if state == "MISSING" or not IsAddOnLoadOnDemand(major) then + -- backwards compatibility for X-AceLibrary + local field = "X-AceLibrary-" .. major + local loaded + for i = 1, GetNumAddOns() do + if GetAddOnMetadata(i, field) then + name, _, _, enabled, loadable = GetAddOnInfo(i) + + loadable = (enabled and loadable) or TryToEnable(name) + if loadable then + loaded = true + LoadAddOn(name) + end + end + end + + return loaded + elseif (enabled and loadable) or TryToEnable(major) then + LoadAddOn(major) + return true + else + return false + end +end + +--[[--------------------------------------------------------------------------- +Notes: + * Return the LibStub library, casing is unimportant. +Arguments: + string - name of the library. +Returns: + table or nil - library + number - minor version +-----------------------------------------------------------------------------]] +local function GetLibStubLibrary(major) + local lib, minor = LibStub:GetLibrary(major, true) + if lib then + return lib, minor + end + major = major:lower() + for m, lib in LibStub:IterateLibraries() do + if m:lower() == major then + return LibStub:GetLibrary(m) + end + end + return nil, nil +end + +local finishLibraryRegistration +--[[--------------------------------------------------------------------------- +Notes: + * create a new library if the version provided is not out of date. +Arguments: + string - name of the library. + number - version of the library. +Returns: + library, oldLibrary + * table or nil - the library with which to manipulate + * table or nil - the old version of the library to upgrade from +Example: + local LibMonkey, oldLib = Rock:NewLibrary("LibMonkey-1.0", 50) + if not LibMonkey then + -- opt out now, out of date + return + end +-----------------------------------------------------------------------------]] +function Rock:NewLibrary(major, version) + if type(major) ~= "string" then + error(("Bad argument #2 to `NewLibrary'. Expected %q, got %q."):format("string", type(major)), 2) + end + if not major:match("^Lib[A-Z][A-Za-z%d%-]*%-%d+%.%d+$") then + error(("Bad argument #2 to `NewLibrary'. Must match %q, got %q."):format("^Lib[A-Z][A-Za-z%d%-]*%-%d+%.%d+$", major), 2) + end + TryToLoadStandalone(major) + version = coerceRevisionToNumber(version) + if type(version) ~= "number" then + error(("Bad argument #3 to `NewLibrary'. Expected %q, got %q."):format("number", type(version)), 2) + end + local library, oldMinor = LibStub:GetLibrary(major, true) + if oldMinor and oldMinor >= version then + -- in case LibStub is acting funny + return nil, nil + end + local library, oldMinor = LibStub:NewLibrary(major, version) + if not library then + return nil, nil + end + local unitTests_major = unitTests[major] + if unitTests_major then + for k,v in pairs(unitTests_major) do + unitTests_major[k] = nil + end + end + for k, v in pairs(recycleData) do + v[major] = nil + end + local mixinToObject_library = mixinToObject[library] + + local oldLib + if oldMinor then + -- previous version exists + local mixins = newList() + for mixin, objectSet in pairs(mixinToObject) do + if objectSet[library] then + mixins[mixin] = true + end + end + for mixin in pairs(mixins) do + mixin:Unembed(library) + end + mixins = del(mixins) + oldLib = newList() + for k, v in pairs(library) do + oldLib[k] = v + library[k] = nil + end + setmetatable(oldLib, getmetatable(library)) + setmetatable(library, nil) + end + finishLibraryRegistration(major, version, library, figureCurrentAddon(2)) + + return library, oldLib +end +function finishLibraryRegistration(major, version, library, folder) + library.name = major + + libraries[major] = library + pendingLibraries[library] = folder + local exportedMethods_library = exportedMethods[library] + if exportedMethods_library then + local mixinToObject_library = mixinToObject[library] + if mixinToObject_library then + for object in pairs(mixinToObject_library) do + for _,v in ipairs(exportedMethods_library) do + object[v] = nil + end + end + end + exportedMethods[library] = del(exportedMethods_library) + end + if library ~= Rock then + Rock:Embed(library) + end + + frame:Show() +end +if not oldRock then + finishLibraryRegistration(MAJOR_VERSION, MINOR_VERSION, Rock, figureCurrentAddon(1)) +end + +-- #NODOC +local function __removeLibrary(libName) + libraries[libName] = nil + if LibStub.libs then + LibStub.libs[libName] = nil + end + if LibStub.minors then + LibStub.minors[libName] = nil + end + local lastCount + repeat + lastCount = collectgarbage('count') + collectgarbage('collect') + until lastCount == collectgarbage('count') +end +local function run(_,a) + if a < 1/30 then + collectgarbage('step') + end +end + +--[[--------------------------------------------------------------------------- +Notes: + * properly finalizes the library, essentially stating that it has loaded properly. + * This will call :OnLibraryLoad("major", library) on every other library + * This will also call :OnLibraryLoad("major", library) on the library provided, using every other library as the arguments. + * An error will occur if this is not done before ADDON_LOADED. +Arguments: + string - name of the library. +Example: + local LibMonkey, oldLib = Rock:NewLibrary("LibMonkey-1.0", 50) + if not LibMonkey then + -- opt out now, out of date + return + end + Rock:FinalizeLibrary("LibMonkey-1.0") +-----------------------------------------------------------------------------]] +function Rock:FinalizeLibrary(major) + if type(major) ~= "string" then + error(("Bad argument #2 to `FinalizeLibrary'. Expected %q, got %q."):format("string", type(major)), 2) + end + local library = libraries[major] + if not library then + error(("Bad argument #2 to `FinalizeLibrary'. %q is not a library."):format("string", major), 2) + end + pendingLibraries[library] = nil + local library_OnLibraryLoad = library.OnLibraryLoad + if library_OnLibraryLoad then + for maj, lib in LibStub:IterateLibraries() do -- for all libraries + if maj ~= major then + local success, ret = pcall(library_OnLibraryLoad, library, maj, lib) + if not success then + geterrorhandler()(ret) + break + end + end + end + end + if finalizedLibraries[library] then + return + end + finalizedLibraries[library] = true + for maj, lib in pairs(libraries) do -- just Rock libraries + if maj ~= major then + local lib_OnLibraryLoad = lib.OnLibraryLoad + if lib_OnLibraryLoad then + local success, ret = pcall(lib_OnLibraryLoad, lib, major, library) + if not success then + geterrorhandler()(ret) + end + end + end + end + if LibRockEvent then + self:DispatchEvent("LibraryLoad", major, library) + end +end + +local function manualFinalize(major, library) + if libraries[major] then -- non-Rock libraries only + return + end + if finalizedLibraries[library] then -- don't do it twice + return + end + finalizedLibraries[library] = true + for maj, lib in pairs(libraries) do -- just Rock libraries + if maj ~= major then + local lib_OnLibraryLoad = lib.OnLibraryLoad + if lib_OnLibraryLoad then + local success, ret = pcall(lib_OnLibraryLoad, lib, major, library) + if not success then + geterrorhandler()(ret) + end + end + end + end + if LibRockEvent then + Rock:DispatchEvent("LibraryLoad", major, library) + end +end + +--[[--------------------------------------------------------------------------- +Arguments: + string - name of the library. + [optional] boolean - whether to not load a library if it is not found. Default: false + [optional] boolean - whether to not error if a library is not found. Default: false +Returns: + library + * table or nil - the library requested +Example: + local LibMonkey = Rock:GetLibrary("LibMonkey-1.0") + -- or + local LibMonkey = Rock("LibMonkey-1.0") +-----------------------------------------------------------------------------]] +function Rock:GetLibrary(major, dontLoad, dontError) + if type(major) ~= "string" then + error(("Bad argument #2 to `GetLibrary'. Expected %q, got %q."):format("string", type(major)), 2) + end + if dontLoad and dontLoad ~= true then + error(("Bad argument #3 to `GetLibrary'. Expected %q or %q, got %q."):format("boolean", "nil", type(dontLoad)), 2) + end + if dontError and dontError ~= true then + error(("Bad argument #4 to `GetLibrary'. Expected %q or %q, got %q."):format("boolean", "nil", type(dontError)), 2) + end + if not dontLoad then + TryToLoadStandalone(major) + end + + local library = GetLibStubLibrary(major) + if not library then + if dontError then + return nil + end + error(("Library %q not found."):format(major), 2) + end + + return library +end + +setmetatable(Rock, { __call = Rock.GetLibrary }) + +--[[--------------------------------------------------------------------------- +Arguments: + string - name of the library. +Returns: + boolean - whether the library exists and is a proper mixin which can be embedded. +Example: + local isMixin = Rock:IsLibraryMixin("LibMonkey-1.0") +-----------------------------------------------------------------------------]] +function Rock:IsLibraryMixin(name) + local library = self:GetLibrary(name, false, true) + if not library then + return false + end + return not not exportedMethods[library] +end + +--[[--------------------------------------------------------------------------- +Arguments: + string - name of the library. + [optional] boolean - whether to not load a library if it is not found. Default: false +Returns: + library + * table or nil - the library requested +Example: + local hasLibMonkey = Rock:HasLibrary("LibMonkey-1.0") +-----------------------------------------------------------------------------]] +function Rock:HasLibrary(major, dontLoad) + if type(major) ~= "string" then + error(("Bad argument #2 to `HasLibrary'. Expected %q, got %q."):format("string", type(major)), 2) + end + if dontLoad and dontLoad ~= true then + error(("Bad argument #3 to `HasLibrary'. Expected %q or %q, got %q."):format("boolean", "nil", type(dontLoad)), 2) + end + if not dontLoad then + TryToLoadStandalone(major) + end + return not not GetLibStubLibrary(major) +end + +--[[--------------------------------------------------------------------------- +Notes: + * This is exported to all libraries +Returns: + major, minor + * string - name of the library + * number - version of the library +Example: + local major, minor = Rock:GetLibraryVersion() -- will be "LibRock-1.0", 12345 + local major, minor = LibMonkey:GetLibraryVersion() -- will be "LibMonkey-1.0", 50 +-----------------------------------------------------------------------------]] +function Rock:GetLibraryVersion() + if type(self) ~= "table" then + return nil, nil + end + local major + local name = self.name + if name and GetLibStubLibrary(name) == self then + major = name + else + for m, instance in LibStub:IterateLibraries() do + if instance == self then + major = m + break + end + end + if not major then + return nil, nil + end + end + local _, minor = GetLibStubLibrary(major) + return major, minor +end + +--[[--------------------------------------------------------------------------- +Returns: + an iterator to traverse all registered libraries. +Example: + for major, library in Rock:IterateLibraries() do + -- do something with major and library + end +-----------------------------------------------------------------------------]] +function Rock:IterateLibraries() + return LibStub:IterateLibraries() +end + +--[[--------------------------------------------------------------------------- +Notes: + * This is exported to all libraries + * Allows you to set precisely what methods for the library to export. + * This automatically turns a library into a mixin. +Arguments: + tuple - the list of method names to export. +Example: + local LibMonkey = Rock:NewLibrary("LibMonkey-1.0", 50) + LibMonkey.FlingPoo = function(self) + return "Splat!" + end + LibMonkey:SetExportedMethods("FlingPoo") + -- later + local Darwin = Rock:NewAddon("Darwin", "LibMonkey-1.0") + assert(Darwin:FlingPoo() == "Splat!") +-----------------------------------------------------------------------------]] +function Rock:SetExportedMethods(...) + if exportedMethods[self] then + error("Cannot call `SetExportedMethods' more than once.", 2) + end + local t = newList(...) + if #t == 0 then + error("Must supply at least 1 method to `SetExportedMethods'.", 2) + end + for i,v in ipairs(t) do + if type(self[v]) ~= "function" then + error(("Bad argument #%d to `SetExportedMethods'. Method %q does not exist."):format(i+1, tostring(v)), 2) + end + end + exportedMethods[self] = t + + local mixinToObject_library = mixinToObject[self] + if mixinToObject_library then + for object in pairs(mixinToObject_library) do + for _,method in ipairs(t) do + object[method] = self[method] + end + end + end +end + +--[[--------------------------------------------------------------------------- +Notes: + * This is exported to all libraries + * Embeds all the methods previously set to export onto a table. + * This will call :OnEmbed(object) on the library if it is available. +Arguments: + table - the table with which to export methods onto. +Returns: + The table provided, after embedding. +Example: + local LibMonkey = Rock:NewLibrary("LibMonkey-1.0", 50) + LibMonkey.FlingPoo = function(self) + return "Splat!" + end + LibMonkey:SetExportedMethods("FlingPoo") + -- later + local Darwin = {} + Rock("LibMonkey-1.0"):Embed(Darwin) + assert(Darwin:FlingPoo() == "Splat!") +-----------------------------------------------------------------------------]] +function Rock:Embed(object) + if not exportedMethods[self] then + error(("Cannot call `Embed' for library %q if `SetExportedMethods' has not been called."):format(tostring(self.name)), 2) + end + if type(object) ~= "table" then + error(("Bad argument #2 to `Embed'. Expected %q, got %q."):format("table", type(object)), 2) + end + + for i,v in ipairs(exportedMethods[self]) do + if type(self[v]) ~= "function" then + error(("Problem embedding method %q from library %q. Expected %q, got %q."):format(tostring(v), better_tostring(self), "function", type(self[v]))) + end + object[v] = self[v] + end + + if not mixinToObject[self] then + -- weak because objects come and go + mixinToObject[self] = setmetatable(newList(), weakKey) + end + if mixinToObject[self][object] then + error(("Cannot embed library %q into the same object %q more than once."):format(better_tostring(self), better_tostring(object)), 2) + end + mixinToObject[self][object] = true + if type(rawget(object, 'mixins')) == "table" then + object.mixins[self] = true + end + + local self_OnEmbed = self.OnEmbed + if self_OnEmbed then + local success, ret = pcall(self_OnEmbed, self, object) + if not success then + geterrorhandler()(ret) + end + end + + return object +end + +--[[--------------------------------------------------------------------------- +Notes: + * This is exported to all libraries + * Unembeds all the methods previously set to export onto a table. + * This will error if the library is not embedded on the object + * This will call :OnUnembed(object) on the library if it is available. +Arguments: + table - the table with which to export methods onto. +Returns: + The table provided, after embedding. +Example: + local LibMonkey = Rock:NewLibrary("LibMonkey-1.0", 50) + LibMonkey.FlingPoo = function(self) + return "Splat!" + end + LibMonkey:SetExportedMethods("FlingPoo") + -- later + local Darwin = {} + Rock("LibMonkey-1.0"):Embed(Darwin) + assert(Darwin:FlingPoo() == "Splat!") + Rock("LibMonkey-1.0"):Unembed(Darwin) + assert(Darwin.FlingPoo == nil) +-----------------------------------------------------------------------------]] +function Rock:Unembed(object) + if not exportedMethods[self] then + error(("Cannot call `Unembed' for library %q if `SetExportedMethods' has not been called."):format(better_tostring(self)), 2) + end + + if not mixinToObject[self] or not mixinToObject[self][object] then + error(("Cannot unembed library %q from object %q, since it is not embedded originally."):format(better_tostring(self), better_tostring(object)), 2) + end + local mixinToObject_self = mixinToObject[self] + mixinToObject_self[object] = nil + if not next(mixinToObject_self) then + mixinToObject[self] = del(mixinToObject_self) + end + + local mixin_OnUnembed = self.OnUnembed + if mixin_OnUnembed then + local success, ret = pcall(mixin_OnUnembed, self, object) + if not success then + geterrorhandler()(ret) + end + end + + for i,v in ipairs(exportedMethods[self]) do + object[v] = nil + end +end + +local function embedAce2Mixin(mixin, object) + if not mixinToObject[mixin] then + mixinToObject[mixin] = setmetatable(newList(), weakKey) + end + mixinToObject[mixin][object] = true + mixin:embed(object) +end + +local function embedLibStubMixin(mixin, object) + if not mixinToObject[mixin] then + mixinToObject[mixin] = setmetatable(newList(), weakKey) + end + mixinToObject[mixin][object] = true + mixin:Embed(object) +end + +--[[--------------------------------------------------------------------------- +Notes: + * create a new addon with the specified name. +Arguments: + string - name of the addon. + tuple - list of mixins with which to embed into this addon. +Returns: + addon + * table - the addon with which to manipulate +Example: + local MyAddon = Rock:NewAddon("MyAddon", "Mixin-1.0", "OtherMixin-2.0") +-----------------------------------------------------------------------------]] +function Rock:NewAddon(name, ...) + if type(name) ~= "string" then + error(("Bad argument #2 to `NewAddon'. Expected %q, got %q"):format("string", type(name)), 2) + end + if name:match("^Lib[A-Z]") then + error(("Bad argument #2 to `NewAddon'. Cannot start with %q, got %q."):format("Lib", name), 2) + end + if self == Rock and name:match("_") then + error(("Bad argument #2 to `NewAddon'. Cannot contain underscores, got %q."):format(name), 2) + end + + if addons[name] then + error(("Bad argument #2 to `NewAddon'. Addon %q already created."):format(name), 2) + end + local addon = setmetatable(newList(), addon_mt) + addon.name = name + + local mixinSet = newList() + + for i = 1, select('#', ...) do + local libName = select(i, ...) + if mixinSet[libName] then + error(("Bad argument #%d to `NewAddon'. %q already stated."):format(i+2, tostring(libName)), 2) + end + mixinSet[libName] = true + TryToLoadStandalone(libName) + local library = Rock:GetLibrary(libName, false, true) + if not library then + error(("Bad argument #%d to `NewAddon'. Library %q is not found."):format(i+2, tostring(libName)), 2) + end + + local style = 'rock' + + if not exportedMethods[library] then + local good = false + if AceLibrary then + local AceOO = AceLibrary:HasInstance("AceOO-2.0", false) and AceLibrary("AceOO-2.0") + if AceOO.inherits(library, AceOO.Mixin) then + good = true + style = 'ace2' + end + end + if not good and type(rawget(library, 'Embed')) == "function" then + good = true + style = 'libstub' + end + if not good then + error(("Bad argument #%d to `NewAddon'. Library %q is not a mixin."):format(i+2, tostring(libName)), 2) + end + end + + if library == Rock then + error(("Bad argument #%d to `NewAddon'. Cannot use %q as a mixin."):format(i+2, tostring(libName)), 2) + end + + if style == 'rock' then + library:Embed(addon) + elseif style == 'ace2' then + embedAce2Mixin(library, addon) + elseif style == 'libstub' then + embedLibStubMixin(library, addon) + end + end + + mixinSet = del(mixinSet) + + addons[name] = addon + pendingAddons[#pendingAddons+1] = addon + pendingAddonsEnable[#pendingAddonsEnable+1] = addon + addonToFolder[addon] = figureCurrentAddon(self == Rock and 2 or 4) + + frame:Show() + + return addon +end + +--[[--------------------------------------------------------------------------- +Arguments: + string - name of the addon. +Returns: + addon + * table or nil - the addon requested +Example: + local MyAddon = Rock:GetAddon("MyAddon") +-----------------------------------------------------------------------------]] +function Rock:GetAddon(name) + if type(name) ~= "string" then + return nil + end + local addon = addons[name] + if addon then + return addon + end + name = name:lower() + for k, v in pairs(addons) do + if k:lower() == name then + return v + end + end + return nil +end + +--[[--------------------------------------------------------------------------- +Arguments: + string or table - name of the addon or the addon itself. +Returns: + boolean - whether the addon requested exists. +Example: + local hasMyAddon = Rock:HasAddon("MyAddon") + -- or + local hasMyAddon = Rock:HasAddon(MyAddon) +-----------------------------------------------------------------------------]] +function Rock:HasAddon(name) + if type(name) == "string" then + local addon = addons[name] + if addon then + return true + end + name = name:lower() + for k, v in pairs(addons) do + if k:lower() == name then + return true + end + end + elseif type(name) == "table" then + for k,v in pairs(addons) do + if v == name then + return true + end + end + end + return false +end + +--[[--------------------------------------------------------------------------- +Returns: + an iterator to traverse all addons created with Rock. +Example: + for name, addon in Rock:IterateAddons() do + -- do something with name and addon + end +-----------------------------------------------------------------------------]] +function Rock:IterateAddons() + return pairs(addons) +end + +--[[--------------------------------------------------------------------------- +Arguments: + string - major version of the mixin library +Returns: + an iterator to traverse all objects that the given mixin has embedded into +Example: + local LibMonkey = Rock:NewLibrary("LibMonkey-1.0") + local Darwin = Rock:NewAddon("Darwin", "LibMonkey-1.0") + for object in LibMonkey:IterateMixinObjects("LibMonkey-1.0") do + assert(object == Darwin) + end +-----------------------------------------------------------------------------]] +function Rock:IterateMixinObjects(mixinName) + local mixin + if type(mixinName) == "table" then + mixin = mixinName + else + if type(mixinName) ~= "string" then + error(("Bad argument #2 to `IterateMixinObjects'. Expected %q or %q, got %q."):format("table", "string", type(mixinName)), 2) + end + mixin = libraries[mixinName] + end + local mixinToObject_mixin = mixinToObject[mixin] + if not mixinToObject_mixin then + return noop + end + return pairs(mixinToObject_mixin) +end + +local function iter(object, mixin) + mixin = next(mixinToObject, mixin) + if not mixin then + return nil + elseif mixinToObject[mixin][object] then + return mixin + end + return iter(object, mixin) -- try next mixin +end +--[[--------------------------------------------------------------------------- +Returns: + an iterator to traverse all mixins that an object has embedded +Example: + local LibMonkey = Rock:NewLibrary("LibMonkey-1.0") + local Darwin = Rock:NewAddon("Darwin", "LibMonkey-1.0") + for mixin in Rock:IterateObjectMixins(Darwin) do + assert(mixin == LibMonkey) + end +-----------------------------------------------------------------------------]] +function Rock:IterateObjectMixins(object) + if type(object) ~= "table" then + error(("Bad argument #2 to `IterateObjectMixins'. Expected %q, got %q."):format("table", type(object)), 2) + end + return iter, object, nil +end + +--[[--------------------------------------------------------------------------- +Arguments: + table - the object to check + string - the mixin to check +Returns: + boolean - whether the object has the given mixin embedded into it. +Example: + local LibMonkey = Rock:NewLibrary("LibMonkey-1.0") + local Darwin = Rock:NewAddon("Darwin", "LibMonkey-1.0") + assert(Rock:DoesObjectUseMixin(Darwin, "LibMonkey-1.0")) +-----------------------------------------------------------------------------]] +function Rock:DoesObjectUseMixin(object, mixinName) + if type(object) ~= "table" then + error(("Bad argument #2 to `IterateObjectMixins'. Expected %q, got %q."):format("table", type(object)), 2) + end + local mixin + if type(mixinName) == "table" then + mixin = mixinName + else + if type(mixinName) ~= "string" then + error(("Bad argument #3 to `IterateMiDoesObjectUseMixininObjects'. Expected %q or %q, got %q."):format("table", "string", type(mixinName)), 2) + end + mixin = libraries[mixinName] + end + if not mixin then + return false + end + + local mixinToObject_mixin = mixinToObject[mixin] + if not mixinToObject_mixin then + return false + end + return not not mixinToObject_mixin[object] +end + +Rock.UID_NUM = oldRock and oldRock.UID_NUM or 0 +--[[--------------------------------------------------------------------------- +Notes: + * This UID is not unique across sessions. If you save a UID in a saved variable, the same UID can be generated in another session. +Returns: + number - a unique number. +Example: + local UID = Rock:GetUID() +-----------------------------------------------------------------------------]] +function Rock:GetUID() + local num = Rock.UID_NUM + 1 + Rock.UID_NUM = num + return num +end + +local function unobfuscateEmail(email) + return email:gsub(" AT ", "@"):gsub(" DOT ", ".") +end +local function fix(char) + return ("%%%02x"):format(char:byte()) +end +local function urlencode(text) + return text:gsub("[^0-9A-Za-z]", fix) +end + +local url +local function makeURLFrame() + makeURLFrame = nil + local function bumpFrameLevels(frame, amount) + frame:SetFrameLevel(frame:GetFrameLevel()+amount) + local children = newList(frame:GetChildren()) + for _,v in ipairs(children) do + bumpFrameLevels(v, amount) + end + children = del(children) + end + -- some code borrowed from Prat here + StaticPopupDialogs["ROCK_SHOW_URL"] = { + text = not IsMacClient() and L["Press Ctrl-C to copy, then Alt-Tab out of the game, open your favorite web browser, and paste the link into the address bar."] or L["Press Cmd-C to copy, then Cmd-Tab out of the game, open your favorite web browser, and paste the link into the address bar."], + button2 = ACCEPT, + hasEditBox = 1, + hasWideEditBox = 1, + showAlert = 1, -- HACK : it's the only way I found to make de StaticPopup have sufficient width to show WideEditBox :( + + OnShow = function() + local editBox = _G[this:GetName() .. "WideEditBox"] + editBox:SetText(url) + editBox:SetFocus() + editBox:HighlightText(0) + editBox:SetScript("OnTextChanged", function() StaticPopup_EditBoxOnTextChanged() end) + + local button = _G[this:GetName() .. "Button2"] + button:ClearAllPoints() + button:SetWidth(200) + button:SetPoint("CENTER", editBox, "CENTER", 0, -30) + + _G[this:GetName() .. "AlertIcon"]:Hide() -- HACK : we hide the false AlertIcon + this:SetFrameStrata("FULLSCREEN_DIALOG") + bumpFrameLevels(this, 30) + end, + OnHide = function() + local editBox = _G[this:GetName() .. "WideEditBox"] + editBox:SetScript("OnTextChanged", nil) + this:SetFrameStrata("DIALOG") + bumpFrameLevels(this, -30) + end, + OnAccept = function() end, + OnCancel = function() end, + EditBoxOnEscapePressed = function() this:GetParent():Hide() end, + EditBoxOnTextChanged = function() + this:SetText(url) + this:SetFocus() + this:HighlightText(0) + end, + timeout = 0, + whileDead = 1, + hideOnEscape = 1 + } +end + +function OpenDonationFrame(self) + if makeURLFrame then + makeURLFrame() + end + + local donate = self.donate + if type(donate) ~= "string" then + donate = "Wowace" + end + local style, data = (":"):split(donate, 2) + style = style:lower() + if style ~= "website" and style ~= "paypal" then + style = "wowace" + end + if style == "wowace" then + url = "http://www.wowace.com/wiki/Donations" + elseif style == "website" then + url = data + else -- PayPal + local text = "https://www.paypal.com/cgi-bin/webscr?cmd=_xclick&business=" .. urlencode(unobfuscateEmail(data)) + local name + if type(self.title) == "string" then + name = self.title + elseif type(self.name) == "string" then + name = self.name + end + if name == MAJOR_VERSION then + name = "Rock" + end + if name then + name = name:gsub("|c%x%x%x%x%x%x%x%x", ""):gsub("|r", "") + text = text .. "&item_name=" .. urlencode(name) + end + url = text + end + + StaticPopup_Show("ROCK_SHOW_URL") +end +function OpenIssueFrame(self) + if makeURLFrame then + makeURLFrame() + end + + local issueTracker = self.issueTracker + if type(issueTracker) ~= "string" then + return + end + local style, data = (":"):split(issueTracker, 2) + style = style:lower() + if style ~= "website" and style ~= "wowace" then + return + end + if style == "wowace" then + url = "http://jira.wowace.com/secure/CreateIssue.jspa?pid=" .. data + elseif style == "website" then + url = data + end + + StaticPopup_Show("ROCK_SHOW_URL") +end +local function donate_hidden(addon) + return type(addon.donate) ~= "string" +end + +local function issue_hidden(addon) + return type(addon.issueTracker) ~= "string" +end + +-- #NODOC +function Rock:GetRockConfigOptions(addon) + return 'active', { + type = 'boolean', + name = L["Enabled"], + desc = L["Enable or disable this addon."], + get = 'IsActive', + set = 'ToggleActive', + handler = addon, + order = -1, + }, 'donate', { + type = 'execute', + name = L["Give donation"], + buttonText = L["Donate"], + desc = L["Give a much-needed donation to the author of this addon."], + func = OpenDonationFrame, + hidden = donate_hidden, + passValue = addon, + order = -2, + }, 'issue', { + type = 'execute', + name = L["File issue"], + buttonText = L["Report"], + desc = L["File a bug or request a new feature or an improvement to this addon."], + func = OpenIssueFrame, + hidden = issue_hidden, + passValue = addon, + order = -3, + } +end + +local function initAddon(addon, name) + name = addonToFolder[addon] or name or "" + -- TOC checks + if addon.title == nil then + addon.title = GetAddOnMetadata(name, "Title") + end + if type(addon.title) == "string" then + addon.title = addon.title:gsub("|c%x%x%x%x%x%x%x%x", ""):gsub("|r", ""):gsub("%-Rock%-$", ""):trim() + end + if addon.notes == nil then + addon.notes = GetAddOnMetadata(name, "Notes") + end + if type(addon.notes) == "string" then + addon.notes = addon.notes:trim() + end + if addon.version == nil then + addon.version = GetAddOnMetadata(name, "Version") + end + if type(addon.version) == "string" then + addon.version = addon.version:trim() + end + if addon.author == nil then + addon.author = GetAddOnMetadata(name, "Author") + end + if type(addon.author) == "string" then + addon.author = addon.author:trim() + end + if addon.credits == nil then + addon.credits = GetAddOnMetadata(name, "X-Credits") + end + if type(addon.credits) == "string" then + addon.credits = addon.credits:trim() + end + if addon.donate == nil then + addon.donate = GetAddOnMetadata(name, "X-Donate") + end + if type(addon.donate) == "string" then + addon.donate = addon.donate:trim() + end + if addon.issueTracker == nil then + addon.issueTracker = GetAddOnMetadata(name, "X-IssueTracker") + end + if type(addon.issueTracker) == "string" then + addon.issueTracker = addon.issueTracker:trim() + end + if addon.category == nil then + addon.category = GetAddOnMetadata(name, "X-Category") + end + if type(addon.category) == "string" then + addon.category = addon.category:trim() + end + if addon.email == nil then + addon.email = GetAddOnMetadata(name, "X-eMail") or GetAddOnMetadata(name, "X-Email") + end + if type(addon.email) == "string" then + addon.email = addon.email:trim() + end + if addon.license == nil then + addon.license = GetAddOnMetadata(name, "X-License") + end + if type(addon.license) == "string" then + addon.license = addon.license:trim() + end + if addon.website == nil then + addon.website = GetAddOnMetadata(name, "X-Website") + end + if type(addon.website) == "string" then + addon.website = addon.website:trim() + end + + for mixin in Rock:IterateObjectMixins(addon) do + local mixin_OnEmbedInitialize = mixin.OnEmbedInitialize + if mixin_OnEmbedInitialize then + local success, ret = pcall(mixin_OnEmbedInitialize, mixin, addon) + if not success then + geterrorhandler()(ret) + end + end + end + + local addon_OnInitialize = addon.OnInitialize + if addon_OnInitialize then + local success, ret = pcall(addon_OnInitialize, addon) + if not success then + geterrorhandler()(ret) + end + end + + if LibRockEvent then + Rock:DispatchEvent("AddonInitialized", addon) + end +end + + +local function manualEnable(addon) + for i,v in ipairs(pendingAddons) do + if v == addon then + return + end + end + if currentlyEnabledAddons[addon] then + return false + end + currentlyEnabledAddons[addon] = true + + local first = not addonsAlreadyEnabled[addon] + addonsAlreadyEnabled[addon] = true + + for mixin in Rock:IterateObjectMixins(addon) do + local mixin_OnEmbedEnable = mixin.OnEmbedEnable + if mixin_OnEmbedEnable then + local success, ret = pcall(mixin_OnEmbedEnable, mixin, addon, first) + if not success then + geterrorhandler()(ret) + end + end + end + local addon_OnEnable = addon.OnEnable + if addon_OnEnable then + local success, ret = pcall(addon_OnEnable, addon, first) + if not success then + geterrorhandler()(ret) + end + end + + if LibRockEvent then + Rock:DispatchEvent("AddonEnabled", addon, first) + end + + return true, first +end + +local function manualDisable(addon) + if not currentlyEnabledAddons[addon] then + return false + end + currentlyEnabledAddons[addon] = nil + + for mixin in Rock:IterateObjectMixins(addon) do + local mixin_OnEmbedDisable = mixin.OnEmbedDisable + if mixin_OnEmbedDisable then + local success, ret = pcall(mixin_OnEmbedDisable, mixin, addon) + if not success then + geterrorhandler()(ret) + end + end + end + local addon_OnDisable = addon.OnDisable + if addon_OnDisable then + local success, ret = pcall(addon_OnDisable, addon) + if not success then + geterrorhandler()(ret) + end + end + + if LibRockEvent then + Rock:DispatchEvent("AddonDisabled", addon) + end + return true +end + +local function enableAddon(addon) + for i,v in ipairs(pendingAddons) do + if v == addon then + return + end + end + if addon_mt___index.IsActive(addon) then + manualEnable(addon) + end +end + +-- #NODOC +-- This is used by internal Rock libraries after updating the active state. +function Rock:RecheckEnabledStates() + local changed = false + for _,addon in pairs(addons) do + local good = true + for _,a in ipairs(pendingAddonsEnable) do + if addon == a then + good = false + break + end + end + if good then + if addon_mt___index.IsActive(addon) then + if manualEnable(addon) then + changed = true + end + else + if manualDisable(addon) then + changed = true + end + end + end + end + if changed then + return self:RecheckEnabledStates() + end +end + +frame:UnregisterAllEvents() +frame:RegisterEvent("ADDON_LOADED") +frame:RegisterEvent("PLAYER_LOGIN") +local function runMainAddonLoadedChunk(name) + local tmp = newList() + tmp, pendingAddons = pendingAddons, tmp + for i, addon in ipairs(tmp) do + local folder = addonToFolder[addon] + if name and folder and not foldersLoaded[folder] then + for j = i, #tmp do + pendingAddons[#pendingAddons+1] = tmp[j] + tmp[j] = nil + end + break + end + initAddon(addon, name) + end + + if IsLoggedIn() then + for i, addon in ipairs(tmp) do + for j, v in ipairs(pendingAddonsEnable) do + if v == addon then + table_remove(pendingAddonsEnable, i) + break + end + end + enableAddon(addon) + end + for i, addon in ipairs(pendingAddonsEnable) do + local good = true + for j, v in ipairs(pendingAddons) do + if v == addon then + good = false + break + end + end + if not good then + break + end + pendingAddonsEnable[i] = nil + enableAddon(addon) + end + end + tmp = del(tmp) + for library, addonName in pairs(pendingLibraries) do + if not name or foldersLoaded[addonName] then + local success, ret = pcall(error, ("Library %q not finalized before ADDON_LOADED."):format(better_tostring(library)), 3) + geterrorhandler()(ret) + Rock:FinalizeLibrary((library:GetLibraryVersion())) + end + end + + if isStandalone then + local LibRock_1_0DB = _G.LibRock_1_0DB + if type(LibRock_1_0DB) ~= "table" then + LibRock_1_0DB = {} + _G.LibRock_1_0DB = LibRock_1_0DB + end + if type(LibRock_1_0DB.unitTests) ~= "table" then + LibRock_1_0DB.unitTests = {} + end + enableContracts = LibRock_1_0DB.contracts or false + unitTestDB = LibRock_1_0DB.unitTests + for namespace, data in pairs(unitTests) do + if not unitTestDB[namespace] then + if data then + del(data) + unitTests[namespace] = false + end + elseif data and (not name or data.addon == name) then + local stats = newList() + for i,v in ipairs(data) do + data[i] = nil + + local libs = newList() + for k,v in pairs(libraries) do + libs[k] = v + end + + local success, ret = pcall(v) + if not success then + geterrorhandler()(ret) + stats[i] = ret + else + stats[i] = false + end + + for k in pairs(libraries) do + if not libs[k] then + __removeLibrary(k) + end + end + libs = del(libs) + + local lastCount + repeat + lastCount = collectgarbage('count') + collectgarbage('collect') + until lastCount == collectgarbage('count') + end + del(data) + unitTests[namespace] = false + if #stats >= 1 then + local pass, fail = 0, 0 + for i,v in ipairs(stats) do + if v then + fail = fail + 1 + else + pass = pass + 1 + end + end + + local color + if fail == 0 then + _G.DEFAULT_CHAT_FRAME:AddMessage(("|cff00ff00%s: %d unit test(s) passed."):format(namespace, pass)) + elseif pass > 0 then + _G.DEFAULT_CHAT_FRAME:AddMessage(("|cffff0000%s: %d unit test(s) passed, %d unit test(s) failed."):format(namespace, pass, fail)) + else + _G.DEFAULT_CHAT_FRAME:AddMessage(("|cffff0000%s: %d unit test(s) failed."):format(namespace, fail)) + end + for i,v in ipairs(stats) do + if v then + _G.DEFAULT_CHAT_FRAME:AddMessage(("|cffff0000%s|r"):format(tostring(v))) + end + end + if fail > 0 then + _G.DEFAULT_CHAT_FRAME:AddMessage("|cffff0000----------|r") + end + end + stats = del(stats) + end + end + end + if isStandalone and name == MAJOR_VERSION then + Rock("LibRockEvent-1.0", false, true) -- load if possible + Rock("LibRockConsole-1.0", false, true) -- load if possible - I like the default chat commands + Rock("LibRockComm-1.0", false, true) -- load if possible - has version checking and the like + Rock("LibRockConfig-1.0", false, true) -- load if possible - LibRock-1.0 registers with it. + end + + for major, library in LibStub:IterateLibraries() do + manualFinalize(major, library) + end + + if IsLoggedIn() then + collectgarbage('collect') + end +end + +frame:Show() +frame:SetScript("OnUpdate", function(this, elapsed) + -- capture all un-initialized addons. + runMainAddonLoadedChunk() + collectgarbage('collect') + this:SetScript("OnUpdate", run) +end) +frame:SetScript("OnEvent", function(this, event, ...) + if event == "ADDON_LOADED" then + -- this creates a new table and flushes the old in case someone LoDs an addon inside ADDON_LOADED. + local name = ... + foldersLoaded[name] = true + runMainAddonLoadedChunk(name) + frame:Show() + elseif event == "PLAYER_LOGIN" then + for i, addon in ipairs(pendingAddonsEnable) do + local good = true + for _, a in ipairs(pendingAddons) do + if a == addon then + good = false + break + end + end + if good then + pendingAddonsEnable[i] = nil + enableAddon(addon) + end + end + collectgarbage('collect') + end +end) + +Rock:SetExportedMethods("SetExportedMethods", "Embed", "Unembed", "GetLibraryVersion") + +Rock:FinalizeLibrary(MAJOR_VERSION) + +for major, library in LibStub:IterateLibraries() do + manualFinalize(major, library) +end + +Rock:AddUnitTest(MAJOR_VERSION, function() + -- test recycling + local newList, newDict, newSet, del = Rock:GetRecyclingFunctions(MAJOR_VERSION .. "_UnitTest", "newList", "newDict", "newSet", "del", "Debug") + local t = newList("Alpha", "Bravo", "Charlie") + assert(t[1] == "Alpha") + assert(t[2] == "Bravo") + assert(t[3] == "Charlie") + t = del(t) + t = newList("Alpha", "Bravo", "Charlie") + -- check recycled table + assert(t[1] == "Alpha") + assert(t[2] == "Bravo") + assert(t[3] == "Charlie") + t = del(t) + t = newDict("Alpha", "Bravo", "Charlie", "Delta") + assert(t.Alpha == "Bravo") + assert(t.Charlie == "Delta") + t = del(t) + t = newSet("Alpha", "Bravo", "Charlie") + assert(t.Alpha) + assert(t.Bravo) + assert(t.Charlie) + t = del(t) + + local debug = recycleData.debugPools[MAJOR_VERSION .. "_UnitTest"] + assert(debug.num == 0) + t = newList() + assert(debug.num == 1) + t[1] = newList() + assert(debug.num == 2) + t[2] = newList() + assert(debug.num == 3) + t[1] = del(t[1]) + assert(debug.num == 2) + t[2] = del(t[2]) + assert(debug.num == 1) + t = del(t) + assert(debug.num == 0) +end) + +Rock:AddUnitTest(MAJOR_VERSION, function() + -- test :GetUID() + local t = {} + for i = 1, 10000 do + local uid = Rock:GetUID() + if t[i] then + error(("UID match for iteration %d, UID %s"):format(i, uid)) + end + t[i] = true + end +end) + +Rock:AddUnitTest(MAJOR_VERSION, function() + -- test basic creation and deletion + assert(not LibStub:GetLibrary("LibRockFakeLib-1.0", true)) + assert(not Rock:HasLibrary("LibRockFakeLib-1.0")) + local lib = Rock:NewLibrary("LibRockFakeLib-1.0", 1) + Rock:FinalizeLibrary("LibRockFakeLib-1.0") + lib = nil + assert(LibStub:GetLibrary("LibRockFakeLib-1.0", true)) + assert(Rock:HasLibrary("LibRockFakeLib-1.0")) + local good = false + for _, lib in pairs(libraries) do + if lib.name == "LibRockFakeLib-1.0" then + good = true + break + end + end + assert(good) + __removeLibrary("LibRockFakeLib-1.0") + for _, lib in pairs(libraries) do + assert(lib.name ~= "LibRockFakeLib-1.0") + end + assert(not LibStub:GetLibrary("LibRockFakeLib-1.0", true)) + assert(not Rock:HasLibrary("LibRockFakeLib-1.0")) +end) + +Rock:AddUnitTest(MAJOR_VERSION, function() + -- test library creation and the like + assert(not Rock:HasLibrary("LibRockFakeLib-1.0")) + for name in Rock:IterateLibraries() do + assert(name ~= "LibRockFakeLib-1.0") + end + + local myLib, oldLib = Rock:NewLibrary("LibRockFakeLib-1.0", 1) + assert(myLib) + assert(myLib.name == "LibRockFakeLib-1.0") + assert(not oldLib) + + assert(myLib:GetLibraryVersion() == "LibRockFakeLib-1.0") + assert(select(2, myLib:GetLibraryVersion()) == 1) + + local good = false + for name in Rock:IterateLibraries() do + if name == "LibRockFakeLib-1.0" then + good = true + break + end + end + assert(good) + assert(Rock:HasLibrary("LibRockFakeLib-1.0")) + assert(Rock:GetLibrary("LibRockFakeLib-1.0") == myLib) + assert(Rock("LibRockFakeLib-1.0") == myLib) + + assert(not Rock:IsLibraryMixin("LibRockFakeLib-1.0")) + function myLib:DoSomething() + return "Something" + end + myLib:SetExportedMethods("DoSomething") + assert(Rock:IsLibraryMixin("LibRockFakeLib-1.0")) + local t = {} + assert(not Rock:DoesObjectUseMixin(t, "LibRockFakeLib-1.0")) + assert(not t.DoSomething) + for mixin in Rock:IterateObjectMixins(t) do + assert(false) + end + for object in Rock:IterateMixinObjects("LibRockFakeLib-1.0") do + assert(false) + end + myLib:Embed(t) + assert(t:DoSomething() == "Something") + assert(Rock:DoesObjectUseMixin(t, "LibRockFakeLib-1.0")) + for mixin in Rock:IterateObjectMixins(t) do + assert(mixin == myLib) + end + for object in Rock:IterateMixinObjects("LibRockFakeLib-1.0") do + assert(object == t) + end + + Rock:FinalizeLibrary("LibRockFakeLib-1.0") + + local myNewLib, oldLib = Rock:NewLibrary("LibRockFakeLib-1.0", 2) + assert(myNewLib == myLib) + assert(oldLib) + assert(Rock:GetLibrary("LibRockFakeLib-1.0") == myLib) + + function myLib:DoSomething() + return "Something else" + end + function myLib:TrySomething() + return "Blah" + end + myLib:SetExportedMethods("DoSomething", "TrySomething") + assert(Rock:IsLibraryMixin("LibRockFakeLib-1.0")) + assert(t:DoSomething() == "Something else") + assert(t:TrySomething() == "Blah") + assert(Rock:DoesObjectUseMixin(t, "LibRockFakeLib-1.0")) + for mixin in Rock:IterateObjectMixins(t) do + assert(mixin == myLib) + end + for object in Rock:IterateMixinObjects("LibRockFakeLib-1.0") do + assert(object == t) + end + + Rock:FinalizeLibrary("LibRockFakeLib-1.0") + + local myNewLib, oldLib = Rock:NewLibrary("LibRockFakeLib-1.0", 3) + assert(myNewLib == myLib) + assert(oldLib) + assert(Rock:GetLibrary("LibRockFakeLib-1.0") == myLib) + + function myLib:DoSomething() + return "Something" + end + myLib:SetExportedMethods("DoSomething") + assert(Rock:IsLibraryMixin("LibRockFakeLib-1.0")) + assert(t:DoSomething() == "Something") + assert(t.TrySomething == nil) + assert(Rock:DoesObjectUseMixin(t, "LibRockFakeLib-1.0")) + for mixin in Rock:IterateObjectMixins(t) do + assert(mixin == myLib) + end + for object in Rock:IterateMixinObjects("LibRockFakeLib-1.0") do + assert(object == t) + end + + Rock:FinalizeLibrary("LibRockFakeLib-1.0") + + assert(not Rock:NewLibrary("LibRockFakeLib-1.0", 2)) -- out of date + assert(not Rock:NewLibrary("LibRockFakeLib-1.0", 3)) -- same revision +end) + +Rock:AddUnitTest(MAJOR_VERSION, function() + assert(not Rock:HasAddon("RockFakeAddon")) + for name in Rock:IterateAddons() do + assert(name ~= "RockFakeAddon") + end + + local myAddon = Rock:NewAddon("RockFakeAddon") + + assert(myAddon) + assert(myAddon.name == "RockFakeAddon") + + local good = false + for name in Rock:IterateAddons() do + if name == "RockFakeAddon" then + good = true + break + end + end + assert(good) + assert(Rock:HasAddon("RockFakeAddon")) + assert(Rock:GetAddon("RockFakeAddon") == myAddon) +end) + +Rock:AddUnitTest(MAJOR_VERSION, function() + -- test :OnLibraryLoad + local lib = Rock:NewLibrary("LibRockFakeLib-1.0", 1) + local triggered = false + function lib:OnLibraryLoad(major, instance) + if major == "LibRockFakeLib-2.0" then + triggered = true + end + end + Rock:FinalizeLibrary("LibRockFakeLib-1.0") + + local lib = Rock:NewLibrary("LibRockFakeLib-2.0", 1) + assert(not triggered) + Rock:FinalizeLibrary("LibRockFakeLib-2.0") + assert(triggered) + triggered = false + local lib = Rock:NewLibrary("LibRockFakeLib-2.0", 2) + assert(not triggered) + Rock:FinalizeLibrary("LibRockFakeLib-2.0") + assert(not triggered) +end)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/modules/FuBar_ReActionFu/lib/LibRock-1.0/LibRock-1.0.toc Wed Apr 02 23:31:13 2008 +0000 @@ -0,0 +1,14 @@ +## Interface: 20300 + +## Title: LibRock-1.0 +## Notes: AddOn development framework +## Author: ckknight +## X-Website: http://www.wowace.com +## X-Category: Library +## X-Donate: Paypal:ckknight AT gmail DOT com +## X-eMail: ckknight AT gmail DOT com +## X-License: LGPL v2.1 +## SavedVariables: LibRock_1_0DB +## OptionalDeps: !BugGrabber, !Swatter + +lib.xml \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/modules/FuBar_ReActionFu/lib/LibRock-1.0/LibStub/LibStub.lua Wed Apr 02 23:31:13 2008 +0000 @@ -0,0 +1,30 @@ +-- LibStub is a simple versioning stub meant for use in Libraries. http://www.wowace.com/wiki/LibStub for more info +-- LibStub is hereby placed in the Public Domain Credits: Kaelten, Cladhaire, ckknight, Mikk, Ammo, Nevcairiel, joshborke +local LIBSTUB_MAJOR, LIBSTUB_MINOR = "LibStub", 2 -- NEVER MAKE THIS AN SVN REVISION! IT NEEDS TO BE USABLE IN ALL REPOS! +local LibStub = _G[LIBSTUB_MAJOR] + +if not LibStub or LibStub.minor < LIBSTUB_MINOR then + LibStub = LibStub or {libs = {}, minors = {} } + _G[LIBSTUB_MAJOR] = LibStub + LibStub.minor = LIBSTUB_MINOR + + function LibStub:NewLibrary(major, minor) + assert(type(major) == "string", "Bad argument #2 to `NewLibrary' (string expected)") + minor = assert(tonumber(strmatch(minor, "%d+")), "Minor version must either be a number or contain a number.") + + local oldminor = self.minors[major] + if oldminor and oldminor >= minor then return nil end + self.minors[major], self.libs[major] = minor, self.libs[major] or {} + return self.libs[major], oldminor + end + + function LibStub:GetLibrary(major, silent) + if not self.libs[major] and not silent then + error(("Cannot find a library instance of %q."):format(tostring(major)), 2) + end + return self.libs[major], self.minors[major] + end + + function LibStub:IterateLibraries() return pairs(self.libs) end + setmetatable(LibStub, { __call = LibStub.GetLibrary }) +end
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/modules/FuBar_ReActionFu/lib/LibRock-1.0/lib.xml Wed Apr 02 23:31:13 2008 +0000 @@ -0,0 +1,5 @@ +<Ui xmlns="http://www.blizzard.com/wow/ui/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.blizzard.com/wow/ui/ +..\FrameXML\UI.xsd"> + <Script file="LibStub\LibStub.lua" /> + <Script file="LibRock-1.0.lua" /> +</Ui> \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/modules/FuBar_ReActionFu/lib/embeds.xml Wed Apr 02 23:31:13 2008 +0000 @@ -0,0 +1,12 @@ +<Ui xmlns="http://www.blizzard.com/wow/ui/" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://www.blizzard.com/wow/ui/..\FrameXML\UI.xsd"> + + <Include file="LibRock-1.0\lib.xml"/> + <Include file="LibFuBarPlugin-3.0\lib.xml"/> + + <Script file="AceLibrary\AceLibrary.lua"/> + <Script file="Dewdrop-2.0\Dewdrop-2.0.lua"/> + <Script file="Tablet-2.0\Tablet-2.0.lua"/> + +</Ui>
--- a/modules/ReAction_Action/ReAction_Action.lua Wed Apr 02 18:22:02 2008 +0000 +++ b/modules/ReAction_Action/ReAction_Action.lua Wed Apr 02 23:31:13 2008 +0000 @@ -195,11 +195,7 @@ -- TODO: re-implement ActionButton event handlers that don't do secure stuff -- this will probably cause taint, using right now for display/debugging purposes - f:SetScript("OnAttributeChanged", - function() - ActionButton_UpdateAction() - end - ) + f:SetScript("OnAttributeChanged", ActionButton_UpdateAction) f:SetAttribute("action", config.actionID) barFrame:SetAttribute("addchild",f) self.frame = f
--- a/modules/ReAction_ConfigUI/ReAction_ConfigUI.lua Wed Apr 02 18:22:02 2008 +0000 +++ b/modules/ReAction_ConfigUI/ReAction_ConfigUI.lua Wed Apr 02 23:31:13 2008 +0000 @@ -4,21 +4,18 @@ This modules creates and manages ReAction configuration elements, including: - - waterfall config menu system + - Interface Options panel - bar dragging and resizing control overlays - contextual menus Individual modules are responsible for populating these - configuration elements via the following functions: - - module:GetGlobalOptions( configModule ) - module:GetGlobalBarOptions( configModule ) - module:GetModuleOptions( configModule ) - module:GetBarConfigOptions( bar, configModule ) - module:GetBarMenuOptions( bar, configModule ) - - the ReAction_ConfigUI module is passed in as a parameter so that - option handlers can refresh the config UI. + configuration elements via ReAction:RegisterOptions(). The + valid values of 'context' are: + + - 'global' : added to the Global Settings tab + - 'module' : added to the Module Settings tab + - 'bar' : added to the Bar Settings tab + - 'barMenu' : shown on the bar contextual menu --]] @@ -26,71 +23,68 @@ local ReAction = ReAction local L = ReAction.L local _G = _G -local Waterfall = AceLibrary("Waterfall-1.0") +local InCombatLockdown = InCombatLockdown + local Dewdrop = AceLibrary("Dewdrop-2.0") -local print = ReAction.print -local InCombatLockdown = InCombatLockdown -- module declaration local moduleID = "ConfigUI" local module = ReAction:NewModule( moduleID, "AceEvent-3.0" - -- mixins go here ) -module.globalOptions = { - type = "group", - args = { - unlock = { - type = "toggle", - handler = module, - name = L["unlock"], - guiName = L["Unlock Bars"], - desc = L["Unlock bars for dragging and resizing with the mouse"], - get = function() return module.configMode end, - set = "SetConfigMode", - disabled = InCombatLockdown, - order = 1 - }, - config = { - type = "execute", - handler = module, - name = L["config"], - guiName = L["Configure..."], - desc = L["Open the configuration dialogue"], - func = "OpenConfig", - disabled = InCombatLockdown, - wfHidden = true, -- don't show this in the waterfall config - order = 2 - }, - } -} - module.configOptions = { type = "group", + childGroups = "tab", args = { + desc = { + type = "description", + name = L["Customizable replacement for Blizzard's Action Bars"], + }, global = { type = "group", name = L["Global Settings"], desc = L["Global configuration settings"], - args = { }, + args = { + unlock = { + type = "toggle", + handler = module, + name = L["Unlock Bars"], + desc = L["Unlock bars for dragging and resizing with the mouse"], + get = function() return module.configMode end, + set = function(info, value) module:SetConfigMode(value) end, + disabled = InCombatLockdown, + order = 1 + }, + }, order = 1, }, module = { type = "group", + childGroups = "select", name = L["Module Settings"], desc = L["Configuration settings for each module"], - args = { }, - order = 2, + args = { + configUI = { + type = "group", + name = "Config UI", + desc = "description", + args = { + foo = { + type = "toggle", + handler = module, + name = "foo", + desc = "description", + get = function() return true end, + set = function() end, + } + } + }, + }, + order = -1, }, - bar = { - type = "group", - name = L["Bars"], - desc = L["Configuration settings for bars"], - args = { }, - order = 3, - }, - } + }, + plugins = { } } -- module methods @@ -100,30 +94,41 @@ profile = { } } ) + self:InitializeOptions() + LibStub("AceConfig-3.0"):RegisterOptionsTable("ReAction",self.configOptions) + LibStub("AceConfigDialog-3.0"):AddToBlizOptions("ReAction", "ReAction") + self:RegisterEvent("PLAYER_REGEN_DISABLED") end -function module:OnEnable() - self:RegisterEvent("PLAYER_REGEN_DISABLED") - Waterfall:Register("ReAction", - "aceOptions", self.configOptions, - "treeLevels", nil, -- infinite - "colorR", 0.8, - "colorG", 0.65, - "colorB", 0, - "defaultPane", "global" ) +function module:InitializeOptions() + for _, m in pairs(ReAction:GetOptions("global")) do + for k, v in pairs(m) do + self.configOptions.args.global.args[k] = v + end + end + ReAction.RegisterCallback(self,"OnOptionsRegistered") end -function module:OnDisable() - self:UnregisterEvent("PLAYER_REGEN_DISABLED") - self:SetConfigMode(false) - Waterfall:UnRegister("ReAction") +function module:OnOptionsRegistered(evt, context, module, opts) + if context == "global" then + for k, v in pairs(opts) do + self.configOptions.args.global.args[k] = v + end + elseif context == "module" then + for k, v in pairs(opts) do + self.configOptions.args.module.args[k] = v + end + elseif context == "bar" then + + elseif context == "barMenu" then + + end end function module:PLAYER_REGEN_DISABLED() if self.configMode == true then UIErrorsFrame:AddMessage(L["ReAction config mode disabled during combat."]) self:SetConfigMode(false) - Waterfall:Close("ReAction") end end @@ -149,107 +154,20 @@ end end -local function refreshWaterfall() - module:RefreshConfig() -end - -local function SafeCall(module, method, ...) +local function safecall(module, method, ...) if module and type(module[method]) == "function" then - return module[method](...) - end -end - -function module:RefreshOptions() - local opts = self.configOptions.args - - for _, m in ReAction:IterateModules() do - local o = SafeCall(m,"GetGlobalOptions",self) - if o then - for k, v in pairs(o) do - opts.global.args[k] = v - end - end - end - - for _, m in ReAction:IterateModules() do - local o = SafeCall(m,"GetGlobalBarOptions",self) - if o then - for k, v in pairs(o) do - opts.bar.args[k] = v - end - end - end - - for _, m in ReAction:IterateModules() do - local o = SafeCall(m,"GetModuleOptions",self) - if o then - for k, v in pairs(o) do - opts.module.args[k] = v - end - end - end - - local barOpts = opts.bar.args - for name, bar in pairs(ReAction.bars) do - if bar then - if barOpts[name] == nil then - barOpts[name] = { - type = "group", - name = name, - desc = name, - handler = bar, - args = { - delete = { - type = "execute", - name = L["Delete Bar"], - desc = L["Remove the bar from the current profile"], - func = function() ReAction:EraseBar(bar); self:RefreshConfig() end - }, - rename = { - type = "text", - name = L["Rename Bar"], - desc = L["Set a name for the bar"], - get = "GetName", - set = function(name) ReAction:RenameBar(bar,name); self:RefreshConfig() end - } - } - } - end - if bar.modConfigOpts == nil then - bar.modConfigOpts = { } - end - for _, m in ReAction:IterateModules() do - local o = SafeCall(m,"GetBarConfigOptions",bar,self) - if o then - for k, v in pairs(o) do - barOpts[name].args[k] = v - end - end - end - end - end - -- remove obsolete bar tables - for name, opt in pairs(barOpts) do - if opt.type == "group" and ReAction.bars[name] == nil then - barOpts[name] = nil - end + return module[method](method, ...) end end function module:OpenConfig(bar) - self:RefreshOptions() Dewdrop:Close() - Waterfall:Open("ReAction",bar and "bar."..bar:GetName()) -end - -function module:RefreshConfig() - self:RefreshOptions() - Waterfall:Refresh("ReAction") + InterfaceOptionsFrame_OpenToFrame("ReAction") end function module:ApplyToBar(bar) if self.configMode then - bar:ShowControls(true) + bar:ShowControls(self.configMode) end end @@ -262,10 +180,6 @@ end end -function module:GetGlobalOptions() - return self.globalOptions.args -end - @@ -547,9 +461,9 @@ -- add bar type and status information to name local name = bar.name for _, m in ReAction:IterateModules() do - local suffix = SafeCall(m,"GetBarNameModifier",bar) + local suffix = safecall(m,"GetBarNameModifier",bar) if suffix then - name = format("%s %s",name,suffix) + name = ("%s %s"):format(name,suffix) end end @@ -611,7 +525,7 @@ self.modMenuOpts = { } end for _, m in ReAction:IterateModules() do - local opts = SafeCall(m,"GetBarMenuOptions",self,module) + local opts = safecall(m,"GetBarMenuOptions",self,module) if opts then for k, v in pairs(opts) do self.menuOpts.args[k] = v
--- a/modules/ReAction_ConfigUI/ReAction_ConfigUI.xml Wed Apr 02 18:22:02 2008 +0000 +++ b/modules/ReAction_ConfigUI/ReAction_ConfigUI.xml Wed Apr 02 23:31:13 2008 +0000 @@ -2,11 +2,7 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.blizzard.com/wow/ui/..\FrameXML\UI.xsd"> - <Script file="lib\AceLibrary\AceLibrary.lua"/> - <Script file="lib\AceOO-2.0\AceOO-2.0.lua"/> - <Script file="lib\Waterfall-1.0\Waterfall-1.0.lua"/> - <Script file="lib\Dewdrop-2.0\Dewdrop-2.0.lua"/> - - <Script file="ReAction_ConfigUI.lua"/> + <Include file="lib\embeds.xml"/> + <Script file="ReAction_ConfigUI.lua"/> </Ui> \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/modules/ReAction_ConfigUI/lib/embeds.xml Wed Apr 02 23:31:13 2008 +0000 @@ -0,0 +1,14 @@ +<Ui xmlns="http://www.blizzard.com/wow/ui/" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://www.blizzard.com/wow/ui/..\FrameXML\UI.xsd"> + + <Script file="AceLibrary\AceLibrary.lua"/> + <Script file="AceOO-2.0\AceOO-2.0.lua"/> + <Script file="Waterfall-1.0\Waterfall-1.0.lua"/> + <Script file="Dewdrop-2.0\Dewdrop-2.0.lua"/> + + <Include file="AceGUI-3.0\AceGUI-3.0.xml"/> + <Include file="AceConfig-3.0\AceConfig-3.0.xml"/> + +</Ui> + \ No newline at end of file
--- a/modules/ReAction_HideBlizzard/ReAction_HideBlizzard.lua Wed Apr 02 18:22:02 2008 +0000 +++ b/modules/ReAction_HideBlizzard/ReAction_HideBlizzard.lua Wed Apr 02 23:31:13 2008 +0000 @@ -29,33 +29,23 @@ self.hiddenFrame = CreateFrame("Frame") self.hiddenFrame:Hide() - - -- disable the buttons to hide/show the blizzard multiaction bars - -- see UIOptionsFrame.lua and .xml - -- This is called every time the options panel is shown, after it is set up - local disabledOptionsButtons = { - _G["UIOptionsFrameCheckButton"..UIOptionsFrameCheckButtons["SHOW_MULTIBAR1_TEXT"].index], - _G["UIOptionsFrameCheckButton"..UIOptionsFrameCheckButtons["SHOW_MULTIBAR2_TEXT"].index], - _G["UIOptionsFrameCheckButton"..UIOptionsFrameCheckButtons["SHOW_MULTIBAR3_TEXT"].index], - _G["UIOptionsFrameCheckButton"..UIOptionsFrameCheckButtons["SHOW_MULTIBAR4_TEXT"].index], - _G["UIOptionsFrameCheckButton"..UIOptionsFrameCheckButtons["ALWAYS_SHOW_MULTIBARS_TEXT"].index], - } - hooksecurefunc("UIOptionsFrame_Load", - function() - if self.db.profile.hide then - for _, f in pairs(disabledOptionsButtons) do - f.disabled = true - OptionsFrame_DisableCheckBox(f) - end - end - end - ) end function module:OnEnable() if self.db.profile.hide then self:HideAll(true) end + ReAction:RegisterOptions("global", self, { + hideBlizzard = { + type = "toggle", + handler = self, + name = L["Hide Blizzard Action Bars"], + desc = L["Hide the default main bar and extra action bars"], + get = "IsHidden", + set = function(info,value) self:SetHidden(value) end, + disabled = InCombatLockdown + } + }) end function module:OnDisable() @@ -143,19 +133,3 @@ end end -function module:GetGlobalOptions() - if self.globalOptions == nil then - self.globalOptions = { - hideBlizzard = { - type = "toggle", - handler = self, - name = L["Hide Default Action Bars"], - desc = L["Hide the default main bar and extra action bars"], - get = "IsHidden", - set = "SetHidden", - disabled = function() return InCombatLockdown() end - } - } - end - return self.globalOptions -end
--- a/modules/modules.xml Wed Apr 02 18:22:02 2008 +0000 +++ b/modules/modules.xml Wed Apr 02 23:31:13 2008 +0000 @@ -2,10 +2,19 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.blizzard.com/wow/ui/..\FrameXML\UI.xsd"> -<Include file="ReAction_Action\ReAction_Action.xml"/> +<!-- config/layout modules --> <Include file="ReAction_ConfigUI\ReAction_ConfigUI.xml"/> <Include file="ReAction_HideBlizzard\ReAction_HideBlizzard.xml"/> + +<!-- action button modules --> +<Include file="ReAction_Action\ReAction_Action.xml"/> + + + +<!-- deprecated <Include file="FuBar_ReActionFu\FuBar_ReActionFu.xml"/> +--> + <!-- in progress <Include file="ReAction_PetAction\ReAction_PetAction.xml"/>