WoWInterface SVN NightWatch

[/] [trunk/] [libs/] [LibSink-2.0/] [LibSink-2.0.lua] - Rev 12

Compare with Previous | Blame | View Log

--[[
Name: Sink-2.0
Revision: $Rev: 72 $
Author(s): Rabbit (rabbit.magtheridon@gmail.com), Antiarc (cheal@gmail.com)
Website: http://rabbit.nihilum.eu
Documentation: http://wiki.wowace.com/index.php/Sink-2.0
SVN: http://svn.wowace.com/wowace/trunk/SinkLib/Sink-2.0
Description: Library that handles chat output.
Dependencies: LibStub, SharedMedia-3.0 (optional)
License: GPL v2 or later.
]]

--[[
Copyright (C) 2008 Rabbit

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
]]

-----------------------------------------------------------------------
-- Sink-2.0

local SINK20 = "LibSink-2.0"
local SINK20_MINOR = 90000 + tonumber(("$Revision: 72 $"):match("(%d+)"))

local sink = LibStub:NewLibrary(SINK20, SINK20_MINOR)
if not sink then return end

-- Start upgrade
sink.storageForAddon = sink.storageForAddon or {}
sink.override = sink.override or {}
sink.msbt_registered_fonts = sink.msbt_registered_fonts or {}
sink.registeredScrollAreaFunctions = sink.registeredScrollAreaFunctions or {}
sink.handlers = sink.handlers or {}

sink.stickyAddons = sink.stickyAddons or {
        Blizzard = true,
        MikSBT = true,
        SCT = true,
        Parrot = true,
        BCF = true,
}

-- Upgrade complete

local L_DEFAULT = "Default"
local L_DEFAULT_DESC = "Route output from this addon through the first available handler, preferring scrolling combat text addons if available."
local L_ROUTE = "Route output from this addon through %s."
local L_SCT = "Scrolling Combat Text"
local L_MSBT = "MikSBT"
local L_BIGWIGS = "BigWigs"
local L_BCF = "BlinkCombatFeedback"
local L_UIERROR = "Blizzard Error Frame"
local L_CHAT = "Chat"
local L_BLIZZARD = "Blizzard FCT"
local L_RW = "Raid Warning"
local L_PARROT = "Parrot"
local L_CHANNEL = "Channel"
local L_OUTPUT = "Output"
local L_OUTPUT_DESC = "Where to route the output from this addon."
local L_SCROLL = "Sub section"
local L_SCROLL_DESC = "Set the sub section where messages should appear.\n\nOnly available for some output sinks."
local L_STICKY = "Sticky"
local L_STICKY_DESC = "Set messages from this addon to appear as sticky.\n\nOnly available for some output sinks."
local L_NONE = "None"
local L_NONE_DESC = "Hide all messages from this addon."
local L_NOTINCHANNEL = " (You tried sending this to the channel %s, but it appears you are not there.)"

local l = GetLocale()
if l == "koKR" then
        L_DEFAULT = "기본"
        L_DEFAULT_DESC = "처음으로 사용 가능한 트레이너를 통해 이 애드온으로부터 출력을 보냅니다."
        L_ROUTE = "%s|1을;를; 통해 이 애드온의 메시지를 출력합니다."
        L_SCT = "Scrolling Combat Text"
        L_MSBT = "MikSBT"
        L_BIGWIGS = "BigWigs"
        L_BCF = "블링크의 전투 메세지"
        L_UIERROR = "블리자드 오류 창"
        L_CHAT = "대화창"
        L_BLIZZARD = "블리자드 FCT"
        L_RW = "공격대 경보"
        L_PARROT = "Parrot"
        L_OUTPUT = "출력"
        L_OUTPUT_DESC = "어디에 이 애드온의 메시지를 출력할지 선택합니다."
        L_SCROLL = "스크롤 영역"
        L_SCROLL_DESC = "메시지를 출력할 스크룰 영역을 설정합니다.\n\nParrot, SCT나 MikSBT만 사용 가능합니다."
        L_STICKY = "점착"
        L_STICKY_DESC = "달라붙는 것처럼 보일 이 애드온의 메시지를 설정합니다.\n\n블리자드 FCT, Parrot, SCT나 MikSBT만 사용 가능합니다."
        L_NONE = "없음"
        L_NONE_DESC = "이 애드온의 모든 메시지를 숨김니다."
