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

--[[
Name: LibChatHandler-1.0
Revision: r25
Date: 2022-11-21T21:48:34Z
Author: Pazza <Bronzebeard> (johnlangone@gmail.com)
Website: http://www.wimaddon.com
Documentation: http://www.wimaddon.com/wiki/LibChatHandler-1.0
svn: https://github.com/Pazza/LibChatHandler-1.0/
Description: Event Model Control View (MCV) for handling chat events.
License: LGPL v2.1
]]
local MAJOR, MINOR = "LibChatHandler-1.0", tonumber(("r25"):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
});