Initial commit: Chatter v1.2.11 (Curse package)
This commit is contained in:
@@ -0,0 +1,105 @@
|
||||
--- AceConfigTab-3.0 provides support for tab-completion to AceConfig tables.
|
||||
-- Note: This library is not yet finalized.
|
||||
-- @class file
|
||||
-- @name AceConfigTab-3.0
|
||||
-- @release $Id: AceConfigTab-3.0.lua 769 2009-04-04 11:05:08Z nevcairiel $
|
||||
|
||||
local MAJOR, MINOR = "AceConfigTab-3.0", 1
|
||||
local lib = LibStub:NewLibrary(MAJOR, MINOR)
|
||||
|
||||
if not lib then return end
|
||||
|
||||
local ac = LibStub("AceConsole-3.0")
|
||||
|
||||
local function printf(...)
|
||||
DEFAULT_CHAT_FRAME:AddMessage(string.format(...))
|
||||
end
|
||||
|
||||
-- getChildren(opt, ...)
|
||||
--
|
||||
-- Retrieve the next valid group args in an AceConfig table.
|
||||
--
|
||||
-- opt - AceConfig options table
|
||||
-- ... - args following the slash command
|
||||
--
|
||||
-- opt will need to be determined by the slash-command
|
||||
-- The args will be obtained using AceConsole:GetArgs() or something similar on the remainder of the line.
|
||||
--
|
||||
-- Returns arg1, arg2, ...
|
||||
local function getLevel(opt, ...)
|
||||
-- Walk down the options tree to the last arg in the commandline, or return if it does not follow the tree.
|
||||
local path = ""
|
||||
local lastChild
|
||||
for i = 1, select('#', ...) do
|
||||
local arg = select(i, ...)
|
||||
if not arg or type(arg) == 'number' then break end
|
||||
if opt.plugins then
|
||||
for k in pairs(opt.plugins) do
|
||||
if string.lower(k) == string.lower(arg) then
|
||||
opt = opt.plugins[k]
|
||||
path = path..arg.." "
|
||||
lastChild = arg
|
||||
break
|
||||
end
|
||||
end
|
||||
elseif opt.args then
|
||||
for k in pairs(opt.args) do
|
||||
if string.lower(k) == string.lower(arg) then
|
||||
opt = opt.args[k]
|
||||
path = path..arg.." "
|
||||
lastChild = arg
|
||||
break
|
||||
end
|
||||
end
|
||||
else
|
||||
break
|
||||
end
|
||||
end
|
||||
return opt, path
|
||||
end
|
||||
|
||||
local function getChildren(opt, ...)
|
||||
local lastChild, path
|
||||
opt, path, lastChild = getLevel(opt, ...)
|
||||
local args = {}
|
||||
for _, field in ipairs({"args", "plugins"}) do
|
||||
if type(opt[field]) == 'table' then
|
||||
for k in pairs(opt[field]) do
|
||||
if opt[field].type ~= 'header' then
|
||||
table.insert(args, k)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
return args, path
|
||||
end
|
||||
|
||||
--LibStub("AceConfig-3.0"):RegisterOptionsTable("ag_UnitFrames", aUF.Options.table)
|
||||
local function createWordlist(t, cmdline, pos)
|
||||
local cmd = string.match(cmdline, "(/[^ \t\n]+)")
|
||||
local argslist = string.sub(cmdline, pos, this:GetCursorPosition())
|
||||
local opt -- TODO: figure out options table using cmd
|
||||
opt = LibStub("AceConfigRegistry-3.0"):GetOptionsTable("ag_UnitFrames", "cmd", "AceTab-3.0") -- hardcoded temporarily for testing
|
||||
if not opt then return end
|
||||
local args, path = getChildren(opt, ac:GetArgs(argslist, #argslist/2)) -- largest # of args representable by a string of length #argslist, since they must be separated by spaces
|
||||
for _, v in ipairs(args) do
|
||||
table.insert(t, path..v)
|
||||
end
|
||||
end
|
||||
|
||||
local function usage(t, matches, _, cmdline)
|
||||
local cmd = string.match(cmdline, "(/[^ \t\n]+)")
|
||||
local argslist = string.sub(cmdline, #cmd, this:GetCursorPosition())
|
||||
local opt -- TODO: figure out options table using cmd
|
||||
opt = LibStub("AceConfigRegistry-3.0"):GetOptionsTable("ag_UnitFrames")("cmd", "AceTab-3.0") -- hardcoded temporarily for testing
|
||||
if not opt then return end
|
||||
local level = getLevel(opt, ac:GetArgs(argslist, #argslist/2)) -- largest # of args representable by a string of length #argslist, since they must be separated by spaces
|
||||
local option
|
||||
for _, m in pairs(matches) do
|
||||
local tail = string.match(m, "([^ \t\n]+)$")
|
||||
option = level.plugins and level.plugins[tail] or level.args and level.args[tail]
|
||||
printf("%s - %s", tail, option.desc)
|
||||
end
|
||||
end
|
||||
|
||||
LibStub("AceTab-3.0"):RegisterTabCompletion("aguftest", "%/%w+ ", createWordlist, usage)
|
||||
@@ -0,0 +1,443 @@
|
||||
--- AceTab-3.0 provides support for tab-completion.
|
||||
-- Note: This library is not yet finalized.
|
||||
-- @class file
|
||||
-- @name AceTab-3.0
|
||||
-- @release $Id: AceTab-3.0.lua 947 2010-06-29 16:44:48Z nevcairiel $
|
||||
|
||||
local ACETAB_MAJOR, ACETAB_MINOR = 'AceTab-3.0', 8
|
||||
local AceTab, oldminor = LibStub:NewLibrary(ACETAB_MAJOR, ACETAB_MINOR)
|
||||
|
||||
if not AceTab then return end -- No upgrade needed
|
||||
|
||||
local is335 = GetBuildInfo() >= "3.3.5"
|
||||
|
||||
AceTab.registry = AceTab.registry or {}
|
||||
|
||||
-- local upvalues
|
||||
local _G = _G
|
||||
local pairs = pairs
|
||||
local ipairs = ipairs
|
||||
local type = type
|
||||
local registry = AceTab.registry
|
||||
|
||||
local strfind = string.find
|
||||
local strsub = string.sub
|
||||
local strlower = string.lower
|
||||
local strformat = string.format
|
||||
local strmatch = string.match
|
||||
|
||||
local function printf(...)
|
||||
DEFAULT_CHAT_FRAME:AddMessage(strformat(...))
|
||||
end
|
||||
|
||||
local function getTextBeforeCursor(this, start)
|
||||
return strsub(this:GetText(), start or 1, this:GetCursorPosition())
|
||||
end
|
||||
|
||||
-- Hook OnTabPressed and OnTextChanged for the frame, give it an empty matches table, and set its curMatch to 0, if we haven't done so already.
|
||||
local function hookFrame(f)
|
||||
if f.hookedByAceTab3 then return end
|
||||
f.hookedByAceTab3 = true
|
||||
if f == (is335 and ChatEdit_GetActiveWindow() or ChatFrameEditBox) then
|
||||
local origCTP = ChatEdit_CustomTabPressed
|
||||
function ChatEdit_CustomTabPressed(...)
|
||||
if AceTab:OnTabPressed(f) then
|
||||
return origCTP(...)
|
||||
else
|
||||
return true
|
||||
end
|
||||
end
|
||||
else
|
||||
local origOTP = f:GetScript('OnTabPressed')
|
||||
if type(origOTP) ~= 'function' then
|
||||
origOTP = function() end
|
||||
end
|
||||
f:SetScript('OnTabPressed', function(...)
|
||||
if AceTab:OnTabPressed(f) then
|
||||
return origOTP(...)
|
||||
end
|
||||
end)
|
||||
end
|
||||
f.at3curMatch = 0
|
||||
f.at3matches = {}
|
||||
end
|
||||
|
||||
local firstPMLength
|
||||
|
||||
local fallbacks, notfallbacks = {}, {} -- classifies completions into those which have preconditions and those which do not. Those without preconditions are only considered if no other completions have matches.
|
||||
local pmolengths = {} -- holds the number of characters to overwrite according to pmoverwrite and the current prematch
|
||||
-- ------------------------------------------------------------------------------
|
||||
-- RegisterTabCompletion( descriptor, prematches, wordlist, usagefunc, listenframes, postfunc, pmoverwrite )
|
||||
-- See http://www.wowace.com/wiki/AceTab-2.0 for detailed API documentation
|
||||
--
|
||||
-- descriptor string Unique identifier for this tab completion set
|
||||
--
|
||||
-- prematches string|table|nil String match(es) AFTER which this tab completion will apply.
|
||||
-- AceTab will ignore tabs NOT preceded by the string(s).
|
||||
-- If no value is passed, will check all tabs pressed in the specified editframe(s) UNLESS a more-specific tab complete applies.
|
||||
--
|
||||
-- wordlist function|table Function that will be passed a table into which it will insert strings corresponding to all possible completions, or an equivalent table.
|
||||
-- The text in the editbox, the position of the start of the word to be completed, and the uncompleted partial word
|
||||
-- are passed as second, third, and fourth arguments, to facilitate pre-filtering or conditional formatting, if desired.
|
||||
--
|
||||
-- usagefunc function|boolean|nil Usage statement function. Defaults to the wordlist, one per line. A boolean true squelches usage output.
|
||||
--
|
||||
-- listenframes string|table|nil EditFrames to monitor. Defaults to ChatFrameEditBox.
|
||||
--
|
||||
-- postfunc function|nil Post-processing function. If supplied, matches will be passed through this function after they've been identified as a match.
|
||||
--
|
||||
-- pmoverwrite boolean|number|nil Offset the beginning of the completion string in the editbox when making a completion. Passing a boolean true indicates that we want to overwrite
|
||||
-- the entire prematch string, and passing a number will overwrite that many characters prior to the cursor.
|
||||
-- This is useful when you want to use the prematch as an indicator character, but ultimately do not want it as part of the text, itself.
|
||||
--
|
||||
-- no return
|
||||
-- ------------------------------------------------------------------------------
|
||||
function AceTab:RegisterTabCompletion(descriptor, prematches, wordlist, usagefunc, listenframes, postfunc, pmoverwrite)
|
||||
-- Arg checks
|
||||
if type(descriptor) ~= 'string' then error("Usage: RegisterTabCompletion(descriptor, prematches, wordlist, usagefunc, listenframes, postfunc, pmoverwrite): 'descriptor' - string expected.", 3) end
|
||||
if prematches and type(prematches) ~= 'string' and type(prematches) ~= 'table' then error("Usage: RegisterTabCompletion(descriptor, prematches, wordlist, usagefunc, listenframes, postfunc, pmoverwrite): 'prematches' - string, table, or nil expected.", 3) end
|
||||
if type(wordlist) ~= 'function' and type(wordlist) ~= 'table' then error("Usage: RegisterTabCompletion(descriptor, prematches, wordlist, usagefunc, listenframes, postfunc, pmoverwrite): 'wordlist' - function or table expected.", 3) end
|
||||
if usagefunc and type(usagefunc) ~= 'function' and type(usagefunc) ~= 'boolean' then error("Usage: RegisterTabCompletion(descriptor, prematches, wordlist, usagefunc, listenframes, postfunc, pmoverwrite): 'usagefunc' - function or boolean expected.", 3) end
|
||||
if listenframes and type(listenframes) ~= 'string' and type(listenframes) ~= 'table' then error("Usage: RegisterTabCompletion(descriptor, prematches, wordlist, usagefunc, listenframes, postfunc, pmoverwrite): 'listenframes' - string or table expected.", 3) end
|
||||
if postfunc and type(postfunc) ~= 'function' then error("Usage: RegisterTabCompletion(descriptor, prematches, wordlist, usagefunc, listenframes, postfunc, pmoverwrite): 'postfunc' - function expected.", 3) end
|
||||
if pmoverwrite and type(pmoverwrite) ~= 'boolean' and type(pmoverwrite) ~= 'number' then error("Usage: RegisterTabCompletion(descriptor, prematches, wordlist, usagefunc, listenframes, postfunc, pmoverwrite): 'pmoverwrite' - boolean or number expected.", 3) end
|
||||
|
||||
local pmtable = type(prematches) == 'table' and prematches or {}
|
||||
-- Mark this group as a fallback group if no value was passed.
|
||||
if not prematches then
|
||||
pmtable[1] = ""
|
||||
fallbacks[descriptor] = true
|
||||
-- Make prematches into a one-element table if it was passed as a string.
|
||||
elseif type(prematches) == 'string' then
|
||||
pmtable[1] = prematches
|
||||
if prematches == "" then
|
||||
fallbacks[descriptor] = true
|
||||
else
|
||||
notfallbacks[descriptor] = true
|
||||
end
|
||||
end
|
||||
|
||||
-- Make listenframes into a one-element table if it was not passed a table of frames.
|
||||
if not listenframes then -- default
|
||||
if is335 then
|
||||
listenframes = {}
|
||||
for i = 1, NUM_CHAT_WINDOWS do
|
||||
listenframes[i] = _G["ChatFrame"..i.."EditBox"]
|
||||
end
|
||||
else
|
||||
listenframes = { ChatFrameEditBox }
|
||||
end
|
||||
elseif type(listenframes) ~= 'table' or type(listenframes[0]) == 'userdata' and type(listenframes.IsObjectType) == 'function' then -- single frame or framename
|
||||
listenframes = { listenframes }
|
||||
end
|
||||
|
||||
-- Hook each registered listenframe and give it a matches table.
|
||||
for _, f in pairs(listenframes) do
|
||||
if type(f) == 'string' then
|
||||
f = _G[f]
|
||||
end
|
||||
if type(f) ~= 'table' or type(f[0]) ~= 'userdata' or type(f.IsObjectType) ~= 'function' then
|
||||
error(format(ACETAB_MAJOR..": Cannot register frame %q; it does not exist", f:GetName()))
|
||||
end
|
||||
if f then
|
||||
if f:GetObjectType() ~= 'EditBox' then
|
||||
error(format(ACETAB_MAJOR..": Cannot register frame %q; it is not an EditBox", f:GetName()))
|
||||
else
|
||||
hookFrame(f)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Everything checks out; register this completion.
|
||||
if not registry[descriptor] then
|
||||
registry[descriptor] = { prematches = pmtable, wordlist = wordlist, usagefunc = usagefunc, listenframes = listenframes, postfunc = postfunc, pmoverwrite = pmoverwrite }
|
||||
end
|
||||
end
|
||||
|
||||
function AceTab:IsTabCompletionRegistered(descriptor)
|
||||
return registry and registry[descriptor]
|
||||
end
|
||||
|
||||
function AceTab:UnregisterTabCompletion(descriptor)
|
||||
registry[descriptor] = nil
|
||||
pmolengths[descriptor] = nil
|
||||
fallbacks[descriptor] = nil
|
||||
notfallbacks[descriptor] = nil
|
||||
end
|
||||
|
||||
-- ------------------------------------------------------------------------------
|
||||
-- gcbs( s1, s2 )
|
||||
--
|
||||
-- s1 string First string to be compared
|
||||
--
|
||||
-- s2 string Second string to be compared
|
||||
--
|
||||
-- returns the greatest common substring beginning s1 and s2
|
||||
-- ------------------------------------------------------------------------------
|
||||
local function gcbs(s1, s2)
|
||||
if not s1 and not s2 then return end
|
||||
if not s1 then s1 = s2 end
|
||||
if not s2 then s2 = s1 end
|
||||
if #s2 < #s1 then
|
||||
s1, s2 = s2, s1
|
||||
end
|
||||
if strfind(strlower(s2), "^"..strlower(s1)) then
|
||||
return s1
|
||||
else
|
||||
return gcbs(strsub(s1, 1, -2), s2)
|
||||
end
|
||||
end
|
||||
|
||||
local cursor -- Holds cursor position. Set in :OnTabPressed().
|
||||
-- ------------------------------------------------------------------------------
|
||||
-- cycleTab()
|
||||
-- For when a tab press has multiple possible completions, we need to allow the user to press tab repeatedly to cycle through them.
|
||||
-- If we have multiple possible completions, all tab presses after the first will call this function to cycle through and insert the different possible matches.
|
||||
-- This function will stop being called after OnTextChanged() is triggered by something other than AceTab (i.e. the user inputs a character).
|
||||
-- ------------------------------------------------------------------------------
|
||||
local previousLength, cMatch, matched, postmatch
|
||||
local function cycleTab(this)
|
||||
cMatch = 0 -- Counter across all sets. The pseudo-index relevant to this value and corresponding to the current match is held in this.at3curMatch
|
||||
matched = false
|
||||
|
||||
-- Check each completion group registered to this frame.
|
||||
for desc, compgrp in pairs(this.at3matches) do
|
||||
|
||||
-- Loop through the valid completions for this set.
|
||||
for m, pm in pairs(compgrp) do
|
||||
cMatch = cMatch + 1
|
||||
if cMatch == this.at3curMatch then -- we're back to where we left off last time through the combined list
|
||||
this.at3lastMatch = m
|
||||
this.at3lastWord = pm
|
||||
this.at3curMatch = cMatch + 1 -- save the new cMatch index
|
||||
matched = true
|
||||
break
|
||||
end
|
||||
end
|
||||
if matched then break end
|
||||
end
|
||||
|
||||
-- If our index is beyond the end of the list, reset the original uncompleted substring and let the cycle start over next time tab is pressed.
|
||||
if not matched then
|
||||
this.at3lastMatch = this.at3origMatch
|
||||
this.at3lastWord = this.at3origWord
|
||||
this.at3curMatch = 1
|
||||
end
|
||||
|
||||
-- Insert the completion.
|
||||
this:HighlightText(this.at3matchStart-1, cursor)
|
||||
this:Insert(this.at3lastWord or '')
|
||||
this.at3_last_precursor = getTextBeforeCursor(this) or ''
|
||||
end
|
||||
|
||||
local IsSecureCmd = IsSecureCmd
|
||||
|
||||
local cands, candUsage = {}, {}
|
||||
local numMatches = 0
|
||||
local firstMatch, hasNonFallback, allGCBS, setGCBS, usage
|
||||
local text_precursor, text_all, text_pmendToCursor
|
||||
local matches, usagefunc -- convenience locals
|
||||
|
||||
-- Fill the this.at3matches[descriptor] tables with matching completion pairs for each entry, based on
|
||||
-- the partial string preceding the cursor position and using the corresponding registered wordlist.
|
||||
--
|
||||
-- The entries of the matches tables are of the format raw_match = formatted_match, where raw_match is the plaintext completion and
|
||||
-- formatted_match is the match after being formatted/altered/processed by the registered postfunc.
|
||||
-- If no postfunc exists, then the formatted and raw matches are the same.
|
||||
local pms, pme, pmt, prematchStart, prematchEnd, text_prematch, entry
|
||||
local function fillMatches(this, desc, fallback)
|
||||
entry = registry[desc]
|
||||
-- See what frames are registered for this completion group. If the frame in which we pressed tab is one of them, then we start building matches.
|
||||
for _, f in ipairs(entry.listenframes) do
|
||||
if f == this then
|
||||
|
||||
-- Try each precondition string registered for this completion group.
|
||||
for _, prematch in ipairs(entry.prematches) do
|
||||
|
||||
-- Test if our prematch string is satisfied.
|
||||
-- If it is, then we find its last occurence prior to the cursor, calculate and store its pmoverwrite value (if applicable), and start considering completions.
|
||||
if fallback then prematch = "%s" end
|
||||
|
||||
-- Find the last occurence of the prematch before the cursor.
|
||||
pms, pme, pmt = nil, 1, ''
|
||||
text_prematch, prematchEnd, prematchStart = nil, nil, nil
|
||||
while true do
|
||||
pms, pme, pmt = strfind(text_precursor, "("..prematch..")", pme)
|
||||
if pms then
|
||||
prematchStart, prematchEnd, text_prematch = pms, pme, pmt
|
||||
pme = pme + 1
|
||||
else
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
if not prematchStart and fallback then
|
||||
prematchStart, prematchEnd, text_prematch = 0, 0, ''
|
||||
end
|
||||
if prematchStart then
|
||||
-- text_pmendToCursor should be the sub-word/phrase to be completed.
|
||||
text_pmendToCursor = strsub(text_precursor, prematchEnd + 1)
|
||||
|
||||
-- How many characters should we eliminate before the completion before writing it in.
|
||||
pmolengths[desc] = entry.pmoverwrite == true and #text_prematch or entry.pmoverwrite or 0
|
||||
|
||||
-- This is where we will insert completions, taking the prematch overwrite into account.
|
||||
this.at3matchStart = prematchEnd + 1 - (pmolengths[desc] or 0)
|
||||
|
||||
-- We're either a non-fallback set or all completions thus far have been fallback sets, and the precondition matches.
|
||||
-- Create cands from the registered wordlist, filling it with all potential (unfiltered) completion strings.
|
||||
local wordlist = entry.wordlist
|
||||
local cands = type(wordlist) == 'table' and wordlist or {}
|
||||
if type(wordlist) == 'function' then
|
||||
wordlist(cands, text_all, prematchEnd + 1, text_pmendToCursor)
|
||||
end
|
||||
if cands ~= false then
|
||||
matches = this.at3matches[desc] or {}
|
||||
for i in pairs(matches) do matches[i] = nil end
|
||||
|
||||
-- Check each of the entries in cands to see if it completes the word before the cursor.
|
||||
-- Finally, increment our match count and set firstMatch, if appropriate.
|
||||
for _, m in ipairs(cands) do
|
||||
if strfind(strlower(m), strlower(text_pmendToCursor), 1, 1) == 1 then -- we have a matching completion!
|
||||
hasNonFallback = not fallback
|
||||
matches[m] = entry.postfunc and entry.postfunc(m, prematchEnd + 1, text_all) or m
|
||||
numMatches = numMatches + 1
|
||||
if numMatches == 1 then
|
||||
firstMatch = matches[m]
|
||||
firstPMLength = pmolengths[desc] or 0
|
||||
end
|
||||
end
|
||||
end
|
||||
this.at3matches[desc] = numMatches > 0 and matches or nil
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function AceTab:OnTabPressed(this)
|
||||
if this:GetText() == '' then return true end
|
||||
|
||||
-- allow Blizzard to handle slash commands, themselves
|
||||
if this == (is335 and ChatEdit_GetActiveWindow() or ChatFrameEditBox) then
|
||||
local command = this:GetText()
|
||||
if strfind(command, "^/[%a%d_]+$") then
|
||||
return true
|
||||
end
|
||||
local cmd = strmatch(command, "^/[%a%d_]+")
|
||||
if cmd and IsSecureCmd(cmd) then
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
cursor = this:GetCursorPosition()
|
||||
|
||||
text_all = this:GetText()
|
||||
text_precursor = getTextBeforeCursor(this) or ''
|
||||
|
||||
-- If we've already found some matches and haven't done anything since the last tab press, then (continue) cycling matches.
|
||||
-- Otherwise, reset this frame's matches and proceed to creating our list of possible completions.
|
||||
this.at3lastMatch = this.at3curMatch > 0 and (this.at3lastMatch or this.at3origWord)
|
||||
-- Detects if we've made any edits since the last tab press. If not, continue cycling completions.
|
||||
if text_precursor == this.at3_last_precursor then
|
||||
return cycleTab(this)
|
||||
else
|
||||
for i in pairs(this.at3matches) do this.at3matches[i] = nil end
|
||||
this.at3curMatch = 0
|
||||
this.at3origWord = nil
|
||||
this.at3origMatch = nil
|
||||
this.at3lastWord = nil
|
||||
this.at3lastMatch = nil
|
||||
this.at3_last_precursor = text_precursor
|
||||
end
|
||||
|
||||
numMatches = 0
|
||||
firstMatch = nil
|
||||
firstPMLength = 0
|
||||
hasNonFallback = false
|
||||
for i in pairs(pmolengths) do pmolengths[i] = nil end
|
||||
|
||||
for desc in pairs(notfallbacks) do
|
||||
fillMatches(this, desc)
|
||||
end
|
||||
if not hasNonFallback then
|
||||
for desc in pairs(fallbacks) do
|
||||
fillMatches(this, desc, true)
|
||||
end
|
||||
end
|
||||
|
||||
if not firstMatch then
|
||||
this.at3_last_precursor = "\0"
|
||||
return true
|
||||
end
|
||||
|
||||
-- We want to replace the entire word with our completion, so highlight it up to the cursor.
|
||||
-- If only one match exists, then stick it in there and append a space.
|
||||
if numMatches == 1 then
|
||||
-- HighlightText takes the value AFTER which the highlighting starts, so we have to subtract 1 to have it start before the first character.
|
||||
this:HighlightText(this.at3matchStart-1, cursor)
|
||||
|
||||
this:Insert(firstMatch)
|
||||
this:Insert(" ")
|
||||
else
|
||||
-- Otherwise, we want to begin cycling through the valid completions.
|
||||
-- Beginning a cycle also causes the usage statement to be printed, if one exists.
|
||||
|
||||
-- Print usage statements for each possible completion (and gather up the GCBS of all matches while we're walking the tables).
|
||||
allGCBS = nil
|
||||
for desc, matches in pairs(this.at3matches) do
|
||||
-- Don't print usage statements for fallback completion groups if we have 'real' completion groups with matches.
|
||||
if hasNonFallback and fallbacks[desc] then break end
|
||||
|
||||
-- Use the group's description as a heading for its usage statements.
|
||||
DEFAULT_CHAT_FRAME:AddMessage(desc..":")
|
||||
|
||||
usagefunc = registry[desc].usagefunc
|
||||
if not usagefunc then
|
||||
-- No special usage processing; just print a list of the (formatted) matches.
|
||||
for m, fm in pairs(matches) do
|
||||
DEFAULT_CHAT_FRAME:AddMessage(fm)
|
||||
allGCBS = gcbs(allGCBS, m)
|
||||
end
|
||||
else
|
||||
-- Print a usage statement based on the corresponding registered usagefunc.
|
||||
-- candUsage is the table passed to usagefunc to be filled with candidate = usage_statement pairs.
|
||||
if type(usagefunc) == 'function' then
|
||||
for i in pairs(candUsage) do candUsage[i] = nil end
|
||||
|
||||
-- usagefunc takes the greatest common substring of valid matches as one of its args, so let's find that now.
|
||||
-- TODO: Make the GCBS function accept a vararg or table, after which we can just pass in the list of matches.
|
||||
setGCBS = nil
|
||||
for m in pairs(matches) do
|
||||
setGCBS = gcbs(setGCBS, m)
|
||||
end
|
||||
allGCBS = gcbs(allGCBS, setGCBS)
|
||||
usage = usagefunc(candUsage, matches, setGCBS, strsub(text_precursor, 1, prematchEnd))
|
||||
|
||||
-- If the usagefunc returns a string, then the entire usage statement has been taken care of by usagefunc, and we need only to print it...
|
||||
if type(usage) == 'string' then
|
||||
DEFAULT_CHAT_FRAME:AddMessage(usage)
|
||||
|
||||
-- ...otherwise, it should have filled candUsage with candidate-usage statement pairs, and we need to print the matching ones.
|
||||
elseif next(candUsage) and numMatches > 0 then
|
||||
for m, fm in pairs(matches) do
|
||||
if candUsage[m] then DEFAULT_CHAT_FRAME:AddMessage(strformat("%s - %s", fm, candUsage[m])) end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Replace the original string with the greatest common substring of all valid completions.
|
||||
this.at3curMatch = 1
|
||||
this.at3origWord = strsub(text_precursor, this.at3matchStart, this.at3matchStart + pmolengths[desc] - 1) .. allGCBS or ""
|
||||
this.at3origMatch = allGCBS or ""
|
||||
this.at3lastWord = this.at3origWord
|
||||
this.at3lastMatch = this.at3origMatch
|
||||
|
||||
this:HighlightText(this.at3matchStart-1, cursor)
|
||||
this:Insert(this.at3origWord)
|
||||
this.at3_last_precursor = getTextBeforeCursor(this) or ''
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -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="AceTab-3.0.lua"/>
|
||||
<Script file="AceConfigTab-3.0.lua"/>
|
||||
</Ui>
|
||||
@@ -0,0 +1,782 @@
|
||||
--[[
|
||||
Name: Sink-2.0
|
||||
Revision: $Rev: 71 $
|
||||
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: 71 $"):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")
|
||||
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
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
<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/
|
||||
C:\Projects\WoW\Bin\Interface\FrameXML\UI.xsd">
|
||||
|
||||
<script file="LibSink-2.0.lua"/>
|
||||
|
||||
</Ui>
|
||||
|
||||
Reference in New Issue
Block a user