elseif l == "frFR" then
        L_DEFAULT = "Par défaut"
        L_DEFAULT_DESC = "Transmet la sortie de cet addon via le premier handler disponible, de préférence les textes de combat défilants s'il y en a."
        L_ROUTE = "Transmet la sortie de cet addon via %s."
        L_SCT = "Scrolling Combat Text"
        L_MSBT = "MikSBT"
        L_BIGWIGS = "BigWigs"
        L_BCF = "BlinkCombatFeedback"
        L_UIERROR = "Cadre des erreurs"
        L_CHAT = "Fenêtre de discussion"
        L_BLIZZARD = "TCF de Blizzard"
        L_RW = "Avertissement raid"
        L_PARROT = "Parrot"
        L_CHANNEL = "Canal"
        L_OUTPUT = "Sortie"
        L_OUTPUT_DESC = "Destination de la sortie de cet addon."
        L_SCROLL = "Sous-section"
        L_SCROLL_DESC = "Définit la sous-section où les messages doivent apparaitre.\n\nDisponible uniquement dans certains cas."
        L_STICKY = "En évidence"
        L_STICKY_DESC = "Fait en sortie que les messages de cet addon apparaissent en évidence.\n\nDisponible uniquement dans certains cas."
        L_NONE = "Aucun"
        L_NONE_DESC = "Masque tous les messages provenant de cet addon."
elseif l == "deDE" then
        L_DEFAULT = "Voreinstellung"
        L_DEFAULT_DESC = "Leitet die Ausgabe von diesem Addon zum ersten verfügbaren Ausgabeort, vorzugsweise Scrollende Kampf Text Addons wenn verfügbar."
        L_ROUTE = "Schickt die Meldungen dieses Addons an %s."
        L_SCT = "Scrolling Combat Text(SCT)"
        L_MSBT = "MikSBT"
        L_BIGWIGS = "BigWigs"
        L_BCF = "BlinkCombatFeedback"
        L_UIERROR = "Blizzard's Fehler Fenster"
        L_CHAT = "Im Chat"
        L_BLIZZARD = "Blizzard's schwebenden Kampftext"
        L_RW = "Schlachtzug's Warnung"
        L_PARROT = "Parrot"
        L_OUTPUT = "Ausgabe"
        L_OUTPUT_DESC = "Wohin die Meldungen des Addons gesendet werden soll."
        L_SCROLL = "Scroll Bereich"
        L_SCROLL_DESC = "Setzt die Scroll Bereich, wo die Meldungen erscheinen sollen.\n\nNur verfügbar für Parrot, SCT oder MikSBT."
        L_STICKY = "Stehend"
        L_STICKY_DESC = "Läßt Nachrichten von diesem Addon als stehende Nachrichten erscheinen.\n\nNur verfügbar für Blizzard FCT, Parrot, SCT oder MikSBT."
        L_NONE = "Nirgends"
        L_NONE_DESC = "Versteckt alle Meldungen von diesem Addon."
elseif l == "zhCN" then
        L_DEFAULT = "默认"
        L_DEFAULT_DESC = "插件的输出方式取决于第一个可用插件,例如有 SCT 插件,则优先使用。"
        L_ROUTE = "经由%s显示信息。"
        L_SCT = "SCT"
        L_MSBT = "MikSBT"
        L_BIGWIGS = "BigWigs"
        L_BCF = "BlinkCombatFeedback"
        L_UIERROR = "Blizzard 错误框体"
        L_CHAT = "聊天框体"
        L_BLIZZARD = "系统自带滚动战斗信息"
        L_RW = "团队警告"
        L_PARROT = "Parrot"
        L_CHANNEL = "频道"
        L_OUTPUT = "输出模式"
        L_OUTPUT_DESC = "设置显示位置。"
        L_SCROLL = "滚动区域"
        L_SCROLL_DESC = "设置滚动信息显示位置。\n\n只有 Parrot、SCT 及 MikSBT 支持。"
        L_STICKY = "固定"
        L_STICKY_DESC = "设置信息固定显示位置。\n\n只有系统自带滚动战斗信息、Parrot、SCT 及 MikSBT 支持。"
        L_NONE = "隐藏"
        L_NONE_DESC = "隐藏所有来自插件的信息。"
