You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
429 lines
14 KiB
429 lines
14 KiB
--[[
|
|
Name: LibChatHandler-1.0
|
|
Revision: $Revision: 18 $
|
|
Date: $Date: 2016-07-19 14:36:20 +0000 (Tue, 19 Jul 2016) $
|
|
Author: Pazza <Bronzebeard> (johnlangone@gmail.com)
|
|
Website: http://www.wimaddon.com
|
|
Documentation: http://www.wimaddon.com/wiki/LibChatHandler-1.0
|
|
svn: svn://svn.curseforge.net/wow/libchathandler/mainline/trunk
|
|
Description: Event Model Control View (MCV) for handling chat events.
|
|
License: LGPL v2.1
|
|
]]
|
|
|
|
local MAJOR, MINOR = "LibChatHandler-1.0", tonumber(("$Revision: 18 $"):match("(%d+)"));
|
|
-- check if lib is already loaded and check versioning -- update as needed.
|
|
local prevLib = LibStub:GetLibrary("LibChatHandler-1.0", 1); -- load library silently.
|
|
local prevDelegatedEvents;
|
|
if(prevLib and prevLib:GetMinor() < MINOR) then
|
|
--upgrade needed perform cleanup operations.
|
|
prevDelegatedEvents = prevLib:GetDelegatedEventsTable();
|
|
prevLib:ReadyUpgrade();
|
|
end
|
|
|
|
local lib = LibStub:NewLibrary(MAJOR, MINOR);
|
|
|
|
if not lib then return; end -- newer version is already loaded
|
|
|
|
|
|
-- locals are always faster than globals
|
|
local tbl_rm = table.remove;
|
|
local tbl_ins = table.insert;
|
|
local type = type;
|
|
local pairs = pairs;
|
|
local regd4Event = GetFramesRegisteredForEvent;
|
|
local str_find = string.find;
|
|
|
|
|
|
local DelegatedEvents = prevDelegatedEvents or {}; -- objects which handle events
|
|
local ChatEvents = {}; -- queued events
|
|
local messagesReceived = {}; -- organizer for delivered messages.
|
|
|
|
local eventHandler = LibChatHander_EventHandler or CreateFrame("Frame", "LibChatHander_EventHandler");
|
|
|
|
|
|
--------------------------------------
|
|
-- table recycling --
|
|
--------------------------------------
|
|
local tablePool = {{}, -- [1] in use
|
|
{}}; -- [2] available
|
|
|
|
-- get index of table from in use pool
|
|
local function getTableIndex(tbl)
|
|
for i=1, #tablePool[1] do
|
|
if(tablePool[1][i] == tbl) then
|
|
return i;
|
|
end
|
|
end
|
|
end
|
|
|
|
local function pack(tbl, ...)
|
|
for i=1, select("#", ...) do
|
|
tbl[i] = select(i, ...);
|
|
end
|
|
end
|
|
|
|
-- get an available or new table
|
|
local function newTable(...)
|
|
if(#tablePool[2] > 0) then
|
|
local tbl = tbl_rm(tablePool[2], 1);
|
|
tbl_ins(tablePool[1], tbl);
|
|
pack(tbl, ...);
|
|
return tbl;
|
|
else
|
|
local tbl = {};
|
|
tbl_ins(tablePool[1], tbl);
|
|
pack(tbl, ...);
|
|
return tbl;
|
|
end
|
|
end
|
|
|
|
local function destroyTable(tbl)
|
|
if(tbl) then
|
|
for k, v in pairs(tbl) do
|
|
if(type(v) == "table" and not v.GetParent and not v.LibChatHandler_Delegate) then
|
|
destroyTable(v);
|
|
end
|
|
tbl[k] = nil;
|
|
end
|
|
-- whether or not the table was created here,
|
|
-- save it for future use.
|
|
local index = getTableIndex(tbl);
|
|
if(index) then
|
|
tbl_rm(tablePool[1], index);
|
|
end
|
|
tbl_ins(tablePool[2], tbl);
|
|
end
|
|
end
|
|
|
|
local function tbl_indexOf(tbl, obj)
|
|
if(type(tbl) == "table") then
|
|
for i=1, #tbl do
|
|
if(tbl[i] == obj) then
|
|
return i;
|
|
end
|
|
end
|
|
end
|
|
return nil;
|
|
end
|
|
|
|
--------------------------------------
|
|
-- Event Object Object --
|
|
--------------------------------------
|
|
|
|
--is delegate in suspended list
|
|
local function isSuspendedBy(eventItem, delegate)
|
|
local tbl = eventItem.suspendedBy;
|
|
return tbl_indexOf(tbl, delegate) and true or false;
|
|
end
|
|
|
|
--is delegate valid for eventItem
|
|
local function isValidDelegate(eventItem, delegate)
|
|
local tbl = DelegatedEvents[eventItem:GetEvent()];
|
|
if(tbl) then
|
|
return tbl_indexOf(tbl, delegate) and true or false;
|
|
else
|
|
return false;
|
|
end
|
|
end
|
|
|
|
-- The following functions provide actions for the chat events.
|
|
local function Suspend(self, delegate)
|
|
if(isValidDelegate(self, delegate)) then
|
|
if(isSuspendedBy(self, delegate)) then
|
|
return false, "Event already suspended by delegate";
|
|
else
|
|
tbl_ins(self.suspendedBy, delegate);
|
|
return true;
|
|
end
|
|
else
|
|
return false, "Invalid delegate - Not registered for event.";
|
|
end
|
|
end
|
|
|
|
local function Release(self, delegate)
|
|
if(isSuspendedBy(self, delegate)) then
|
|
tbl_rm(self.suspendedBy, tbl_indexOf(self.suspendedBy, delegate));
|
|
if(#self.suspendedBy == 0) then
|
|
lib:popEvents();
|
|
end
|
|
return true;
|
|
else
|
|
return false, "Delegate hasn't claimed responsibility for suspending this event.";
|
|
end
|
|
end
|
|
|
|
local function Block(self)
|
|
self.flag_block = true;
|
|
end
|
|
|
|
local function BlockFromChatFrame(self)
|
|
self.flag_blocked_from_chatFrame = true;
|
|
end
|
|
|
|
local function BlockFromDelegate(self, delegate)
|
|
if(isValidDelegate(self, delegate)) then
|
|
tbl_ins(self.blockFrom, delegate);
|
|
return true;
|
|
else
|
|
return false, "Invalid delegate - Not registered for event.";
|
|
end
|
|
end
|
|
|
|
local function Allow(self)
|
|
-- do nothing to event
|
|
end
|
|
|
|
-- provide some get methods for args & events
|
|
local function GetArgs(self)
|
|
-- function returns 15 args incase blizzard ever decides to add more.
|
|
return self.arg[1], self.arg[2], self.arg[3], self.arg[4], self.arg[5],
|
|
self.arg[6], self.arg[7], self.arg[8], self.arg[9], self.arg[10],
|
|
self.arg[11], self.arg[12], self.arg[13], self.arg[14], self.arg[15];
|
|
end
|
|
|
|
local function GetEvent(self)
|
|
return self.event;
|
|
end
|
|
|
|
local function GetNumDelegates(self)
|
|
return #DelegatedEvents[self:GetEvent()];
|
|
end
|
|
|
|
local function GetDelegate(self, index)
|
|
return DelegatedEvents[self:GetEvent()][index];
|
|
end
|
|
|
|
|
|
-- instantiate new chat event object
|
|
local function newChatEvent(event, ...)
|
|
local c = newTable();
|
|
c.event = event;
|
|
c.arg = newTable(...);
|
|
c.frames = newTable(regd4Event(event));
|
|
c.flag_block = false;
|
|
c.flag_blocked_from_chatFrame = false;
|
|
c.suspendedBy = newTable();
|
|
c.blockFrom = newTable();
|
|
--event actions
|
|
c.Suspend = Suspend;
|
|
c.Release = Release;
|
|
c.Block = Block;
|
|
c.BlockFromChatFrame = BlockFromChatFrame;
|
|
c.BlockFromDelegate = BlockFromDelegate;
|
|
c.Allow = Allow;
|
|
--event data
|
|
c.GetArgs = GetArgs;
|
|
c.GetEvent = GetEvent;
|
|
c.GetNumDelegate = GetNumDelegate;
|
|
c.GetDelegate = GetDelegate;
|
|
return c;
|
|
end
|
|
|
|
|
|
|
|
-- PROTOTYPE to AVOID HOOKING
|
|
local missingIdIndex = 10000;
|
|
local function filterFunc(self, event, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg15, arg15, arg16)
|
|
if(self and type(self.GetName) == "function" and string.match(self:GetName(), "^ChatFrame%d+$" )) then
|
|
if(not arg11) then
|
|
-- create arg11 (msgID) if none exists.
|
|
arg11 = missingIdIndex*(-1);
|
|
missingIdIndex = missingIdIndex + 1;
|
|
end
|
|
messagesReceived[arg11] = messagesReceived[arg11] or -1;
|
|
-- block for now
|
|
return messagesReceived[arg11] < 0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg15, arg15, arg16;
|
|
end
|
|
return false, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, arg15, arg15, arg16;
|
|
end
|
|
|
|
--------------------------------------
|
|
-- Event Handling --
|
|
--------------------------------------
|
|
|
|
local function isChatEvent(event)
|
|
if(type(event) == "string" and event ~= "CHAT_MSG_ADDON" and str_find(event, "^CHAT_")) then
|
|
return true;
|
|
else
|
|
return false;
|
|
end
|
|
end
|
|
|
|
local function popEvents()
|
|
for i=#ChatEvents, 1, -1 do
|
|
local e = ChatEvents[i];
|
|
local arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11 = e:GetArgs(); -- need arg11
|
|
if(e.flag_sent_to_delegates and #e.suspendedBy == 0) then
|
|
if(not e.flag_block) then
|
|
tbl_rm(ChatEvents, i);
|
|
-- first return to registered objects
|
|
local delegates = DelegatedEvents[e:GetEvent()];
|
|
if(delegates) then
|
|
-- for each delegate handling the event
|
|
for j=1, #delegates do
|
|
if(not tbl_indexOf(e.blockFrom, delegates[j])) then
|
|
local handler = delegates[j][e:GetEvent()];
|
|
if(handler) then
|
|
handler(delegates[j], e:GetArgs());
|
|
end
|
|
end
|
|
end
|
|
end
|
|
-- next return to chat frame... only if asked to...
|
|
if(not e.flag_blocked_from_chatFrame) then
|
|
messagesReceived[arg11] = time();
|
|
for j=1,#e.frames do
|
|
--send ChatFrame* windows only
|
|
local f = e.frames[j];
|
|
local fName = f and f.GetName and f:GetName();
|
|
if(fName and str_find(fName, "^ChatFrame%d+$")) then
|
|
--ChatFrame_MessageEventHandler_orig(e.frames[j], e:GetEvent(), e:GetArgs());
|
|
ChatFrame_OnEvent(e.frames[j], e:GetEvent(), e:GetArgs());
|
|
end
|
|
end
|
|
else
|
|
--Supressed from chat frame, so fire FlashClientIcon() manually
|
|
FlashClientIcon()
|
|
end
|
|
else
|
|
tbl_rm(ChatEvents, i);
|
|
end
|
|
messagesReceived[arg11] = nil;
|
|
-- destroy event
|
|
destroyTable(e);
|
|
end
|
|
end
|
|
end
|
|
|
|
-- eventHandler OnEvent handler
|
|
local function eventHandler_OnEvent(self, event, ...)
|
|
if(str_find(event, "^CHAT_")) then
|
|
local e = newChatEvent(event, ...);
|
|
local delegates = DelegatedEvents[event];
|
|
tbl_ins(ChatEvents, 1, e);
|
|
for i=1, #delegates do
|
|
local delegate = delegates[i][event.."_CONTROLLER"];
|
|
if(delegate) then
|
|
delegate(delegates[i], e, ...);
|
|
end
|
|
end
|
|
e.flag_sent_to_delegates = true;
|
|
popEvents();
|
|
end
|
|
end
|
|
eventHandler:SetScript("OnEvent", eventHandler_OnEvent);
|
|
-- if we are upgrading, we want to make sure all events are accounted for.
|
|
for event, _ in pairs(DelegatedEvents) do
|
|
eventHandler:RegisterEvent(event);
|
|
end
|
|
|
|
|
|
--------------------------------------
|
|
-- local lib declarations --
|
|
--------------------------------------
|
|
|
|
local function RegisterChatEvent(self, chatEvent, priority)
|
|
-- only register if chat event
|
|
if(isChatEvent(chatEvent)) then
|
|
-- register delegate and event
|
|
if(DelegatedEvents[chatEvent]) then
|
|
-- don't register the event more than once
|
|
if(not tbl_indexOf(DelegatedEvents[chatEvent], self)) then
|
|
if(priority) then
|
|
tbl_ins(DelegatedEvents[chatEvent], 1, self);
|
|
else
|
|
tbl_ins(DelegatedEvents[chatEvent], self);
|
|
end
|
|
end
|
|
else
|
|
ChatFrame_AddMessageEventFilter(chatEvent, filterFunc);
|
|
DelegatedEvents[chatEvent] = newTable(self);
|
|
end
|
|
eventHandler:RegisterEvent(chatEvent);
|
|
end
|
|
end
|
|
|
|
local function UnregisterChatEvent(self, chatEvent)
|
|
if(isChatEvent(chatEvent) and DelegatedEvents[chatEvent]) then
|
|
local index = tbl_indexOf(DelegatedEvents[chatEvent], self);
|
|
if(index) then
|
|
tbl_rm(DelegatedEvents[chatEvent], index);
|
|
if(#DelegatedEvents[chatEvent] == 0) then
|
|
ChatFrame_RemoveMessageEventFilter(chatEvent, filterFunc)
|
|
eventHandler:UnregisterEvent(chatEvent);
|
|
local tbl = DelegatedEvents[chatEvent];
|
|
DelegatedEvents[chatEvent] = nil;
|
|
destroyTable(tbl);
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
local function prepDelegate(delegate)
|
|
delegate.RegisterChatEvent = RegisterChatEvent;
|
|
delegate.UnregisterChatEvent = UnregisterChatEvent;
|
|
delegate.LibChatHandler_Delegate = true;
|
|
end
|
|
|
|
|
|
--------------------------------------
|
|
-- Lib API --
|
|
--------------------------------------
|
|
|
|
-- debugging information
|
|
function lib:ShowStats()
|
|
local cf = DEFAULT_CHAT_FRAME;
|
|
cf:AddMessage("Tables in use: ".. #tablePool[1]);
|
|
cf:AddMessage("Tables available: ".. #tablePool[2]);
|
|
cf:AddMessage("Pending events in queue: "..#ChatEvents);
|
|
cf:AddMessage("Delegated Events:");
|
|
for event, tbl in pairs(DelegatedEvents) do
|
|
cf:AddMessage("+-- "..event.." ("..#tbl..")");
|
|
end
|
|
end
|
|
|
|
|
|
-- available lib API
|
|
function lib:NewDelegate()
|
|
local delegate = newTable();
|
|
prepDelegate(delegate);
|
|
|
|
return delegate;
|
|
end
|
|
|
|
function lib:Embed(tbl)
|
|
-- tbl must be a table, if not, do nothing
|
|
if(type(tbl) ~= "table") then
|
|
return;
|
|
end
|
|
-- prep the delegate
|
|
prepDelegate(tbl);
|
|
end
|
|
|
|
|
|
-- get MINOR - for upgrade checks.
|
|
local function GetMinor()
|
|
return MINOR;
|
|
end
|
|
|
|
-- get DelegatedEvents table for upgrade. (really only needed if Loaded on demand)
|
|
local function GetDelegatedEventsTable()
|
|
return DelegatedEvents;
|
|
end
|
|
|
|
-- ready the lib to be upgraded.
|
|
local function ReadyUpgrade()
|
|
-- restore hook
|
|
--ChatFrame_MessageEventHandler = ChatFrame_MessageEventHandler_orig;
|
|
ChatFrame_OnEvent = ChatFrame_OnEvent_orig;
|
|
end
|
|
|
|
-- load hidden API
|
|
setmetatable(lib, {
|
|
__index = function(t, k)
|
|
if(k == "popEvents") then return popEvents; end
|
|
if(k == "GetMinor") then return GetMinor; end
|
|
if(k == "GetDelegatedEventsTable") then return GetDelegatedEventsTable; end
|
|
if(k == "ReadyUpgrade") then return ReadyUpgrade; end
|
|
end
|
|
});
|