local E, L, V, P, G = unpack(select(2, ...)); --Import: Engine, Locales, PrivateDB, ProfileDB, GlobalDB local CH = E:GetModule("Chat") local LO = E:GetModule("Layout") local Skins = E:GetModule("Skins") local LibBase64 = E.Libs.Base64 local LSM = E.Libs.LSM --Lua functions local _G = _G local time = time local pairs, ipairs, unpack, select, tostring, pcall, next, tonumber, type = pairs, ipairs, unpack, select, tostring, pcall, next, tonumber, type local tinsert, tremove, tconcat, wipe = table.insert, table.remove, table.concat, table.wipe local gsub, find, gmatch, format, strtrim = string.gsub, string.find, string.gmatch, string.format, string.trim local strlower, strmatch, strsub, strlen, strupper = strlower, strmatch, strsub, strlen, strupper --WoW API / Variables local BetterDate = BetterDate local ChatEdit_ActivateChat = ChatEdit_ActivateChat local ChatEdit_ChooseBoxForSend = ChatEdit_ChooseBoxForSend local ChatEdit_ParseText = ChatEdit_ParseText local ChatEdit_SetLastTellTarget = ChatEdit_SetLastTellTarget local ChatFrame_ConfigEventHandler = ChatFrame_ConfigEventHandler local ChatFrame_GetMessageEventFilters = ChatFrame_GetMessageEventFilters local ChatFrame_SendTell = ChatFrame_SendTell local ChatFrame_SystemEventHandler = ChatFrame_SystemEventHandler local ChatHistory_GetAccessID = ChatHistory_GetAccessID local Chat_GetChatCategory = Chat_GetChatCategory local CreateFrame = CreateFrame local FCFManager_ShouldSuppressMessage = FCFManager_ShouldSuppressMessage local FCFTab_UpdateAlpha = FCFTab_UpdateAlpha local FCF_GetCurrentChatFrame = FCF_GetCurrentChatFrame local FCF_SavePositionAndDimensions = FCF_SavePositionAndDimensions local FCF_SetLocked = FCF_SetLocked local FCF_StartAlertFlash = FCF_StartAlertFlash local FloatingChatFrame_OnEvent = FloatingChatFrame_OnEvent local GMChatFrame_IsGM = GMChatFrame_IsGM local GetChannelName = GetChannelName local GetGuildRosterMOTD = GetGuildRosterMOTD local GetMouseFocus = GetMouseFocus local GetNumPartyMembers = GetNumPartyMembers local GetNumRaidMembers = GetNumRaidMembers local GetPlayerInfoByGUID = GetPlayerInfoByGUID local HasLFGRestrictions = HasLFGRestrictions local InCombatLockdown = InCombatLockdown local IsAltKeyDown = IsAltKeyDown local IsInInstance = IsInInstance local IsMouseButtonDown = IsMouseButtonDown local IsShiftKeyDown = IsShiftKeyDown local PlaySoundFile = PlaySoundFile local ScrollFrameTemplate_OnMouseWheel = ScrollFrameTemplate_OnMouseWheel local StaticPopup_Visible = StaticPopup_Visible local ToggleFrame = ToggleFrame local UnitIsSameServer = UnitIsSameServer local UnitName = UnitName local hooksecurefunc = hooksecurefunc local AFK = AFK local CHAT_BN_CONVERSATION_GET_LINK = CHAT_BN_CONVERSATION_GET_LINK local CHAT_FILTERED = CHAT_FILTERED local CHAT_FRAMES = CHAT_FRAMES local CHAT_IGNORED = CHAT_IGNORED local CHAT_OPTIONS = CHAT_OPTIONS local CHAT_RESTRICTED = CHAT_RESTRICTED local DEFAULT_CHAT_FRAME = DEFAULT_CHAT_FRAME local DND = DND local ICON_LIST = ICON_LIST local ICON_TAG_LIST = ICON_TAG_LIST local MAX_WOW_CHAT_CHANNELS = MAX_WOW_CHAT_CHANNELS local NUM_CHAT_WINDOWS = NUM_CHAT_WINDOWS local RAID_WARNING = RAID_WARNING local throttle = {} CH.GuidCache = {} CH.ClassNames = {} CH.Keywords = {} CH.Smileys = {} local DEFAULT_STRINGS = { BATTLEGROUND = L["BG"], GUILD = L["G"], PARTY = L["P"], RAID = L["R"], OFFICER = L["O"], BATTLEGROUND_LEADER = L["BGL"], PARTY_LEADER = L["PL"], RAID_LEADER = L["RL"], } local hyperlinkTypes = { ["item"] = true, ["spell"] = true, ["unit"] = true, ["quest"] = true, ["enchant"] = true, ["achievement"] = true, ["instancelock"] = true, ["talent"] = true, ["glyph"] = true, } local tabTexs = { "", "Selected", "Highlight" } local historyTypes = { -- the events set on the chats are still in FindURL_Events, this is used to ignore some types only CHAT_MSG_WHISPER = "WHISPER", CHAT_MSG_WHISPER_INFORM = "WHISPER", CHAT_MSG_BN_WHISPER = "WHISPER", CHAT_MSG_BN_WHISPER_INFORM = "WHISPER", CHAT_MSG_GUILD = "GUILD", CHAT_MSG_GUILD_ACHIEVEMENT = "GUILD", CHAT_MSG_OFFICER = "OFFICER", CHAT_MSG_PARTY = "PARTY", CHAT_MSG_PARTY_LEADER = "PARTY", CHAT_MSG_RAID = "RAID", CHAT_MSG_RAID_LEADER = "RAID", CHAT_MSG_RAID_WARNING = "RAID", CHAT_MSG_BATTLEGROUND = "BATTLEGROUND", CHAT_MSG_BATTLEGROUND_LEADER = "BATTLEGROUND", CHAT_MSG_CHANNEL = "CHANNEL", CHAT_MSG_SAY = "SAY", CHAT_MSG_YELL = "YELL", CHAT_MSG_EMOTE = "EMOTE" -- this never worked, check it sometime. } function CH:RemoveSmiley(key) if key and (type(key) == "string") then CH.Smileys[key] = nil end end function CH:AddSmiley(key, texture) if key and (type(key) == "string" and not find(key, ":%%", 1, true)) and texture then CH.Smileys[key] = texture end end local specialChatIcons do --this can save some main file locals local y = ":13:25" -- local ElvMelon = E:TextureString(E.Media.ChatLogos.ElvMelon,y) -- local ElvRainbow = E:TextureString(E.Media.ChatLogos.ElvRainbow,y) -- local ElvRed = E:TextureString(E.Media.ChatLogos.ElvRed,y) -- local ElvOrange = E:TextureString(E.Media.ChatLogos.ElvOrange,y) -- local ElvYellow = E:TextureString(E.Media.ChatLogos.ElvYellow,y) -- local ElvGreen = E:TextureString(E.Media.ChatLogos.ElvGreen,y) -- local ElvBlue = E:TextureString(E.Media.ChatLogos.ElvBlue,y) -- local ElvPurple = E:TextureString(E.Media.ChatLogos.ElvPurple,y) local ElvPink = E:TextureString(E.Media.ChatLogos.ElvPink,y) specialChatIcons = { ["Крольчонак-x100"] = ElvPink, } end local function ChatFrame_OnMouseScroll(frame, delta) if delta < 0 then if IsShiftKeyDown() then frame:ScrollToBottom() elseif IsAltKeyDown() then frame:ScrollDown() else for _ = 1, (CH.db.numScrollMessages or 3) do frame:ScrollDown() end end elseif delta > 0 then if IsShiftKeyDown() then frame:ScrollToTop() elseif IsAltKeyDown() then frame:ScrollUp() else for _ = 1, (CH.db.numScrollMessages or 3) do frame:ScrollUp() end end if CH.db.scrollDownInterval ~= 0 then if frame.ScrollTimer then CH:CancelTimer(frame.ScrollTimer, true) end frame.ScrollTimer = CH:ScheduleTimer("ScrollToBottom", CH.db.scrollDownInterval, frame) end end end function CH:GetGroupDistribution() local inInstance, kind = IsInInstance() if inInstance and (kind == "pvp") then return "/bg " elseif GetNumRaidMembers() > 0 then return "/ra " elseif GetNumPartyMembers() > 0 then return "/p " else return "/s " end end function CH:InsertEmotions(msg) for word in gmatch(msg, "%s-(%S+)%s*") do local pattern = E:EscapeString(word) local emoji = CH.Smileys[pattern] if emoji then pattern = format("%s%s%s", "([%s%p]-)", pattern, "([%s%p]*)") if strmatch(msg, pattern) then local base64 = LibBase64:Encode(word) if base64 then msg = gsub(msg, pattern, format("%s%s%s%s%s", "%1|Helvmoji:%%", base64, "|h|cFFffffff|r|h", emoji, "%2")) else msg = gsub(msg, pattern, format("%s%s%s", "%1", emoji, "%2")) end end end end return msg end function CH:GetSmileyReplacementText(msg) if not msg or not self.db.emotionIcons or find(msg, "/run") or find(msg, "/dump") or find(msg, "/script") then return msg end local origlen = strlen(msg) local startpos = 1 local outstr = "" local _, pos, endpos while startpos <= origlen do pos = find(msg, "|H", startpos, true) endpos = pos or origlen outstr = outstr .. CH:InsertEmotions(strsub(msg, startpos, endpos)) --run replacement on this bit startpos = endpos + 1 if pos then _, endpos = find(msg, "|h.-|h", startpos) endpos = endpos or origlen if startpos < endpos then outstr = outstr .. strsub(msg, startpos, endpos) --don't run replacement on this bit startpos = endpos + 1 end end end return outstr end function CH:StyleChat(frame) local name = frame:GetName() _G[name.."TabText"]:FontTemplate(LSM:Fetch("font", self.db.tabFont), self.db.tabFontSize, self.db.tabFontOutline) if frame.styled then return end frame:SetFrameLevel(4) frame:SetClampRectInsets(0, 0, 0, 0) frame:SetClampedToScreen(false) frame:StripTextures(true) _G[name.."ButtonFrame"]:Kill() local id = frame:GetID() local tab = _G[name.."Tab"] local editbox = _G[name.."EditBox"] local language = _G[name.."EditBoxLanguage"] --Character count local charCount = editbox:CreateFontString() charCount:FontTemplate() charCount:SetTextColor(190, 190, 190, 0.4) charCount:Point("TOPRIGHT", editbox, "TOPRIGHT", -5, 0) charCount:Point("BOTTOMRIGHT", editbox, "BOTTOMRIGHT", -5, 0) charCount:SetJustifyH("CENTER") charCount:Width(40) editbox.characterCount = charCount for _, texName in ipairs(tabTexs) do _G[format("%sTab%sLeft", name, texName)]:SetTexture(nil) _G[format("%sTab%sMiddle", name, texName)]:SetTexture(nil) _G[format("%sTab%sRight", name, texName)]:SetTexture(nil) end tab:SetHitRectInsets(0, 0, 11, 1) tab.glow:Point("BOTTOMLEFT", 8, 2) tab.glow:Point("BOTTOMRIGHT", -8, 2) hooksecurefunc(tab, "SetAlpha", function(t, alpha) if alpha ~= 1 and (not t.isDocked or GeneralDockManager.selected:GetID() == t:GetID()) then t:SetAlpha(1) elseif alpha < 0.6 then t:SetAlpha(0.6) end end) tab.text = _G[name.."TabText"] if tab.conversationIcon then tab.text:Point("LEFT", tab.leftTexture, "RIGHT", 10, -5) tab.conversationIcon:ClearAllPoints() tab.conversationIcon:Point("RIGHT", tab.text, "LEFT", -1, 0) end local function OnTextChanged(editBox) local text = editBox:GetText() local len = strlen(text) local MIN_REPEAT_CHARACTERS = CH.db.numAllowedCombatRepeat if MIN_REPEAT_CHARACTERS ~= 0 and InCombatLockdown() then if len > MIN_REPEAT_CHARACTERS then local repeatChar = true for i = 1, MIN_REPEAT_CHARACTERS do if strsub(text, -i, -i) ~= strsub(text, (-1 - i), (-1 - i)) then repeatChar = false break end end if repeatChar then editBox:Hide() return end end end if text == "/tt " then local unitname, realm = UnitName("target") if unitname and realm and not UnitIsSameServer("player", "target") then unitname = format("%s-%s", unitname, gsub(realm, " ", "")) end if unitname then ChatFrame_SendTell(unitname, editBox.chatFrame) else UIErrorsFrame:AddMessage(E.InfoColor..L["Invalid Target"]) end elseif text == "/gr " then editBox:SetText(CH:GetGroupDistribution()..strsub(text, 5)) ChatEdit_ParseText(editBox, 0) end editbox.characterCount:SetText(len > 0 and (255 - len) or "") end local a, b, c = select(6, editbox:GetRegions()) a:Kill() b:Kill() c:Kill() _G[format("%sEditBoxFocusLeft", name)]:Kill() _G[format("%sEditBoxFocusMid", name)]:Kill() _G[format("%sEditBoxFocusRight", name)]:Kill() editbox:SetTemplate(nil, true) editbox:SetAltArrowKeyMode(CH.db.useAltKey) editbox:SetAllPoints(LeftChatDataPanel) editbox:Hide() for _, text in ipairs(ElvCharacterDB.ChatEditHistory) do editbox:AddHistoryLine(text) end editbox:HookScript("OnTextChanged", OnTextChanged) self:SecureHook(editbox, "AddHistoryLine", "ChatEdit_AddHistory") editbox:HookScript("OnEditFocusGained", function(editBox) if not LeftChatPanel:IsShown() then LeftChatPanel.editboxforced = true LeftChatToggleButton:GetScript("OnEnter")(LeftChatToggleButton) editBox:Show() end end) editbox:HookScript("OnEditFocusLost", function(editBox) if LeftChatPanel.editboxforced then LeftChatPanel.editboxforced = nil if LeftChatPanel:IsShown() then LeftChatToggleButton:GetScript("OnLeave")(LeftChatToggleButton) editBox:Hide() end end end) language:Height(22) language:StripTextures() language:SetTemplate("Transparent") language:Point("LEFT", editbox, "RIGHT", -32, 0) --copy chat button local copyButton = CreateFrame("Frame", format("CopyChatButton%d", id), frame) copyButton:EnableMouse(true) copyButton:SetAlpha(0.35) copyButton:Size(20, 22) copyButton:Point("TOPRIGHT", 0, id == 2 and -7 or -2) copyButton:SetFrameLevel(frame:GetFrameLevel() + 5) frame.copyButton = copyButton local copyTexture = frame.copyButton:CreateTexture(nil, "OVERLAY") copyTexture:SetInside() copyTexture:SetTexture(E.Media.Textures.Copy) copyButton.texture = copyTexture copyButton:SetScript("OnMouseUp", function(_, btn) if btn == "RightButton" and id == 1 then ToggleFrame(ChatMenu) else CH:CopyChat(frame) end end) copyButton:SetScript("OnEnter", function(button) button:SetAlpha(1) end) copyButton:SetScript("OnLeave", function(button) if _G[button:GetParent():GetName().."TabText"]:IsShown() then button:SetAlpha(0.35) else button:SetAlpha(0) end end) frame.styled = true end function CH:AddMessage(msg, infoR, infoG, infoB, infoID, accessID, typeID, extraData, isHistory, historyTime) if CH.db.timeStampFormat ~= "NONE" then local timeStamp = BetterDate(CH.db.timeStampFormat, isHistory == "ElvUI_ChatHistory" and historyTime or time()) if CH.db.useCustomTimeColor then local color = CH.db.customTimeColor local hexColor = E:RGBToHex(color.r, color.g, color.b) msg = format("%s[%s]|r %s", hexColor, timeStamp, msg) else msg = format("[%s] %s", timeStamp, msg) end end self.OldAddMessage(self, msg, infoR, infoG, infoB, infoID, accessID, typeID, extraData) end function CH:UpdateSettings() for _, frameName in ipairs(CHAT_FRAMES) do _G[frameName.."EditBox"]:SetAltArrowKeyMode(CH.db.useAltKey) end end local removeIconFromLine do local raidIconFunc = function(x) x = x ~= "" and _G["RAID_TARGET_"..x] return x and ("{"..strlower(x).."}") or "" end local stripTextureFunc = function(w, x, y) if x == "" then return (w ~= "" and w) or (y ~= "" and y) or "" end end local hyperLinkFunc = function(w, x, y) if w ~= "" then return end local emoji = (x ~= "" and x) and strmatch(x, "elvmoji:%%(.+)") return (emoji and LibBase64:Decode(emoji)) or y end removeIconFromLine = function(text) text = gsub(text, "|TInterface\\TargetingFrame\\UI%-RaidTargetingIcon_(%d+):0|t", raidIconFunc) --converts raid icons into {star} etc, if possible. text = gsub(text, "(%s?)(|?)|T.-|t(%s?)", stripTextureFunc) --strip any other texture out but keep a single space from the side(s). text = gsub(text, "(|?)|H(.-)|h(.-)|h", hyperLinkFunc) --strip hyperlink data only keeping the actual text. return text end end local function colorizeLine(text, r, g, b) local hexCode = E:RGBToHex(r, g, b) local hexReplacement = format("|r%s", hexCode) text = gsub(text, "|r", hexReplacement) -- If the message contains color strings then we need to add message color hex code after every "|r" text = format("%s%s|r", hexCode, text) -- Add message color return text end local chatTypeIndexToName = {} local copyLines = {} for chatType in pairs(ChatTypeInfo) do chatTypeIndexToName[GetChatTypeIndex(chatType)] = chatType end function CH:GetLines(frame) local lineCount = 0 local _, message, lineID, info, r, g, b for i = 1, frame:GetNumMessages() do message, _, lineID = frame:GetMessageInfo(i) if message then info = ChatTypeInfo[chatTypeIndexToName[lineID]] if info then r, g, b = info.r, info.g, info.b else r, g, b = 1, 1, 1 end message = removeIconFromLine(message) message = colorizeLine(message, r, g, b) lineCount = lineCount + 1 copyLines[lineCount] = message end end return lineCount end function CH:CopyChat(frame) if not self.copyChatFrame:IsShown() then local lineCount = self:GetLines(frame) local text = tconcat(copyLines, "\n", 1, lineCount) self.copyChatFrame.editBox:SetText(text) self.copyChatFrame:Show() else self.copyChatFrame:Hide() end end function CH:OnEnter(frame) _G[frame:GetName().."Text"]:Show() if frame.conversationIcon then frame.conversationIcon:Show() end end function CH:OnLeave(frame) _G[frame:GetName().."Text"]:Hide() if frame.conversationIcon then frame.conversationIcon:Hide() end end function CH:SetupChatTabs(frame, hook) if hook and (not self.hooks or not self.hooks[frame] or not self.hooks[frame].OnEnter) then self:HookScript(frame, "OnEnter") self:HookScript(frame, "OnLeave") elseif not hook and self.hooks and self.hooks[frame] and self.hooks[frame].OnEnter then self:Unhook(frame, "OnEnter") self:Unhook(frame, "OnLeave") end if not hook then _G[frame:GetName().."Text"]:Show() if frame.owner and frame.owner.button and GetMouseFocus() ~= frame.owner.button then frame.owner.button:SetAlpha(0.35) end if frame.conversationIcon then frame.conversationIcon:Show() end elseif GetMouseFocus() ~= frame then _G[frame:GetName().."Text"]:Hide() if frame.owner and frame.owner.button and GetMouseFocus() ~= frame.owner.button then frame.owner.button:SetAlpha(0) end if frame.conversationIcon then frame.conversationIcon:Hide() end end end function CH:UpdateAnchors() for _, frameName in ipairs(CHAT_FRAMES) do local frame = _G[frameName.."EditBox"] frame:ClearAllPoints() if not E.db.datatexts.leftChatPanel and self.db.editBoxPosition == "BELOW_CHAT" then frame:Point("TOPLEFT", ChatFrame1, "BOTTOMLEFT", -4, -4) frame:Point("BOTTOMRIGHT", ChatFrame1, "BOTTOMRIGHT", 7, -LeftChatTab:GetHeight() - 4) elseif self.db.editBoxPosition == "BELOW_CHAT" then frame:SetAllPoints(LeftChatDataPanel) else frame:Point("BOTTOMLEFT", ChatFrame1, "TOPLEFT", -1, 3) frame:Point("TOPRIGHT", ChatFrame1, "TOPRIGHT", 4, LeftChatTab:GetHeight() + 3) end end CH:PositionChat(true) end local function FindRightChatID() local rightChatID for id, frameName in ipairs(CHAT_FRAMES) do local chat = _G[frameName] if E:FramesOverlap(chat, RightChatPanel) and not E:FramesOverlap(chat, LeftChatPanel) then rightChatID = id break end end return rightChatID end function CH:UpdateChatTabs() local fadeUndockedTabs = self.db.fadeUndockedTabs local fadeTabsNoBackdrop = self.db.fadeTabsNoBackdrop for id, frameName in ipairs(CHAT_FRAMES) do local chat = _G[frameName] local tab = _G[format("%sTab", frameName)] if chat:IsShown() and (id <= NUM_CHAT_WINDOWS) and (id == self.RightChatWindowID) then if self.db.panelBackdrop == "HIDEBOTH" or self.db.panelBackdrop == "LEFT" then CH:SetupChatTabs(tab, fadeTabsNoBackdrop and true or false) else CH:SetupChatTabs(tab, false) end elseif not chat.isDocked and chat:IsShown() then tab:SetParent(RightChatPanel) chat:SetParent(RightChatPanel) CH:SetupChatTabs(tab, fadeUndockedTabs and true or false) else if self.db.panelBackdrop == "HIDEBOTH" or self.db.panelBackdrop == "RIGHT" then CH:SetupChatTabs(tab, fadeTabsNoBackdrop and true or false) else CH:SetupChatTabs(tab, false) end end end end function CH:RefreshToggleButtons() LeftChatToggleButton:SetAlpha(E.db.LeftChatPanelFaded and E.db.chat.fadeChatToggles and 0 or 1) RightChatToggleButton:SetAlpha(E.db.RightChatPanelFaded and E.db.chat.fadeChatToggles and 0 or 1) end function CH:PositionChat(override) if (InCombatLockdown() and not override and self.initialMove) or (IsMouseButtonDown("LeftButton") and not override) then return end if not RightChatPanel or not LeftChatPanel then return end if not self.db.lockPositions or not E.private.chat.enable then return end RightChatPanel:Size(self.db.separateSizes and self.db.panelWidthRight or self.db.panelWidth, self.db.separateSizes and self.db.panelHeightRight or self.db.panelHeight) LeftChatPanel:Size(self.db.panelWidth, self.db.panelHeight) CombatLogQuickButtonFrame_Custom:Size(LeftChatTab:GetWidth(), LeftChatTab:GetHeight()) self.RightChatWindowID = FindRightChatID() local fadeUndockedTabs = self.db.fadeUndockedTabs local fadeTabsNoBackdrop = self.db.fadeTabsNoBackdrop for id, frameName in ipairs(CHAT_FRAMES) do local BASE_OFFSET = 57 + E.Spacing*3 local chat = _G[frameName] local tab = _G[format("%sTab", frameName)] tab.isDocked = chat.isDocked tab.owner = chat if chat:IsShown() and (id <= NUM_CHAT_WINDOWS) and id == self.RightChatWindowID then chat:ClearAllPoints() if E.db.datatexts.rightChatPanel then chat:Point("BOTTOMLEFT", RightChatDataPanel, "TOPLEFT", 1, 4) else BASE_OFFSET = BASE_OFFSET - 24 chat:Point("BOTTOMLEFT", RightChatDataPanel, "BOTTOMLEFT", 1, 2) end if id ~= 2 then chat:Size((self.db.separateSizes and self.db.panelWidthRight or self.db.panelWidth) - 11, (self.db.separateSizes and self.db.panelHeightRight or self.db.panelHeight) - BASE_OFFSET) else chat:Size(self.db.panelWidth - 11, (self.db.panelHeight - BASE_OFFSET) - CombatLogQuickButtonFrame_Custom:GetHeight()) end --Pass a 2nd argument which prevents an infinite loop in our ON_FCF_SavePositionAndDimensions function if chat:GetLeft() then FCF_SavePositionAndDimensions(chat, true) end tab:SetParent(RightChatPanel) chat:SetParent(RightChatPanel) if chat:IsMovable() then chat:SetUserPlaced(true) end if self.db.panelBackdrop == "HIDEBOTH" or self.db.panelBackdrop == "LEFT" then CH:SetupChatTabs(tab, fadeTabsNoBackdrop and true or false) else CH:SetupChatTabs(tab, false) end elseif not chat.isDocked and chat:IsShown() then tab:SetParent(UIParent) chat:SetParent(UIParent) CH:SetupChatTabs(tab, fadeUndockedTabs and true or false) else if id ~= 2 and (id <= NUM_CHAT_WINDOWS) then chat:ClearAllPoints() if E.db.datatexts.leftChatPanel then chat:Point("BOTTOMLEFT", LeftChatToggleButton, "TOPLEFT", 1, 4) else BASE_OFFSET = BASE_OFFSET - 24 chat:Point("BOTTOMLEFT", LeftChatToggleButton, "BOTTOMLEFT", 1, 2) end chat:Size(self.db.panelWidth - 11, (self.db.panelHeight - BASE_OFFSET)) --Pass a 2nd argument which prevents an infinite loop in our ON_FCF_SavePositionAndDimensions function if chat:GetLeft() then FCF_SavePositionAndDimensions(chat, true) end end chat:SetParent(LeftChatPanel) if id > 2 then tab:SetParent(GeneralDockManagerScrollFrameChild) else tab:SetParent(GeneralDockManager) end if chat:IsMovable() then chat:SetUserPlaced(true) end if self.db.panelBackdrop == "HIDEBOTH" or self.db.panelBackdrop == "RIGHT" then CH:SetupChatTabs(tab, fadeTabsNoBackdrop and true or false) else CH:SetupChatTabs(tab, false) end end end LO:RepositionChatDataPanels() self.initialMove = true end function CH:Panels_ColorUpdate() local panelColor = self.db.panelColor LeftChatPanel.backdrop:SetBackdropColor(panelColor.r, panelColor.g, panelColor.b, panelColor.a) RightChatPanel.backdrop:SetBackdropColor(panelColor.r, panelColor.g, panelColor.b, panelColor.a) end function CH:UpdateChatTabColors() for _, frameName in ipairs(CHAT_FRAMES) do local tab = _G[format("%sTab", frameName)] CH:FCFTab_UpdateColors(tab, tab.selected) end end E.valueColorUpdateFuncs[CH.UpdateChatTabColors] = true function CH:ScrollToBottom(frame) frame:ScrollToBottom() self:CancelTimer(frame.ScrollTimer, true) end function CH:PrintURL(url) return "|cFFFFFFFF[|Hurl:"..url.."|h"..url.."|h]|r " end local tempURLs = {} local tempURLsCount = 0 local function tempReplaceURL(url) tempURLsCount = tempURLsCount + 1 local id = "|Hurl:"..tempURLsCount.."|h" tempURLs[id] = CH:PrintURL(url) return id end function CH:FindURL(event, msg, author, ...) if not CH.db.url then msg = CH:CheckKeyword(msg, author) msg = CH:GetSmileyReplacementText(msg) return false, msg, author, ... end local text, tag = msg, strmatch(msg, "{(.-)}") if tag and ICON_TAG_LIST[strlower(tag)] then text = gsub(gsub(text, "(%S)({.-})", "%1 %2"), "({.-})(%S)", "%1 %2") end local x, found = 0 local newMsg = gsub(gsub(text, "(%S)(|c.-|H.-|h.-|h|r)", "%1 %2"), "(|c.-|H.-|h.-|h|r)(%S)", "%1 %2") -- https://example.com newMsg, found = gsub(newMsg, "([A-z][A-z0-9+-%.]+://%S+)", tempReplaceURL) x = x + found -- www.example.com newMsg, found = gsub(newMsg, "(www%.[A-z0-9-]+%.%S+)", tempReplaceURL) x = x + found -- example@example.com newMsg, found = gsub(newMsg, "(%S+@[A-z][A-z0-9-]+%.[A-z0-9-]+)", tempReplaceURL) x = x + found -- 1.1.1.1[:1337] newMsg, found = gsub(newMsg, "(%d%d?%d?%.%d%d?%d?%.%d%d?%d?%.%d%d?%d?[:%d]*)", tempReplaceURL) x = x + found if x > 0 then newMsg = gsub(newMsg, "(|Hurl:%d+|h)", tempURLs) wipe(tempURLs) tempURLsCount = 0 newMsg = CH:CheckKeyword(newMsg, author) newMsg = CH:GetSmileyReplacementText(newMsg) return false, newMsg, author, ... end msg = CH:CheckKeyword(msg, author) msg = CH:GetSmileyReplacementText(msg) return false, msg, author, ... end function CH:SetChatEditBoxMessage(message) local ChatFrameEditBox = ChatEdit_ChooseBoxForSend() local editBoxText = ChatFrameEditBox:GetText() if not ChatFrameEditBox:IsShown() then ChatEdit_ActivateChat(ChatFrameEditBox) end if editBoxText and editBoxText ~= "" then ChatFrameEditBox:SetText("") end ChatFrameEditBox:Insert(message) ChatFrameEditBox:HighlightText() end local function HyperLinkedURL(data) if strsub(data, 1, 3) == "url" then local currentLink = strsub(data, 5) if currentLink and currentLink ~= "" then CH:SetChatEditBoxMessage(currentLink) end end end local SetHyperlink = ItemRefTooltip.SetHyperlink function ItemRefTooltip:SetHyperlink(data, ...) if strsub(data, 1, 3) == "url" then HyperLinkedURL(data) else SetHyperlink(self, data, ...) end end local hyperLinkEntered function CH:OnHyperlinkEnter(frame, refString) if InCombatLockdown() then return end local linkToken = strmatch(refString, "^([^:]+)") if hyperlinkTypes[linkToken] then GameTooltip:SetOwner(frame, "ANCHOR_CURSOR") GameTooltip:SetHyperlink(refString) GameTooltip:Show() hyperLinkEntered = frame end end function CH:OnHyperlinkLeave() if hyperLinkEntered then hyperLinkEntered = nil GameTooltip:Hide() end end function CH:OnMessageScrollChanged(frame) if hyperLinkEntered == frame then hyperLinkEntered = nil GameTooltip:Hide() end end function CH:ToggleHyperlink(enable) for _, frameName in ipairs(CHAT_FRAMES) do local frame = _G[frameName] local hooked = self.hooks and self.hooks[frame] and self.hooks[frame].OnHyperlinkEnter if enable and not hooked then self:HookScript(frame, "OnHyperlinkEnter") self:HookScript(frame, "OnHyperlinkLeave") self:HookScript(frame, "OnMessageScrollChanged") elseif not enable and hooked then self:Unhook(frame, "OnHyperlinkEnter") self:Unhook(frame, "OnHyperlinkLeave") self:Unhook(frame, "OnMessageScrollChanged") end end end function CH:DisableChatThrottle() wipe(throttle) end function CH:ShortChannel() return format("|Hchannel:%s|h[%s]|h", self, DEFAULT_STRINGS[strupper(self)] or gsub(self, "channel:", "")) end function CH:HandleShortChannels(msg) msg = gsub(msg, "|Hchannel:(.-)|h%[(.-)%]|h", self.ShortChannel) msg = gsub(msg, "CHANNEL:", "") msg = gsub(msg, "^(.-|h) "..L["whispers"], "%1") msg = gsub(msg, "^(.-|h) "..L["says"], "%1") msg = gsub(msg, "^(.-|h) "..L["yells"], "%1") msg = gsub(msg, "<"..AFK..">", "[|cffFF0000"..AFK.."|r] ") msg = gsub(msg, "<"..DND..">", "[|cffE7E716"..DND.."|r] ") msg = gsub(msg, "^%["..RAID_WARNING.."%]", "["..L["RW"].."]") return msg end local PluginIconsCalls = {} function CH:AddPluginIcons(func) tinsert(PluginIconsCalls, func) end function CH:GetPluginIcon(sender, name, realm) local icon for _,func in ipairs(PluginIconsCalls) do icon = func(sender, name, realm) if icon and icon ~= "" then break end end return icon end function CH:GetColoredName(event, _, arg2, _, _, _, _, _, arg8, _, _, _, arg12) local chatType = strsub(event, 10) if strsub(chatType, 1, 7) == "WHISPER" then chatType = "WHISPER" elseif strsub(chatType, 1, 7) == "CHANNEL" then chatType = "CHANNEL"..arg8 end local info = ChatTypeInfo[chatType] if info and info.colorNameByClass and arg12 ~= "" then local _, englishClass = GetPlayerInfoByGUID(arg12) if englishClass then local classColorTable = RAID_CLASS_COLORS[englishClass] if arg12 == UnitName("player") then classColorTable = E.media.herocolor end if not classColorTable then return arg2 end return format("\124cff%.2x%.2x%.2x", classColorTable.r*255, classColorTable.g*255, classColorTable.b*255)..arg2.."\124r" end end return arg2 end function CH:ChatFrame_MessageEventHandler(frame, event, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12, arg13, isHistory, historyTime, historyName) if strsub(event, 1, 8) == "CHAT_MSG" then local historySavedName --we need to extend the arguments on CH.ChatFrame_MessageEventHandler so we can properly handle saved names without overriding if isHistory == "ElvUI_ChatHistory" then historySavedName = historyName end local chatType = strsub(event, 10) local info = ChatTypeInfo[chatType] local chatFilters = ChatFrame_GetMessageEventFilters(event) if chatFilters then for _, filterFunc in next, chatFilters do local filter, newarg1, newarg2, newarg3, newarg4, newarg5, newarg6, newarg7, newarg8, newarg9, newarg10, newarg11, newarg12 = filterFunc(frame, event, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12) if filter then return true elseif newarg1 then arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12 = newarg1, newarg2, newarg3, newarg4, newarg5, newarg6, newarg7, newarg8, newarg9, newarg10, newarg11, newarg12 end end end local _, _, englishClass, _, _, _, name, realm = pcall(GetPlayerInfoByGUID, arg12) local coloredName = historySavedName or CH:GetColoredName(event, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10, arg11, arg12) local nameWithRealm = strmatch(realm ~= "" and realm or E.myrealm, "%s*(%S+)$") if name and name ~= "" then nameWithRealm = name.."-"..nameWithRealm CH.ClassNames[strlower(name)] = englishClass CH.ClassNames[strlower(nameWithRealm)] = englishClass end local channelLength = strlen(arg4) local infoType = chatType if (strsub(chatType, 1, 7) == "CHANNEL") and (chatType ~= "CHANNEL_LIST") and ((arg1 ~= "INVITE") or (chatType ~= "CHANNEL_NOTICE_USER")) then if arg1 == "WRONG_PASSWORD" then local staticPopup = _G[StaticPopup_Visible("CHAT_CHANNEL_PASSWORD") or ""] if staticPopup and strupper(staticPopup.data) == strupper(arg9) then -- Don't display invalid password messages if we're going to prompt for a password (bug 102312) return end end local found = 0 for index, value in pairs(frame.channelList) do if channelLength > strlen(value) then -- arg9 is the channel name without the number in front... if ((arg7 > 0) and (frame.zoneChannelList[index] == arg7)) or (strupper(value) == strupper(arg9)) then found = 1 infoType = "CHANNEL"..arg8 info = ChatTypeInfo[infoType] if (chatType == "CHANNEL_NOTICE") and (arg1 == "YOU_LEFT") then frame.channelList[index] = nil frame.zoneChannelList[index] = nil end break end end end if (found == 0) or not info then return true end end local chatGroup = Chat_GetChatCategory(chatType) local chatTarget if chatGroup == "CHANNEL" or chatGroup == "BN_CONVERSATION" then chatTarget = tostring(arg8) elseif chatGroup == "WHISPER" or chatGroup == "BN_WHISPER" then chatTarget = strupper(arg2) end if FCFManager_ShouldSuppressMessage(frame, chatGroup, chatTarget) then return true end if chatGroup == "WHISPER" or chatGroup == "BN_WHISPER" then if frame.privateMessageList and not frame.privateMessageList[strlower(arg2)] then return true elseif frame.excludePrivateMessageList and frame.excludePrivateMessageList[strlower(arg2)] then return true end elseif chatGroup == "BN_CONVERSATION" then if frame.bnConversationList and not frame.bnConversationList[arg8] then return true elseif frame.excludeBNConversationList and frame.excludeBNConversationList[arg8] then return true end end if chatType == "SYSTEM" or chatType == "SKILL" or chatType == "LOOT" or chatType == "MONEY" or chatType == "OPENING" or chatType == "TRADESKILLS" or chatType == "PET_INFO" or chatType == "TARGETICONS" then frame:AddMessage(arg1, info.r, info.g, info.b, info.id, false, nil, nil, isHistory, historyTime) elseif strsub(chatType,1,7) == "COMBAT_" then frame:AddMessage(arg1, info.r, info.g, info.b, info.id, false, nil, nil, isHistory, historyTime) elseif strsub(chatType,1,6) == "SPELL_" then frame:AddMessage(arg1, info.r, info.g, info.b, info.id, false, nil, nil, isHistory, historyTime) elseif strsub(chatType,1,10) == "BG_SYSTEM_" then frame:AddMessage(arg1, info.r, info.g, info.b, info.id, false, nil, nil, isHistory, historyTime) elseif strsub(chatType,1,11) == "ACHIEVEMENT" then frame:AddMessage(format(arg1, "|Hplayer:"..arg2.."|h".."["..coloredName.."]".."|h"), info.r, info.g, info.b, info.id, false, nil, nil, isHistory, historyTime) elseif strsub(chatType,1,18) == "GUILD_ACHIEVEMENT" then frame:AddMessage(format(arg1, "|Hplayer:"..arg2.."|h".."["..coloredName.."]".."|h"), info.r, info.g, info.b, info.id, false, nil, nil, isHistory, historyTime) elseif chatType == "IGNORED" then frame:AddMessage(format(CHAT_IGNORED, arg2), info.r, info.g, info.b, info.id, false, nil, nil, isHistory, historyTime) elseif chatType == "FILTERED" then frame:AddMessage(format(CHAT_FILTERED, arg2), info.r, info.g, info.b, info.id, false, nil, nil, isHistory, historyTime) elseif chatType == "RESTRICTED" then frame:AddMessage(CHAT_RESTRICTED, info.r, info.g, info.b, info.id, false, nil, nil, isHistory, historyTime) elseif chatType == "CHANNEL_LIST" then if channelLength > 0 then frame:AddMessage(format(_G["CHAT_"..chatType.."_GET"]..arg1, tonumber(arg8), arg4), info.r, info.g, info.b, info.id, false, nil, nil, isHistory, historyTime) else frame:AddMessage(arg1, info.r, info.g, info.b, info.id, false, nil, nil, isHistory, historyTime) end elseif chatType == "CHANNEL_NOTICE_USER" then local globalstring = _G["CHAT_"..arg1.."_NOTICE_BN"] if not globalstring then globalstring = _G["CHAT_"..arg1.."_NOTICE"] end if arg5 ~= "" then -- TWO users in this notice (E.G. x kicked y) frame:AddMessage(format(globalstring, arg8, arg4, arg2, arg5), info.r, info.g, info.b, info.id, false, nil, nil, isHistory, historyTime) elseif arg1 == "INVITE" then frame:AddMessage(format(globalstring, arg4, arg2), info.r, info.g, info.b, info.id, false, nil, nil, isHistory, historyTime) else frame:AddMessage(format(globalstring, arg8, arg4, arg2), info.r, info.g, info.b, info.id, false, nil, nil, isHistory, historyTime) end elseif chatType == "CHANNEL_NOTICE" then if arg1 == "NOT_IN_LFG" or string.isNilOrEmpty(arg1) then return end local globalstring = _G["CHAT_"..arg1.."_NOTICE_BN"] if not globalstring then globalstring = _G["CHAT_"..arg1.."_NOTICE"] end if arg10 > 0 then arg4 = arg4.." "..arg10 end if not globalstring then return end local accessID = ChatHistory_GetAccessID(Chat_GetChatCategory(chatType), arg8) local typeID = ChatHistory_GetAccessID(infoType, arg8) frame:AddMessage(format(globalstring, arg8, arg4), info.r, info.g, info.b, info.id, false, accessID, typeID, isHistory, historyTime) else local body -- Add AFK/DND flags -- Player Flags local pflag, chatIcon, pluginChatIcon = "", specialChatIcons[nameWithRealm], CH:GetPluginIcon(nameWithRealm, name, realm) if type(chatIcon) == "function" then chatIcon = chatIcon() end if arg6 ~= "" then if arg6 == "GM" then --If it was a whisper, dispatch it to the GMChat addon. if chatType == "WHISPER" then return end --Add Blizzard Icon, this was sent by a GM pflag = "|TInterface\\ChatFrame\\UI-ChatIcon-Blizz.blp:0:4:4:-3|t " elseif arg6 == "DEV" then --Add Blizzard Icon, this was sent by a Dev pflag = "|TInterface\\ChatFrame\\UI-ChatIcon-Blizz.blp:0:4:4:-3|t " elseif arg6 == "DND" or arg6 == "AFK" then pflag = (pflag or "").._G["CHAT_FLAG_"..arg6] else pflag = _G["CHAT_FLAG_"..arg6] end else -- Special Chat Icon if chatIcon then pflag = pflag..chatIcon end -- Plugin Chat Icon if pluginChatIcon then pflag = pflag..pluginChatIcon end end if chatType == "WHISPER_INFORM" and GMChatFrame_IsGM and GMChatFrame_IsGM(arg2) then return end local showLink = 1 if strsub(chatType, 1, 7) == "MONSTER" or strsub(chatType, 1, 9) == "RAID_BOSS" then showLink = nil else arg1 = gsub(arg1, "%%", "%%%%") end if chatType == "PARTY_LEADER" and HasLFGRestrictions() then chatType = "PARTY_GUIDE" end -- Search for icon links and replace them with texture links. local term for tag in gmatch(arg1, "%b{}") do term = strlower(gsub(tag, "[{}]", "")) if ICON_TAG_LIST[term] and ICON_LIST[ICON_TAG_LIST[term]] then arg1 = gsub(arg1, tag, ICON_LIST[ICON_TAG_LIST[term]].."0|t") end end local playerLink if chatType ~= "BN_WHISPER" and chatType ~= "BN_WHISPER_INFORM" and chatType ~= "BN_CONVERSATION" then playerLink = "|Hplayer:"..arg2..":"..arg11..":"..chatGroup..(chatTarget and ":"..chatTarget or "").."|h" else playerLink = "|HBNplayer:"..arg2..":"..arg13..":"..arg11..":"..chatGroup..(chatTarget and ":"..chatTarget or "").."|h" end if arg3 ~= "" and arg3 ~= "Universal" and arg3 ~= frame.defaultLanguage then local languageHeader = "["..arg3.."] " if showLink and arg2 ~= "" then body = format(_G["CHAT_"..chatType.."_GET"]..languageHeader..arg1, pflag..playerLink.."["..coloredName.."]".."|h") else body = format(_G["CHAT_"..chatType.."_GET"]..languageHeader..arg1, pflag..arg2) end else if not showLink or strlen(arg2) == 0 then body = format(_G["CHAT_"..chatType.."_GET"]..arg1, pflag..arg2, arg2) else if chatType == "EMOTE" then body = format(_G["CHAT_"..chatType.."_GET"]..arg1, pflag..playerLink..coloredName.."|h") elseif chatType == "TEXT_EMOTE" then body = gsub(arg1, arg2, pflag..playerLink..coloredName.."|h", 1) else body = format(_G["CHAT_"..chatType.."_GET"]..arg1, pflag..playerLink.."["..coloredName.."]".."|h") end end end -- Add Channel arg4 = gsub(arg4, "%s%-%s.*", "") if chatGroup == "BN_CONVERSATION" then body = format(CHAT_BN_CONVERSATION_GET_LINK, arg8, MAX_WOW_CHAT_CHANNELS + arg8)..body elseif channelLength > 0 then body = "|Hchannel:channel:"..arg8.."|h["..arg4.."]|h "..body end if CH.db.shortChannels and (chatType ~= "EMOTE" and chatType ~= "TEXT_EMOTE") then body = CH:HandleShortChannels(body) end local accessID = ChatHistory_GetAccessID(chatGroup, chatTarget) local typeID = ChatHistory_GetAccessID(infoType, chatTarget) if not historySavedName and arg2 ~= E.myname and not CH.SoundTimer and (not CH.db.noAlertInCombat or not InCombatLockdown()) then local channels = chatGroup ~= "WHISPER" and chatGroup or (chatType == "WHISPER" or chatType == "BN_WHISPER") and "WHISPER" local alertType = CH.db.channelAlerts[channels] if alertType and alertType ~= "None" then PlaySoundFile(LSM:Fetch("sound", alertType), "Master") CH.SoundTimer = E:Delay(1, CH.ThrottleSound) end end frame:AddMessage(body, info.r, info.g, info.b, info.id, false, accessID, typeID, isHistory, historyTime) if not historySavedName and (chatType == "WHISPER" or chatType == "BN_WHISPER") then ChatEdit_SetLastTellTarget(arg2) end end if not historySavedName and not frame:IsShown() then if (frame == DEFAULT_CHAT_FRAME and info.flashTabOnGeneral) or (frame ~= DEFAULT_CHAT_FRAME and info.flashTab) then if not CHAT_OPTIONS.HIDE_FRAME_ALERTS or chatType == "WHISPER" or chatType == "BN_WHISPER" then FCF_StartAlertFlash(frame) --This would taint if we were not using LibChatAnims end end end return true end end function CH:ChatFrame_ConfigEventHandler(...) return ChatFrame_ConfigEventHandler(...) end function CH:ChatFrame_SystemEventHandler(...) return ChatFrame_SystemEventHandler(...) end function CH:ChatFrame_OnEvent(...) if CH:ChatFrame_ConfigEventHandler(...) then return end if CH:ChatFrame_SystemEventHandler(...) then return end if CH:ChatFrame_MessageEventHandler(...) then return end end function CH:FloatingChatFrame_OnEvent(...) CH:ChatFrame_OnEvent(...) FloatingChatFrame_OnEvent(...) end local function FloatingChatFrameOnEvent(...) CH:FloatingChatFrame_OnEvent(...) end function CH:UpdateDockState() if self.db.lockPositions then FCF_SetLocked(ChatFrame1, 1) GeneralDockManager:SetParent(LeftChatPanel) GeneralDockManagerOverflowButton:ClearAllPoints() GeneralDockManagerOverflowButton:Point("BOTTOMRIGHT", LeftChatTab, "BOTTOMRIGHT", -2, 2) else GeneralDockManager:SetParent(UIParent) GeneralDockManagerOverflowButton:ClearAllPoints() GeneralDockManagerOverflowButton:Point("BOTTOMRIGHT", GeneralDockManager, 0, -1) end end function CH:SetupChat() if not E.private.chat.enable then return end for id, frameName in ipairs(CHAT_FRAMES) do local frame = _G[frameName] self:StyleChat(frame) FCFTab_UpdateAlpha(frame) local _, fontSize = frame:GetFont() frame:FontTemplate(LSM:Fetch("font", self.db.font), fontSize, self.db.fontOutline) if self.db.fontOutline ~= "NONE" then frame:SetShadowColor(0, 0, 0, 0.2) else frame:SetShadowColor(0, 0, 0, 1) end if self.db.maxLines ~= frame:GetMaxLines() then frame:SetMaxLines(self.db.maxLines) end frame:SetTimeVisible(self.db.inactivityTimer) frame:SetShadowOffset(E.mult, -E.mult) frame:SetFading(self.db.fade) if id ~= 2 and not frame.OldAddMessage then --Don't add timestamps to combat log, they don't work. --This usually taints, but LibChatAnims should make sure it doesn't. frame.OldAddMessage = frame.AddMessage frame.AddMessage = CH.AddMessage end if not frame.scriptsSet then frame:SetScript("OnMouseWheel", ChatFrame_OnMouseScroll) if id ~= 2 then frame:SetScript("OnEvent", FloatingChatFrameOnEvent) end hooksecurefunc(frame, "SetScript", function(f, script, func) if script == "OnMouseWheel" and func ~= ChatFrame_OnMouseScroll then f:SetScript(script, ChatFrame_OnMouseScroll) end end) frame.scriptsSet = true end end self:ToggleHyperlink(self.db.hyperlinkHover) self:UpdateDockState() self:PositionChat(true) if not self.HookSecured then self:SecureHook("FCF_OpenTemporaryWindow", "SetupChat") self.HookSecured = true end end local function PrepareMessage(author, message) if author ~= "" and message ~= "" then return format("%s%s", strupper(author), message) end end function CH:ChatThrottleHandler(author, msg, when) msg = PrepareMessage(author, msg) if msg then for message, msgTime in pairs(throttle) do if (when - msgTime) >= self.db.throttleInterval then throttle[message] = nil end end if not throttle[msg] then throttle[msg] = when end end end function CH:ChatThrottleBlockFlag(author, message, when) if author ~= E.myname and self.db.throttleInterval ~= 0 then message = PrepareMessage(author, message) local msgTime = message and throttle[message] if msgTime then if (when - msgTime) > self.db.throttleInterval then return false, message end else return false end else return false end return true end function CH:ChatThrottleIntervalHandler(event, message, author, ...) local when = time() local blockFlag, formattedMessage = self:ChatThrottleBlockFlag(author, message, when) if blockFlag then return true else if formattedMessage then throttle[formattedMessage] = when end return self:FindURL(event, message, author, ...) end end function CH:CHAT_MSG_CHANNEL(event, message, author, ...) return CH:ChatThrottleIntervalHandler(event, message, author, ...) end function CH:CHAT_MSG_YELL(event, message, author, ...) return CH:ChatThrottleIntervalHandler(event, message, author, ...) end function CH:CHAT_MSG_SAY(event, message, author, ...) return CH:ChatThrottleIntervalHandler(event, message, author, ...) end function CH:ThrottleSound() CH.SoundTimer = nil end local protectLinks = {} function CH:CheckKeyword(message, author) local canPlaySound = author ~= E.myname and not self.SoundTimer and self.db.keywordSound ~= "None" and (not self.db.noAlertInCombat or not InCombatLockdown()) for hyperLink in gmatch(message, "|%x+|H.-|h.-|h|r") do local tempLink = gsub(hyperLink, "%s", "|s") message = gsub(message, E:EscapeString(hyperLink), tempLink) protectLinks[hyperLink] = tempLink if canPlaySound then for keyword in pairs(CH.Keywords) do if hyperLink == keyword then PlaySoundFile(LSM:Fetch("sound", self.db.keywordSound), "Master") self.SoundTimer = E:Delay(1, self.ThrottleSound) canPlaySound = nil break end end end end local rebuiltString local isFirstWord = true local protectLinksNext = next(protectLinks) for word in gmatch(message, "%s-%S+%s*") do if not protectLinksNext or not protectLinks[gsub(gsub(word, "%s", ""), "|s", " ")] then local tempWord = gsub(word, "[%s%p]", "") local lowerCaseWord = strlower(tempWord) for keyword in pairs(CH.Keywords) do if lowerCaseWord == strlower(keyword) then word = gsub(word, tempWord, format("%s%s|r", E.media.hexvaluecolor, tempWord)) if canPlaySound then PlaySoundFile(LSM:Fetch("sound", self.db.keywordSound), "Master") self.SoundTimer = E:Delay(1, self.ThrottleSound) canPlaySound = nil end end end if self.db.classColorMentionsChat then tempWord = gsub(word, "^[%s%p]-([^%s%p]+)([%-]?[^%s%p]-)[%s%p]*$", "%1%2") lowerCaseWord = strlower(tempWord) local classMatch = CH.ClassNames[lowerCaseWord] local wordMatch = classMatch and lowerCaseWord if wordMatch and not E.global.chat.classColorMentionExcludedNames[wordMatch] then local classColorTable = RAID_CLASS_COLORS[classMatch] word = gsub(word, gsub(tempWord, "%-", "%%-"), format("\124cff%.2x%.2x%.2x%s\124r", classColorTable.r*255, classColorTable.g*255, classColorTable.b*255, tempWord)) end end end if isFirstWord then rebuiltString = word isFirstWord = nil else rebuiltString = rebuiltString..word end end for hyperLink, tempLink in pairs(protectLinks) do rebuiltString = gsub(rebuiltString, E:EscapeString(tempLink), hyperLink) protectLinks[hyperLink] = nil end return rebuiltString end function CH:AddLines(lines, ...) for i = select("#", ...), 1, -1 do local x = select(i, ...) if x:IsObjectType("FontString") and not x:GetName() then tinsert(lines, x:GetText()) end end end function CH:ChatEdit_OnEnterPressed(editBox) local chatType = editBox:GetAttribute("chatType") local chatFrame = chatType and editBox:GetParent() if chatFrame and (not chatFrame.isTemporary) and (ChatTypeInfo[chatType].sticky == 1) then if not self.db.sticky then chatType = "SAY" end editBox:SetAttribute("chatType", chatType) end end function CH:SetChatFont(dropDown, chatFrame, fontSize) if not chatFrame then chatFrame = FCF_GetCurrentChatFrame() end if not fontSize then fontSize = dropDown.value end chatFrame:FontTemplate(LSM:Fetch("font", self.db.font), fontSize, self.db.fontOutline) if self.db.fontOutline ~= "NONE" then chatFrame:SetShadowColor(0, 0, 0, 0.2) else chatFrame:SetShadowColor(0, 0, 0, 1) end chatFrame:SetShadowOffset(E.mult, -E.mult) end CH.SecureSlashCMD = { "^/assist", "^/camp", "^/cancelaura", "^/cancelform", "^/cast", "^/castsequence", "^/equip", "^/exit", "^/logout", "^/reload", "^/rl", "^/startattack", "^/stopattack", "^/tar", "^/target", "^/use" } function CH:ChatEdit_AddHistory(_, text) text = strtrim(text) if text ~= "" then for _, command in ipairs(CH.SecureSlashCMD) do if find(text, command) then return end end for i, historyText in ipairs(ElvCharacterDB.ChatEditHistory) do if historyText == text then tremove(ElvCharacterDB.ChatEditHistory, i) break end end tinsert(ElvCharacterDB.ChatEditHistory, text) if #ElvCharacterDB.ChatEditHistory > CH.db.editboxHistorySize then tremove(ElvCharacterDB.ChatEditHistory, 1) end end end function CH:UpdateChatKeywords() wipe(CH.Keywords) local keywords = self.db.keywords keywords = gsub(keywords, ",%s", ",") for stringValue in gmatch(keywords, "[^,]+") do if stringValue ~= "" then CH.Keywords[stringValue] = true end end end function CH:UpdateFading() for _, frameName in ipairs(CHAT_FRAMES) do local frame = _G[frameName] frame:SetTimeVisible(self.db.inactivityTimer) frame:SetFading(self.db.fade) end end function CH:DisplayChatHistory() local data = ElvCharacterDB.ChatHistoryLog if not next(data) then return end CH.SoundTimer = true for _, frameName in ipairs(CHAT_FRAMES) do for _, d in ipairs(data) do if type(d) == "table" then for _, messageType in ipairs(_G[frameName].messageTypeList) do if (not historyTypes[d[50]] or self.db.showHistory[historyTypes[d[50]]]) and gsub(strsub(d[50], 10), "_INFORM", "") == messageType then self:ChatFrame_MessageEventHandler(_G[frameName],d[50],d[1],d[2],d[3],d[4],d[5],d[6],d[7],d[8],d[9],d[10],d[11],d[12],0,"ElvUI_ChatHistory",d[51],d[52]) end end end end end CH.SoundTimer = nil end tremove(ChatTypeGroup.GUILD, 2) function CH:DelayGuildMOTD() tinsert(ChatTypeGroup.GUILD, 2, "GUILD_MOTD") local registerGuildEvents = function(msg) for _, frameName in ipairs(CHAT_FRAMES) do local chat = _G[frameName] if chat:IsEventRegistered("CHAT_MSG_GUILD") then if msg then CH:ChatFrame_SystemEventHandler(chat, "GUILD_MOTD", msg) end chat:RegisterEvent("GUILD_MOTD") end end end if not IsInGuild() then registerGuildEvents() return end local delay, checks = 0, 0 CreateFrame("Frame"):SetScript("OnUpdate", function(df, elapsed) delay = delay + elapsed if delay < 5 then return end local msg = GetGuildRosterMOTD() if msg and msg ~= "" then registerGuildEvents(msg) df:SetScript("OnUpdate", nil) else -- 5 seconds can be too fast for the API response. let's try once every 5 seconds (max 5 checks). delay, checks = 0, checks + 1 if checks >= 5 then registerGuildEvents() df:SetScript("OnUpdate", nil) end end end) end function CH:SaveChatHistory(event, ...) if historyTypes[event] and not self.db.showHistory[historyTypes[event]] then return end if self.db.throttleInterval ~= 0 and (event == "CHAT_MSG_SAY" or event == "CHAT_MSG_YELL" or event == "CHAT_MSG_CHANNEL") then local message, author = ... local when = time() if not self:ChatThrottleBlockFlag(author, message, when) then self:ChatThrottleHandler(author, message, when) else return end end if not CH.db.chatHistory then return end if select("#", ...) > 0 then local historyLog = ElvCharacterDB.ChatHistoryLog local historyEntry = {...} historyEntry[50] = event historyEntry[51] = time() historyEntry[52] = self:GetColoredName(event, ...) while #historyLog >= self.db.historySize do tremove(historyLog, 1) end tinsert(historyLog, historyEntry) end end function CH:FCF_SetWindowAlpha(frame, alpha) frame.oldAlpha = alpha or 1 end function CH:ON_FCF_SavePositionAndDimensions(_, noLoop) if not noLoop then CH:PositionChat() end if not self.db.lockPositions then CH:UpdateChatTabs() --It was not done in PositionChat, so do it now end end local FindURL_Events = { "CHAT_MSG_WHISPER", "CHAT_MSG_WHISPER_INFORM", "CHAT_MSG_BN_WHISPER", "CHAT_MSG_BN_WHISPER_INFORM", "CHAT_MSG_GUILD_ACHIEVEMENT", "CHAT_MSG_GUILD", "CHAT_MSG_OFFICER", "CHAT_MSG_PARTY", "CHAT_MSG_PARTY_LEADER", "CHAT_MSG_RAID", "CHAT_MSG_RAID_LEADER", "CHAT_MSG_RAID_WARNING", "CHAT_MSG_BATTLEGROUND", "CHAT_MSG_BATTLEGROUND_LEADER", "CHAT_MSG_CHANNEL", "CHAT_MSG_SAY", "CHAT_MSG_YELL", "CHAT_MSG_EMOTE", "CHAT_MSG_TEXT_EMOTE", "CHAT_MSG_AFK", "CHAT_MSG_DND", } function CH:DefaultSmileys() local x = ":16:16" if next(CH.Smileys) then wipe(CH.Smileys) end -- new keys CH:AddSmiley(":angry:", E:TextureString(E.Media.ChatEmojis.Angry, x)) CH:AddSmiley(":blush:", E:TextureString(E.Media.ChatEmojis.Blush, x)) CH:AddSmiley(":broken_heart:", E:TextureString(E.Media.ChatEmojis.BrokenHeart, x)) CH:AddSmiley(":call_me:", E:TextureString(E.Media.ChatEmojis.CallMe, x)) CH:AddSmiley(":cry:", E:TextureString(E.Media.ChatEmojis.Cry, x)) CH:AddSmiley(":facepalm:", E:TextureString(E.Media.ChatEmojis.Facepalm, x)) CH:AddSmiley(":grin:", E:TextureString(E.Media.ChatEmojis.Grin, x)) CH:AddSmiley(":heart:", E:TextureString(E.Media.ChatEmojis.Heart, x)) CH:AddSmiley(":heart_eyes:", E:TextureString(E.Media.ChatEmojis.HeartEyes, x)) CH:AddSmiley(":joy:", E:TextureString(E.Media.ChatEmojis.Joy, x)) CH:AddSmiley(":kappa:", E:TextureString(E.Media.ChatEmojis.Kappa, x)) CH:AddSmiley(":middle_finger:", E:TextureString(E.Media.ChatEmojis.MiddleFinger, x)) CH:AddSmiley(":murloc:", E:TextureString(E.Media.ChatEmojis.Murloc, x)) CH:AddSmiley(":ok_hand:", E:TextureString(E.Media.ChatEmojis.OkHand, x)) CH:AddSmiley(":open_mouth:", E:TextureString(E.Media.ChatEmojis.OpenMouth, x)) CH:AddSmiley(":poop:", E:TextureString(E.Media.ChatEmojis.Poop, x)) CH:AddSmiley(":rage:", E:TextureString(E.Media.ChatEmojis.Rage, x)) CH:AddSmiley(":sadkitty:", E:TextureString(E.Media.ChatEmojis.SadKitty, x)) CH:AddSmiley(":scream:", E:TextureString(E.Media.ChatEmojis.Scream, x)) CH:AddSmiley(":scream_cat:", E:TextureString(E.Media.ChatEmojis.ScreamCat, x)) CH:AddSmiley(":slight_frown:", E:TextureString(E.Media.ChatEmojis.SlightFrown, x)) CH:AddSmiley(":smile:", E:TextureString(E.Media.ChatEmojis.Smile, x)) CH:AddSmiley(":smirk:", E:TextureString(E.Media.ChatEmojis.Smirk, x)) CH:AddSmiley(":sob:", E:TextureString(E.Media.ChatEmojis.Sob, x)) CH:AddSmiley(":sunglasses:", E:TextureString(E.Media.ChatEmojis.Sunglasses, x)) CH:AddSmiley(":thinking:", E:TextureString(E.Media.ChatEmojis.Thinking, x)) CH:AddSmiley(":thumbs_up:", E:TextureString(E.Media.ChatEmojis.ThumbsUp, x)) CH:AddSmiley(":semi_colon:", E:TextureString(E.Media.ChatEmojis.SemiColon, x)) CH:AddSmiley(":wink:", E:TextureString(E.Media.ChatEmojis.Wink, x)) CH:AddSmiley(":zzz:", E:TextureString(E.Media.ChatEmojis.ZZZ, x)) CH:AddSmiley(":stuck_out_tongue:", E:TextureString(E.Media.ChatEmojis.StuckOutTongue, x)) CH:AddSmiley(":stuck_out_tongue_closed_eyes:", E:TextureString(E.Media.ChatEmojis.StuckOutTongueClosedEyes, x)) -- Darth's keys CH:AddSmiley(":meaw:", E:TextureString(E.Media.ChatEmojis.Meaw, x)) -- Simpy's keys CH:AddSmiley(">:%(", E:TextureString(E.Media.ChatEmojis.Rage, x)) CH:AddSmiley(":%$", E:TextureString(E.Media.ChatEmojis.Blush, x)) CH:AddSmiley("<\\3", E:TextureString(E.Media.ChatEmojis.BrokenHeart, x)) CH:AddSmiley(":\'%)", E:TextureString(E.Media.ChatEmojis.Joy, x)) CH:AddSmiley(";\'%)", E:TextureString(E.Media.ChatEmojis.Joy, x)) CH:AddSmiley(",,!,,", E:TextureString(E.Media.ChatEmojis.MiddleFinger, x)) CH:AddSmiley("D:<", E:TextureString(E.Media.ChatEmojis.Rage, x)) CH:AddSmiley(":o3", E:TextureString(E.Media.ChatEmojis.ScreamCat, x)) CH:AddSmiley("XP", E:TextureString(E.Media.ChatEmojis.StuckOutTongueClosedEyes, x)) CH:AddSmiley("8%-%)", E:TextureString(E.Media.ChatEmojis.Sunglasses, x)) CH:AddSmiley("8%)", E:TextureString(E.Media.ChatEmojis.Sunglasses, x)) CH:AddSmiley(":%+1:", E:TextureString(E.Media.ChatEmojis.ThumbsUp, x)) CH:AddSmiley(":;:", E:TextureString(E.Media.ChatEmojis.SemiColon, x)) CH:AddSmiley(";o;", E:TextureString(E.Media.ChatEmojis.Sob, x)) -- old keys CH:AddSmiley(":%-@", E:TextureString(E.Media.ChatEmojis.Angry, x)) CH:AddSmiley(":@", E:TextureString(E.Media.ChatEmojis.Angry, x)) CH:AddSmiley(":%-%)", E:TextureString(E.Media.ChatEmojis.Smile, x)) CH:AddSmiley(":%)", E:TextureString(E.Media.ChatEmojis.Smile, x)) CH:AddSmiley(":D", E:TextureString(E.Media.ChatEmojis.Grin, x)) CH:AddSmiley(":%-D", E:TextureString(E.Media.ChatEmojis.Grin, x)) CH:AddSmiley(";%-D", E:TextureString(E.Media.ChatEmojis.Grin, x)) CH:AddSmiley(";D", E:TextureString(E.Media.ChatEmojis.Grin, x)) CH:AddSmiley("=D", E:TextureString(E.Media.ChatEmojis.Grin, x)) CH:AddSmiley("xD", E:TextureString(E.Media.ChatEmojis.Grin, x)) CH:AddSmiley("XD", E:TextureString(E.Media.ChatEmojis.Grin, x)) CH:AddSmiley(":%-%(", E:TextureString(E.Media.ChatEmojis.SlightFrown, x)) CH:AddSmiley(":%(", E:TextureString(E.Media.ChatEmojis.SlightFrown, x)) CH:AddSmiley(":o", E:TextureString(E.Media.ChatEmojis.OpenMouth, x)) CH:AddSmiley(":%-o", E:TextureString(E.Media.ChatEmojis.OpenMouth, x)) CH:AddSmiley(":%-O", E:TextureString(E.Media.ChatEmojis.OpenMouth, x)) CH:AddSmiley(":O", E:TextureString(E.Media.ChatEmojis.OpenMouth, x)) CH:AddSmiley(":%-0", E:TextureString(E.Media.ChatEmojis.OpenMouth, x)) CH:AddSmiley(":P", E:TextureString(E.Media.ChatEmojis.StuckOutTongue, x)) CH:AddSmiley(":%-P", E:TextureString(E.Media.ChatEmojis.StuckOutTongue, x)) CH:AddSmiley(":p", E:TextureString(E.Media.ChatEmojis.StuckOutTongue, x)) CH:AddSmiley(":%-p", E:TextureString(E.Media.ChatEmojis.StuckOutTongue, x)) CH:AddSmiley("=P", E:TextureString(E.Media.ChatEmojis.StuckOutTongue, x)) CH:AddSmiley("=p", E:TextureString(E.Media.ChatEmojis.StuckOutTongue, x)) CH:AddSmiley(";%-p", E:TextureString(E.Media.ChatEmojis.StuckOutTongueClosedEyes, x)) CH:AddSmiley(";p", E:TextureString(E.Media.ChatEmojis.StuckOutTongueClosedEyes, x)) CH:AddSmiley(";P", E:TextureString(E.Media.ChatEmojis.StuckOutTongueClosedEyes, x)) CH:AddSmiley(";%-P", E:TextureString(E.Media.ChatEmojis.StuckOutTongueClosedEyes, x)) CH:AddSmiley(";%-%)", E:TextureString(E.Media.ChatEmojis.Wink, x)) CH:AddSmiley(";%)", E:TextureString(E.Media.ChatEmojis.Wink, x)) CH:AddSmiley(":S", E:TextureString(E.Media.ChatEmojis.Smirk, x)) CH:AddSmiley(":%-S", E:TextureString(E.Media.ChatEmojis.Smirk, x)) CH:AddSmiley(":,%(", E:TextureString(E.Media.ChatEmojis.Cry, x)) CH:AddSmiley(":,%-%(", E:TextureString(E.Media.ChatEmojis.Cry, x)) CH:AddSmiley(":\'%(", E:TextureString(E.Media.ChatEmojis.Cry, x)) CH:AddSmiley(":\'%-%(", E:TextureString(E.Media.ChatEmojis.Cry, x)) CH:AddSmiley(":F", E:TextureString(E.Media.ChatEmojis.MiddleFinger, x)) CH:AddSmiley("<3", E:TextureString(E.Media.ChatEmojis.Heart, x)) CH:AddSmiley("|r%s%s<|r", ARROW1 = "%s>|r %s %s<|r", ARROW2 = "%s<|r%s%s>|r", ARROW3 = "%s<|r %s %s>|r", BOX = "%s[|r%s%s]|r", BOX1 = "%s[|r %s %s]|r", CURLY = "%s{|r%s%s}|r", CURLY1 = "%s{|r %s %s}|r", CURVE = "%s(|r%s%s)|r", CURVE1 = "%s(|r %s %s)|r" } function CH:FCFTab_UpdateColors(tab, selected) local chat = _G[format("ChatFrame%s", tab:GetID())] tab.selected = selected if selected and chat.isDocked then if self.db.tabSelector ~= "NONE" then local color = self.db.tabSelectorColor local hexColor = E:RGBToHex(color.r, color.g, color.b) tab:SetFormattedText(self.TabStyles[self.db.tabSelector] or self.TabStyles.ARROW1, hexColor, chat.name, hexColor) tab.textReformatted = true elseif tab.textReformatted then tab:SetText(chat.name) tab.textReformatted = nil end if self.db.tabSelectedTextEnabled then local color = self.db.tabSelectedTextColor tab:GetFontString():SetTextColor(color.r, color.g, color.b) return end elseif tab.textReformatted then tab:SetText(chat.name) tab.textReformatted = nil end tab:GetFontString():SetTextColor(unpack(E.media.rgbvaluecolor)) end function CH:GetPlayerInfoByGUID(guid) local data = CH.GuidCache[guid] if not data then local ok, localizedClass, englishClass, localizedRace, englishRace, sex, name, realm = pcall(GetPlayerInfoByGUID, guid) if not (ok and englishClass) then return end local nameWithRealm = name..'-'..GetRealmName() -- move em into a table data = { localizedClass = localizedClass, englishClass = englishClass, localizedRace = localizedRace, englishRace = englishRace, sex = sex, name = name, realm = realm, nameWithRealm = nameWithRealm -- we use this to correct mobile to link with the realm as well } -- add it to ClassNames if name then CH.ClassNames[strlower(name)] = englishClass end if nameWithRealm then CH.ClassNames[strlower(nameWithRealm)] = englishClass end -- push into the cache CH.GuidCache[guid] = data end -- we still need to recheck this each time because CUSTOM_CLASS_COLORS can change if data then data.classColor = E:ClassColor(data.englishClass) end return data end function CH:ResetEditboxHistory() wipe(ElvCharacterDB.ChatEditHistory) end function CH:ResetHistory() wipe(ElvCharacterDB.ChatHistoryLog) end function CH:Initialize() self:DelayGuildMOTD() --Keep this before `is Chat Enabled` check if not E.private.chat.enable then return end self.Initialized = true self.db = E.db.chat if not ElvCharacterDB.ChatEditHistory then ElvCharacterDB.ChatEditHistory = {} end if not ElvCharacterDB.ChatHistoryLog or not self.db.chatHistory then ElvCharacterDB.ChatHistoryLog = {} end FriendsMicroButton:Kill() ChatFrameMenuButton:Kill() self:SetupChat() self:DefaultSmileys() self:UpdateChatKeywords() self:UpdateFading() self:UpdateAnchors() self:Panels_ColorUpdate() self:SecureHook("ChatEdit_OnEnterPressed") self:SecureHook("FCF_SetWindowAlpha") self:SecureHook("FCFTab_UpdateColors") self:SecureHook("FCF_SetChatWindowFontSize", "SetChatFont") self:SecureHook("FCF_SavePositionAndDimensions", "ON_FCF_SavePositionAndDimensions") self:RegisterEvent("UPDATE_CHAT_WINDOWS", "SetupChat") self:RegisterEvent("UPDATE_FLOATING_CHAT_WINDOWS", "SetupChat") if WIM then WIM.RegisterWidgetTrigger("chat_display", "whisper,chat,w2w,demo", "OnHyperlinkClick", function(self) CH.clickedframe = self end) WIM.RegisterItemRefHandler("url", HyperLinkedURL) end if not self.db.lockPositions then CH:UpdateChatTabs() end --It was not done in PositionChat, so do it now for _, event in ipairs(FindURL_Events) do ChatFrame_AddMessageEventFilter(event, CH[event] or CH.FindURL) local nType = strsub(event, 10) if nType ~= "AFK" and nType ~= "DND" then self:RegisterEvent(event, "SaveChatHistory") end end if self.db.chatHistory then self:DisplayChatHistory() end self:BuildCopyChatFrame() -- Editbox Backdrop Color hooksecurefunc("ChatEdit_UpdateHeader", function(editbox) local chatType = editbox:GetAttribute("chatType") if not chatType then return end local chanTarget = editbox:GetAttribute("channelTarget") local chanName = chanTarget and GetChannelName(chanTarget) --Increase inset on right side to make room for character count text local insetLeft, insetRight, insetTop, insetBottom = editbox:GetTextInsets() editbox:SetTextInsets(insetLeft, insetRight + 30, insetTop, insetBottom) if chanName and (chatType == "CHANNEL") then if chanName == 0 then editbox:SetBackdropBorderColor(unpack(E.media.bordercolor)) else local info = ChatTypeInfo[chatType..chanName] editbox:SetBackdropBorderColor(info.r, info.g, info.b) end else local info = ChatTypeInfo[chatType] editbox:SetBackdropBorderColor(info.r, info.g, info.b) end end) GeneralDockManagerOverflowButton:Size(17) GeneralDockManagerOverflowButtonList:SetTemplate("Transparent") hooksecurefunc(GeneralDockManagerScrollFrame, "SetPoint", function(self, point, anchor, attachTo, x, y) if anchor == GeneralDockManagerOverflowButton and x == 0 and y == 0 then self:Point(point, anchor, attachTo, -2, -4) end end) CombatLogQuickButtonFrame_Custom:StripTextures() CombatLogQuickButtonFrame_Custom:CreateBackdrop("Default", true) CombatLogQuickButtonFrame_Custom.backdrop:Point("TOPLEFT", 0, -1) CombatLogQuickButtonFrame_Custom.backdrop:Point("BOTTOMRIGHT", -22, -1) CombatLogQuickButtonFrame_CustomProgressBar:StripTextures() CombatLogQuickButtonFrame_CustomProgressBar:SetStatusBarTexture(E.media.normTex) CombatLogQuickButtonFrame_CustomProgressBar:SetStatusBarColor(0.31, 0.31, 0.31) CombatLogQuickButtonFrame_CustomProgressBar:ClearAllPoints() CombatLogQuickButtonFrame_CustomProgressBar:SetInside(CombatLogQuickButtonFrame_Custom.backdrop) Skins:HandleNextPrevButton(CombatLogQuickButtonFrame_CustomAdditionalFilterButton) CombatLogQuickButtonFrame_CustomAdditionalFilterButton:Size(22) CombatLogQuickButtonFrame_CustomAdditionalFilterButton:Point("TOPRIGHT", CombatLogQuickButtonFrame_Custom, "TOPRIGHT", 3, -1) CombatLogQuickButtonFrame_CustomAdditionalFilterButton:SetHitRectInsets(0, 0, 0, 0) end local function InitializeCallback() CH:Initialize() end E:RegisterModule(CH:GetName(), InitializeCallback)