elseif l == "zhTW" then
        L_DEFAULT = "預設"
        L_DEFAULT_DESC = "插件輸出經由第一個可使用的處理器顯示,如果有 SCT 的話,則優先使用。"
        L_ROUTE = "插件輸出經由%s顯示。"
        L_SCT = "SCT"
        L_MSBT = "MikSBT"
        L_BIGWIGS = "BigWigs"
        L_BCF = "BlinkCombatFeedback"
        L_UIERROR = "Blizzard 錯誤訊息框架"
        L_CHAT = "聊天視窗"
        L_BLIZZARD = "Blizzard 浮動戰鬥文字"
        L_RW = "團隊警告"
        L_PARROT = "Parrot"
        L_OUTPUT = "顯示模式"
        L_OUTPUT_DESC = "插件輸出經由哪裡顯示。"
        L_SCROLL = "滾動區域"
        L_SCROLL_DESC = "設定滾動訊息出現位置。\n\n只有 Parrot,SCT 及 MikSBT 有支援。"
        L_STICKY = "固定"
        L_STICKY_DESC = "設定使用固定訊息。\n\n只有 Blizzard 浮動戰鬥文字,Parrot,SCT 及 MikSBT 有支援。"
        L_NONE = "隱藏"
        L_NONE_DESC = "隱藏所有插件輸出。"
elseif l == "ruRU" then
        L_DEFAULT = "По умолчанию"
        L_DEFAULT_DESC = "Маршрут вывода сообщений данного аддона через первое доступное устройство, предпочитая доступные аддоны прокрутки текста боя."
        L_ROUTE = "Маршрут вывода сообщений данного аддона через %s."
        L_SCT = "SCT"
        L_MSBT = "MikSBT"
        L_BIGWIGS = "BigWigs"
        L_BCF = "BlinkCombatFeedback"
        L_UIERROR = "Фрейм ошибок Blizzard"
        L_CHAT = "Чат"
        L_BLIZZARD = "Blizzard FCT"
        L_RW = "Объявление рейду"
        L_PARROT = "Parrot"
        L_CHANNEL = "Канал"
        L_OUTPUT = "Вывод"
        L_OUTPUT_DESC = "Куда выводить сообщения данного аддона."
        L_SCROLL = "Область прокрутки"
        L_SCROLL_DESC = "Назначить область прокрутки куда должны выводиться сообщения.\n\nДоступно только для Parrotа, SCT или MikSBT."
        L_STICKY = "Клейкий"
        L_STICKY_DESC = "Сделать сообщения данного аддона клейкими.\n\nДоступно только для Blizzard FCT, Parrot, SCT или MikSBT."
        L_NONE = "Нету"
        L_NONE_DESC = "Скрыть все сообщения данного аддона."
end

local SML = LibStub("LibSharedMedia-3.0", true)

local _G = getfenv(0)

local function getSticky(addon)
        return sink.storageForAddon[addon] and sink.storageForAddon[addon].sink20Sticky or nil
end

-- Thanks to Antiarc and his Soar-1.0 library for most of the 'meat' of the
-- sink-specific functions.

local function parrot(addon, text, r, g, b, font, size, outline, sticky, loc, icon)
        local location = sink.storageForAddon[addon] and sink.storageForAddon[addon].sink20ScrollArea or "Notification"
        local s = getSticky(addon) or sticky
        Parrot:ShowMessage(text, location, s, r, g, b, font, size, outline, icon)
end

local sct_color = {}
local function sct(addon, text, r, g, b, font, size, outline, sticky, _, icon)
        sct_color.r, sct_color.g, sct_color.b = r, g, b
        local loc = sink.storageForAddon[addon] and sink.storageForAddon[addon].sink20ScrollArea or "Messages"
        local location = (loc == "Outgoing" and SCT.FRAME1) or (loc == "Incoming" and SCT.FRAME2) or SCT.MSG
        local s = getSticky(addon) or sticky
        SCT:DisplayCustomEvent(text, sct_color, s, location, nil, icon)
