Files
pfUI/modules/chat.lua
T
Brues 8eee766652 chat: apply Blizzard per-window transparency to docked backdrops
The native chat background transparency slider drives FCF_SetWindowAlpha,
which only touches the ChatFrame*Background textures pfUI hides on docked
frames, so the visible pfUI backdrop never responded to it.

Mirror the selected window's stored alpha (GetChatWindowInfo) onto the pfUI
backdrop and refresh it from RefreshChat, FCF_SetWindowAlpha and
FCF_SelectDockFrame so the native slider drives it live and persists across
/reload. Default the SetupPositions window alpha to 0.8 to keep the historical
look, and migrate existing installs whose windows still carry the old hard-0
alpha so their chat background is not suddenly transparent.

Fixes #48
2026-09-02 01:05:47 -05:00

934 lines
35 KiB
Lua

pfUI:RegisterModule("chat", function ()
local panelfont = C.panel.use_unitfonts == "1" and pfUI.font_unit or pfUI.font_default
local panelfont_size = C.panel.use_unitfonts == "1" and C.global.font_unit_size or C.global.font_size
local rawborder, default_border = GetBorderSize("chat")
-- The 'GetChatWindowInfo' function returns shown as 'false' while the UIParent is hidden (Alt+Z).
-- Based on that return value, the 'FloatingChatFrame_Update' would remove every chat frame,
-- that isn't tabbed while the interface is hidden. With this hook we prevent the original
-- 'FloatingChatFrame_Update' function from being called during that time.
local HookFloatingChatFrame_Update = _G.FloatingChatFrame_Update
_G.FloatingChatFrame_Update = function(id, onUpdateEvent)
if not UIParent:IsShown() then return end
HookFloatingChatFrame_Update(id, onUpdateEvent)
end
_G.CHAT_FONT_HEIGHTS = { 8, 10, 12, 14, 16, 18, 20 }
-- add dropdown menu button to ignore player
UnitPopupButtons["IGNORE_PLAYER"] = { text = IGNORE_PLAYER, dist = 0 }
for index,value in ipairs(UnitPopupMenus["FRIEND"]) do
if value == "GUILD_LEAVE" then
table.insert(UnitPopupMenus["FRIEND"], index+1, "IGNORE_PLAYER")
end
end
hooksecurefunc("UnitPopup_OnClick", function(self)
if this.value == "IGNORE_PLAYER" then
AddIgnore(_G[UIDROPDOWNMENU_INIT_MENU].name)
end
end)
local realm = GetRealmName()
local player = UnitName("player")
local history
local function SaveChatHistory(id, msg, r, g, b)
-- create cache tables if not existing
pfUI_cache = pfUI_cache or {}
pfUI_cache["chathistory"] = pfUI_cache["chathistory"] or {}
pfUI_cache["chathistory"][realm] = pfUI_cache["chathistory"][realm] or {}
pfUI_cache["chathistory"][realm][player] = pfUI_cache["chathistory"][realm][player] or {}
pfUI_cache["chathistory"][realm][player][id] = pfUI_cache["chathistory"][realm][player][id] or {}
if r and g and b then
local color = rgbhex(r*.5+.2, g*.5+.2, b*.5+.2)
msg = string.gsub(msg, "^", color)
msg = string.gsub(msg, "|r", "|r" .. color)
end
history = pfUI_cache["chathistory"][realm][player][id]
table.insert(history, 1, msg)
if history[30] then table.remove(history, 30) end
end
local function GetChatHistory(id)
-- create cache tables if not existing
pfUI_cache = pfUI_cache or {}
pfUI_cache["chathistory"] = pfUI_cache["chathistory"] or {}
pfUI_cache["chathistory"][realm] = pfUI_cache["chathistory"][realm] or {}
pfUI_cache["chathistory"][realm][player] = pfUI_cache["chathistory"][realm][player] or {}
pfUI_cache["chathistory"][realm][player][id] = pfUI_cache["chathistory"][realm][player][id] or {}
return pfUI_cache["chathistory"][realm][player][id]
end
pfUI.chat = CreateFrame("Frame",nil,UIParent)
pfUI.chat.left = CreateFrame("Frame", "pfChatLeft", UIParent)
pfUI.chat.left.OnMove = function()
pfUI.chat:RefreshChat()
end
pfUI.chat.left:SetFrameStrata("BACKGROUND")
pfUI.chat.left:SetWidth(C.chat.left.width)
pfUI.chat.left:SetHeight(C.chat.left.height)
pfUI.chat.left:SetPoint("BOTTOMLEFT", 2*default_border,2*default_border)
pfUI.chat.left:SetScript("OnShow", function() pfUI.chat:RefreshChat() end)
UpdateMovable(pfUI.chat.left)
CreateBackdrop(pfUI.chat.left, default_border, nil, .8)
if C.chat.global.frameshadow == "1" then
CreateBackdropShadow(pfUI.chat.left)
end
if C.chat.global.custombg == "1" then
local r, g, b, a = GetStringColor(C.chat.global.background)
pfUI.chat.left.backdrop:SetBackdropColor(tonumber(r), tonumber(g), tonumber(b), tonumber(a))
local r, g, b, a = GetStringColor(C.chat.global.border)
pfUI.chat.left.backdrop:SetBackdropBorderColor(tonumber(r), tonumber(g), tonumber(b), tonumber(a))
end
pfUI.chat.left.panelTop = CreateFrame("Frame", "leftChatPanelTop", pfUI.chat.left)
pfUI.chat.left.panelTop:ClearAllPoints()
pfUI.chat.left.panelTop:SetHeight(C.global.font_size+default_border*2)
pfUI.chat.left.panelTop:SetPoint("TOPLEFT", pfUI.chat.left, "TOPLEFT", default_border, -default_border)
pfUI.chat.left.panelTop:SetPoint("TOPRIGHT", pfUI.chat.left, "TOPRIGHT", -default_border, -default_border)
if C.chat.global.tabdock == "1" then
CreateBackdrop(pfUI.chat.left.panelTop, default_border, nil, .8)
end
pfUI.chat.URLPattern = {
WWW = {
["rx"]=" (www%d-)%.([_A-Za-z0-9-]+)%.(%S+)%s?",
["fm"]="%s.%s.%s"},
PROTOCOL = {
["rx"]=" (%a+)://(%S+)%s?",
["fm"]="%s://%s"},
EMAIL = {
["rx"]=" ([_A-Za-z0-9-%.:]+)@([_A-Za-z0-9-]+)(%.)([_A-Za-z0-9-]+%.?[_A-Za-z0-9-]*)%s?",
["fm"]="%s@%s%s%s"},
PORTIP = {
["rx"]=" (%d%d?%d?)%.(%d%d?%d?)%.(%d%d?%d?)%.(%d%d?%d?):(%d%d?%d?%d?%d?)%s?",
["fm"]="%s.%s.%s.%s:%s"},
IP = {
["rx"]=" (%d%d?%d?)%.(%d%d?%d?)%.(%d%d?%d?)%.(%d%d?%d?)%s?",
["fm"]="%s.%s.%s.%s"},
SHORTURL = {
["rx"]=" (%a+)%.(%a+)/(%S+)%s?",
["fm"]="%s.%s/%s"},
URLIP = {
["rx"]=" ([_A-Za-z0-9-]+)%.([_A-Za-z0-9-]+)%.(%S+)%:([_0-9-]+)%s?",
["fm"]="%s.%s.%s:%s"},
URL = {
["rx"]=" ([_A-Za-z0-9-]+)%.([_A-Za-z0-9-]+)%.(%S+)%s?",
["fm"]="%s.%s.%s"},
}
pfUI.chat.URLFuncs = {
["WWW"] = function(a1,a2,a3) return pfUI.chat:FormatLink(pfUI.chat.URLPattern.WWW.fm,a1,a2,a3) end,
["PROTOCOL"] = function(a1,a2) return pfUI.chat:FormatLink(pfUI.chat.URLPattern.PROTOCOL.fm,a1,a2) end,
["EMAIL"] = function(a1,a2,a3,a4) return pfUI.chat:FormatLink(pfUI.chat.URLPattern.EMAIL.fm,a1,a2,a3,a4) end,
["PORTIP"] = function(a1,a2,a3,a4,a5) return pfUI.chat:FormatLink(pfUI.chat.URLPattern.PORTIP.fm,a1,a2,a3,a4,a5) end,
["IP"] = function(a1,a2,a3,a4) return pfUI.chat:FormatLink(pfUI.chat.URLPattern.IP.fm,a1,a2,a3,a4) end,
["SHORTURL"] = function(a1,a2,a3) return pfUI.chat:FormatLink(pfUI.chat.URLPattern.SHORTURL.fm,a1,a2,a3) end,
["URLIP"] = function(a1,a2,a3,a4) return pfUI.chat:FormatLink(pfUI.chat.URLPattern.URLIP.fm,a1,a2,a3,a4) end,
["URL"] = function(a1,a2,a3) return pfUI.chat:FormatLink(pfUI.chat.URLPattern.URL.fm,a1,a2,a3) end,
}
-- url copy dialog
function pfUI.chat:FormatLink(formatter,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10)
if not (formatter and a1) then return end
local newtext = string.format(formatter,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10)
-- check the last capture index for consecutive trailing dots (invalid top level domain)
local invalidtld
for _, arg in pairs({a10,a9,a8,a7,a6,a5,a4,a3,a2,a1}) do
if arg then
invalidtld = string.find(arg, "(%.%.)$")
break
end
end
if (invalidtld) then return newtext end
if formatter == self.URLPattern.EMAIL.fm then -- email parser
local colon = string.find(a1,":")
if (colon) and string.len(a1) > colon then
if not (string.sub(a1,1,6) == "mailto") then
local prefix,address = string.sub(newtext,1,colon),string.sub(newtext,colon+1)
return string.format(" %s|cffccccff|Hurl:%s|h[%s]|h|r ",prefix,address,address)
end
end
end
return " |cffccccff|Hurl:" .. newtext .. "|h[" .. newtext .. "]|h|r "
end
function pfUI.chat:HandleLink(text)
local URLPattern = pfUI.chat.URLPattern
text = string.gsub (text, URLPattern.WWW.rx, pfUI.chat.URLFuncs.WWW)
text = string.gsub (text, URLPattern.PROTOCOL.rx, pfUI.chat.URLFuncs.PROTOCOL)
text = string.gsub (text, URLPattern.EMAIL.rx, pfUI.chat.URLFuncs.EMAIL)
text = string.gsub (text, URLPattern.PORTIP.rx, pfUI.chat.URLFuncs.PORTIP)
text = string.gsub (text, URLPattern.IP.rx, pfUI.chat.URLFuncs.IP)
text = string.gsub (text, URLPattern.SHORTURL.rx, pfUI.chat.URLFuncs.SHORTURL)
text = string.gsub (text, URLPattern.URLIP.rx, pfUI.chat.URLFuncs.URLIP)
text = string.gsub (text, URLPattern.URL.rx, pfUI.chat.URLFuncs.URL)
return text
end
pfUI.chat.urlcopy = CreateFrame("Frame", "pfURLCopy", UIParent)
pfUI.chat.urlcopy:Hide()
pfUI.chat.urlcopy:SetWidth(270)
pfUI.chat.urlcopy:SetHeight(65)
pfUI.chat.urlcopy:SetFrameStrata("FULLSCREEN")
pfUI.chat.urlcopy:SetPoint("CENTER", 0, 0)
CreateBackdrop(pfUI.chat.urlcopy, nil, nil, 0.8)
pfUI.chat.urlcopy:SetMovable(true)
pfUI.chat.urlcopy:EnableMouse(true)
pfUI.chat.urlcopy:RegisterForDrag("LeftButton")
pfUI.chat.urlcopy:SetScript("OnDragStart",function()
this:StartMoving()
end)
pfUI.chat.urlcopy:SetScript("OnDragStop",function()
this:StopMovingOrSizing()
end)
pfUI.chat.urlcopy:SetScript("OnShow", function()
this.text:HighlightText()
end)
pfUI.chat.urlcopy.text = CreateFrame("EditBox", "pfURLCopyEditBox", pfUI.chat.urlcopy)
pfUI.chat.urlcopy.text:SetTextColor(.2,1,.8,1)
pfUI.chat.urlcopy.text:SetJustifyH("CENTER")
pfUI.chat.urlcopy.text:SetWidth(250)
pfUI.chat.urlcopy.text:SetHeight(20)
pfUI.chat.urlcopy.text:SetPoint("TOP", pfUI.chat.urlcopy, "TOP", 0, -10)
pfUI.chat.urlcopy.text:SetFontObject(GameFontNormal)
CreateBackdrop(pfUI.chat.urlcopy.text)
pfUI.chat.urlcopy.text:SetScript("OnEscapePressed", function(self)
pfUI.chat.urlcopy:Hide()
end)
pfUI.chat.urlcopy.text:SetScript("OnEditFocusLost", function(self)
pfUI.chat.urlcopy:Hide()
end)
pfUI.chat.urlcopy.close = CreateFrame("Button", "pfURLCopyClose", pfUI.chat.urlcopy, "UIPanelButtonTemplate")
pfUI.api.SkinButton(pfUI.chat.urlcopy.close)
pfUI.chat.urlcopy.close:SetWidth(70)
pfUI.chat.urlcopy.close:SetHeight(18)
pfUI.chat.urlcopy.close:SetPoint("BOTTOMRIGHT", pfUI.chat.urlcopy, "BOTTOMRIGHT", -10, 10)
pfUI.chat.urlcopy.close:SetText(T["Close"])
pfUI.chat.urlcopy.close:SetScript("OnClick", function()
pfUI.chat.urlcopy:Hide()
end)
pfUI.chat.urlcopy.SetItemRef = SetItemRef
pfUI.chat.urlcopy.CopyText = function(text)
pfUI.chat.urlcopy.text:SetText(text)
pfUI.chat.urlcopy:Show()
end
function _G.SetItemRef(link, text, button)
if (strsub(link, 1, 3) == "url") then
if string.len(link) > 4 and string.sub(link,1,4) == "url:" then
pfUI.chat.urlcopy.CopyText(string.sub(link,5, string.len(link)))
end
return
end
pfUI.chat.urlcopy.SetItemRef(link, text, button)
end
pfUI.chat.right = CreateFrame("Frame", "pfChatRight", UIParent)
pfUI.chat.right:SetFrameStrata("BACKGROUND")
pfUI.chat.right:SetWidth(C.chat.right.width)
pfUI.chat.right:SetHeight(C.chat.right.height)
pfUI.chat.right:SetPoint("BOTTOMRIGHT", -2*default_border,2*default_border)
pfUI.chat.right:SetScript("OnShow", function() pfUI.chat:RefreshChat() end)
UpdateMovable(pfUI.chat.right)
CreateBackdrop(pfUI.chat.right, default_border, nil, .8)
if C.chat.global.frameshadow == "1" then
CreateBackdropShadow(pfUI.chat.right)
end
if C.chat.global.custombg == "1" then
local r, g, b, a = GetStringColor(C.chat.global.background)
pfUI.chat.right.backdrop:SetBackdropColor(tonumber(r), tonumber(g), tonumber(b), tonumber(a))
local r, g, b, a = GetStringColor(C.chat.global.border)
pfUI.chat.right.backdrop:SetBackdropBorderColor(tonumber(r), tonumber(g), tonumber(b), tonumber(a))
end
pfUI.chat.right.panelTop = CreateFrame("Frame", "rightChatPanelTop", pfUI.chat.right)
pfUI.chat.right.panelTop:ClearAllPoints()
pfUI.chat.right.panelTop:SetHeight(C.global.font_size+default_border*2)
pfUI.chat.right.panelTop:SetPoint("TOPLEFT", pfUI.chat.right, "TOPLEFT", default_border, -default_border)
pfUI.chat.right.panelTop:SetPoint("TOPRIGHT", pfUI.chat.right, "TOPRIGHT", -default_border, -default_border)
if C.chat.global.tabdock == "1" then
CreateBackdrop(pfUI.chat.right.panelTop, default_border, nil, .8)
end
pfUI.chat:RegisterEvent("PLAYER_ENTERING_WORLD")
pfUI.chat:RegisterEvent("UI_SCALE_CHANGED")
local function ChatOnMouseWheel()
if (arg1 > 0) then
if IsShiftKeyDown() then
this:ScrollToTop()
else
for i=1, C.chat.global.scrollspeed do
this:ScrollUp()
end
end
elseif (arg1 < 0) then
if IsShiftKeyDown() then
this:ScrollToBottom()
else
for i=1, C.chat.global.scrollspeed do
this:ScrollDown()
end
end
end
end
-- [ Chat Background Alpha ]
-- Blizzard's per-window transparency slider drives FCF_SetWindowAlpha, which only
-- touches the ChatFrame*Background textures that pfUI hides on docked frames. Mirror
-- the window's stored alpha onto the visible pfUI backdrop so the native slider
-- controls it directly. The slider is authoritative for the panel's alpha.
local function ApplyChatBackgroundAlpha(panel, frame)
if not (panel and panel.backdrop and frame) then return end
local _, _, _, _, _, alpha = GetChatWindowInfo(frame:GetID())
alpha = tonumber(alpha)
if not alpha then return end
local r, g, b = panel.backdrop:GetBackdropColor()
panel.backdrop:SetBackdropColor(r, g, b, alpha)
end
function pfUI.chat:RefreshBackgroundAlpha()
-- left panel follows the currently selected docked tab
local selected = SELECTED_CHAT_FRAME
if not (selected and selected:GetParent() == pfUI.chat.left) then
selected = ChatFrame1
end
ApplyChatBackgroundAlpha(pfUI.chat.left, selected)
if C.chat.right.enable == "1" then
ApplyChatBackgroundAlpha(pfUI.chat.right, ChatFrame3)
end
end
function pfUI.chat:MigrateBackgroundAlpha()
-- Runs once per character. The previous SetupPositions default stored a hard 0 window
-- alpha, which now renders the pfUI backdrop fully transparent. Restore the historical
-- 0.8 look for any pfUI-managed window still sitting at 0. Only existing installs that
-- already ran chat setup carry that legacy 0; fresh installs get 0.8 from SetupPositions.
if pfUI_init.chatbgalpha then return end
if not pfUI_init["chat_position"] then return end
pfUI_init.chatbgalpha = true
local frames = { ChatFrame1, ChatFrame2 }
if C.chat.right.enable == "1" then table.insert(frames, ChatFrame3) end
for _, frame in ipairs(frames) do
local _, _, _, _, _, alpha = GetChatWindowInfo(frame:GetID())
alpha = tonumber(alpha)
if alpha and alpha <= 0 then
FCF_SetWindowAlpha(frame, 0.8)
end
end
end
function pfUI.chat:RefreshChat()
local panelheight = C.global.font_size*1.5 + default_border*2 + 2
if C.chat.global.sticky == "1" then
ChatTypeInfo.WHISPER.sticky = 1
ChatTypeInfo.OFFICER.sticky = 1
ChatTypeInfo.RAID_WARNING.sticky = 1
ChatTypeInfo.CHANNEL.sticky = 1
end
ChatFrameMenuButton:Hide()
ChatMenu:SetClampedToScreen(true)
CreateBackdrop(ChatMenu)
CreateBackdrop(EmoteMenu)
CreateBackdrop(LanguageMenu)
CreateBackdrop(VoiceMacroMenu)
local combatlogpanel = (CombatLogQuickButtonFrame_Custom and CombatLogQuickButtonFrame_Custom:GetHeight() or 0)
for i=1, NUM_CHAT_WINDOWS do
local frame = _G["ChatFrame"..i]
local tab = _G["ChatFrame"..i.."Tab"]
local combat = 0
for _, msg in pairs(_G["ChatFrame"..i].messageTypeList) do
if strfind(msg, "SPELL", 1) or strfind(msg, "COMBAT", 1) then
combat = combat + 1
end
end
if combat > 5 then
frame.pfCombatLog = true
else
frame.pfCombatLog = nil
end
if not frame.pfStartMoving then
frame.pfStartMoving = frame.StartMoving
frame.StartMoving = function(a1)
pfUI.chat.hideLock = true
frame.pfStartMoving(a1)
end
end
if not frame.pfStopMovingOrSizing then
frame.pfStopMovingOrSizing = frame.StopMovingOrSizing
frame.StopMovingOrSizing = function(a1)
frame.pfStopMovingOrSizing(a1)
pfUI.chat.RefreshChat()
pfUI.chat.hideLock = false
end
end
if C.chat.global.fadeout == "1" then
frame:SetFading(true)
frame:SetTimeVisible(tonumber(C.chat.global.fadetime))
else
frame:SetFading(false)
end
if i == 3 and C.chat.right.enable == "1" then
-- Loot & Spam
local bottompadding = pfUI.panel and pfUI.panel.right:IsShown() and not pfUI_config.position["pfPanelRight"] and panelheight or default_border
tab:SetParent(pfUI.chat.right.panelTop)
frame:SetParent(pfUI.chat.right)
frame:ClearAllPoints()
frame:SetPoint("TOPLEFT", pfUI.chat.right ,"TOPLEFT", default_border, -panelheight)
frame:SetPoint("BOTTOMRIGHT", pfUI.chat.right ,"BOTTOMRIGHT", -default_border, bottompadding)
frame:Show()
elseif frame.isDocked then
-- Left Chat
local bottompadding = pfUI.panel and pfUI.panel.left:IsShown() and not pfUI_config.position["pfPanelLeft"] and panelheight or default_border
FCF_DockFrame(frame)
tab:SetParent(pfUI.chat.left.panelTop)
frame:SetParent(pfUI.chat.left)
frame:ClearAllPoints()
if i == 2 then
frame:SetPoint("TOPLEFT", pfUI.chat.left ,"TOPLEFT", default_border, -panelheight - combatlogpanel)
else
frame:SetPoint("TOPLEFT", pfUI.chat.left ,"TOPLEFT", default_border, -panelheight)
end
frame:SetPoint("BOTTOMRIGHT", pfUI.chat.left ,"BOTTOMRIGHT", -default_border, bottompadding)
else
FCF_UnDockFrame(frame)
frame:SetParent(UIParent)
tab:SetParent(UIParent)
end
-- Combat Log
if C.chat.global.combathide == "1" and frame.pfCombatLog then
FCF_UnDockFrame(frame)
FCF_Close(frame)
end
-- hide textures
for j,v in ipairs({tab:GetRegions()}) do
if j==5 then v:SetTexture(0,0,0,0) end
v:SetHeight(C.global.font_size+default_border*2)
end
-- remove background on docked frames
for _, tex in pairs(CHAT_FRAME_TEXTURES) do
local texture = _G["ChatFrame"..i..tex]
if tex == "Background" then
texture.oldTexture = texture.oldTexture or texture:GetTexture()
if frame:GetParent() == pfUI.chat.left or frame:GetParent() == pfUI.chat.right then
texture:SetTexture()
texture:Hide()
else
texture:SetTexture(texture.oldTexture)
texture:Show()
end
else
texture:SetTexture()
texture:Hide()
end
end
_G["ChatFrame" .. i .. "ResizeBottom"]:Hide()
_G["ChatFrame" .. i .. "TabText"]:SetJustifyV("CENTER")
_G["ChatFrame" .. i .. "TabText"]:SetHeight(C.global.font_size+default_border*2)
_G["ChatFrame" .. i .. "TabText"]:SetPoint("BOTTOM", 0, default_border)
_G["ChatFrame" .. i .. "TabLeft"]:SetAlpha(0)
_G["ChatFrame" .. i .. "TabMiddle"]:SetAlpha(0)
_G["ChatFrame" .. i .. "TabRight"]:SetAlpha(0)
if C.chat.global.chatflash == "1" then
_G["ChatFrame" .. i .. "TabFlash"]:SetAllPoints(_G["ChatFrame" .. i .. "TabText"])
else
_G["ChatFrame" .. i .. "TabFlash"].Show = function() return end
end
local class = UnitClassBase("player")
local classColor = PFUI_CLASS_COLORS[class]
_G["ChatFrame" .. i .. "TabText"]:SetTextColor((classColor.r + .3) * .5, (classColor.g + .3) * .5, (classColor.b + .3) * .5, 1)
_G["ChatFrame" .. i .. "TabText"]:SetFont(panelfont,panelfont_size, "OUTLINE")
if _G["ChatFrame" .. i].isDocked or _G["ChatFrame" .. i]:IsVisible() then
_G["ChatFrame" .. i .. "Tab"]:Show()
end
frame:EnableMouseWheel(true)
frame:SetScript("OnMouseWheel", ChatOnMouseWheel)
end
-- update dock frame for all windows
for index, value in pairs(DOCKED_CHAT_FRAMES) do
FCF_UpdateButtonSide(value)
end
pfUI.chat:RefreshBackgroundAlpha()
end
hooksecurefunc("FCF_SaveDock", pfUI.chat.RefreshChat)
hooksecurefunc("FCF_SetWindowAlpha", function() pfUI.chat:RefreshBackgroundAlpha() end)
hooksecurefunc("FCF_SelectDockFrame", function() pfUI.chat:RefreshBackgroundAlpha() end)
if C.chat.global.tabmouse == "1" then
pfUI.chat.mouseovertab = CreateFrame("Frame")
pfUI.chat.mouseovertab:SetScript("OnUpdate", function()
-- throttle
if ( this.tick or .1) > GetTime() then return else this.tick = GetTime() + pfUI.throttle:Get("chat_tab") end -- Default: Normal (10 FPS)
if pfUI.chat.hideLock then return end
if MouseIsOver(pfUI.chat.left, 10, -10, -10, 10) then
pfUI.chat.left.panelTop:Show()
FCF_DockUpdate()
elseif MouseIsOver(pfUI.chat.right, 10, -10, -10, 10) then
-- disable while dock is active
if pfUI.chat.right:GetAlpha() == 0 then return end
pfUI.chat.right.panelTop:Show()
FCF_DockUpdate()
else
pfUI.chat.left.panelTop:Hide()
pfUI.chat.right.panelTop:Hide()
end
end)
end
function pfUI.chat.SetupRightChat(state)
if state then
C.chat.right.enable = "1"
pfUI.chat.right:Show()
else
C.chat.right.enable = "0"
pfUI.chat.right:Hide()
end
end
function pfUI.chat.SetupPositions()
-- close all chat windows
for i=1, NUM_CHAT_WINDOWS do
FCF_Close(_G["ChatFrame"..i])
FCF_DockUpdate()
end
-- Main Window
ChatFrame1:ClearAllPoints()
ChatFrame1:SetPoint("TOPLEFT", pfUI.chat.left ,"TOPLEFT", 5, -25)
ChatFrame1:SetPoint("BOTTOMRIGHT", pfUI.chat.left ,"BOTTOMRIGHT", -5, 25)
FCF_SetLocked(ChatFrame1, 1)
FCF_SetWindowName(ChatFrame1, GENERAL)
FCF_SetWindowColor(ChatFrame1, 0, 0, 0)
FCF_SetWindowAlpha(ChatFrame1, 0.8)
FCF_SetChatWindowFontSize(ChatFrame1, 12)
ChatFrame1:SetUserPlaced(1)
-- Combat Log
FCF_SetLocked(ChatFrame2, 1)
FCF_SetWindowName(ChatFrame2, COMBAT_LOG)
FCF_SetWindowColor(ChatFrame2, 0, 0, 0)
FCF_SetWindowAlpha(ChatFrame2, 0.8)
FCF_SetChatWindowFontSize(ChatFrame2, 12)
ChatFrame2:SetUserPlaced(1)
-- Loot & Spam
if C.chat.right.enable == "1" then
-- set position of Loot & Spam
FCF_SetLocked(ChatFrame3, 1)
FCF_SetWindowName(ChatFrame3, T["Loot & Spam"])
FCF_SetWindowColor(ChatFrame3, 0, 0, 0)
FCF_SetWindowAlpha(ChatFrame3, 0.8)
FCF_SetChatWindowFontSize(ChatFrame3, 12)
FCF_UnDockFrame(ChatFrame3)
FCF_SetTabPosition(ChatFrame3, 0)
ChatFrame3:ClearAllPoints()
ChatFrame3:SetPoint("TOPLEFT", pfUI.chat.right ,"TOPLEFT", 5, -25)
ChatFrame3:SetPoint("BOTTOMRIGHT", pfUI.chat.right ,"BOTTOMRIGHT", -5, 25)
ChatFrame3:SetUserPlaced(1)
end
pfUI.chat:RefreshChat()
FCF_DockUpdate()
end
function pfUI.chat.SetupChannels()
ChatFrame_RemoveAllMessageGroups(ChatFrame1)
ChatFrame_RemoveAllMessageGroups(ChatFrame2)
ChatFrame_RemoveAllMessageGroups(ChatFrame3)
ChatFrame_RemoveAllChannels(ChatFrame1)
ChatFrame_RemoveAllChannels(ChatFrame2)
ChatFrame_RemoveAllChannels(ChatFrame3)
local normalg = { "SYSTEM", "SAY", "YELL", "WHISPER", "PARTY", "GUILD", "GUILD_OFFICER", "CREATURE", "CHANNEL", "EMOTE", "RAID", "RAID_LEADER", "RAID_WARNING", "BATTLEGROUND", "BATTLEGROUND_LEADER", "MONSTER_SAY", "MONSTER_EMOTE", "MONSTER_YELL", "MONSTER_WHISPER", "MONSTER_BOSS_EMOTE", "MONSTER_BOSS_WHISPER" }
for _,group in pairs(normalg) do
ChatFrame_AddMessageGroup(ChatFrame1, group)
end
ChatFrame_ActivateCombatMessages(ChatFrame2)
if C.chat.right.enable == "1" then
local spamg = { "COMBAT_XP_GAIN", "COMBAT_HONOR_GAIN", "COMBAT_FACTION_CHANGE", "SKILL", "LOOT" }
for _,group in pairs(spamg) do
ChatFrame_AddMessageGroup(ChatFrame3, group)
end
for _, chan in pairs({EnumerateServerChannels()}) do
ChatFrame_AddChannel(ChatFrame3, chan)
ChatFrame_RemoveChannel(ChatFrame1, chan)
end
JoinChannelByName("World")
ChatFrame_AddChannel(ChatFrame3, "World")
end
pfUI.chat:RefreshChat()
end
pfUI.chat:SetScript("OnEvent", function()
-- restore legacy chat windows stuck at 0 alpha before anything reads it
pfUI.chat:MigrateBackgroundAlpha()
-- set the default chat
FCF_SelectDockFrame(SELECTED_CHAT_FRAME)
-- update all chat settings
pfUI.chat:RefreshChat()
FCF_DockUpdate()
if C.chat.right.enable == "0" then
pfUI.chat.right:Hide()
end
end)
local function SkipFading(self, alpha)
UIFrameFadeRemoveFrame(self)
if SELECTED_CHAT_FRAME:GetID() == self:GetID() then
self:_SetAlpha(1)
else
self:_SetAlpha(0.5)
end
end
for i=1, NUM_CHAT_WINDOWS do
_G["ChatFrame" .. i .. "UpButton"]:Hide()
_G["ChatFrame" .. i .. "UpButton"].Show = function() return end
_G["ChatFrame" .. i .. "DownButton"]:Hide()
_G["ChatFrame" .. i .. "DownButton"].Show = function() return end
_G["ChatFrame" .. i .. "BottomButton"]:Hide()
_G["ChatFrame" .. i .. "BottomButton"].Show = function() return end
_G["ChatFrame" .. i .. "Tab"]._SetAlpha = _G["ChatFrame" .. i .. "Tab"].SetAlpha
_G["ChatFrame" .. i .. "Tab"].SetAlpha = SkipFading
end
pfUI.chat.editbox = CreateFrame("Frame", "pfChatInputBox", UIParent)
pfUI.chat.editbox:SetFrameStrata("DIALOG")
if C.chat.text.input_height == "0" then
pfUI.chat.editbox:SetHeight(22)
if ChatFrameEditBoxLanguage then
SkinButton(ChatFrameEditBoxLanguage)
ChatFrameEditBoxLanguage:SetWidth(22)
ChatFrameEditBoxLanguage:SetHeight(22)
end
else
pfUI.chat.editbox:SetHeight(C.chat.text.input_height)
if ChatFrameEditBoxLanguage then
SkinButton(ChatFrameEditBoxLanguage)
ChatFrameEditBoxLanguage:SetWidth(C.chat.text.input_height)
ChatFrameEditBoxLanguage:SetHeight(C.chat.text.input_height)
end
end
-- to make sure the bars are set up properly, we need to wait.
local pfChatArrangeFrame = CreateFrame("Frame", "pfChatArrange", UIParent)
pfChatArrangeFrame:RegisterEvent("CVAR_UPDATE")
pfChatArrangeFrame:RegisterEvent("PLAYER_ENTERING_WORLD")
pfChatArrangeFrame:SetScript("OnEvent", function()
pfUI.chat.editbox:ClearAllPoints()
local anchor = pfUI.chat.left
if pfUI.bars and pfUI.bars[6]:IsShown() then
anchor = pfUI.bars[6]
elseif pfUI.bars and pfUI.bars[1]:IsShown() then
anchor = pfUI.bars[1]
end
if C.chat.text.input_width ~= "0" then
pfUI.chat.editbox:SetPoint("BOTTOM", anchor, "TOP", 0, default_border*3)
pfUI.chat.editbox:SetWidth(C.chat.text.input_width)
else
pfUI.chat.editbox:SetWidth(anchor:GetWidth())
pfUI.chat.editbox:SetPoint("BOTTOMLEFT", anchor, "TOPLEFT", 0, default_border*3)
pfUI.chat.editbox:SetPoint("BOTTOMRIGHT", anchor, "TOPRIGHT", 0, default_border*3)
end
UpdateMovable(pfUI.chat.editbox)
end)
ChatFrameEditBox:SetParent(pfUI.chat.editbox)
ChatFrameEditBox:SetAllPoints(pfUI.chat.editbox)
CreateBackdrop(ChatFrameEditBox, default_border)
for i,v in ipairs({ChatFrameEditBox:GetRegions()}) do
if i==6 or i==7 or i==8 then v:Hide() end
if v.SetFont then
v:SetFont(pfUI.font_default, C.global.font_size + 1, "OUTLINE")
end
end
ChatFrameEditBox:SetAltArrowKeyMode(false)
if C.chat.text.mouseover == "1" then
for i=1, NUM_CHAT_WINDOWS do
local frame = _G["ChatFrame" .. i]
frame:SetScript("OnHyperlinkEnter", function()
local _, _, linktype = string.find(arg1, "^(.-):(.+)$")
if linktype == "item" then
GameTooltip:SetOwner(this, "ANCHOR_CURSOR")
GameTooltip:SetHyperlink(arg1)
GameTooltip:Show()
end
end)
frame:SetScript("OnHyperlinkLeave", function()
GameTooltip:Hide()
end)
end
end
-- read and parse whisper color settings
local cr, cg, cb, ca = GetStringColor(C.chat.global.whisper)
cr, cg, cb = tonumber(cr), tonumber(cg), tonumber(cb)
local wcol = rgbhex(cr, cg, cb)
-- read and parse chat bracket settings
local left = "|r" .. string.sub(C.chat.text.bracket, 1, 1)
local right = string.sub(C.chat.text.bracket, 2, 2) .. "|r"
-- read and parse chat time bracket settings
local tleft = string.sub(C.chat.text.timebracket, 1, 1)
local tright = string.sub(C.chat.text.timebracket, 2, 2)
-- shorten chat channel indicators
local default = " " .. "%s" .. "|r:" .. "\32"
_G.CHAT_CHANNEL_GET = "%s" .. "|r:" .. "\32"
_G.CHAT_GUILD_GET = left .. "G" .. right .. default
_G.CHAT_OFFICER_GET = left .. "O" .. right .. default
_G.CHAT_PARTY_GET = left .. "P" .. right .. default
_G.CHAT_RAID_GET = left .. "R" .. right .. default
_G.CHAT_RAID_LEADER_GET = left .. "RL" .. right .. default
_G.CHAT_RAID_WARNING_GET = left .. "RW" .. right .. default
_G.CHAT_BATTLEGROUND_GET = left .. "BG" .. right .. default
_G.CHAT_BATTLEGROUND_LEADER_GET = left .. "BL" .. right .. default
_G.CHAT_SAY_GET = left .. "S" .. right .. default
_G.CHAT_YELL_GET = left .. "Y" .. right ..default
if C.chat.global.whispermod == "1" then
_G.CHAT_WHISPER_GET = wcol .. '[W]' .. default
_G.CHAT_WHISPER_INFORM_GET = '[W]' .. default
end
local r,g,b,a = GetStringColor(C.chat.text.timecolor)
local timecolorhex = rgbhex(r,g,b,a)
local r,g,b = GetStringColor(C.chat.text.unknowncolor)
local unknowncolorhex = rgbhex(r,g,b)
-- Suppress FriendsFrame's WHO_LIST_UPDATE handling for our DLL-issued
-- name lookups so they don't pop the friends panel. User-issued /who
-- still works because IsWhoQueryPending only reports true for queries
-- issued via C_FriendList.SendWhoQueryByName.
local original_FriendsFrame_OnEvent = FriendsFrame_OnEvent
_G.FriendsFrame_OnEvent = function()
if _G.event == "WHO_LIST_UPDATE" and C_FriendList.IsWhoQueryPending() then return end
return original_FriendsFrame_OnEvent()
end
local function GetPlayerLevel(name)
if not pfUI_playerDB then return nil end
if not pfUI_playerDB[name] then return nil end
return pfUI_playerDB[name].level
end
local function AddMessage(frame, text, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17)
if not text then return end
-- skip chat parsing on combat log
if frame.pfCombatLog then
return frame:HookAddMessage(text, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17)
end
-- Remove prat CLINKs
text = gsub(text, "{CLINK:(%x+):([%d-]-:[%d-]-:[%d-]-:[%d-]-:[%d-]-:[%d-]-:[%d-]-:[%d-]-):([^}]-)}", "|c%1|Hitem:%2|h[%3]|h|r") -- tbc
text = gsub(text, "{CLINK:(%x+):([%d-]-:[%d-]-:[%d-]-:[%d-]-):([^}]-)}", "|c%1|Hitem:%2|h[%3]|h|r") -- vanilla
-- Remove chatter CLINKs
text = gsub(text, "{CLINK:item:(%x+):([%d-]-:[%d-]-:[%d-]-:[%d-]-:[%d-]-:[%d-]-:[%d-]-:[%d-]-):([^}]-)}", "|c%1|Hitem:%2|h[%3]|h|r")
text = gsub(text, "{CLINK:enchant:(%x+):([%d-]-):([^}]-)}", "|c%1|Henchant:%2|h[%3]|h|r")
text = gsub(text, "{CLINK:spell:(%x+):([%d-]-):([^}]-)}", "|c%1|Hspell:%2|h[%3]|h|r")
text = gsub(text, "{CLINK:quest:(%x+):([%d-]-):([%d-]-):([^}]-)}", "|c%1|Hquest:%2:%3|h[%4]|h|r")
-- detect urls
if C.chat.text.detecturl == "1" then
text = pfUI.chat:HandleLink(text)
end
-- display class colors if already indexed
if C.chat.text.classcolor == "1" then
for name in gfind(text, "|Hplayer:(.-)|h") do
local real, _ = strsplit(":", name)
local color = unknowncolorhex
local match = false
local _, class = C_PlayerCache.GetPlayerInfoByName(real)
-- local guid = GetCurrentChatGUID()
-- if guid then
-- _, class = GetPlayerInfoByGUID(guid)
-- end
if not class then
class = GetUnitInfo(real)
end
if class then
if class ~= UNKNOWN then
color = "|c" .. PFUI_CLASS_COLORS[class].colorStr
match = true
end
elseif C.chat.text.whosearchunknown == "1" then
C_FriendList.SendWhoQueryByName(name) -- cooldown + state managed by DLL
end
if C.chat.text.tintunknown == "1" or match then
text = string.gsub(text, "|Hplayer:"..name.."|h%["..real.."%]|h(.-:-)",
left..color.."|Hplayer:"..name.."|h" .. color .. real .. "|h|r"..right.."%1")
end
end
end
-- display player levels if available
if C.chat.text.playerlevel == "1" then
for name in gfind(text, "|Hplayer:(.-)|h") do
local real, _ = strsplit(":", name)
local level = GetPlayerLevel(real)
if level and level > 0 then
local levelcolor = rgbhex(GetDifficultyColor(level))
-- Add level after the player name, before the closing bracket
text = string.gsub(text, "(|Hplayer:" .. name .. "|h.-|h|r)" .. right,
"%1 " .. levelcolor .. level .. "|r" .. right)
elseif level and level <= 0 then
-- Show ?? for unknown levels (e.g. -1 from UnitLevel)
text = string.gsub(text, "(|Hplayer:" .. name .. "|h.-|h|r)" .. right,
"%1 |cffff0000??|r" .. right)
end
end
end
-- reduce channel name to number
if C.chat.text.channelnumonly == "1" then
local channel = string.gsub(text, ".*%[(.-)%]%s+(.*|Hplayer).+", "%1")
if string.find(channel, "%d+%. ") then
channel = string.gsub(channel, "(%d+)%..*", "channel%1")
channel = string.gsub(channel, "channel", "")
text = string.gsub(text, "%[%d+%..-%]%s+(.*|Hplayer)", left .. channel .. right .. " %1")
end
end
-- detect the whisper prefix BEFORE prepending a timestamp; the timestamp
-- pushes the whisper colour code off position 1 and the find below fails
local isWhisper = C.chat.global.whispermod == "1" and string.find(text, wcol, 1) == 1
-- show timestamp in chat
if C.chat.text.time == "1" then
text = timecolorhex .. tleft .. date(C.chat.text.timeformat) .. tright .. "|r " .. text
end
-- save chat history
if isWhisper then
SaveChatHistory(frame:GetID(), string.gsub(text, wcol, ""), cr, cg, cb)
else
SaveChatHistory(frame:GetID(), text, a1, a2, a3)
end
if isWhisper then
-- patch incoming whisper string to match the colors
text = string.gsub(text, "|r", "|r" .. wcol)
end
frame:HookAddMessage(text, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17)
end
for i=1,NUM_CHAT_WINDOWS do
if C.chat.global.maxlines ~= "128" then
_G["ChatFrame"..i]:SetMaxLines(tonumber(C.chat.global.maxlines) or 128)
end
if not _G["ChatFrame"..i].HookAddMessage then
if C.chat.text.history == "1" then
-- write history to chat
local history = GetChatHistory(i)
for j=30,0,-1 do
if history[j] then
_G["ChatFrame"..i]:AddMessage(history[j], .7,.7,.7)
end
end
end
-- add chat parse and history hooks
_G["ChatFrame"..i].HookAddMessage = _G["ChatFrame"..i].AddMessage
_G["ChatFrame"..i].AddMessage = AddMessage
end
end
-- create playerlinks on shift-click
if C.chat.text.playerlinks == "1" then
local pfHookSetItemRef = SetItemRef
_G.SetItemRef = function(link, text, button)
if ( strsub(link, 1, 6) == "player" ) then
local name = strsub(link, 8)
if ( name and (strlen(name) > 0) ) then
local name, _ = strsplit(":", name)
name = gsub(name, "([^%s]*)%s+([^%s]*)%s+([^%s]*)", "%3")
name = gsub(name, "([^%s]*)%s+([^%s]*)", "%2")
if IsShiftKeyDown() and ChatFrameEditBox:IsVisible() then
ChatFrameEditBox:Insert("|cffffffff|Hplayer:"..name.."|h["..name.."]|h|r")
return
end
end
end
pfHookSetItemRef(link, text, button)
end
end
end)