end

local msbt_outlines = {["NORMAL"] = 1, ["OUTLINE"] = 2, ["THICKOUTLINE"] = 3}
local function msbt(addon, text, r, g, b, font, size, outline, sticky, _, icon)
        if font and SML and not sink.msbt_registered_fonts[font] then
                MikSBT.RegisterFont(font, SML:Fetch("font", font))
                sink.msbt_registered_fonts[font] = true
        end
        local location = sink.storageForAddon[addon] and sink.storageForAddon[addon].sink20ScrollArea or MikSBT.DISPLAYTYPE_NOTIFICATION
        local s = getSticky(addon) or sticky
        MikSBT.DisplayMessage(text, location, s, r * 255, g * 255, b * 255, size, font, msbt_outlines[outline], icon)
end

local bcf_outlines = {NORMAL = "", OUTLINE = "OUTLINE", THICKOUTLINE = "THICKOUTLINE"}
local function bcf(addon, text, r, g, b, font, size, outline, sticky, _, icon)
        if icon then text = "|T"..icon..":20:20:-5|t"..text end
        local loc = sink.storageForAddon[addon] and sink.storageForAddon[addon].sink20ScrollArea or "Sticky"
        local s = getSticky(addon) or sticky
        BlinkCombatFeedback:DisplayCustomEvent({display = {msg = text, color = ("%02x%02x%02x"):format(r * 255, g * 255, b * 255), scrollArea = loc, scrollType = s and "Sticky" or "up", size = size, outling = bcf_outlines[outline], align = "center", font = font}})
end

local function blizzard(addon, text, r, g, b, font, size, outline, sticky, _, icon)
        if icon then text = "|T"..icon..":20:20:-5|t"..text end
        if tostring(SHOW_COMBAT_TEXT) ~= "0" then
                local s = getSticky(addon) or sticky
                CombatText_AddMessage(text, CombatText_StandardScroll, r, g, b, s and "crit" or nil, false)
        else
                UIErrorsFrame:AddMessage(text, r, g, b, 1.0)
        end
end

sink.channelMapping = sink.channelMapping or {
        [SAY] = "SAY",
        [PARTY] = "PARTY",
        [BATTLEGROUND] = "BATTLEGROUND",
        [GUILD_CHAT] = "GUILD",
        [OFFICER_CHAT] = "OFFICER",
        [YELL] = "YELL",
        [RAID] = "RAID",
        [RAID_WARNING] = "RAID_WARNING",
        [GROUP] = "GROUP",
}
sink.frame = sink.frame or CreateFrame("Frame")
sink.frame:RegisterEvent("CHANNEL_UI_UPDATE")
sink.frame:RegisterEvent("PLAYER_ENTERING_WORLD")
do
        local newChannels = {}
        local function loop(...)
                wipe(newChannels)
                for i = 1, select("#", ...), 2 do
                        local id, name = select(i, ...)
                        newChannels[name] = true
                end
                for k, v in pairs(sink.channelMapping) do
                        if v == "CHANNEL" and not newChannels[k] then
                                sink.channelMapping[k] = nil
                        end
                end
                for k in pairs(newChannels) do sink.channelMapping[k] = "CHANNEL" end
        end
        local function rescanChannels() loop(GetChannelList()) end
        sink.frame:SetScript("OnEvent", rescanChannels)
        rescanChannels()
end

local function channel(addon, text)
        -- Sanitize the text, remove all color codes.
        text = text:gsub("(|c%x%x%x%x%x%x%x%x)", ""):gsub("(|r)", "")
        local loc = sink.storageForAddon[addon] and sink.storageForAddon[addon].sink20ScrollArea or "SAY"
        local chan = sink.channelMapping[loc]
        if chan == "GROUP" then
--              chan = select(2, IsInInstance()) == "pvp" and "BATTLEGROUND" or (UnitInRaid("player") and "RAID" or "PARTY")
                chan = UnitInRaid("player") and "RAID" or "PARTY"
                if chan == "PARTY" and GetNumPartyMembers() == 0 then chan = "SAY" end
        elseif chan == "CHANNEL" then
                local id, name = GetChannelName(loc)
                if name then
                        SendChatMessage(text, "CHANNEL", nil, id)
                else
                        print(text .. L_NOTINCHANNEL)
                end
                return
        end
        SendChatMessage(text, chan or "SAY")
end

local function chat(addon, text, r, g, b, _, _, _, _, _, icon)
        if icon then text = "|T"..icon..":15|t"..text end
        DEFAULT_CHAT_FRAME:AddMessage(text, r, g, b)
end

local function uierror(addon, text, r, g, b, _, _, _, _, _, icon)
        if icon then text = "|T"..icon..":20:20:-5|t"..text end
        UIErrorsFrame:AddMessage(text, r, g, b, 1.0)
end

local rw
do
        local white = {r = 1, g = 1, b = 1}
        function rw(addon, text, r, g, b, _, _, _, _, _, icon)
                if r or g or b then
                        local c = "|cff" .. string.format("%02x%02x%02x", (r or 0) * 255, (g or 0) * 255, (b or 0) * 255)
                        text = c .. text .. "|r"
                end
                if icon then text = "|T"..icon..":20:20:-5|t"..text end
                RaidNotice_AddMessage(RaidWarningFrame, text, white)
        end
end

local function noop() --[[ noop! ]] end

local handlerPriority = { "Parrot", "SCT", "MikSBT", "BCF" }
-- Thanks to ckk for these
local customHandlersEnabled = {
        Parrot = function()
                if not _G.Parrot then return end
                return _G.Parrot.IsEnabled and _G.Parrot:IsEnabled() or _G.Parrot:IsActive()
        end,
        SCT = function()
                return _G.SCT and _G.SCT:IsEnabled()
        end,
        BCF = function()
                return bcfDB and bcfDB["enable"]
        end,
}

-- Default to version 5 or higher now
local msbtVersion = tonumber(string.match(GetAddOnMetadata("MikScrollingBattleText", "Version") or "","^%d+\.%d+")) or 5
local isMSBTFive = math.floor(msbtVersion) > 4 and true or nil
if isMSBTFive then
        customHandlersEnabled.MikSBT = function()
                return _G.MikSBT and not _G.MikSBT.IsModDisabled()
        end
else
        customHandlersEnabled.MikSBT = function()
                return _G.MikSBT and _G.MSBTProfiles and _G.MSBTProfiles.GetSavedVariables() and not MSBTProfiles.GetSavedVariables().UserDisabled
        end
end

local currentHandler = nil
local function getPrioritizedSink()
        if currentHandler then
                local check = customHandlersEnabled[currentHandler]
                if check and check() then
                        return sink.handlers[currentHandler]
                end
        end
        for i, v in next, handlerPriority do
                local check = customHandlersEnabled[v]
                if check and check() then
                        currentHandler = v
                        return sink.handlers[v]
                end
        end
        if SHOW_COMBAT_TEXT and tostring(SHOW_COMBAT_TEXT) ~= "0" then
                return blizzard
        end
        return chat
end

local function pour(addon, text, r, g, b, ...)
        local func = sink.override and sink.handlers[sink.override] or nil
        if not func and sink.storageForAddon[addon] and sink.storageForAddon[addon].sink20OutputSink then
                local h = sink.storageForAddon[addon].sink20OutputSink
                func = sink.handlers[h]
                -- If this sink is not available now, find one manually.
                if customHandlersEnabled[h] and not customHandlersEnabled[h]() then
                        func = nil
                end
        end
        if not func then
                func = getPrioritizedSink()
        end
        if not func then func = chat end
        func(addon, text, r or 1, g or 1, b or 1, ...)
end

function sink:Pour(textOrAddon, ...)
        local t = type(textOrAddon)
        if t == "string" then
                pour(self, textOrAddon, ...)
        elseif t == "number" then
                pour(self, tostring(textOrAddon), ...)
        elseif t == "table" then
                pour(textOrAddon, ...)
        else
                error("Invalid argument 2 to :Pour, must be either a string or a table.")
        end
end

local sinks
do
        -- Maybe we want to hide them instead of disable
        local function shouldDisableSCT()
                return not _G.SCT
        end
        local function shouldDisableMSBT()
                return not _G.MikSBT
        end
        local function shouldDisableBCF()
                return not ( bcfDB and bcfDB["enable"] )
        end
        local function shouldDisableParrot()
                return not _G.Parrot
        end
        local function shouldDisableFCT()
                return not SHOW_COMBAT_TEXT or tostring(SHOW_COMBAT_TEXT) == "0"
        end

        local sctFrames = {"Incoming", "Outgoing", "Messages"}
        local msbtFrames = nil
        local tmp = {}
        local function getScrollAreasForAddon(addon)
                if type(addon) ~= "string" then return nil end
                if addon == "Parrot" then
                        if Parrot.GetScrollAreasChoices then
                                return Parrot:GetScrollAreasChoices()
                        else
                                return Parrot:GetScrollAreasValidate()
                        end
                elseif addon == "MikSBT" then
                        if isMSBTFive then
                                if not msbtFrames then
                                        msbtFrames = {}
                                        for key, name in MikSBT.IterateScrollAreas() do
                                                table.insert(msbtFrames, name)
                                        end
                                end
                                return msbtFrames
                        else
                                return MikSBT.GetScrollAreaList()
                        end
                elseif addon == "BCF" then
                        if bcfDB then
                                local bcfAreas = {}
                                for i = 1, #bcfDB["scrollAreas"] do
                                        bcfAreas[#bcfAreas + 1] = bcfDB["scrollAreas"][i]["name"]
                                end
                                return bcfAreas
                        end
                elseif addon == "SCT" then
                        return sctFrames
                elseif addon == "Channel" then
                        wipe(tmp)
                        for k in pairs(sink.channelMapping) do
                                tmp[#tmp + 1] = k
                        end
                        return tmp
                elseif sink.registeredScrollAreaFunctions[addon] then
                        return sink.registeredScrollAreaFunctions[addon]()
                end
                return nil
        end

        local emptyTable, args, options = {}, {}, {}
        sinks = {
                Default = {L_DEFAULT, L_DEFAULT_DESC},
                SCT = {L_SCT, nil, shouldDisableSCT},
                MikSBT = {L_MSBT, nil, shouldDisableMSBT},
                BCF = {L_BCF, nil, shouldDisableBCF},
                Parrot = {L_PARROT, nil, shouldDisableParrot},
                Blizzard = {L_BLIZZARD, nil, shouldDisableFCT},
                RaidWarning = {L_RW},
                ChatFrame = {L_CHAT},
                Channel = {L_CHANNEL},
                UIErrorsFrame = {L_UIERROR},
                None = {L_NONE, L_NONE_DESC}
        }

        local function getAce2SinkOptions(key, opts)
                local name, desc, hidden = unpack(opts)
                args["Ace2"][key] = {
                        type = "toggle",
                        name = name,
                        desc = desc or L_ROUTE:format(name),
                        isRadio = true,
                        hidden = hidden
                }
        end

        function sink.GetSinkAce2OptionsDataTable(addon)
                options["Ace2"][addon] = options["Ace2"][addon] or {
                        output = {
                                type = "group",
                                name = L_OUTPUT,
                                desc = L_OUTPUT_DESC,
                                pass = true,
                                get = function(key)
                                        if not sink.storageForAddon[addon] then
                                                return "Default"
                                        end
                                        if tostring(key) == "nil" then
                                                -- Means AceConsole wants to list the output option,
                                                -- so we should show which sink is currently used.
                                                return sink.storageForAddon[addon].sink20OutputSink or L_DEFAULT
                                        end
                                        if key == "ScrollArea" then
                                                return sink.storageForAddon[addon].sink20ScrollArea
                                        elseif key == "Sticky" then
                                                return sink.storageForAddon[addon].sink20Sticky
                                        else
                                                if sink.storageForAddon[addon].sink20OutputSink == key then
                                                        local sa = getScrollAreasForAddon(key)
                                                        options["Ace2"][addon].output.args.ScrollArea.validate = sa or emptyTable
                                                        options["Ace2"][addon].output.args.ScrollArea.disabled = not sa
                                                        options["Ace2"][addon].output.args.Sticky.disabled = not sink.stickyAddons[key]
                                                end
                                                return sink.storageForAddon[addon].sink20OutputSink and sink.storageForAddon[addon].sink20OutputSink == key or nil
                                        end
                                end,
                                set = function(key, value)
                                        if not sink.storageForAddon[addon] then return end
                                        if key == "ScrollArea" then
                                                sink.storageForAddon[addon].sink20ScrollArea = value
                                        elseif key == "Sticky" then
                                                sink.storageForAddon[addon].sink20Sticky = value
                                        elseif value then
                                                local sa = getScrollAreasForAddon(key)
                                                options["Ace2"][addon].output.args.ScrollArea.validate = sa or emptyTable
                                                options["Ace2"][addon].output.args.ScrollArea.disabled = not sa
                                                options["Ace2"][addon].output.args.Sticky.disabled = not sink.stickyAddons[key]
                                                sink.storageForAddon[addon].sink20OutputSink = key
                                        end
                                end,
                                args = args["Ace2"],
                                disabled = function()
                                        return (type(addon.IsActive) == "function" and not addon:IsActive()) or nil
                                end
                        }
                }
                return options["Ace2"][addon]
        end

        -- Ace3 options data table format
        local function getAce3SinkOptions(key, opts)
                local name, desc, hidden = unpack(opts)
                args["Ace3"][key] = {
                        type = "toggle",
                        name = name,
                        desc = desc or L_ROUTE:format(name),
                        hidden = hidden
                }
        end

        function sink.GetSinkAce3OptionsDataTable(addon)
                if not options["Ace3"][addon] then
                        options["Ace3"][addon] = {
                                type = "group",
                                name = L_OUTPUT,
                                desc = L_OUTPUT_DESC,
                                args = args["Ace3"],
                                get = function(info)
                                        local key = info[#info]
                                        if not sink.storageForAddon[addon] then
                                                return "Default"
                                        end
                                        if tostring(key) == "nil" then
                                                -- Means AceConsole wants to list the output option,
                                                -- so we should show which sink is currently used.
                                                return sink.storageForAddon[addon].sink20OutputSink or L_DEFAULT
                                        end
                                        if key == "ScrollArea" then
                                                return sink.storageForAddon[addon].sink20ScrollArea
                                        elseif key == "Sticky" then
                                                return sink.storageForAddon[addon].sink20Sticky
                                        else
                                                if sink.storageForAddon[addon].sink20OutputSink == key then
                                                        local sa = getScrollAreasForAddon(key)
                                                        if sa then
                                                                for k,v in ipairs(sa) do
                                                                        sa[k] = nil
                                                                        sa[v] = v
                                                                end
                                                        end
                                                        options["Ace3"][addon].args.ScrollArea.values = sa or emptyTable
                                                        options["Ace3"][addon].args.ScrollArea.disabled = not sa
                                                        options["Ace3"][addon].args.Sticky.disabled = not sink.stickyAddons[key]
                                                end
                                                return sink.storageForAddon[addon].sink20OutputSink and sink.storageForAddon[addon].sink20OutputSink == key or nil
                                        end
                                end,
                                set = function(info, v)
                                        local key = info[#info]
                                        if not sink.storageForAddon[addon] then return end
                                        if key == "ScrollArea" then
                                                sink.storageForAddon[addon].sink20ScrollArea = v
                                        elseif key == "Sticky" then
                                                sink.storageForAddon[addon].sink20Sticky = v
                                        elseif v then
                                                local sa = getScrollAreasForAddon(key)
                                                if sa then
                                                        for k,v in ipairs(sa) do
                                                                sa[k] = nil
                                                                sa[v] = v
                                                        end
                                                end
                                                options["Ace3"][addon].args.ScrollArea.values = sa or emptyTable
                                                options["Ace3"][addon].args.ScrollArea.disabled = not sa
                                                options["Ace3"][addon].args.Sticky.disabled = not sink.stickyAddons[key]
                                                sink.storageForAddon[addon].sink20OutputSink = key
                                        end
                                end,
                                disabled = function()
                                        return (type(addon.IsEnabled) == "function" and not addon:IsEnabled()) or nil
                                end,
                        }
                end
                return options["Ace3"][addon]
        end

        local sinkOptionGenerators = {
                ["Ace2"] = getAce2SinkOptions,
                ["Ace3"] = getAce3SinkOptions
        }
        for generatorName, generator in pairs(sinkOptionGenerators) do
                options[generatorName] = options[generatorName] or {}
                args[generatorName] = args[generatorName] or {}
                for name, opts in pairs(sinks) do
                        generator(name, opts)
                end
        end

        args["Ace2"].ScrollArea = {
                type = "text",
                name = L_SCROLL,
                desc = L_SCROLL_DESC,
                validate = emptyTable,
                order = -1,
                disabled = true
        }
        args["Ace2"].Sticky = {
                type = "toggle",
                name = L_STICKY,
                desc = L_STICKY_DESC,
                validate = emptyTable,
                order = -2,
                disabled = true
        }

        args["Ace3"].ScrollArea = {
                type = "select",
                name = L_SCROLL,
                desc = L_SCROLL_DESC,
                values = emptyTable,
                order = -1,
                disabled = true
        }
        args["Ace3"].Sticky = {
                type = "toggle",
                name = L_STICKY,
                desc = L_STICKY_DESC,
                order = -2,
                disabled = true
        }

        function sink:RegisterSink(shortName, name, desc, func, scrollAreaFunc, hasSticky)
                assert(type(shortName) == "string")
                assert(type(name) == "string")
                assert(type(desc) == "string" or desc == nil)
                assert(type(func) == "function" or type(func) == "string")
                assert(type(scrollAreas) == "function" or scrollAreas == nil)
                assert(type(hasSticky) == "boolean" or hasSticky == nil)

                if sinks[shortName] or sink.handlers[shortName] then
                        error("There's already a sink by the short name %q.", shortName)
                end
                sinks[shortName] = {name, desc}
                -- Save it for library upgrades.
                if not sink.registeredSinks then sink.registeredSinks = {} end
                sink.registeredSinks[shortName] = sinks[shortName]

                if type(func) == "function" then
                        sink.handlers[shortName] = func
                else
                        sink.handlers[shortName] = function(...)
                                self[func](self, ...)
                        end
                end
                if type(scrollAreaFunc) == "function" then
                        sink.registeredScrollAreaFunctions[shortName] = scrollAreaFunc
                elseif type(scrollAreaFunc) == "string" then
                        sink.registeredScrollAreaFunctions[shortName] = function(...)
                                return self[scrollAreaFunc](self, ...)
                        end
                end
                sink.stickyAddons[shortName] = hasSticky and true or nil

                for k, v in pairs(sinkOptionGenerators) do
                        v(shortName, sinks[shortName])
                end
        end
end

function sink.SetSinkStorage(addon, storage)
        assert(type(addon) == "table")
        assert(type(storage) == "table", "Storage must be a table")
        sink.storageForAddon[addon] = storage
end

-- Sets a sink override for -all- addons, librarywide.
function sink:SetSinkOverride(override)
        assert(type(override) == "string" or override == nil)
        if override and not sink.handlers[override] then
                error("There's no %q sink.", override)
        end
        sink.override = override
end

-- Put this at the bottom, because we need the local functions to exist first.
local handlers = {
        Parrot = parrot,
        SCT = sct,
        MikSBT = msbt,
        BCF = bcf,
        ChatFrame = chat,
        Channel = channel,
        UIErrorsFrame = uierror,
        Blizzard = blizzard,
        RaidWarning = rw,
        None = noop,
}
-- Overwrite any handler functions from the old library
for k, v in pairs(handlers) do
        sink.handlers[k] = v
end

-----------------------------------------------------------------------
-- Embed handling

sink.embeds = sink.embeds or {}

local mixins = {
        "Pour", "RegisterSink", "SetSinkStorage",
        "GetSinkAce2OptionsDataTable", "GetSinkAce3OptionsDataTable"
}

function sink:Embed(target)
        sink.embeds[target] = true
        for _,v in pairs(mixins) do
                target[v] = sink[v]
        end
        return target
end

for addon in pairs(sink.embeds) do
        sink:Embed(addon)
end

Compare with Previous | Blame