6898 lines
257 KiB
Lua
6898 lines
257 KiB
Lua
--------------------------------------------------------------------------------
|
||
-- S-Frames: Chat takeover skin (Chat.lua)
|
||
-- Keeps Blizzard chat backend while replacing visuals and tab/filter workflow.
|
||
--------------------------------------------------------------------------------
|
||
|
||
SFrames.Chat = SFrames.Chat or {}
|
||
|
||
local DEFAULTS = {
|
||
enable = true,
|
||
showBorder = false,
|
||
borderClassColor = false,
|
||
showPlayerLevel = true,
|
||
width = 360,
|
||
height = 220,
|
||
scale = 1,
|
||
fontSize = 12,
|
||
sidePadding = 10,
|
||
topPadding = 30,
|
||
bottomPadding = 8,
|
||
bgAlpha = 0.45,
|
||
activeTab = 1,
|
||
editBoxPosition = "bottom",
|
||
editBoxX = 0,
|
||
editBoxY = 200,
|
||
}
|
||
|
||
local DEFAULT_COMBAT_TAB_NAME = COMBATLOG or COMBAT_LOG or "Combat"
|
||
|
||
local HIDDEN_OBJECTS = {
|
||
"ChatFrameMenuButton",
|
||
"ChatFrameChannelButton",
|
||
"ChatFrameToggleVoiceMuteButton",
|
||
"ChatFrameToggleVoiceDeafenButton",
|
||
"ChatFrameToggleVoiceSelfMuteButton",
|
||
"ChatFrameToggleVoiceSelfDeafenButton",
|
||
"ChatFrameUpButton",
|
||
"ChatFrameDownButton",
|
||
"ChatFrameBottomButton",
|
||
}
|
||
|
||
local EDITBOX_TEXTURES = {
|
||
"ChatFrameEditBoxLeft",
|
||
"ChatFrameEditBoxMid",
|
||
"ChatFrameEditBoxRight",
|
||
"ChatFrameEditBoxFocusLeft",
|
||
"ChatFrameEditBoxFocusMid",
|
||
"ChatFrameEditBoxFocusRight",
|
||
"ChatFrame1EditBoxLeft",
|
||
"ChatFrame1EditBoxMid",
|
||
"ChatFrame1EditBoxRight",
|
||
"ChatFrame1EditBoxFocusLeft",
|
||
"ChatFrame1EditBoxFocusMid",
|
||
"ChatFrame1EditBoxFocusRight",
|
||
}
|
||
|
||
local FILTER_DEFS = {
|
||
{ key = "say", label = "说话" },
|
||
{ key = "yell", label = "大喊" },
|
||
{ key = "emote", label = "动作" },
|
||
{ key = "guild", label = "公会" },
|
||
{ key = "party", label = "小队" },
|
||
{ key = "raid", label = "团队" },
|
||
{ key = "whisper", label = "密语" },
|
||
{ key = "system", label = "系统" },
|
||
{ key = "loot", label = "拾取" },
|
||
{ key = "money", label = "获益" },
|
||
}
|
||
local DEFAULT_FILTERS = {
|
||
say = true,
|
||
yell = true,
|
||
emote = true,
|
||
guild = true,
|
||
party = true,
|
||
raid = true,
|
||
whisper = true,
|
||
system = true,
|
||
loot = false,
|
||
money = false,
|
||
}
|
||
|
||
local AUTO_TRANSLATE_TARGET_LANG = "zh"
|
||
|
||
local TRANSLATE_FILTER_KEYS = {
|
||
say = true,
|
||
yell = true,
|
||
emote = true,
|
||
guild = true,
|
||
party = true,
|
||
raid = true,
|
||
whisper = true,
|
||
}
|
||
|
||
local TRANSLATE_FILTER_ORDER = {
|
||
"say",
|
||
"yell",
|
||
"emote",
|
||
"guild",
|
||
"party",
|
||
"raid",
|
||
"whisper",
|
||
}
|
||
|
||
local FILTER_GROUPS = {
|
||
say = { "SAY" },
|
||
yell = { "YELL" },
|
||
emote = { "EMOTE", "TEXT_EMOTE", "MONSTER_EMOTE" },
|
||
guild = { "GUILD", "OFFICER" },
|
||
party = { "PARTY", "PARTY_LEADER" },
|
||
raid = { "RAID", "RAID_LEADER", "RAID_WARNING", "BATTLEGROUND", "BATTLEGROUND_LEADER" },
|
||
whisper = { "WHISPER", "WHISPER_INFORM", "REPLY", "AFK", "DND", "IGNORED", "MONSTER_WHISPER", "MONSTER_BOSS_WHISPER" },
|
||
channel = { "CHANNEL", "CHANNEL_JOIN", "CHANNEL_LEAVE", "CHANNEL_LIST", "CHANNEL_NOTICE", "BG_HORDE", "BG_ALLIANCE" },
|
||
system = { "SYSTEM", "MONSTER_SAY", "MONSTER_YELL", "MONSTER_BOSS_EMOTE" },
|
||
loot = { "LOOT", "OPENING", "TRADESKILLS" },
|
||
money = { "MONEY", "COMBAT_XP_GAIN", "COMBAT_HONOR_GAIN", "COMBAT_FACTION_CHANGE", "SKILL", "PET_INFO", "COMBAT_MISC_INFO" },
|
||
}
|
||
|
||
local ALL_MESSAGE_GROUPS = {
|
||
"SYSTEM",
|
||
"SAY",
|
||
"YELL",
|
||
"EMOTE",
|
||
"TEXT_EMOTE",
|
||
"MONSTER_SAY",
|
||
"MONSTER_YELL",
|
||
"MONSTER_EMOTE",
|
||
"MONSTER_WHISPER",
|
||
"MONSTER_BOSS_EMOTE",
|
||
"MONSTER_BOSS_WHISPER",
|
||
"WHISPER",
|
||
"WHISPER_INFORM",
|
||
"REPLY",
|
||
"AFK",
|
||
"DND",
|
||
"IGNORED",
|
||
"GUILD",
|
||
"OFFICER",
|
||
"PARTY",
|
||
"PARTY_LEADER",
|
||
"RAID",
|
||
"RAID_LEADER",
|
||
"RAID_WARNING",
|
||
"BATTLEGROUND",
|
||
"BATTLEGROUND_LEADER",
|
||
"CHANNEL",
|
||
"CHANNEL_JOIN",
|
||
"CHANNEL_LEAVE",
|
||
"CHANNEL_LIST",
|
||
"CHANNEL_NOTICE",
|
||
"BG_HORDE",
|
||
"BG_ALLIANCE",
|
||
"LOOT",
|
||
"OPENING",
|
||
"TRADESKILLS",
|
||
"MONEY",
|
||
"COMBAT_XP_GAIN",
|
||
"COMBAT_HONOR_GAIN",
|
||
"COMBAT_FACTION_CHANGE",
|
||
"SKILL",
|
||
"PET_INFO",
|
||
"COMBAT_MISC_INFO",
|
||
}
|
||
|
||
local TRANSLATE_EVENT_FILTERS = {
|
||
CHAT_MSG_SAY = "say",
|
||
CHAT_MSG_YELL = "yell",
|
||
CHAT_MSG_EMOTE = "emote",
|
||
CHAT_MSG_TEXT_EMOTE = "emote",
|
||
CHAT_MSG_GUILD = "guild",
|
||
CHAT_MSG_OFFICER = "guild",
|
||
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 = "raid",
|
||
CHAT_MSG_BATTLEGROUND_LEADER = "raid",
|
||
CHAT_MSG_WHISPER = "whisper",
|
||
CHAT_MSG_REPLY = "whisper",
|
||
CHAT_MSG_MONSTER_WHISPER = "whisper",
|
||
CHAT_MSG_MONSTER_BOSS_WHISPER = "whisper",
|
||
CHAT_MSG_CHANNEL = "channel",
|
||
}
|
||
|
||
local function Clamp(value, minValue, maxValue)
|
||
value = tonumber(value) or minValue
|
||
if value < minValue then return minValue end
|
||
if value > maxValue then return maxValue end
|
||
return value
|
||
end
|
||
|
||
-- ============================================================
|
||
-- 共享:玩家职业颜色工具(供 Chat / Roll 等模块复用)
|
||
-- ============================================================
|
||
SFrames.ClassColorHex = SFrames.ClassColorHex or {
|
||
WARRIOR = "c79c6e",
|
||
PALADIN = "f58cba",
|
||
HUNTER = "abd473",
|
||
ROGUE = "fff569",
|
||
PRIEST = "ffffff",
|
||
SHAMAN = "0070de",
|
||
MAGE = "69ccf0",
|
||
WARLOCK = "9482c9",
|
||
DRUID = "ff7d0a",
|
||
}
|
||
|
||
SFrames.PlayerClassColorCache = SFrames.PlayerClassColorCache or {}
|
||
SFrames.PlayerLevelCache = SFrames.PlayerLevelCache or {}
|
||
|
||
local function EnsureGlobalClassCache()
|
||
if not SFramesGlobalDB then SFramesGlobalDB = {} end
|
||
if not SFramesGlobalDB.classColorCache then SFramesGlobalDB.classColorCache = {} end
|
||
return SFramesGlobalDB.classColorCache
|
||
end
|
||
|
||
local function EnsureGlobalLevelCache()
|
||
if not SFramesGlobalDB then SFramesGlobalDB = {} end
|
||
if not SFramesGlobalDB.levelCache then SFramesGlobalDB.levelCache = {} end
|
||
return SFramesGlobalDB.levelCache
|
||
end
|
||
|
||
local function LoadPersistentClassCache()
|
||
local persistent = EnsureGlobalClassCache()
|
||
local runtime = SFrames.PlayerClassColorCache
|
||
for name, hex in pairs(persistent) do
|
||
if not runtime[name] then
|
||
runtime[name] = hex
|
||
end
|
||
end
|
||
local persistentLvl = EnsureGlobalLevelCache()
|
||
local runtimeLvl = SFrames.PlayerLevelCache
|
||
for name, lvl in pairs(persistentLvl) do
|
||
if not runtimeLvl[name] then
|
||
runtimeLvl[name] = lvl
|
||
end
|
||
end
|
||
end
|
||
|
||
local function PersistClassCache()
|
||
local persistent = EnsureGlobalClassCache()
|
||
for name, hex in pairs(SFrames.PlayerClassColorCache) do
|
||
persistent[name] = hex
|
||
end
|
||
local persistentLvl = EnsureGlobalLevelCache()
|
||
for name, lvl in pairs(SFrames.PlayerLevelCache) do
|
||
persistentLvl[name] = lvl
|
||
end
|
||
end
|
||
|
||
do
|
||
local reverseClassMap = nil
|
||
function SFrames:LocalizedClassToEN(localizedName)
|
||
if not localizedName then return nil end
|
||
if not reverseClassMap then
|
||
reverseClassMap = {}
|
||
local classTable = LOCALIZED_CLASS_NAMES_MALE or {}
|
||
for en, loc in pairs(classTable) do
|
||
reverseClassMap[loc] = en
|
||
reverseClassMap[string.upper(loc)] = en
|
||
end
|
||
local classTableF = LOCALIZED_CLASS_NAMES_FEMALE or {}
|
||
for en, loc in pairs(classTableF) do
|
||
reverseClassMap[loc] = en
|
||
reverseClassMap[string.upper(loc)] = en
|
||
end
|
||
local fallback = {
|
||
["战士"] = "WARRIOR", ["圣骑士"] = "PALADIN", ["猎人"] = "HUNTER",
|
||
["盗贼"] = "ROGUE", ["牧师"] = "PRIEST", ["萨满祭司"] = "SHAMAN",
|
||
["法师"] = "MAGE", ["术士"] = "WARLOCK", ["德鲁伊"] = "DRUID",
|
||
}
|
||
for loc, en in pairs(fallback) do
|
||
if not reverseClassMap[loc] then reverseClassMap[loc] = en end
|
||
end
|
||
end
|
||
return reverseClassMap[localizedName] or reverseClassMap[string.upper(localizedName)]
|
||
end
|
||
end
|
||
|
||
function SFrames:RefreshClassColorCache()
|
||
local now = GetTime()
|
||
if self._lastClassCacheRefresh and (now - self._lastClassCacheRefresh) < 2 then
|
||
return
|
||
end
|
||
self._lastClassCacheRefresh = now
|
||
|
||
local cache = self.PlayerClassColorCache
|
||
local lvlCache = self.PlayerLevelCache
|
||
local hex = self.ClassColorHex
|
||
if UnitName and UnitClass then
|
||
local selfName = UnitName("player")
|
||
local _, selfClass = UnitClass("player")
|
||
if selfName and selfClass and hex[selfClass] then
|
||
cache[selfName] = hex[selfClass]
|
||
end
|
||
if selfName and UnitLevel then
|
||
local selfLevel = UnitLevel("player")
|
||
if selfLevel and selfLevel > 0 then
|
||
lvlCache[selfName] = selfLevel
|
||
end
|
||
end
|
||
end
|
||
if GetNumRaidMembers then
|
||
local count = GetNumRaidMembers()
|
||
for i = 1, count do
|
||
local rName, _, _, rLevel, _, rClass = GetRaidRosterInfo(i)
|
||
if rName and rClass and hex[rClass] then
|
||
cache[rName] = hex[rClass]
|
||
end
|
||
if rName and rLevel and rLevel > 0 then
|
||
lvlCache[rName] = rLevel
|
||
end
|
||
end
|
||
end
|
||
if GetNumPartyMembers and UnitName and UnitClass then
|
||
local count = GetNumPartyMembers()
|
||
for i = 1, count do
|
||
local unit = "party" .. i
|
||
local pName = UnitName(unit)
|
||
local _, pClass = UnitClass(unit)
|
||
if pName and pClass and hex[pClass] then
|
||
cache[pName] = hex[pClass]
|
||
end
|
||
if pName and UnitLevel then
|
||
local pLevel = UnitLevel(unit)
|
||
if pLevel and pLevel > 0 then
|
||
lvlCache[pName] = pLevel
|
||
end
|
||
end
|
||
end
|
||
end
|
||
if GetNumGuildMembers then
|
||
local count = GetNumGuildMembers()
|
||
for i = 1, count do
|
||
local gName, _, _, gLevel, gClassLoc, _, _, _, _, _, classEN = GetGuildRosterInfo(i)
|
||
if gName then
|
||
if not classEN or classEN == "" then
|
||
classEN = self:LocalizedClassToEN(gClassLoc)
|
||
end
|
||
if classEN and hex[string.upper(classEN)] then
|
||
cache[gName] = hex[string.upper(classEN)]
|
||
end
|
||
if gLevel and gLevel > 0 then
|
||
lvlCache[gName] = gLevel
|
||
end
|
||
end
|
||
end
|
||
end
|
||
if GetNumFriends then
|
||
local count = GetNumFriends()
|
||
for i = 1, count do
|
||
local fName, fLevel, fClass, fArea, fConnected = GetFriendInfo(i)
|
||
if fName and fClass then
|
||
local classEN = self:LocalizedClassToEN(fClass)
|
||
if classEN and hex[classEN] then
|
||
cache[fName] = hex[classEN]
|
||
end
|
||
end
|
||
if fName and fLevel and fLevel > 0 then
|
||
lvlCache[fName] = fLevel
|
||
end
|
||
end
|
||
end
|
||
if GetNumWhoResults then
|
||
local count = GetNumWhoResults()
|
||
for i = 1, count do
|
||
local wName, wGuild, wLevel, wRace, wClassEN = GetWhoInfo(i)
|
||
if wName and wClassEN and wClassEN ~= "" and hex[string.upper(wClassEN)] then
|
||
cache[wName] = hex[string.upper(wClassEN)]
|
||
end
|
||
if wName and wLevel and wLevel > 0 then
|
||
lvlCache[wName] = wLevel
|
||
end
|
||
end
|
||
end
|
||
PersistClassCache()
|
||
end
|
||
|
||
function SFrames:GetClassHexForName(name)
|
||
if not name or name == "" then return nil end
|
||
local cache = self.PlayerClassColorCache
|
||
if cache[name] then return cache[name] end
|
||
self:RefreshClassColorCache()
|
||
return cache[name]
|
||
end
|
||
|
||
function SFrames:GetLevelForName(name)
|
||
if not name or name == "" then return nil end
|
||
local cache = self.PlayerLevelCache
|
||
if cache[name] then return cache[name] end
|
||
return nil
|
||
end
|
||
|
||
-- 本地别名,供 Chat.lua 内部使用
|
||
local function GetClassHexForName(name)
|
||
return SFrames:GetClassHexForName(name)
|
||
end
|
||
|
||
local function GetLevelForName(name)
|
||
return SFrames:GetLevelForName(name)
|
||
end
|
||
|
||
-- 将聊天文本中所有 |Hplayer:NAME|h[NAME]|h 替换为职业颜色版本,并可选附加等级
|
||
local function ColorPlayerNamesInText(text)
|
||
if not text or text == "" then return text end
|
||
local showLevel = SFramesDB and SFramesDB.Chat and SFramesDB.Chat.showPlayerLevel ~= false
|
||
local result = string.gsub(text, "(|Hplayer:([^|]+)|h%[([^%]]+)%]|h)", function(full, linkName, displayName)
|
||
local baseName = string.gsub(linkName, "%-.*", "")
|
||
local hex = GetClassHexForName(baseName)
|
||
local level = showLevel and GetLevelForName(baseName) or nil
|
||
local levelSuffix = ""
|
||
if level then
|
||
levelSuffix = "|cff888888:" .. level .. "|r"
|
||
end
|
||
if hex then
|
||
return "|Hplayer:" .. linkName .. "|h|cff" .. hex .. "[" .. displayName .. "]|r|h" .. levelSuffix
|
||
end
|
||
if level then
|
||
return full .. levelSuffix
|
||
end
|
||
return full
|
||
end)
|
||
return result
|
||
end
|
||
|
||
local function Trim(text)
|
||
text = tostring(text or "")
|
||
text = string.gsub(text, "^%s+", "")
|
||
text = string.gsub(text, "%s+$", "")
|
||
return text
|
||
end
|
||
|
||
local function Dummy() end
|
||
|
||
local function CopyTable(src)
|
||
local out = {}
|
||
for k, v in pairs(src) do
|
||
if type(v) == "table" then
|
||
out[k] = CopyTable(v)
|
||
else
|
||
out[k] = v
|
||
end
|
||
end
|
||
return out
|
||
end
|
||
|
||
local function BuildDefaultTranslateFilters()
|
||
local out = {}
|
||
for key in pairs(TRANSLATE_FILTER_KEYS) do
|
||
out[key] = false
|
||
end
|
||
return out
|
||
end
|
||
|
||
local function ShortText(text, maxLen)
|
||
text = tostring(text or "")
|
||
local n = maxLen or 10
|
||
if string.len(text) <= n then return text end
|
||
return string.sub(text, 1, n - 1) .. "."
|
||
end
|
||
|
||
local function GetFilterLabel(key)
|
||
for i = 1, table.getn(FILTER_DEFS) do
|
||
local def = FILTER_DEFS[i]
|
||
if def and def.key == key then
|
||
return def.label or key
|
||
end
|
||
end
|
||
return key
|
||
end
|
||
|
||
local function NormalizeChannelName(name)
|
||
local clean = Trim(name)
|
||
if clean == "" then return "" end
|
||
clean = string.gsub(clean, "|Hchannel:[^|]+|h%[([^%]]+)%]|h", "%1")
|
||
clean = string.gsub(clean, "^%[", "")
|
||
clean = string.gsub(clean, "%]$", "")
|
||
clean = string.gsub(clean, "^%d+%s*%.%s*", "")
|
||
clean = string.gsub(clean, "^%d+%s*:%s*", "")
|
||
clean = Trim(clean)
|
||
return clean
|
||
end
|
||
|
||
local function ChannelKey(name)
|
||
local clean = NormalizeChannelName(name)
|
||
if clean == "" then return "" end
|
||
return string.lower(clean)
|
||
end
|
||
|
||
-- Groups of channel aliases that should be treated as the same logical channel.
|
||
-- If a user enables any one key in a group, all keys in that group are considered enabled.
|
||
local CHANNEL_ALIAS_GROUPS = {
|
||
{ "hc", "hardcore", "hard core", "hard-core", "h", "硬核" },
|
||
{ "lfg", "lft", "lookingforgroup", "looking for group", "group" },
|
||
}
|
||
|
||
-- Build a reverse lookup: channel key -> alias group index
|
||
local CHANNEL_ALIAS_GROUP_INDEX = {}
|
||
for gIdx, group in ipairs(CHANNEL_ALIAS_GROUPS) do
|
||
for _, alias in ipairs(group) do
|
||
CHANNEL_ALIAS_GROUP_INDEX[alias] = gIdx
|
||
end
|
||
end
|
||
|
||
-- Returns all alias keys for the group that `name` belongs to (or nil if not in any group).
|
||
local function GetChannelAliasKeys(name)
|
||
local key = ChannelKey(name)
|
||
if key == "" then return nil end
|
||
-- Check exact match first
|
||
local gIdx = CHANNEL_ALIAS_GROUP_INDEX[key]
|
||
if not gIdx then
|
||
-- Check substring match for each alias in each group.
|
||
-- Only use aliases 3+ chars long to avoid false positives (e.g. "h" matching "whisper").
|
||
for i, group in ipairs(CHANNEL_ALIAS_GROUPS) do
|
||
for _, alias in ipairs(group) do
|
||
if string.len(alias) >= 3 and string.find(key, alias, 1, true) then
|
||
gIdx = i
|
||
break
|
||
end
|
||
end
|
||
if gIdx then break end
|
||
end
|
||
end
|
||
if not gIdx then return nil end
|
||
return CHANNEL_ALIAS_GROUPS[gIdx]
|
||
end
|
||
|
||
local function IsIgnoredChannelByDefault(name)
|
||
local key = ChannelKey(name)
|
||
if key == "" then return false end
|
||
-- Check via alias groups
|
||
local aliases = GetChannelAliasKeys(name)
|
||
if aliases then return true end
|
||
return false
|
||
end
|
||
|
||
-- Channels discovered at runtime from actual chat messages / join events.
|
||
-- Keys are normalized names, values are true.
|
||
local discoveredChannels = {}
|
||
|
||
local function TrackDiscoveredChannel(name)
|
||
if type(name) ~= "string" or name == "" then return end
|
||
local clean = NormalizeChannelName(name)
|
||
if clean ~= "" then
|
||
discoveredChannels[clean] = true
|
||
end
|
||
end
|
||
|
||
local function UntrackDiscoveredChannel(name)
|
||
if type(name) ~= "string" or name == "" then return end
|
||
local clean = NormalizeChannelName(name)
|
||
if clean ~= "" then
|
||
discoveredChannels[clean] = nil
|
||
end
|
||
end
|
||
|
||
local function GetJoinedChannels()
|
||
local out = {}
|
||
if not GetChannelList then return out end
|
||
|
||
local raw = { GetChannelList() }
|
||
local i = 1
|
||
local seen = {}
|
||
while i <= table.getn(raw) do
|
||
local id = raw[i]
|
||
local name = raw[i + 1]
|
||
if type(id) == "number" and type(name) == "string" and Trim(name) ~= "" then
|
||
table.insert(out, { id = id, name = name })
|
||
local key = ChannelKey(name)
|
||
if key ~= "" then
|
||
seen[key] = true
|
||
end
|
||
end
|
||
i = i + 3
|
||
end
|
||
|
||
table.sort(out, function(a, b)
|
||
if a.id == b.id then
|
||
return string.lower(a.name) < string.lower(b.name)
|
||
end
|
||
return a.id < b.id
|
||
end)
|
||
|
||
-- Add one representative per alias group that isn't already present,
|
||
-- plus individual channels that are not aliases.
|
||
-- For alias groups (like hc/hardcore/硬核), only add ONE representative
|
||
-- so we don't create duplicate conflicting entries.
|
||
local customChannels = { "hc", "硬核", "hardcore", "h", "交易", "综合", "世界防务", "本地防务", "world" }
|
||
local seenAliasGroups = {}
|
||
for _, cname in ipairs(customChannels) do
|
||
local key = ChannelKey(cname)
|
||
if key == "" then
|
||
-- skip
|
||
elseif seen[key] then
|
||
-- already in the joined list
|
||
local aliases = GetChannelAliasKeys(cname)
|
||
if aliases then
|
||
-- Mark the whole group as seen so no other alias gets added
|
||
local gIdx = CHANNEL_ALIAS_GROUP_INDEX[key]
|
||
if gIdx then seenAliasGroups[gIdx] = true end
|
||
end
|
||
else
|
||
local aliases = GetChannelAliasKeys(cname)
|
||
if aliases then
|
||
-- This is an alias channel - check if any alias is already seen/added
|
||
local gIdx = CHANNEL_ALIAS_GROUP_INDEX[key]
|
||
-- Also check substring aliases
|
||
if not gIdx then
|
||
for i, group in ipairs(CHANNEL_ALIAS_GROUPS) do
|
||
for _, a in ipairs(group) do
|
||
if string.find(key, a, 1, true) then
|
||
gIdx = i
|
||
break
|
||
end
|
||
end
|
||
if gIdx then break end
|
||
end
|
||
end
|
||
local alreadyInJoined = false
|
||
if gIdx then
|
||
for _, a in ipairs(CHANNEL_ALIAS_GROUPS[gIdx]) do
|
||
if seen[a] then
|
||
alreadyInJoined = true
|
||
break
|
||
end
|
||
end
|
||
end
|
||
if not alreadyInJoined and (not gIdx or not seenAliasGroups[gIdx]) then
|
||
table.insert(out, { id = 99, name = cname })
|
||
seen[key] = true
|
||
if gIdx then seenAliasGroups[gIdx] = true end
|
||
end
|
||
else
|
||
table.insert(out, { id = 99, name = cname })
|
||
seen[key] = true
|
||
end
|
||
end
|
||
end
|
||
|
||
-- Include dynamically discovered channels (from actual chat messages).
|
||
-- Verify each with GetChannelName() to confirm the player is still in it.
|
||
for dName, _ in pairs(discoveredChannels) do
|
||
local key = ChannelKey(dName)
|
||
if key ~= "" and not seen[key] then
|
||
local aliasKeys = GetChannelAliasKeys(dName)
|
||
local aliasAlreadySeen = false
|
||
if aliasKeys then
|
||
for _, a in ipairs(aliasKeys) do
|
||
if seen[a] then aliasAlreadySeen = true; break end
|
||
end
|
||
end
|
||
if not aliasAlreadySeen then
|
||
if GetChannelName then
|
||
local chId, chRealName = GetChannelName(dName)
|
||
if type(chId) == "number" and chId > 0 then
|
||
table.insert(out, { id = chId, name = chRealName or dName })
|
||
seen[key] = true
|
||
end
|
||
else
|
||
table.insert(out, { id = 99, name = dName })
|
||
seen[key] = true
|
||
end
|
||
end
|
||
end
|
||
end
|
||
|
||
return out
|
||
end
|
||
|
||
local function MatchJoinedChannelName(rawName)
|
||
local key = ChannelKey(rawName)
|
||
if key == "" then return "" end
|
||
|
||
local channels = GetJoinedChannels()
|
||
|
||
-- First: exact key match
|
||
for i = 1, table.getn(channels) do
|
||
local name = channels[i] and channels[i].name
|
||
if ChannelKey(name) == key then
|
||
return name
|
||
end
|
||
end
|
||
|
||
-- Second: alias-group match (e.g. "Hardcore" -> joined "hc")
|
||
local aliases = GetChannelAliasKeys(rawName)
|
||
if aliases then
|
||
for _, alias in ipairs(aliases) do
|
||
local aliasKey = ChannelKey(alias)
|
||
if aliasKey ~= "" then
|
||
for i = 1, table.getn(channels) do
|
||
local name = channels[i] and channels[i].name
|
||
if ChannelKey(name) == aliasKey then
|
||
return name
|
||
end
|
||
end
|
||
end
|
||
end
|
||
end
|
||
|
||
return NormalizeChannelName(rawName)
|
||
end
|
||
|
||
local function GetChannelNameFromMessageEvent(channelString, channelBaseName, channelName, fallback)
|
||
local candidates = {
|
||
channelName,
|
||
channelBaseName,
|
||
channelString,
|
||
arg9,
|
||
arg8,
|
||
arg4,
|
||
fallback,
|
||
}
|
||
|
||
for i = 1, table.getn(candidates) do
|
||
local candidate = candidates[i]
|
||
if type(candidate) == "string" and candidate ~= "" then
|
||
local matched = MatchJoinedChannelName(candidate)
|
||
if matched ~= "" then
|
||
return matched
|
||
end
|
||
end
|
||
end
|
||
|
||
for i = 1, table.getn(candidates) do
|
||
local candidate = candidates[i]
|
||
if type(candidate) == "string" and candidate ~= "" then
|
||
local normalized = NormalizeChannelName(candidate)
|
||
if normalized ~= "" then
|
||
return normalized
|
||
end
|
||
end
|
||
end
|
||
|
||
return ""
|
||
end
|
||
|
||
local function GetChannelNameFromChatLine(text)
|
||
if type(text) ~= "string" or text == "" then return nil end
|
||
|
||
local _, _, label = string.find(text, "|Hchannel:[^|]+|h%[([^%]]+)%]|h")
|
||
if not label then
|
||
local raw = string.gsub(text, "|c%x%x%x%x%x%x%x%x", "")
|
||
raw = string.gsub(raw, "|r", "")
|
||
raw = string.gsub(raw, "^%s+", "")
|
||
_, _, label = string.find(raw, "^%[([^%]]+)%]")
|
||
end
|
||
if not label or label == "" then return nil end
|
||
|
||
label = string.gsub(label, "^%d+%s*%.%s*", "")
|
||
return label
|
||
end
|
||
|
||
local function GetTranslateFilterKeyForEvent(event)
|
||
return TRANSLATE_EVENT_FILTERS[event]
|
||
end
|
||
|
||
local function ParseHardcoreDeathMessage(text)
|
||
if type(text) ~= "string" or text == "" then return nil end
|
||
local clean = string.gsub(text, "|c%x%x%x%x%x%x%x%x", "")
|
||
clean = string.gsub(clean, "|r", "")
|
||
|
||
-- Check for Hardcore death signatures
|
||
if string.find(string.lower(clean), "hc news") or string.find(clean, "硬核") or string.find(clean, "死亡") or string.find(string.lower(clean), "has fallen") or string.find(string.lower(clean), "died") or string.find(string.lower(clean), "slain") then
|
||
-- Turtle English "Level 14"
|
||
local _, _, lvlStr = string.find(clean, "Level%s+(%d+)")
|
||
if lvlStr then return tonumber(lvlStr) end
|
||
|
||
-- Chinese "14级"
|
||
local _, _, lvlStr2 = string.find(clean, "(%d+)%s*级")
|
||
if lvlStr2 then return tonumber(lvlStr2) end
|
||
|
||
-- Fallback
|
||
local _, _, lvlStr3 = string.find(clean, "Level:%s+(%d+)")
|
||
if lvlStr3 then return tonumber(lvlStr3) end
|
||
|
||
-- If it matches death signatures but no level is found, return 1 as a baseline to trigger the filter.
|
||
if string.find(string.lower(clean), "hc news") or (string.find(clean, "硬核") and (string.find(clean, "死亡") or string.find(clean, "has fallen"))) then
|
||
return 1
|
||
end
|
||
end
|
||
return nil
|
||
end
|
||
|
||
local function CleanTextForTranslation(text)
|
||
if type(text) ~= "string" then return "" end
|
||
local clean = text
|
||
clean = string.gsub(clean, "|c%x%x%x%x%x%x%x%x", "")
|
||
clean = string.gsub(clean, "|r", "")
|
||
clean = string.gsub(clean, "|H.-|h(.-)|h", "%1")
|
||
clean = string.gsub(clean, "^%s+", "")
|
||
clean = string.gsub(clean, "%s+$", "")
|
||
return clean
|
||
end
|
||
|
||
local function ForceHide(object)
|
||
if not object then return end
|
||
object:Hide()
|
||
if object.SetAlpha then object:SetAlpha(0) end
|
||
if object.EnableMouse then object:EnableMouse(false) end
|
||
object.Show = Dummy
|
||
end
|
||
|
||
local function ForceInvisible(object)
|
||
if not object then return end
|
||
object:Hide()
|
||
if object.SetAlpha then object:SetAlpha(0) end
|
||
if object.EnableMouse then object:EnableMouse(false) end
|
||
end
|
||
|
||
local function CreateFont(parent, size, justify)
|
||
if SFrames and SFrames.CreateFontString then
|
||
return SFrames:CreateFontString(parent, size, justify)
|
||
end
|
||
|
||
local fs = parent:CreateFontString(nil, "OVERLAY")
|
||
fs:SetFont("Fonts\\ARIALN.TTF", size or 11, "OUTLINE")
|
||
fs:SetJustifyH(justify or "LEFT")
|
||
fs:SetTextColor(1, 1, 1)
|
||
return fs
|
||
end
|
||
|
||
local configWidgetId = 0
|
||
|
||
local function NextConfigWidget(prefix)
|
||
configWidgetId = configWidgetId + 1
|
||
return "SFramesChatCfg" .. prefix .. tostring(configWidgetId)
|
||
end
|
||
|
||
local CFG_THEME = SFrames.ActiveTheme
|
||
|
||
local function EnsureCfgBackdrop(frame)
|
||
if not frame then return end
|
||
if frame.sfCfgBackdrop then return end
|
||
if SFrames and SFrames.CreateBackdrop then
|
||
SFrames:CreateBackdrop(frame)
|
||
elseif frame.SetBackdrop then
|
||
frame:SetBackdrop({
|
||
bgFile = "Interface\\Buttons\\WHITE8X8",
|
||
edgeFile = "Interface\\Buttons\\WHITE8X8",
|
||
tile = false, edgeSize = 1,
|
||
insets = { left = 1, right = 1, top = 1, bottom = 1 },
|
||
})
|
||
end
|
||
frame.sfCfgBackdrop = true
|
||
end
|
||
|
||
local function HideCfgTexture(tex)
|
||
if not tex then return end
|
||
if tex.SetTexture then tex:SetTexture(nil) end
|
||
tex:Hide()
|
||
end
|
||
|
||
local function StyleCfgButton(btn)
|
||
if not btn or btn.sfCfgStyled then return btn end
|
||
btn.sfCfgStyled = true
|
||
|
||
btn:SetBackdrop({
|
||
bgFile = "Interface\\Tooltips\\UI-Tooltip-Background",
|
||
edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
|
||
tile = true, tileSize = 16, edgeSize = 14,
|
||
insets = { left = 3, right = 3, top = 3, bottom = 3 },
|
||
})
|
||
HideCfgTexture(btn.GetNormalTexture and btn:GetNormalTexture())
|
||
HideCfgTexture(btn.GetPushedTexture and btn:GetPushedTexture())
|
||
HideCfgTexture(btn.GetHighlightTexture and btn:GetHighlightTexture())
|
||
HideCfgTexture(btn.GetDisabledTexture and btn:GetDisabledTexture())
|
||
|
||
local name = btn:GetName() or ""
|
||
for _, suffix in ipairs({ "Left", "Right", "Middle" }) do
|
||
local tex = _G[name .. suffix]
|
||
if tex then tex:SetAlpha(0) tex:Hide() end
|
||
end
|
||
|
||
local function SetVisual(state)
|
||
local bg = CFG_THEME.buttonBg
|
||
local border = CFG_THEME.buttonBorder
|
||
local text = CFG_THEME.buttonText
|
||
if state == "hover" then
|
||
bg = CFG_THEME.buttonHoverBg
|
||
border = { CFG_THEME.buttonBorder[1] * 1.5, CFG_THEME.buttonBorder[2] * 1.5, CFG_THEME.buttonBorder[3] * 1.5, 0.98 }
|
||
text = { 1, 0.92, 0.96 }
|
||
elseif state == "down" then
|
||
bg = CFG_THEME.buttonDownBg
|
||
elseif state == "disabled" then
|
||
bg = CFG_THEME.buttonDisabledBg
|
||
text = CFG_THEME.buttonDisabledText
|
||
end
|
||
if btn.SetBackdropColor then
|
||
btn:SetBackdropColor(bg[1], bg[2], bg[3], bg[4])
|
||
end
|
||
if btn.SetBackdropBorderColor then
|
||
btn:SetBackdropBorderColor(border[1], border[2], border[3], border[4])
|
||
end
|
||
local fs = btn.GetFontString and btn:GetFontString()
|
||
if fs then
|
||
fs:SetTextColor(text[1], text[2], text[3])
|
||
end
|
||
end
|
||
|
||
btn.RefreshVisual = function()
|
||
if btn.IsEnabled and not btn:IsEnabled() then
|
||
SetVisual("disabled")
|
||
else
|
||
SetVisual("normal")
|
||
end
|
||
end
|
||
|
||
local oldEnter = btn:GetScript("OnEnter")
|
||
local oldLeave = btn:GetScript("OnLeave")
|
||
local oldDown = btn:GetScript("OnMouseDown")
|
||
local oldUp = btn:GetScript("OnMouseUp")
|
||
|
||
btn:SetScript("OnEnter", function()
|
||
if oldEnter then oldEnter() end
|
||
if this.IsEnabled and this:IsEnabled() then
|
||
SetVisual("hover")
|
||
end
|
||
end)
|
||
btn:SetScript("OnLeave", function()
|
||
if oldLeave then oldLeave() end
|
||
if this.IsEnabled and this:IsEnabled() then
|
||
SetVisual("normal")
|
||
end
|
||
end)
|
||
btn:SetScript("OnMouseDown", function()
|
||
if oldDown then oldDown() end
|
||
if this.IsEnabled and this:IsEnabled() then
|
||
SetVisual("down")
|
||
end
|
||
end)
|
||
btn:SetScript("OnMouseUp", function()
|
||
if oldUp then oldUp() end
|
||
if this.IsEnabled and this:IsEnabled() then
|
||
SetVisual("hover")
|
||
end
|
||
end)
|
||
|
||
btn:RefreshVisual()
|
||
return btn
|
||
end
|
||
|
||
local function StyleCfgCheck(cb)
|
||
if not cb or cb.sfCfgStyled then return cb end
|
||
cb.sfCfgStyled = true
|
||
|
||
local box = CreateFrame("Frame", nil, cb)
|
||
box:SetPoint("TOPLEFT", cb, "TOPLEFT", 2, -2)
|
||
box:SetPoint("BOTTOMRIGHT", cb, "BOTTOMRIGHT", -2, 2)
|
||
local boxLevel = (cb:GetFrameLevel() or 1) - 1
|
||
if boxLevel < 0 then boxLevel = 0 end
|
||
box:SetFrameLevel(boxLevel)
|
||
EnsureCfgBackdrop(box)
|
||
if box.SetBackdropColor then
|
||
box:SetBackdropColor(CFG_THEME.checkBg[1], CFG_THEME.checkBg[2], CFG_THEME.checkBg[3], CFG_THEME.checkBg[4])
|
||
end
|
||
if box.SetBackdropBorderColor then
|
||
box:SetBackdropBorderColor(CFG_THEME.checkBorder[1], CFG_THEME.checkBorder[2], CFG_THEME.checkBorder[3], CFG_THEME.checkBorder[4])
|
||
end
|
||
cb.sfCfgBox = box
|
||
|
||
HideCfgTexture(cb.GetNormalTexture and cb:GetNormalTexture())
|
||
HideCfgTexture(cb.GetPushedTexture and cb:GetPushedTexture())
|
||
HideCfgTexture(cb.GetHighlightTexture and cb:GetHighlightTexture())
|
||
HideCfgTexture(cb.GetDisabledTexture and cb:GetDisabledTexture())
|
||
|
||
if cb.SetCheckedTexture then
|
||
cb:SetCheckedTexture("Interface\\Buttons\\WHITE8X8")
|
||
end
|
||
local checked = cb.GetCheckedTexture and cb:GetCheckedTexture()
|
||
if checked then
|
||
checked:ClearAllPoints()
|
||
checked:SetPoint("TOPLEFT", cb, "TOPLEFT", 5, -5)
|
||
checked:SetPoint("BOTTOMRIGHT", cb, "BOTTOMRIGHT", -5, 5)
|
||
if checked.SetDrawLayer then
|
||
checked:SetDrawLayer("OVERLAY", 7)
|
||
end
|
||
if checked.SetAlpha then
|
||
checked:SetAlpha(1)
|
||
end
|
||
checked:SetVertexColor(CFG_THEME.checkFill[1], CFG_THEME.checkFill[2], CFG_THEME.checkFill[3], CFG_THEME.checkFill[4])
|
||
end
|
||
|
||
if cb.SetDisabledCheckedTexture then
|
||
cb:SetDisabledCheckedTexture("Interface\\Buttons\\WHITE8X8")
|
||
end
|
||
local disChecked = cb.GetDisabledCheckedTexture and cb:GetDisabledCheckedTexture()
|
||
if disChecked then
|
||
disChecked:ClearAllPoints()
|
||
disChecked:SetPoint("TOPLEFT", cb, "TOPLEFT", 5, -5)
|
||
disChecked:SetPoint("BOTTOMRIGHT", cb, "BOTTOMRIGHT", -5, 5)
|
||
if disChecked.SetDrawLayer then
|
||
disChecked:SetDrawLayer("OVERLAY", 6)
|
||
end
|
||
disChecked:SetVertexColor(0.5, 0.5, 0.55, 0.8)
|
||
end
|
||
|
||
local label = _G[cb:GetName() .. "Text"]
|
||
if label then
|
||
label:SetTextColor(CFG_THEME.text[1], CFG_THEME.text[2], CFG_THEME.text[3])
|
||
end
|
||
|
||
local oldEnter = cb:GetScript("OnEnter")
|
||
local oldLeave = cb:GetScript("OnLeave")
|
||
cb:SetScript("OnEnter", function()
|
||
if oldEnter then oldEnter() end
|
||
if this.sfCfgBox and this.sfCfgBox.SetBackdropBorderColor then
|
||
this.sfCfgBox:SetBackdropBorderColor(CFG_THEME.checkHoverBorder[1], CFG_THEME.checkHoverBorder[2], CFG_THEME.checkHoverBorder[3], CFG_THEME.checkHoverBorder[4])
|
||
end
|
||
end)
|
||
cb:SetScript("OnLeave", function()
|
||
if oldLeave then oldLeave() end
|
||
if this.sfCfgBox and this.sfCfgBox.SetBackdropBorderColor then
|
||
this.sfCfgBox:SetBackdropBorderColor(CFG_THEME.checkBorder[1], CFG_THEME.checkBorder[2], CFG_THEME.checkBorder[3], CFG_THEME.checkBorder[4])
|
||
end
|
||
end)
|
||
return cb
|
||
end
|
||
|
||
local function StyleCfgSlider(slider, low, high, text)
|
||
if not slider or slider.sfCfgStyled then return slider end
|
||
slider.sfCfgStyled = true
|
||
|
||
local regions = { slider:GetRegions() }
|
||
for i = 1, table.getn(regions) do
|
||
local region = regions[i]
|
||
if region and region.GetObjectType and region:GetObjectType() == "Texture" then
|
||
region:SetTexture(nil)
|
||
end
|
||
end
|
||
|
||
local track = slider:CreateTexture(nil, "BACKGROUND")
|
||
track:SetTexture("Interface\\Buttons\\WHITE8X8")
|
||
track:SetPoint("LEFT", slider, "LEFT", 0, 0)
|
||
track:SetPoint("RIGHT", slider, "RIGHT", 0, 0)
|
||
track:SetHeight(4)
|
||
track:SetVertexColor(CFG_THEME.sliderTrack[1], CFG_THEME.sliderTrack[2], CFG_THEME.sliderTrack[3], CFG_THEME.sliderTrack[4])
|
||
slider.sfTrack = track
|
||
|
||
local fill = slider:CreateTexture(nil, "ARTWORK")
|
||
fill:SetTexture("Interface\\Buttons\\WHITE8X8")
|
||
fill:SetPoint("LEFT", track, "LEFT", 0, 0)
|
||
fill:SetPoint("TOP", track, "TOP", 0, 0)
|
||
fill:SetPoint("BOTTOM", track, "BOTTOM", 0, 0)
|
||
fill:SetWidth(1)
|
||
fill:SetVertexColor(CFG_THEME.sliderFill[1], CFG_THEME.sliderFill[2], CFG_THEME.sliderFill[3], CFG_THEME.sliderFill[4])
|
||
slider.sfFill = fill
|
||
|
||
if slider.SetThumbTexture then
|
||
slider:SetThumbTexture("Interface\\Buttons\\WHITE8X8")
|
||
end
|
||
local thumb = slider.GetThumbTexture and slider:GetThumbTexture()
|
||
if thumb then
|
||
thumb:SetWidth(8)
|
||
thumb:SetHeight(16)
|
||
thumb:SetVertexColor(CFG_THEME.sliderThumb[1], CFG_THEME.sliderThumb[2], CFG_THEME.sliderThumb[3], CFG_THEME.sliderThumb[4])
|
||
end
|
||
|
||
local function UpdateFill()
|
||
local minValue, maxValue = slider:GetMinMaxValues()
|
||
local value = slider:GetValue() or minValue
|
||
local pct = 0
|
||
if maxValue > minValue then
|
||
pct = (value - minValue) / (maxValue - minValue)
|
||
end
|
||
pct = Clamp(pct, 0, 1)
|
||
local width = math.floor((slider:GetWidth() or 1) * pct + 0.5)
|
||
if width < 1 then width = 1 end
|
||
slider.sfFill:SetWidth(width)
|
||
end
|
||
|
||
local oldChanged = slider:GetScript("OnValueChanged")
|
||
slider:SetScript("OnValueChanged", function()
|
||
if oldChanged then oldChanged() end
|
||
UpdateFill()
|
||
end)
|
||
UpdateFill()
|
||
|
||
if low then
|
||
low:SetTextColor(0.74, 0.72, 0.8)
|
||
low:ClearAllPoints()
|
||
low:SetPoint("TOPLEFT", slider, "BOTTOMLEFT", 0, 0)
|
||
end
|
||
if high then
|
||
high:SetTextColor(0.74, 0.72, 0.8)
|
||
high:ClearAllPoints()
|
||
high:SetPoint("TOPRIGHT", slider, "BOTTOMRIGHT", 0, 0)
|
||
end
|
||
if text then
|
||
text:SetTextColor(CFG_THEME.text[1], CFG_THEME.text[2], CFG_THEME.text[3])
|
||
text:ClearAllPoints()
|
||
text:SetPoint("BOTTOM", slider, "TOP", 0, 2)
|
||
end
|
||
return slider
|
||
end
|
||
|
||
local function StyleCfgEditBox(eb)
|
||
if not eb or eb.sfCfgStyled then return eb end
|
||
eb.sfCfgStyled = true
|
||
|
||
local name = eb:GetName()
|
||
if name and name ~= "" then
|
||
HideCfgTexture(_G[name .. "Left"])
|
||
HideCfgTexture(_G[name .. "Middle"])
|
||
HideCfgTexture(_G[name .. "Right"])
|
||
end
|
||
|
||
local bg = CreateFrame("Frame", nil, eb)
|
||
bg:SetPoint("TOPLEFT", eb, "TOPLEFT", -3, 3)
|
||
bg:SetPoint("BOTTOMRIGHT", eb, "BOTTOMRIGHT", 3, -3)
|
||
bg:SetFrameLevel((eb:GetFrameLevel() or 1) - 1)
|
||
EnsureCfgBackdrop(bg)
|
||
if bg.SetBackdropColor then
|
||
bg:SetBackdropColor(0.13, 0.14, 0.17, 0.94)
|
||
end
|
||
if bg.SetBackdropBorderColor then
|
||
bg:SetBackdropBorderColor(0.52, 0.5, 0.58, 0.88)
|
||
end
|
||
eb.sfCfgBg = bg
|
||
if eb.SetTextInsets then
|
||
eb:SetTextInsets(4, 4, 0, 0)
|
||
end
|
||
if eb.SetTextColor then
|
||
eb:SetTextColor(CFG_THEME.text[1], CFG_THEME.text[2], CFG_THEME.text[3])
|
||
end
|
||
return eb
|
||
end
|
||
|
||
local function CreateCfgSection(parent, title, x, y, width, height, font)
|
||
local sec = CreateFrame("Frame", NextConfigWidget("Section"), parent)
|
||
sec:SetWidth(width)
|
||
sec:SetHeight(height)
|
||
sec:SetPoint("TOPLEFT", parent, "TOPLEFT", x, y)
|
||
|
||
EnsureCfgBackdrop(sec)
|
||
if sec.SetBackdropColor then
|
||
sec:SetBackdropColor(CFG_THEME.sectionBg[1], CFG_THEME.sectionBg[2], CFG_THEME.sectionBg[3], CFG_THEME.sectionBg[4])
|
||
end
|
||
if sec.SetBackdropBorderColor then
|
||
sec:SetBackdropBorderColor(CFG_THEME.sectionBorder[1], CFG_THEME.sectionBorder[2], CFG_THEME.sectionBorder[3], CFG_THEME.sectionBorder[4])
|
||
end
|
||
|
||
local header = sec:CreateFontString(nil, "OVERLAY")
|
||
header:SetFont(font or "Fonts\\ARIALN.TTF", 11, "OUTLINE")
|
||
header:SetPoint("TOPLEFT", sec, "TOPLEFT", 8, -8)
|
||
header:SetText(title or "")
|
||
header:SetTextColor(CFG_THEME.title[1], CFG_THEME.title[2], CFG_THEME.title[3])
|
||
|
||
local div = sec:CreateTexture(nil, "ARTWORK")
|
||
div:SetTexture("Interface\\Buttons\\WHITE8X8")
|
||
div:SetHeight(1)
|
||
div:SetPoint("TOPLEFT", sec, "TOPLEFT", 8, -24)
|
||
div:SetPoint("TOPRIGHT", sec, "TOPRIGHT", -8, -24)
|
||
div:SetVertexColor(CFG_THEME.sectionBorder[1], CFG_THEME.sectionBorder[2], CFG_THEME.sectionBorder[3], 0.6)
|
||
|
||
return sec
|
||
end
|
||
|
||
local function CreateCfgButton(parent, text, x, y, width, height, onClick)
|
||
local btn = CreateFrame("Button", NextConfigWidget("Button"), parent, "UIPanelButtonTemplate")
|
||
btn:SetWidth(width)
|
||
btn:SetHeight(height)
|
||
btn:SetPoint("TOPLEFT", parent, "TOPLEFT", x, y)
|
||
btn:SetText(text or "")
|
||
btn:SetScript("OnClick", onClick)
|
||
StyleCfgButton(btn)
|
||
return btn
|
||
end
|
||
|
||
local function AddBtnIcon(btn, iconKey, size, align)
|
||
if not (SFrames and SFrames.CreateIcon) then return end
|
||
local sz = size or 12
|
||
local gap = 3
|
||
local ico = SFrames:CreateIcon(btn, iconKey, sz)
|
||
ico:SetDrawLayer("OVERLAY")
|
||
btn.nanamiIcon = ico
|
||
local fs = btn:GetFontString()
|
||
if fs then
|
||
fs:ClearAllPoints()
|
||
if align == "left" then
|
||
ico:SetPoint("LEFT", btn, "LEFT", 8, 0)
|
||
fs:SetPoint("LEFT", ico, "RIGHT", gap, 0)
|
||
fs:SetPoint("RIGHT", btn, "RIGHT", -4, 0)
|
||
fs:SetJustifyH("LEFT")
|
||
else
|
||
fs:SetPoint("CENTER", btn, "CENTER", (sz + gap) / 2, 0)
|
||
ico:SetPoint("RIGHT", fs, "LEFT", -gap, 0)
|
||
end
|
||
else
|
||
ico:SetPoint("CENTER", btn, "CENTER", 0, 0)
|
||
end
|
||
end
|
||
|
||
local function CreateCfgCheck(parent, text, x, y, getter, setter, onChanged)
|
||
local cb = CreateFrame("CheckButton", NextConfigWidget("Check"), parent, "UICheckButtonTemplate")
|
||
cb:SetWidth(20)
|
||
cb:SetHeight(20)
|
||
cb:SetPoint("TOPLEFT", parent, "TOPLEFT", x, y)
|
||
StyleCfgCheck(cb)
|
||
local label = _G[cb:GetName() .. "Text"]
|
||
if label then
|
||
label:SetText(text or "")
|
||
label:SetJustifyH("LEFT")
|
||
end
|
||
|
||
local internal = false
|
||
cb:SetScript("OnClick", function()
|
||
if internal then return end
|
||
local checked = (this:GetChecked() and true or false)
|
||
if setter then setter(checked) end
|
||
if onChanged then onChanged(checked) end
|
||
end)
|
||
|
||
cb.Refresh = function()
|
||
internal = true
|
||
cb:SetChecked(getter and getter() and true or false)
|
||
internal = false
|
||
end
|
||
|
||
cb:Refresh()
|
||
return cb
|
||
end
|
||
|
||
local function CreateCfgSlider(parent, labelText, x, y, width, minValue, maxValue, step, getter, setter, formatter, onChanged)
|
||
local sliderName = NextConfigWidget("Slider")
|
||
local slider = CreateFrame("Slider", sliderName, parent, "OptionsSliderTemplate")
|
||
slider:SetWidth(width)
|
||
slider:SetHeight(26)
|
||
slider:SetPoint("TOPLEFT", parent, "TOPLEFT", x, y)
|
||
slider:SetMinMaxValues(minValue, maxValue)
|
||
slider:SetValueStep(step)
|
||
if slider.SetObeyStepOnDrag then slider:SetObeyStepOnDrag(true) end
|
||
|
||
local low = _G[sliderName .. "Low"]
|
||
local high = _G[sliderName .. "High"]
|
||
local text = _G[sliderName .. "Text"]
|
||
if low then low:SetText(tostring(minValue)) end
|
||
if high then high:SetText(tostring(maxValue)) end
|
||
|
||
local internal = false
|
||
local function UpdateLabel(value)
|
||
local display = formatter and formatter(value) or value
|
||
if text then text:SetText((labelText or "") .. ": " .. tostring(display)) end
|
||
end
|
||
|
||
slider:SetScript("OnValueChanged", function()
|
||
if internal then return end
|
||
local raw = this:GetValue() or minValue or 0
|
||
local safeStep = step or 1
|
||
local value
|
||
if safeStep >= 1 then
|
||
value = math.floor(raw + 0.5)
|
||
else
|
||
if safeStep == 0 then safeStep = 1 end
|
||
value = math.floor(raw / safeStep + 0.5) * safeStep
|
||
end
|
||
value = Clamp(value, minValue, maxValue)
|
||
if setter then setter(value) end
|
||
UpdateLabel(value)
|
||
if onChanged then onChanged(value) end
|
||
end)
|
||
|
||
slider.Refresh = function()
|
||
local value = tonumber(getter and getter() or minValue) or minValue
|
||
value = Clamp(value, minValue, maxValue)
|
||
internal = true
|
||
slider:SetValue(value)
|
||
internal = false
|
||
UpdateLabel(value)
|
||
end
|
||
|
||
StyleCfgSlider(slider, low, high, text)
|
||
slider:Refresh()
|
||
return slider
|
||
end
|
||
|
||
local function CreateCfgEditBox(parent, x, y, width, height, getter, setter)
|
||
local eb = CreateFrame("EditBox", NextConfigWidget("Edit"), parent, "InputBoxTemplate")
|
||
eb:SetWidth(width)
|
||
eb:SetHeight(height)
|
||
eb:SetPoint("TOPLEFT", parent, "TOPLEFT", x, y)
|
||
eb:SetAutoFocus(false)
|
||
StyleCfgEditBox(eb)
|
||
eb:SetScript("OnEnterPressed", function()
|
||
if setter then setter(this:GetText() or "") end
|
||
this:ClearFocus()
|
||
end)
|
||
eb:SetScript("OnEscapePressed", function()
|
||
this:ClearFocus()
|
||
if getter then this:SetText(getter() or "") end
|
||
end)
|
||
eb.Refresh = function()
|
||
if getter then eb:SetText(getter() or "") end
|
||
end
|
||
eb:Refresh()
|
||
return eb
|
||
end
|
||
|
||
local function BuildDefaultTab(id, name)
|
||
return {
|
||
id = id,
|
||
name = name or ("Tab" .. tostring(id)),
|
||
filters = CopyTable(DEFAULT_FILTERS),
|
||
channelFilters = {},
|
||
translateFilters = BuildDefaultTranslateFilters(),
|
||
channelTranslateFilters = {},
|
||
}
|
||
end
|
||
|
||
local function BuildCombatTab(id, name)
|
||
local tab = BuildDefaultTab(id, name or DEFAULT_COMBAT_TAB_NAME)
|
||
tab.kind = "combat"
|
||
tab.filters.say = false
|
||
tab.filters.yell = false
|
||
tab.filters.emote = false
|
||
tab.filters.guild = false
|
||
tab.filters.party = false
|
||
tab.filters.raid = false
|
||
tab.filters.whisper = false
|
||
tab.filters.channel = false
|
||
tab.filters.system = true
|
||
tab.filters.loot = true
|
||
tab.filters.money = true
|
||
return tab
|
||
end
|
||
|
||
local function IsCombatTab(tab)
|
||
if type(tab) ~= "table" then return false end
|
||
if tab.kind == "combat" then return true end
|
||
|
||
local nameKey = ChannelKey(tab.name)
|
||
if nameKey == "combat" then return true end
|
||
if nameKey == "combat log" then return true end
|
||
|
||
local localizedKey = ChannelKey(DEFAULT_COMBAT_TAB_NAME)
|
||
if localizedKey ~= "" and nameKey == localizedKey then return true end
|
||
return false
|
||
end
|
||
|
||
local function SanitizeTab(tab, fallbackId, fallbackName)
|
||
if type(tab) ~= "table" then tab = {} end
|
||
if type(tab.id) ~= "number" then tab.id = fallbackId or 1 end
|
||
if type(tab.kind) ~= "string" then tab.kind = nil end
|
||
|
||
if type(tab.name) ~= "string" or tab.name == "" then
|
||
tab.name = fallbackName or ("Tab" .. tostring(tab.id))
|
||
else
|
||
tab.name = Trim(tab.name)
|
||
if tab.name == "" then
|
||
tab.name = fallbackName or ("Tab" .. tostring(tab.id))
|
||
end
|
||
end
|
||
|
||
if type(tab.filters) ~= "table" then tab.filters = {} end
|
||
for key, defaultValue in pairs(DEFAULT_FILTERS) do
|
||
if tab.filters[key] == nil then
|
||
tab.filters[key] = defaultValue
|
||
else
|
||
tab.filters[key] = (tab.filters[key] == true)
|
||
end
|
||
end
|
||
|
||
if type(tab.channelFilters) ~= "table" then tab.channelFilters = {} end
|
||
-- Pass 1: normalize all keys and collect alias group values
|
||
local cfGroupValues = {} -- gIdx -> value (last-write wins: true beats false)
|
||
for key, value in pairs(tab.channelFilters) do
|
||
if type(key) ~= "string" then
|
||
tab.channelFilters[key] = nil
|
||
else
|
||
local normalized = ChannelKey(key)
|
||
if normalized == "" then
|
||
tab.channelFilters[key] = nil
|
||
else
|
||
local aliases = GetChannelAliasKeys(normalized)
|
||
if aliases then
|
||
-- Find this key's group index
|
||
local gIdx = CHANNEL_ALIAS_GROUP_INDEX[normalized]
|
||
if not gIdx then
|
||
for gi, group in ipairs(CHANNEL_ALIAS_GROUPS) do
|
||
for _, a in ipairs(group) do
|
||
if string.find(normalized, a, 1, true) then gIdx = gi break end
|
||
end
|
||
if gIdx then break end
|
||
end
|
||
end
|
||
if gIdx then
|
||
local enabled = (value == true)
|
||
-- true overrides false (if any alias is enabled, keep enabled)
|
||
if cfGroupValues[gIdx] == nil or enabled then
|
||
cfGroupValues[gIdx] = enabled
|
||
end
|
||
end
|
||
tab.channelFilters[key] = nil
|
||
else
|
||
local enabled = (value == true)
|
||
if normalized ~= key then
|
||
tab.channelFilters[key] = nil
|
||
tab.channelFilters[normalized] = enabled
|
||
else
|
||
tab.channelFilters[key] = enabled
|
||
end
|
||
end
|
||
end
|
||
end
|
||
end
|
||
-- Pass 2: write canonical key for each alias group that has a saved value
|
||
for gi, enabled in pairs(cfGroupValues) do
|
||
local group = CHANNEL_ALIAS_GROUPS[gi]
|
||
if group then
|
||
local canonicalKey = ChannelKey(group[1] or "")
|
||
if canonicalKey ~= "" then
|
||
tab.channelFilters[canonicalKey] = enabled
|
||
end
|
||
end
|
||
end
|
||
|
||
if type(tab.translateFilters) ~= "table" then tab.translateFilters = {} end
|
||
for key in pairs(TRANSLATE_FILTER_KEYS) do
|
||
tab.translateFilters[key] = (tab.translateFilters[key] == true)
|
||
end
|
||
for key, value in pairs(tab.translateFilters) do
|
||
if not TRANSLATE_FILTER_KEYS[key] then
|
||
tab.translateFilters[key] = nil
|
||
else
|
||
tab.translateFilters[key] = (value == true)
|
||
end
|
||
end
|
||
|
||
if type(tab.channelTranslateFilters) ~= "table" then tab.channelTranslateFilters = {} end
|
||
local ctfGroupValues = {}
|
||
for key, value in pairs(tab.channelTranslateFilters) do
|
||
if type(key) ~= "string" then
|
||
tab.channelTranslateFilters[key] = nil
|
||
else
|
||
local normalized = ChannelKey(key)
|
||
if normalized == "" then
|
||
tab.channelTranslateFilters[key] = nil
|
||
else
|
||
local aliases = GetChannelAliasKeys(normalized)
|
||
if aliases then
|
||
local gIdx = CHANNEL_ALIAS_GROUP_INDEX[normalized]
|
||
if not gIdx then
|
||
for gi, group in ipairs(CHANNEL_ALIAS_GROUPS) do
|
||
for _, a in ipairs(group) do
|
||
if string.find(normalized, a, 1, true) then gIdx = gi break end
|
||
end
|
||
if gIdx then break end
|
||
end
|
||
end
|
||
if gIdx then
|
||
local enabled = (value == true)
|
||
if ctfGroupValues[gIdx] == nil or enabled then
|
||
ctfGroupValues[gIdx] = enabled
|
||
end
|
||
end
|
||
tab.channelTranslateFilters[key] = nil
|
||
else
|
||
local enabled = (value == true)
|
||
if normalized ~= key then
|
||
tab.channelTranslateFilters[key] = nil
|
||
tab.channelTranslateFilters[normalized] = enabled
|
||
else
|
||
tab.channelTranslateFilters[key] = enabled
|
||
end
|
||
end
|
||
end
|
||
end
|
||
end
|
||
for gi, enabled in pairs(ctfGroupValues) do
|
||
local group = CHANNEL_ALIAS_GROUPS[gi]
|
||
if group then
|
||
local canonicalKey = ChannelKey(group[1] or "")
|
||
if canonicalKey ~= "" then
|
||
tab.channelTranslateFilters[canonicalKey] = enabled
|
||
end
|
||
end
|
||
end
|
||
return tab
|
||
end
|
||
|
||
local function EnsureProtectedTabs(db, maxId)
|
||
local tabs = db.tabs
|
||
local generalIdx = nil
|
||
local combatIdx = nil
|
||
|
||
for i = 1, table.getn(tabs) do
|
||
if IsCombatTab(tabs[i]) then
|
||
if not combatIdx then combatIdx = i end
|
||
else
|
||
if not generalIdx then generalIdx = i end
|
||
end
|
||
end
|
||
|
||
local function AllocTabId()
|
||
local id = db.nextTabId
|
||
if type(id) ~= "number" or id <= maxId then
|
||
id = maxId + 1
|
||
end
|
||
db.nextTabId = id + 1
|
||
maxId = id
|
||
return id
|
||
end
|
||
|
||
if not generalIdx then
|
||
local tab = BuildDefaultTab(AllocTabId(), GENERAL or "General")
|
||
tab.kind = "general"
|
||
table.insert(tabs, 1, tab)
|
||
generalIdx = 1
|
||
if combatIdx then combatIdx = combatIdx + 1 end
|
||
end
|
||
|
||
if not combatIdx then
|
||
local tab = BuildCombatTab(AllocTabId(), DEFAULT_COMBAT_TAB_NAME)
|
||
table.insert(tabs, tab)
|
||
combatIdx = table.getn(tabs)
|
||
end
|
||
|
||
for i = 1, table.getn(tabs) do
|
||
local tab = tabs[i]
|
||
if i == generalIdx then
|
||
tab.kind = "general"
|
||
tab.locked = true
|
||
if Trim(tab.name) == "" then tab.name = GENERAL or "General" end
|
||
elseif i == combatIdx then
|
||
tab.kind = "combat"
|
||
tab.locked = true
|
||
if Trim(tab.name) == "" then tab.name = DEFAULT_COMBAT_TAB_NAME end
|
||
else
|
||
if tab.kind == "general" or tab.kind == "combat" then tab.kind = nil end
|
||
tab.locked = nil
|
||
end
|
||
end
|
||
|
||
return maxId
|
||
end
|
||
|
||
local function EnsureDB()
|
||
if not SFramesDB then SFramesDB = {} end
|
||
if type(SFramesDB.Chat) ~= "table" then SFramesDB.Chat = {} end
|
||
|
||
local db = SFramesDB.Chat
|
||
if db.enable == nil then db.enable = DEFAULTS.enable end
|
||
if db.showBorder == nil then db.showBorder = DEFAULTS.showBorder end
|
||
if db.borderClassColor == nil then db.borderClassColor = DEFAULTS.borderClassColor end
|
||
if db.showPlayerLevel == nil then db.showPlayerLevel = DEFAULTS.showPlayerLevel end
|
||
if type(db.width) ~= "number" then db.width = DEFAULTS.width end
|
||
if type(db.height) ~= "number" then db.height = DEFAULTS.height end
|
||
if type(db.scale) ~= "number" then db.scale = DEFAULTS.scale end
|
||
if type(db.fontSize) ~= "number" then db.fontSize = DEFAULTS.fontSize end
|
||
if type(db.sidePadding) ~= "number" then db.sidePadding = DEFAULTS.sidePadding end
|
||
if type(db.topPadding) ~= "number" then db.topPadding = DEFAULTS.topPadding end
|
||
if type(db.bottomPadding) ~= "number" then db.bottomPadding = DEFAULTS.bottomPadding end
|
||
if type(db.bgAlpha) ~= "number" then db.bgAlpha = DEFAULTS.bgAlpha end
|
||
if type(db.editBoxPosition) ~= "string" then db.editBoxPosition = DEFAULTS.editBoxPosition end
|
||
if type(db.editBoxX) ~= "number" then db.editBoxX = DEFAULTS.editBoxX end
|
||
if type(db.editBoxY) ~= "number" then db.editBoxY = DEFAULTS.editBoxY end
|
||
if type(db.layoutVersion) ~= "number" then db.layoutVersion = 1 end
|
||
if db.layoutVersion < 2 then
|
||
db.topPadding = DEFAULTS.topPadding
|
||
db.layoutVersion = 2
|
||
end
|
||
if db.layoutVersion < 3 then
|
||
db.showBorder = false
|
||
db.layoutVersion = 3
|
||
end
|
||
|
||
if type(db.tabs) ~= "table" or table.getn(db.tabs) == 0 then
|
||
db.tabs = {
|
||
BuildDefaultTab(1, GENERAL or "General"),
|
||
BuildCombatTab(2, DEFAULT_COMBAT_TAB_NAME),
|
||
}
|
||
end
|
||
|
||
local maxId = 0
|
||
for i = 1, table.getn(db.tabs) do
|
||
db.tabs[i] = SanitizeTab(db.tabs[i], i, "Tab" .. tostring(i))
|
||
if IsCombatTab(db.tabs[i]) then
|
||
db.tabs[i].kind = "combat"
|
||
end
|
||
if db.tabs[i].id > maxId then maxId = db.tabs[i].id end
|
||
end
|
||
|
||
if type(db.nextTabId) ~= "number" or db.nextTabId <= maxId then
|
||
db.nextTabId = maxId + 1
|
||
end
|
||
|
||
if db.layoutVersion < 4 then
|
||
local hasCombat = false
|
||
for i = 1, table.getn(db.tabs) do
|
||
if IsCombatTab(db.tabs[i]) then
|
||
hasCombat = true
|
||
break
|
||
end
|
||
end
|
||
|
||
if not hasCombat then
|
||
local id = db.nextTabId
|
||
if type(id) ~= "number" or id <= maxId then
|
||
id = maxId + 1
|
||
end
|
||
table.insert(db.tabs, BuildCombatTab(id, DEFAULT_COMBAT_TAB_NAME))
|
||
db.nextTabId = id + 1
|
||
end
|
||
|
||
db.layoutVersion = 4
|
||
end
|
||
|
||
if type(db.activeTab) ~= "number" then db.activeTab = DEFAULTS.activeTab end
|
||
db.activeTab = Clamp(math.floor(db.activeTab + 0.5), 1, table.getn(db.tabs))
|
||
|
||
if db.layoutVersion < 5 then
|
||
local activeTab = db.tabs[db.activeTab]
|
||
if IsCombatTab(activeTab) then
|
||
for i = 1, table.getn(db.tabs) do
|
||
if not IsCombatTab(db.tabs[i]) then
|
||
db.activeTab = i
|
||
break
|
||
end
|
||
end
|
||
end
|
||
db.layoutVersion = 5
|
||
end
|
||
|
||
maxId = EnsureProtectedTabs(db, maxId)
|
||
if type(db.nextTabId) ~= "number" or db.nextTabId <= maxId then
|
||
db.nextTabId = maxId + 1
|
||
end
|
||
if db.layoutVersion < 6 then
|
||
db.layoutVersion = 6
|
||
end
|
||
|
||
db.activeTab = Clamp(math.floor((db.activeTab or 1) + 0.5), 1, table.getn(db.tabs))
|
||
return db
|
||
end
|
||
|
||
local popupFrameCache = {}
|
||
|
||
local function RememberPopupFrame(whichKey, popup)
|
||
if type(whichKey) ~= "string" or whichKey == "" then return end
|
||
if type(popup) ~= "table" then return end
|
||
popupFrameCache[whichKey] = popup
|
||
end
|
||
|
||
local function GetPopupEditBox(popup)
|
||
if not popup then return nil end
|
||
if popup.editBox then return popup.editBox end
|
||
if popup.GetName then
|
||
local eb = _G[popup:GetName() .. "EditBox"]
|
||
if eb then return eb end
|
||
end
|
||
return nil
|
||
end
|
||
|
||
local function GetPopupEditText(popup)
|
||
local eb = GetPopupEditBox(popup)
|
||
if eb and eb.GetText then
|
||
return eb:GetText() or ""
|
||
end
|
||
return ""
|
||
end
|
||
|
||
local function SetPopupEditText(popup, text)
|
||
local eb = GetPopupEditBox(popup)
|
||
if eb and eb.SetText then
|
||
eb:SetText(text or "")
|
||
end
|
||
end
|
||
|
||
local function FocusPopupEdit(popup)
|
||
local eb = GetPopupEditBox(popup)
|
||
if not eb then return end
|
||
if eb.SetFocus then eb:SetFocus() end
|
||
if eb.HighlightText then eb:HighlightText() end
|
||
end
|
||
|
||
local function ResolvePopupFrame(whichKey, dialog)
|
||
if dialog and dialog.GetParent then
|
||
local parent = dialog:GetParent()
|
||
if parent and parent.which == whichKey and GetPopupEditBox(parent) then
|
||
RememberPopupFrame(whichKey, parent)
|
||
return parent
|
||
end
|
||
end
|
||
|
||
if dialog and dialog.editBox then
|
||
RememberPopupFrame(whichKey, dialog)
|
||
return dialog
|
||
end
|
||
|
||
if this then
|
||
if this.editBox then
|
||
RememberPopupFrame(whichKey, this)
|
||
return this
|
||
end
|
||
|
||
if this.GetParent then
|
||
local parent = this:GetParent()
|
||
if parent and parent.which == whichKey and GetPopupEditBox(parent) then
|
||
RememberPopupFrame(whichKey, parent)
|
||
return parent
|
||
end
|
||
end
|
||
end
|
||
|
||
local cached = popupFrameCache[whichKey]
|
||
if cached and cached.which == whichKey and GetPopupEditBox(cached) then
|
||
return cached
|
||
end
|
||
|
||
if StaticPopup_FindVisible then
|
||
local popup = StaticPopup_FindVisible(whichKey)
|
||
if popup then
|
||
RememberPopupFrame(whichKey, popup)
|
||
return popup
|
||
end
|
||
end
|
||
|
||
for i = 1, 4 do
|
||
local popup = _G["StaticPopup" .. tostring(i)]
|
||
if popup and popup.which == whichKey then
|
||
RememberPopupFrame(whichKey, popup)
|
||
return popup
|
||
end
|
||
end
|
||
|
||
return dialog
|
||
end
|
||
|
||
local function EnsurePopupDialogs()
|
||
if not StaticPopupDialogs then return end
|
||
|
||
if not StaticPopupDialogs["SFRAMES_CHAT_NEW_TAB"] then
|
||
StaticPopupDialogs["SFRAMES_CHAT_NEW_TAB"] = {
|
||
text = "请输入新标签标题",
|
||
button1 = ACCEPT or "确定",
|
||
button2 = CANCEL or "取消",
|
||
hasEditBox = 1,
|
||
maxLetters = 32,
|
||
timeout = 0,
|
||
whileDead = 1,
|
||
hideOnEscape = 1,
|
||
preferredIndex = 3,
|
||
OnShow = function(dialog)
|
||
local popup = ResolvePopupFrame("SFRAMES_CHAT_NEW_TAB", dialog)
|
||
if not popup then return end
|
||
local suggested = "Tab"
|
||
if SFrames and SFrames.Chat and SFrames.Chat.GetNextTabName then
|
||
suggested = SFrames.Chat:GetNextTabName()
|
||
end
|
||
SetPopupEditText(popup, suggested)
|
||
FocusPopupEdit(popup)
|
||
end,
|
||
OnAccept = function(dialog)
|
||
local popup = ResolvePopupFrame("SFRAMES_CHAT_NEW_TAB", dialog)
|
||
local text = ""
|
||
if popup then
|
||
text = Trim(GetPopupEditText(popup))
|
||
end
|
||
if text == "" then
|
||
if SFrames and SFrames.Chat and SFrames.Chat.GetNextTabName then
|
||
text = SFrames.Chat:GetNextTabName()
|
||
else
|
||
text = "Tab"
|
||
end
|
||
end
|
||
if SFrames and SFrames.Chat then
|
||
SFrames.Chat:AddTab(text)
|
||
end
|
||
end,
|
||
}
|
||
end
|
||
|
||
if not StaticPopupDialogs["SFRAMES_CHAT_RENAME_TAB"] then
|
||
StaticPopupDialogs["SFRAMES_CHAT_RENAME_TAB"] = {
|
||
text = "重命名标签",
|
||
button1 = ACCEPT or "确定",
|
||
button2 = CANCEL or "取消",
|
||
hasEditBox = 1,
|
||
maxLetters = 32,
|
||
timeout = 0,
|
||
whileDead = 1,
|
||
hideOnEscape = 1,
|
||
preferredIndex = 3,
|
||
OnShow = function(dialog, data)
|
||
local popup = ResolvePopupFrame("SFRAMES_CHAT_RENAME_TAB", dialog)
|
||
if not popup then return end
|
||
local idx = tonumber(data or (popup and popup.data) or (SFrames and SFrames.Chat and SFrames.Chat.pendingRenameIndex))
|
||
local name = ""
|
||
if SFrames and SFrames.Chat then
|
||
local tab = nil
|
||
if type(idx) == "number" then
|
||
tab = SFrames.Chat:GetTab(idx)
|
||
else
|
||
tab = SFrames.Chat:GetActiveTab()
|
||
end
|
||
if tab and tab.name then
|
||
name = tab.name
|
||
end
|
||
end
|
||
SetPopupEditText(popup, name)
|
||
FocusPopupEdit(popup)
|
||
end,
|
||
OnAccept = function(dialog, data)
|
||
local popup = ResolvePopupFrame("SFRAMES_CHAT_RENAME_TAB", dialog)
|
||
if not popup then return end
|
||
local idx = tonumber(data or (popup and popup.data) or (SFrames and SFrames.Chat and SFrames.Chat.pendingRenameIndex))
|
||
local text = GetPopupEditText(popup)
|
||
if not (SFrames and SFrames.Chat) then return end
|
||
if type(idx) == "number" then
|
||
SFrames.Chat:RenameTab(idx, text)
|
||
else
|
||
SFrames.Chat:RenameActiveTab(text)
|
||
end
|
||
SFrames.Chat.pendingRenameIndex = nil
|
||
end,
|
||
}
|
||
end
|
||
|
||
if not StaticPopupDialogs["SFRAMES_CHAT_RELOAD_PROMPT"] then
|
||
StaticPopupDialogs["SFRAMES_CHAT_RELOAD_PROMPT"] = {
|
||
text = "更改聊天界面的启用状态需要重载界面(Reload UI)。\n确定要现在重载吗?",
|
||
button1 = ACCEPT or "确定",
|
||
button2 = CANCEL or "取消",
|
||
timeout = 0,
|
||
whileDead = 1,
|
||
hideOnEscape = 1,
|
||
OnAccept = function()
|
||
ReloadUI()
|
||
end,
|
||
OnCancel = function()
|
||
-- 恢复之前的勾选状态
|
||
local db = SFrames.Chat:EnsureDB()
|
||
db.enable = not db.enable
|
||
SFrames.Chat:RefreshConfigFrame()
|
||
end,
|
||
}
|
||
end
|
||
end
|
||
|
||
local function NewDropDownInfo()
|
||
if UIDropDownMenu_CreateInfo then
|
||
return UIDropDownMenu_CreateInfo()
|
||
end
|
||
return {}
|
||
end
|
||
|
||
SFrames.Chat.FilterDefs = FILTER_DEFS
|
||
|
||
function SFrames.Chat:EnsureDB()
|
||
return EnsureDB()
|
||
end
|
||
|
||
function SFrames.Chat:GetConfig()
|
||
local db = EnsureDB()
|
||
local editBoxPosition = tostring(db.editBoxPosition or DEFAULTS.editBoxPosition)
|
||
if editBoxPosition ~= "top" and editBoxPosition ~= "bottom" and editBoxPosition ~= "free" then
|
||
editBoxPosition = DEFAULTS.editBoxPosition
|
||
end
|
||
|
||
return {
|
||
enable = db.enable ~= false,
|
||
showBorder = db.showBorder ~= false,
|
||
borderClassColor = db.borderClassColor == true,
|
||
width = math.floor(Clamp(db.width, 320, 900) + 0.5),
|
||
height = math.floor(Clamp(db.height, 120, 460) + 0.5),
|
||
scale = Clamp(db.scale, 0.75, 1.4),
|
||
fontSize = math.floor(Clamp(db.fontSize, 10, 18) + 0.5),
|
||
sidePadding = math.floor(Clamp(db.sidePadding, 6, 20) + 0.5),
|
||
topPadding = math.floor(Clamp(db.topPadding, 24, 64) + 0.5),
|
||
bottomPadding = math.floor(Clamp(db.bottomPadding, 4, 18) + 0.5),
|
||
bgAlpha = Clamp(db.bgAlpha, 0, 1),
|
||
editBoxPosition = editBoxPosition,
|
||
editBoxX = tonumber(db.editBoxX) or DEFAULTS.editBoxX,
|
||
editBoxY = tonumber(db.editBoxY) or DEFAULTS.editBoxY,
|
||
}
|
||
end
|
||
|
||
function SFrames.Chat:GetTabs()
|
||
return EnsureDB().tabs
|
||
end
|
||
|
||
function SFrames.Chat:GetActiveTabIndex()
|
||
local db = EnsureDB()
|
||
return Clamp(math.floor((db.activeTab or 1) + 0.5), 1, table.getn(db.tabs))
|
||
end
|
||
|
||
function SFrames.Chat:GetActiveTab()
|
||
local db = EnsureDB()
|
||
return db.tabs[self:GetActiveTabIndex()]
|
||
end
|
||
|
||
function SFrames.Chat:GetTab(index)
|
||
local db = EnsureDB()
|
||
index = Clamp(math.floor(tonumber(index) or 1), 1, table.getn(db.tabs))
|
||
return db.tabs[index], index
|
||
end
|
||
|
||
function SFrames.Chat:GetNextTabName()
|
||
local db = EnsureDB()
|
||
local id = db.nextTabId or (table.getn(db.tabs) + 1)
|
||
return "Tab" .. tostring(id)
|
||
end
|
||
|
||
function SFrames.Chat:IsTabProtected(index)
|
||
local tab = self:GetTab(index)
|
||
if not tab then return false end
|
||
if tab.locked == true then return true end
|
||
if tab.kind == "general" or tab.kind == "combat" then return true end
|
||
if IsCombatTab(tab) then return true end
|
||
return false
|
||
end
|
||
|
||
function SFrames.Chat:GetJoinedChannels()
|
||
return GetJoinedChannels()
|
||
end
|
||
|
||
function SFrames.Chat:GetTabChannelFilter(index, channelName)
|
||
local tab = self:GetTab(index)
|
||
if not tab then return true end
|
||
if type(tab.channelFilters) ~= "table" then tab.channelFilters = {} end
|
||
local key = ChannelKey(channelName)
|
||
if key == "" then return true end
|
||
|
||
-- Direct lookup first
|
||
local saved = tab.channelFilters[key]
|
||
if saved ~= nil then
|
||
return saved == true
|
||
end
|
||
|
||
-- Alias-aware lookup: if this channel belongs to an alias group,
|
||
-- check if any alias key has been explicitly saved.
|
||
local aliases = GetChannelAliasKeys(channelName)
|
||
if aliases then
|
||
for _, alias in ipairs(aliases) do
|
||
local aliasKey = ChannelKey(alias)
|
||
if aliasKey ~= "" and aliasKey ~= key then
|
||
local aliasSaved = tab.channelFilters[aliasKey]
|
||
if aliasSaved ~= nil then
|
||
return aliasSaved == true
|
||
end
|
||
end
|
||
end
|
||
-- No explicit save found for any alias; default to blocked
|
||
return false
|
||
end
|
||
|
||
-- Not an ignored channel; default to shown
|
||
return true
|
||
end
|
||
|
||
function SFrames.Chat:GetTabTranslateFilter(index, key)
|
||
local tab = self:GetTab(index)
|
||
if not tab or not TRANSLATE_FILTER_KEYS[key] then return false end
|
||
if type(tab.translateFilters) ~= "table" then
|
||
tab.translateFilters = BuildDefaultTranslateFilters()
|
||
end
|
||
return tab.translateFilters[key] == true
|
||
end
|
||
|
||
function SFrames.Chat:SetTabTranslateFilter(index, key, enabled)
|
||
local tab = self:GetTab(index)
|
||
if not tab or not TRANSLATE_FILTER_KEYS[key] then return end
|
||
if type(tab.translateFilters) ~= "table" then
|
||
tab.translateFilters = BuildDefaultTranslateFilters()
|
||
end
|
||
tab.translateFilters[key] = (enabled == true)
|
||
if self.translateConfigFrame and self.RefreshTranslateConfigFrame then
|
||
self:RefreshTranslateConfigFrame()
|
||
end
|
||
self:NotifyConfigUI()
|
||
end
|
||
|
||
function SFrames.Chat:SetActiveTabTranslateFilter(key, enabled)
|
||
self:SetTabTranslateFilter(self:GetActiveTabIndex(), key, enabled)
|
||
end
|
||
|
||
function SFrames.Chat:GetTabChannelTranslateFilter(index, channelName)
|
||
local tab = self:GetTab(index)
|
||
if not tab then return false end
|
||
if type(tab.channelTranslateFilters) ~= "table" then tab.channelTranslateFilters = {} end
|
||
local key = ChannelKey(channelName)
|
||
if key == "" then return false end
|
||
if not self:GetTabChannelFilter(index, channelName) then
|
||
return false
|
||
end
|
||
-- Direct lookup
|
||
local saved = tab.channelTranslateFilters[key]
|
||
if saved ~= nil then return saved == true end
|
||
-- Alias-aware lookup
|
||
local aliases = GetChannelAliasKeys(channelName)
|
||
if aliases then
|
||
for _, alias in ipairs(aliases) do
|
||
local ak = ChannelKey(alias)
|
||
if ak ~= "" and ak ~= key then
|
||
local aliasSaved = tab.channelTranslateFilters[ak]
|
||
if aliasSaved ~= nil then return aliasSaved == true end
|
||
end
|
||
end
|
||
end
|
||
return false
|
||
end
|
||
|
||
function SFrames.Chat:SetTabChannelTranslateFilter(index, channelName, enabled)
|
||
local tab = self:GetTab(index)
|
||
if not tab then return end
|
||
if type(tab.channelTranslateFilters) ~= "table" then tab.channelTranslateFilters = {} end
|
||
local key = ChannelKey(channelName)
|
||
if key == "" then return end
|
||
-- Save under canonical alias key and clear other alias keys
|
||
local canonicalKey = key
|
||
local aliases = GetChannelAliasKeys(channelName)
|
||
if aliases then
|
||
local firstAlias = ChannelKey(aliases[1] or "")
|
||
if firstAlias ~= "" then canonicalKey = firstAlias end
|
||
for _, alias in ipairs(aliases) do
|
||
local ak = ChannelKey(alias)
|
||
if ak ~= "" and ak ~= canonicalKey then
|
||
tab.channelTranslateFilters[ak] = nil
|
||
end
|
||
end
|
||
end
|
||
tab.channelTranslateFilters[canonicalKey] = (enabled == true)
|
||
if self.translateConfigFrame and self.RefreshTranslateConfigFrame then
|
||
self:RefreshTranslateConfigFrame()
|
||
end
|
||
self:NotifyConfigUI()
|
||
end
|
||
|
||
function SFrames.Chat:SetActiveTabChannelTranslateFilter(channelName, enabled)
|
||
self:SetTabChannelTranslateFilter(self:GetActiveTabIndex(), channelName, enabled)
|
||
end
|
||
|
||
function SFrames.Chat:GetTabIndexForChatFrame(frame)
|
||
if not frame then return nil end
|
||
if self.chatFrameToTabIndex and self.chatFrameToTabIndex[frame] then
|
||
return self.chatFrameToTabIndex[frame]
|
||
end
|
||
local db = EnsureDB()
|
||
for i = 1, table.getn(db.tabs) do
|
||
local chatFrame = self:GetChatFrameForTab(db.tabs[i])
|
||
if chatFrame == frame then
|
||
if not self.chatFrameToTabIndex then self.chatFrameToTabIndex = {} end
|
||
self.chatFrameToTabIndex[frame] = i
|
||
return i
|
||
end
|
||
end
|
||
return nil
|
||
end
|
||
|
||
function SFrames.Chat:FindTabIndexById(tabId)
|
||
if type(tabId) ~= "number" then return nil end
|
||
local db = EnsureDB()
|
||
for i = 1, table.getn(db.tabs) do
|
||
local tab = db.tabs[i]
|
||
if tab and tab.id == tabId then
|
||
return i
|
||
end
|
||
end
|
||
return nil
|
||
end
|
||
|
||
function SFrames.Chat:ShouldAutoTranslateForTab(index, filterKey, channelName)
|
||
local tab = self:GetTab(index)
|
||
if not tab then return false end
|
||
if filterKey == "channel" then
|
||
return self:GetTabChannelTranslateFilter(index, channelName)
|
||
end
|
||
if not TRANSLATE_FILTER_KEYS[filterKey] then
|
||
return false
|
||
end
|
||
if tab.filters and tab.filters[filterKey] == false then
|
||
return false
|
||
end
|
||
if not self:GetTabTranslateFilter(index, filterKey) then
|
||
return false
|
||
end
|
||
return true
|
||
end
|
||
|
||
function SFrames.Chat:SetTabChannelFilter(index, channelName, enabled)
|
||
local tab, idx = self:GetTab(index)
|
||
if not tab then return end
|
||
if type(tab.channelFilters) ~= "table" then tab.channelFilters = {} end
|
||
local key = ChannelKey(channelName)
|
||
if key == "" then return end
|
||
-- Save under the canonical key only (first alias in the group, or the key itself)
|
||
local canonicalKey = key
|
||
local aliases = GetChannelAliasKeys(channelName)
|
||
if aliases then
|
||
-- Use the first alias as the canonical key, but only if it's a simple exact key
|
||
local firstAlias = ChannelKey(aliases[1] or "")
|
||
if firstAlias ~= "" then canonicalKey = firstAlias end
|
||
-- Also clear any previously-saved keys for other aliases in the same group
|
||
for _, alias in ipairs(aliases) do
|
||
local ak = ChannelKey(alias)
|
||
if ak ~= "" and ak ~= canonicalKey then
|
||
tab.channelFilters[ak] = nil
|
||
end
|
||
end
|
||
end
|
||
tab.channelFilters[canonicalKey] = (enabled == true)
|
||
if self:GetActiveTabIndex() == idx then
|
||
self:ApplyTabChannels(idx)
|
||
end
|
||
if self.translateConfigFrame and self.RefreshTranslateConfigFrame then
|
||
self:RefreshTranslateConfigFrame()
|
||
end
|
||
self:NotifyConfigUI()
|
||
end
|
||
|
||
function SFrames.Chat:SetActiveTabChannelFilter(channelName, enabled)
|
||
self:SetTabChannelFilter(self:GetActiveTabIndex(), channelName, enabled)
|
||
end
|
||
|
||
function SFrames.Chat:NotifyConfigUI()
|
||
if SFrames and SFrames.ConfigUI and SFrames.ConfigUI.activePage == "chat" and SFrames.ConfigUI.RefreshChatPage then
|
||
SFrames.ConfigUI:RefreshChatPage()
|
||
end
|
||
end
|
||
|
||
function SFrames.Chat:CanUseAutoTranslateAPI()
|
||
if SFramesDB and SFramesDB.Chat and SFramesDB.Chat.translateEnabled == false then
|
||
return false
|
||
end
|
||
return _G.STranslateAPI
|
||
and _G.STranslateAPI.IsReady
|
||
and _G.STranslateAPI.IsReady()
|
||
and _G.STranslateAPI.Translate
|
||
end
|
||
|
||
function SFrames.Chat:RequestAutoTranslation(text, callback)
|
||
local cleanText = CleanTextForTranslation(text)
|
||
if cleanText == "" then
|
||
if callback then callback(nil, "EMPTY_TEXT") end
|
||
return false
|
||
end
|
||
if not self:CanUseAutoTranslateAPI() then
|
||
if callback then callback(nil, "API_UNAVAILABLE") end
|
||
return false
|
||
end
|
||
|
||
if not self.translationCache then self.translationCache = {} end
|
||
if not self.translationPending then self.translationPending = {} end
|
||
|
||
local cacheKey = AUTO_TRANSLATE_TARGET_LANG .. "\031" .. cleanText
|
||
local cached = self.translationCache[cacheKey]
|
||
if cached and cached ~= "" then
|
||
if callback then callback(cached, nil, true) end
|
||
return true
|
||
end
|
||
|
||
local pending = self.translationPending[cacheKey]
|
||
if pending then
|
||
if callback then table.insert(pending, callback) end
|
||
return true
|
||
end
|
||
|
||
self.translationPending[cacheKey] = {}
|
||
if callback then
|
||
table.insert(self.translationPending[cacheKey], callback)
|
||
end
|
||
|
||
local chat = self
|
||
_G.STranslateAPI.Translate(cleanText, "auto", AUTO_TRANSLATE_TARGET_LANG, function(result, err, meta)
|
||
local callbacks = chat.translationPending and chat.translationPending[cacheKey]
|
||
if chat.translationPending then
|
||
chat.translationPending[cacheKey] = nil
|
||
end
|
||
|
||
if result and result ~= "" then
|
||
if not chat.translationCache then chat.translationCache = {} end
|
||
chat.translationCache[cacheKey] = result
|
||
end
|
||
|
||
if callbacks then
|
||
for i = 1, table.getn(callbacks) do
|
||
local cb = callbacks[i]
|
||
if cb then
|
||
cb(result, err, meta)
|
||
end
|
||
end
|
||
end
|
||
end, "Nanami-UI")
|
||
return true
|
||
end
|
||
|
||
function SFrames.Chat:AppendAutoTranslatedLine(tabId, filterKey, channelName, sourceText, translatedText, senderName)
|
||
if type(translatedText) ~= "string" or translatedText == "" then return end
|
||
|
||
local tabIndex = self:FindTabIndexById(tabId)
|
||
if not tabIndex then return end
|
||
if not self:ShouldAutoTranslateForTab(tabIndex, filterKey, channelName) then
|
||
return
|
||
end
|
||
|
||
local cleanSource = CleanTextForTranslation(sourceText)
|
||
local cleanTranslated = CleanTextForTranslation(translatedText)
|
||
if cleanSource == "" or cleanTranslated == "" or cleanSource == cleanTranslated then
|
||
return
|
||
end
|
||
|
||
local tab = self:GetTab(tabIndex)
|
||
local chatFrame = self:GetChatFrameForTab(tab)
|
||
if not chatFrame then return end
|
||
|
||
if not chatFrame.AddMessage then return end
|
||
|
||
local prefix = "|cff6ecf6e[AI]|r "
|
||
if type(channelName) == "string" and channelName ~= "" then
|
||
prefix = prefix .. "|cff8cb4d8[" .. channelName .. "]|r "
|
||
end
|
||
if type(senderName) == "string" and senderName ~= "" then
|
||
prefix = prefix .. "|cffdab777" .. senderName .. ":|r "
|
||
end
|
||
local fullText = prefix .. "|cffe8dcc8" .. cleanTranslated .. "|r"
|
||
chatFrame:AddMessage(fullText)
|
||
if type(senderName) == "string" and senderName ~= "" and SFrames.Chat.MessageIndex then
|
||
if not SFrames.Chat.MessageSenders then SFrames.Chat.MessageSenders = {} end
|
||
SFrames.Chat.MessageSenders[SFrames.Chat.MessageIndex] = senderName
|
||
end
|
||
end
|
||
|
||
function SFrames.Chat:QueueAutoTranslationForFrame(frame, tabIndex, event, messageText, channelName)
|
||
if not frame or type(messageText) ~= "string" or messageText == "" then
|
||
return
|
||
end
|
||
|
||
local filterKey = GetTranslateFilterKeyForEvent(event)
|
||
if not filterKey then
|
||
return
|
||
end
|
||
if not self:ShouldAutoTranslateForTab(tabIndex, filterKey, channelName) then
|
||
return
|
||
end
|
||
|
||
local tab = self:GetTab(tabIndex)
|
||
if not tab or type(tab.id) ~= "number" then
|
||
return
|
||
end
|
||
|
||
local cleanText = CleanTextForTranslation(messageText)
|
||
if cleanText == "" then
|
||
return
|
||
end
|
||
|
||
self:RequestAutoTranslation(cleanText, function(result, err, meta)
|
||
if result and result ~= "" then
|
||
SFrames.Chat:AppendAutoTranslatedLine(tab.id, filterKey, channelName, cleanText, result)
|
||
end
|
||
end)
|
||
end
|
||
|
||
function SFrames.Chat:SavePosition()
|
||
if not (self.frame and self.frame.GetPoint) then return end
|
||
if not SFramesDB then SFramesDB = {} end
|
||
if not SFramesDB.Positions then SFramesDB.Positions = {} end
|
||
|
||
local point, _, relativePoint, xOfs, yOfs = self.frame:GetPoint()
|
||
SFramesDB.Positions["ChatFrame"] = {
|
||
point = point,
|
||
relativePoint = relativePoint,
|
||
xOfs = xOfs,
|
||
yOfs = yOfs,
|
||
}
|
||
end
|
||
|
||
function SFrames.Chat:SaveSizeFromFrame()
|
||
if not self.frame then return end
|
||
local db = EnsureDB()
|
||
db.width = math.floor(Clamp(self.frame:GetWidth() or DEFAULTS.width, 320, 900) + 0.5)
|
||
db.height = math.floor(Clamp(self.frame:GetHeight() or DEFAULTS.height, 120, 460) + 0.5)
|
||
self:NotifyConfigUI()
|
||
end
|
||
|
||
function SFrames.Chat:ResetPosition()
|
||
if not self.frame then return end
|
||
if not SFramesDB then SFramesDB = {} end
|
||
if not SFramesDB.Positions then SFramesDB.Positions = {} end
|
||
SFramesDB.Positions["ChatFrame"] = nil
|
||
self.frame:ClearAllPoints()
|
||
self.frame:SetPoint("BOTTOMLEFT", UIParent, "BOTTOMLEFT", 30, 30)
|
||
self:SavePosition()
|
||
if SFrames and SFrames.Print then
|
||
SFrames:Print("Chat frame position reset.")
|
||
end
|
||
end
|
||
|
||
function SFrames.Chat:SetTabFilter(index, key, enabled)
|
||
local tab = self:GetTab(index)
|
||
if not tab or tab.filters[key] == nil then return end
|
||
tab.filters[key] = (enabled == true)
|
||
if self:GetActiveTabIndex() == index then
|
||
self:ApplyTabFilters(index)
|
||
-- Restore cached messages after filter change cleared the frame
|
||
local chatFrame = self:GetChatFrameForTab(tab)
|
||
if chatFrame then
|
||
self:RestoreCachedMessages(chatFrame)
|
||
end
|
||
end
|
||
self:NotifyConfigUI()
|
||
end
|
||
|
||
function SFrames.Chat:SetActiveTabFilter(key, enabled)
|
||
self:SetTabFilter(self:GetActiveTabIndex(), key, enabled)
|
||
end
|
||
|
||
function SFrames.Chat:RenameTab(index, name)
|
||
local tab, idx = self:GetTab(index)
|
||
if not tab then return false end
|
||
local clean = Trim(name)
|
||
if clean == "" then return false end
|
||
tab.name = clean
|
||
if self.frame then self:RefreshTabButtons() end
|
||
self:NotifyConfigUI()
|
||
if idx == self:GetActiveTabIndex() then
|
||
self:SwitchActiveChatFrame(self:GetActiveTab())
|
||
end
|
||
return true
|
||
end
|
||
|
||
function SFrames.Chat:RenameActiveTab(name)
|
||
return self:RenameTab(self:GetActiveTabIndex(), name)
|
||
end
|
||
|
||
function SFrames.Chat:PromptNewTab()
|
||
EnsurePopupDialogs()
|
||
if StaticPopup_Show then
|
||
local popup = StaticPopup_Show("SFRAMES_CHAT_NEW_TAB")
|
||
if not popup then
|
||
local fallbackName = self:GetNextTabName()
|
||
self:AddTab(fallbackName)
|
||
end
|
||
else
|
||
self:AddTab(self:GetNextTabName())
|
||
end
|
||
end
|
||
|
||
function SFrames.Chat:PromptRenameTab(index)
|
||
local _, idx = self:GetTab(index or self:GetActiveTabIndex())
|
||
self.pendingRenameIndex = idx
|
||
EnsurePopupDialogs()
|
||
if StaticPopup_Show then
|
||
StaticPopup_Show("SFRAMES_CHAT_RENAME_TAB", nil, nil, idx)
|
||
end
|
||
end
|
||
|
||
function SFrames.Chat:AddTab(name)
|
||
local db = EnsureDB()
|
||
local maxWindows = tonumber(NUM_CHAT_WINDOWS) or 7
|
||
if table.getn(db.tabs) >= maxWindows then
|
||
if SFrames and SFrames.Print then SFrames:Print("最多只能创建 " .. maxWindows .. " 个聊天标签。") end
|
||
return false
|
||
end
|
||
|
||
local clean = Trim(name)
|
||
if clean == "" then clean = self:GetNextTabName() end
|
||
local id = db.nextTabId or (table.getn(db.tabs) + 1)
|
||
db.nextTabId = id + 1
|
||
table.insert(db.tabs, BuildDefaultTab(id, clean))
|
||
db.activeTab = table.getn(db.tabs)
|
||
|
||
self:RefreshTabButtons()
|
||
self:ApplyAllTabsSetup()
|
||
self:NotifyConfigUI()
|
||
return true
|
||
end
|
||
|
||
function SFrames.Chat:DeleteTab(index)
|
||
local db = EnsureDB()
|
||
local tab, idx = self:GetTab(index)
|
||
if not tab then return false end
|
||
|
||
if self:IsTabProtected(idx) then
|
||
if SFrames and SFrames.Print then
|
||
SFrames:Print("Default General/Combat tabs cannot be deleted.")
|
||
end
|
||
return false
|
||
end
|
||
|
||
if table.getn(db.tabs) <= 2 then
|
||
if SFrames and SFrames.Print then
|
||
SFrames:Print("Keep at least default General and Combat tabs.")
|
||
end
|
||
return false
|
||
end
|
||
|
||
table.remove(db.tabs, idx)
|
||
if idx <= db.activeTab then db.activeTab = db.activeTab - 1 end
|
||
if db.activeTab < 1 then db.activeTab = 1 end
|
||
if db.activeTab > table.getn(db.tabs) then db.activeTab = table.getn(db.tabs) end
|
||
|
||
self:RefreshTabButtons()
|
||
self:ApplyAllTabsSetup()
|
||
self:NotifyConfigUI()
|
||
return true
|
||
end
|
||
|
||
function SFrames.Chat:DeleteActiveTab()
|
||
return self:DeleteTab(self:GetActiveTabIndex())
|
||
end
|
||
|
||
function SFrames.Chat:OpenFilterConfigForTab(index)
|
||
if index then
|
||
self:SetActiveTab(index)
|
||
end
|
||
self:OpenConfigFrame()
|
||
end
|
||
|
||
function SFrames.Chat:SetActiveTab(index)
|
||
local db = EnsureDB()
|
||
index = Clamp(math.floor(tonumber(index) or 1), 1, table.getn(db.tabs))
|
||
if db.activeTab == index then return end
|
||
db.activeTab = index
|
||
self:RefreshTabButtons()
|
||
self:SwitchActiveChatFrame(self:GetActiveTab())
|
||
self:NotifyConfigUI()
|
||
end
|
||
|
||
function SFrames.Chat:StepTab(delta)
|
||
local db = EnsureDB()
|
||
local count = table.getn(db.tabs)
|
||
local target = db.activeTab + (delta or 1)
|
||
if target < 1 then target = count end
|
||
if target > count then target = 1 end
|
||
self:SetActiveTab(target)
|
||
end
|
||
|
||
function SFrames.Chat:EnsureTabContextMenu()
|
||
if self.tabContextMenu then return end
|
||
if not (CreateFrame and UIDropDownMenu_Initialize and UIDropDownMenu_AddButton) then return end
|
||
|
||
local menu = CreateFrame("Frame", "SFramesChatTabContextMenu", UIParent, "UIDropDownMenuTemplate")
|
||
UIDropDownMenu_Initialize(menu, function()
|
||
local idx = SFrames.Chat.contextMenuTabIndex or SFrames.Chat:GetActiveTabIndex()
|
||
local tab = SFrames.Chat:GetTab(idx)
|
||
if not tab then return end
|
||
local cfg = SFrames.Chat:GetConfig()
|
||
|
||
local info = NewDropDownInfo()
|
||
info.text = tab.name or ("标签 " .. tostring(idx))
|
||
info.isTitle = 1
|
||
info.notCheckable = 1
|
||
UIDropDownMenu_AddButton(info)
|
||
|
||
info = NewDropDownInfo()
|
||
info.text = "重命名标签"
|
||
info.notCheckable = 1
|
||
info.func = function()
|
||
SFrames.Chat:PromptRenameTab(idx)
|
||
end
|
||
UIDropDownMenu_AddButton(info)
|
||
|
||
info = NewDropDownInfo()
|
||
info.text = "删除标签"
|
||
info.notCheckable = 1
|
||
info.disabled = (SFrames.Chat:IsTabProtected(idx) or table.getn(SFrames.Chat:GetTabs()) <= 2) and 1 or nil
|
||
info.func = function()
|
||
SFrames.Chat:DeleteTab(idx)
|
||
end
|
||
UIDropDownMenu_AddButton(info)
|
||
|
||
info = NewDropDownInfo()
|
||
info.text = "过滤设置"
|
||
info.notCheckable = 1
|
||
info.func = function()
|
||
SFrames.Chat:OpenFilterConfigForTab(idx)
|
||
end
|
||
UIDropDownMenu_AddButton(info)
|
||
|
||
info = NewDropDownInfo()
|
||
info.text = "当前字号: " .. tostring(cfg.fontSize)
|
||
info.notCheckable = 1
|
||
info.disabled = 1
|
||
UIDropDownMenu_AddButton(info)
|
||
|
||
info = NewDropDownInfo()
|
||
info.text = "增大字号 +1"
|
||
info.notCheckable = 1
|
||
info.disabled = (cfg.fontSize >= 18) and 1 or nil
|
||
info.func = function()
|
||
SFrames.Chat:SetFontSize((SFrames.Chat:GetConfig().fontSize or DEFAULTS.fontSize) + 1)
|
||
end
|
||
UIDropDownMenu_AddButton(info)
|
||
|
||
info = NewDropDownInfo()
|
||
info.text = "减小字号 -1"
|
||
info.notCheckable = 1
|
||
info.disabled = (cfg.fontSize <= 10) and 1 or nil
|
||
info.func = function()
|
||
SFrames.Chat:SetFontSize((SFrames.Chat:GetConfig().fontSize or DEFAULTS.fontSize) - 1)
|
||
end
|
||
UIDropDownMenu_AddButton(info)
|
||
end, "MENU")
|
||
|
||
self.tabContextMenu = menu
|
||
end
|
||
|
||
function SFrames.Chat:OpenTabContextMenu(index)
|
||
self.contextMenuTabIndex = index
|
||
self:EnsureTabContextMenu()
|
||
|
||
if self.tabContextMenu and ToggleDropDownMenu then
|
||
ToggleDropDownMenu(1, nil, self.tabContextMenu, "cursor", 0, 0)
|
||
else
|
||
self:PromptRenameTab(index)
|
||
end
|
||
end
|
||
|
||
local function BoolText(v)
|
||
if v then return "ON" end
|
||
return "OFF"
|
||
end
|
||
|
||
local function GetPlayerClassColor()
|
||
if not UnitClass then return nil end
|
||
local _, classFile = UnitClass("player")
|
||
if not classFile then return nil end
|
||
|
||
local colors = CUSTOM_CLASS_COLORS or RAID_CLASS_COLORS
|
||
if not colors then return nil end
|
||
|
||
local classColor = colors[classFile]
|
||
if not classColor then return nil end
|
||
|
||
local r = classColor.r or classColor[1]
|
||
local g = classColor.g or classColor[2]
|
||
local b = classColor.b or classColor[3]
|
||
if r and g and b then
|
||
return r, g, b
|
||
end
|
||
return nil
|
||
end
|
||
|
||
function SFrames.Chat:GetBorderColorRGB()
|
||
local cfg = self:GetConfig()
|
||
if cfg.borderClassColor then
|
||
local r, g, b = GetPlayerClassColor()
|
||
if r and g and b then
|
||
return r, g, b
|
||
end
|
||
end
|
||
return 0.92, 0.56, 0.78
|
||
end
|
||
|
||
function SFrames.Chat:SetWindowSize(width, height)
|
||
local db = EnsureDB()
|
||
db.width = math.floor(Clamp(width or db.width, 320, 900) + 0.5)
|
||
db.height = math.floor(Clamp(height or db.height, 120, 460) + 0.5)
|
||
self:ApplyConfig()
|
||
end
|
||
|
||
function SFrames.Chat:SetWindowScale(scale)
|
||
local db = EnsureDB()
|
||
db.scale = Clamp(scale or db.scale, 0.75, 1.4)
|
||
self:ApplyConfig()
|
||
end
|
||
|
||
function SFrames.Chat:SetFontSize(size)
|
||
local db = EnsureDB()
|
||
db.fontSize = math.floor(Clamp(size or db.fontSize, 10, 18) + 0.5)
|
||
self:ApplyConfig()
|
||
end
|
||
|
||
function SFrames.Chat:PrintFilters()
|
||
local tab = self:GetActiveTab()
|
||
if not tab then return end
|
||
if SFrames and SFrames.Print then
|
||
SFrames:Print("闁荤喐绮庢晶妤呭箰閸涘﹥娅犻柣妯款嚙閸愨偓闂佹悶鍎弲鈺呭礉? " .. tostring(tab.name))
|
||
for i = 1, table.getn(FILTER_DEFS) do
|
||
local def = FILTER_DEFS[i]
|
||
SFrames:Print(" - " .. def.key .. ": " .. BoolText(tab.filters[def.key] ~= false))
|
||
end
|
||
end
|
||
end
|
||
|
||
function SFrames.Chat:PrintHelp()
|
||
if not (SFrames and SFrames.Print) then return end
|
||
SFrames:Print("/nui chat (闂備胶鎳撻悘姘跺箰閸濄儲顫曢柟杈鹃檮閸ゅ倸鈹戦悩鎻掆偓鑸电椤栫偞鈷戦柡澶庢硶鑲栭梺?")
|
||
SFrames:Print("/nui chat ui")
|
||
SFrames:Print("/nui chat size <w> <h>")
|
||
SFrames:Print("/nui chat scale <0.75-1.4>")
|
||
SFrames:Print("/nui chat font <10-18>")
|
||
SFrames:Print("/nui chat reset")
|
||
SFrames:Print("/nui chat tab new <name>")
|
||
SFrames:Print("/nui chat tab del")
|
||
SFrames:Print("/nui chat tab next|prev|<index>")
|
||
SFrames:Print("/nui chat tab rename <name>")
|
||
SFrames:Print("/nui chat filter <key> on|off")
|
||
SFrames:Print("/nui chat filters")
|
||
end
|
||
|
||
function SFrames.Chat:GetConfigFrameActiveTabName()
|
||
local tab = self:GetActiveTab()
|
||
if tab and tab.name and tab.name ~= "" then
|
||
return tab.name
|
||
end
|
||
return "Tab"
|
||
end
|
||
|
||
function SFrames.Chat:RefreshConfigFrame()
|
||
if not self.configFrame then return end
|
||
|
||
if self.cfgCurrentTabText then
|
||
self.cfgCurrentTabText:SetText("当前标签: " .. self:GetConfigFrameActiveTabName())
|
||
end
|
||
|
||
if self.cfgChannelHint then
|
||
local channels = self:GetJoinedChannels()
|
||
self.cfgChannelHint:SetText("已加入频道: " .. tostring(table.getn(channels)))
|
||
end
|
||
|
||
if self.cfgRenameBox and self.cfgRenameBox.Refresh then
|
||
self.cfgRenameBox:Refresh()
|
||
end
|
||
|
||
if self.cfgDeleteTabButton then
|
||
local protected = self:IsTabProtected(self:GetActiveTabIndex())
|
||
if protected and self.cfgDeleteTabButton.Disable then
|
||
self.cfgDeleteTabButton:Disable()
|
||
elseif (not protected) and self.cfgDeleteTabButton.Enable then
|
||
self.cfgDeleteTabButton:Enable()
|
||
end
|
||
end
|
||
|
||
if self.configControls then
|
||
for i = 1, table.getn(self.configControls) do
|
||
local ctrl = self.configControls[i]
|
||
if ctrl and ctrl.Refresh then
|
||
ctrl:Refresh()
|
||
end
|
||
end
|
||
end
|
||
|
||
if self.translateConfigFrame and self.RefreshTranslateConfigFrame then
|
||
self:RefreshTranslateConfigFrame()
|
||
end
|
||
end
|
||
|
||
function SFrames.Chat:RefreshTranslateConfigFrame()
|
||
if not self.translateConfigFrame then return end
|
||
|
||
if self.translateCurrentTabText then
|
||
self.translateCurrentTabText:SetText("Current Tab: " .. self:GetConfigFrameActiveTabName())
|
||
end
|
||
|
||
if self.translateChannelHint then
|
||
local channels = self:GetJoinedChannels()
|
||
self.translateChannelHint:SetText("Joined Channels: " .. tostring(table.getn(channels)))
|
||
end
|
||
|
||
if self.translateConfigControls then
|
||
for i = 1, table.getn(self.translateConfigControls) do
|
||
local ctrl = self.translateConfigControls[i]
|
||
if ctrl and ctrl.Refresh then
|
||
ctrl:Refresh()
|
||
end
|
||
end
|
||
end
|
||
end
|
||
|
||
function SFrames.Chat:EnsureTranslateConfigFrame()
|
||
if self.translateConfigFrame then return end
|
||
|
||
local fontPath = (SFrames and SFrames.GetFont and SFrames:GetFont()) or "Fonts\\ARIALN.TTF"
|
||
local panel = CreateFrame("Frame", "SFramesChatTranslateConfigPanel", UIParent)
|
||
panel:SetWidth(540)
|
||
panel:SetHeight(520)
|
||
panel:SetPoint("CENTER", UIParent, "CENTER", 180, 0)
|
||
panel:SetMovable(true)
|
||
panel:EnableMouse(true)
|
||
panel:SetClampedToScreen(true)
|
||
panel:SetFrameStrata("DIALOG")
|
||
panel:SetFrameLevel(151)
|
||
panel:RegisterForDrag("LeftButton")
|
||
panel:SetScript("OnDragStart", function() this:StartMoving() end)
|
||
panel:SetScript("OnDragStop", function() this:StopMovingOrSizing() end)
|
||
|
||
table.insert(UISpecialFrames, "SFramesChatTranslateConfigPanel")
|
||
|
||
if SFrames and SFrames.CreateBackdrop then
|
||
SFrames:CreateBackdrop(panel)
|
||
else
|
||
panel:SetBackdrop({
|
||
bgFile = "Interface\\DialogFrame\\UI-DialogBox-Background",
|
||
edgeFile = "Interface\\Buttons\\WHITE8X8",
|
||
tile = true, tileSize = 32, edgeSize = 1,
|
||
insets = { left = 1, right = 1, top = 1, bottom = 1 },
|
||
})
|
||
end
|
||
panel:SetBackdropColor(CFG_THEME.panelBg[1], CFG_THEME.panelBg[2], CFG_THEME.panelBg[3], CFG_THEME.panelBg[4])
|
||
panel:SetBackdropBorderColor(CFG_THEME.panelBorder[1], CFG_THEME.panelBorder[2], CFG_THEME.panelBorder[3], CFG_THEME.panelBorder[4])
|
||
|
||
local title = panel:CreateFontString(nil, "OVERLAY")
|
||
title:SetFont(fontPath, 14, "OUTLINE")
|
||
title:SetPoint("TOP", panel, "TOP", 0, -12)
|
||
title:SetText("Chat AI Translate")
|
||
title:SetTextColor(CFG_THEME.title[1], CFG_THEME.title[2], CFG_THEME.title[3])
|
||
|
||
local closeBtn = CreateFrame("Button", nil, panel, "UIPanelCloseButton")
|
||
closeBtn:SetPoint("TOPRIGHT", panel, "TOPRIGHT", -4, -4)
|
||
|
||
local controls = {}
|
||
local function AddControl(ctrl)
|
||
table.insert(controls, ctrl)
|
||
end
|
||
|
||
local tabSection = CreateCfgSection(panel, "Tab", 10, -36, 520, 92, fontPath)
|
||
self.translateCurrentTabText = tabSection:CreateFontString(nil, "OVERLAY")
|
||
self.translateCurrentTabText:SetFont(fontPath, 11, "OUTLINE")
|
||
self.translateCurrentTabText:SetPoint("TOPLEFT", tabSection, "TOPLEFT", 14, -28)
|
||
self.translateCurrentTabText:SetText("Current Tab: " .. self:GetConfigFrameActiveTabName())
|
||
|
||
CreateCfgButton(tabSection, "Prev", 14, -48, 92, 22, function()
|
||
SFrames.Chat:StepTab(-1)
|
||
SFrames.Chat:RefreshConfigFrame()
|
||
SFrames.Chat:RefreshTranslateConfigFrame()
|
||
end)
|
||
CreateCfgButton(tabSection, "Next", 112, -48, 92, 22, function()
|
||
SFrames.Chat:StepTab(1)
|
||
SFrames.Chat:RefreshConfigFrame()
|
||
SFrames.Chat:RefreshTranslateConfigFrame()
|
||
end)
|
||
|
||
local filterSection = CreateCfgSection(panel, "Message Translate", 10, -134, 520, 126, fontPath)
|
||
for i = 1, table.getn(TRANSLATE_FILTER_ORDER) do
|
||
local key = TRANSLATE_FILTER_ORDER[i]
|
||
local col = math.mod(i - 1, 2)
|
||
local row = math.floor((i - 1) / 2)
|
||
local x = 14 + col * 240
|
||
local y = -28 - row * 24
|
||
|
||
AddControl(CreateCfgCheck(filterSection, GetFilterLabel(key), x, y,
|
||
function()
|
||
return SFrames.Chat:GetTabTranslateFilter(SFrames.Chat:GetActiveTabIndex(), key)
|
||
end,
|
||
function(checked)
|
||
SFrames.Chat:SetActiveTabTranslateFilter(key, checked)
|
||
end,
|
||
function()
|
||
SFrames.Chat:RefreshTranslateConfigFrame()
|
||
end
|
||
))
|
||
end
|
||
|
||
local channelSection = CreateCfgSection(panel, "Channel Translate", 10, -266, 520, 198, fontPath)
|
||
self.translateChannelChecks = {}
|
||
self.translateChannelHint = channelSection:CreateFontString(nil, "OVERLAY")
|
||
self.translateChannelHint:SetFont(fontPath, 10, "OUTLINE")
|
||
self.translateChannelHint:SetPoint("BOTTOMLEFT", channelSection, "BOTTOMLEFT", 14, 8)
|
||
self.translateChannelHint:SetTextColor(0.84, 0.8, 0.86)
|
||
self.translateChannelHint:SetText("Joined Channels:")
|
||
|
||
local maxChannelChecks = 15
|
||
for i = 1, maxChannelChecks do
|
||
local slot = i
|
||
local col = math.mod(i - 1, 3)
|
||
local row = math.floor((i - 1) / 3)
|
||
local x = 14 + col * 165
|
||
local y = -24 - row * 24
|
||
|
||
local cb = CreateFrame("CheckButton", NextConfigWidget("TranslateChannelCheck"), channelSection, "UICheckButtonTemplate")
|
||
cb:SetWidth(20)
|
||
cb:SetHeight(20)
|
||
cb:SetPoint("TOPLEFT", channelSection, "TOPLEFT", x, y)
|
||
StyleCfgCheck(cb)
|
||
|
||
local label = _G[cb:GetName() .. "Text"]
|
||
if label then
|
||
label:SetText("")
|
||
label:SetWidth(140)
|
||
label:SetTextColor(CFG_THEME.text[1], CFG_THEME.text[2], CFG_THEME.text[3])
|
||
end
|
||
|
||
cb:SetScript("OnClick", function()
|
||
local name = this.channelName
|
||
if not name or name == "" then return end
|
||
SFrames.Chat:SetActiveTabChannelTranslateFilter(name, this:GetChecked() and true or false)
|
||
SFrames.Chat:RefreshTranslateConfigFrame()
|
||
end)
|
||
|
||
cb.Refresh = function()
|
||
local channels = SFrames.Chat:GetJoinedChannels()
|
||
local info = channels[slot]
|
||
if info then
|
||
local activeIndex = SFrames.Chat:GetActiveTabIndex()
|
||
local enabled = SFrames.Chat:GetTabChannelFilter(activeIndex, info.name)
|
||
and SFrames.Chat:GetTabTranslateFilter(activeIndex, "channel")
|
||
cb.channelName = info.name
|
||
if label then
|
||
label:SetText(ShortText(info.name, 24))
|
||
if enabled then
|
||
label:SetTextColor(CFG_THEME.text[1], CFG_THEME.text[2], CFG_THEME.text[3])
|
||
else
|
||
label:SetTextColor(0.45, 0.45, 0.45)
|
||
end
|
||
end
|
||
cb:SetChecked(SFrames.Chat:GetTabChannelTranslateFilter(SFrames.Chat:GetActiveTabIndex(), info.name) and true or false)
|
||
if enabled then
|
||
cb:Enable()
|
||
else
|
||
cb:Disable()
|
||
end
|
||
cb:Show()
|
||
else
|
||
cb.channelName = nil
|
||
cb:SetChecked(false)
|
||
if label then label:SetText("") end
|
||
cb:Hide()
|
||
end
|
||
end
|
||
|
||
AddControl(cb)
|
||
table.insert(self.translateChannelChecks, cb)
|
||
end
|
||
|
||
local tip = channelSection:CreateFontString(nil, "OVERLAY")
|
||
tip:SetFont(fontPath, 10, "OUTLINE")
|
||
tip:SetPoint("TOPLEFT", channelSection, "TOPLEFT", 14, -150)
|
||
tip:SetText("Only active receiving channels can auto-translate.")
|
||
tip:SetTextColor(0.7, 0.7, 0.74)
|
||
|
||
local close = CreateCfgButton(panel, "Close", 200, -474, 140, 26, function()
|
||
SFrames.Chat.translateConfigFrame:Hide()
|
||
end)
|
||
StyleCfgButton(close)
|
||
|
||
self.translateConfigControls = controls
|
||
self.translateConfigFrame = panel
|
||
end
|
||
|
||
function SFrames.Chat:OpenTranslateConfigFrame()
|
||
self:EnsureTranslateConfigFrame()
|
||
if not self.translateConfigFrame then return end
|
||
self:RefreshTranslateConfigFrame()
|
||
self.translateConfigFrame:Show()
|
||
self.translateConfigFrame:Raise()
|
||
end
|
||
|
||
function SFrames.Chat:ToggleTranslateConfigFrame()
|
||
self:EnsureTranslateConfigFrame()
|
||
if not self.translateConfigFrame then return end
|
||
if self.translateConfigFrame:IsShown() then
|
||
self.translateConfigFrame:Hide()
|
||
else
|
||
self:OpenTranslateConfigFrame()
|
||
end
|
||
end
|
||
|
||
function SFrames.Chat:EnsureConfigFrame()
|
||
if self.configFrame then return end
|
||
|
||
local fontPath = (SFrames and SFrames.GetFont and SFrames:GetFont()) or "Fonts\\ARIALN.TTF"
|
||
local panel = CreateFrame("Frame", "SFramesChatConfigPanel", UIParent)
|
||
panel:SetWidth(540)
|
||
panel:SetHeight(786)
|
||
panel:SetPoint("CENTER", UIParent, "CENTER", 120, 0)
|
||
panel:SetMovable(true)
|
||
panel:EnableMouse(true)
|
||
panel:SetClampedToScreen(true)
|
||
panel:SetFrameStrata("DIALOG")
|
||
panel:SetFrameLevel(150)
|
||
panel:RegisterForDrag("LeftButton")
|
||
panel:SetScript("OnDragStart", function() this:StartMoving() end)
|
||
panel:SetScript("OnDragStop", function() this:StopMovingOrSizing() end)
|
||
|
||
table.insert(UISpecialFrames, "SFramesChatConfigPanel")
|
||
|
||
if SFrames and SFrames.CreateBackdrop then
|
||
SFrames:CreateBackdrop(panel)
|
||
else
|
||
panel:SetBackdrop({
|
||
bgFile = "Interface\\DialogFrame\\UI-DialogBox-Background",
|
||
edgeFile = "Interface\\Buttons\\WHITE8X8",
|
||
tile = true, tileSize = 32, edgeSize = 1,
|
||
insets = { left = 1, right = 1, top = 1, bottom = 1 },
|
||
})
|
||
end
|
||
panel:SetBackdropColor(CFG_THEME.panelBg[1], CFG_THEME.panelBg[2], CFG_THEME.panelBg[3], CFG_THEME.panelBg[4])
|
||
panel:SetBackdropBorderColor(CFG_THEME.panelBorder[1], CFG_THEME.panelBorder[2], CFG_THEME.panelBorder[3], CFG_THEME.panelBorder[4])
|
||
|
||
local title = panel:CreateFontString(nil, "OVERLAY")
|
||
title:SetFont(fontPath, 14, "OUTLINE")
|
||
title:SetPoint("TOP", panel, "TOP", 0, -12)
|
||
title:SetText("Nanami 聊天设置")
|
||
title:SetTextColor(CFG_THEME.title[1], CFG_THEME.title[2], CFG_THEME.title[3])
|
||
|
||
local closeBtn = CreateFrame("Button", nil, panel, "UIPanelCloseButton")
|
||
closeBtn:SetPoint("TOPRIGHT", panel, "TOPRIGHT", -4, -4)
|
||
|
||
local controls = {}
|
||
|
||
local function AddControl(ctrl)
|
||
table.insert(controls, ctrl)
|
||
end
|
||
|
||
local windowSection = CreateCfgSection(panel, "窗口", 10, -36, 520, 226, fontPath)
|
||
AddControl(CreateCfgSlider(windowSection, "宽度", 16, -46, 235, 320, 900, 1,
|
||
function() return EnsureDB().width end,
|
||
function(v) EnsureDB().width = v end,
|
||
function(v) return tostring(math.floor(v + 0.5)) end,
|
||
function() SFrames.Chat:ApplyConfig(); SFrames.Chat:RefreshConfigFrame() end
|
||
))
|
||
AddControl(CreateCfgSlider(windowSection, "高度", 270, -46, 235, 120, 460, 1,
|
||
function() return EnsureDB().height end,
|
||
function(v) EnsureDB().height = v end,
|
||
function(v) return tostring(math.floor(v + 0.5)) end,
|
||
function() SFrames.Chat:ApplyConfig(); SFrames.Chat:RefreshConfigFrame() end
|
||
))
|
||
AddControl(CreateCfgSlider(windowSection, "缩放", 16, -108, 235, 0.75, 1.40, 0.05,
|
||
function() return EnsureDB().scale end,
|
||
function(v) EnsureDB().scale = v end,
|
||
function(v) return string.format("%.2f", v) end,
|
||
function() SFrames.Chat:ApplyConfig(); SFrames.Chat:RefreshConfigFrame() end
|
||
))
|
||
AddControl(CreateCfgSlider(windowSection, "字号", 270, -108, 235, 10, 18, 1,
|
||
function() return EnsureDB().fontSize end,
|
||
function(v) EnsureDB().fontSize = v end,
|
||
function(v) return tostring(math.floor(v + 0.5)) end,
|
||
function() SFrames.Chat:ApplyConfig(); SFrames.Chat:RefreshConfigFrame() end
|
||
))
|
||
AddControl(CreateCfgCheck(windowSection, "启用聊天", 360, -122,
|
||
function() return EnsureDB().enable ~= false end,
|
||
function(checked)
|
||
local oldState = EnsureDB().enable ~= false
|
||
if oldState ~= checked then
|
||
EnsureDB().enable = (checked == true)
|
||
SFrames.Chat:EnsureConfigFrame()
|
||
if StaticPopup_Show then
|
||
StaticPopup_Show("SFRAMES_CHAT_RELOAD_PROMPT")
|
||
else
|
||
ReloadUI()
|
||
end
|
||
end
|
||
end,
|
||
function() SFrames.Chat:RefreshConfigFrame() end
|
||
))
|
||
AddControl(CreateCfgCheck(windowSection, "显示边框", 360, -142,
|
||
function() return EnsureDB().showBorder ~= false end,
|
||
function(checked) EnsureDB().showBorder = (checked == true) end,
|
||
function() SFrames.Chat:ApplyConfig(); SFrames.Chat:RefreshConfigFrame() end
|
||
))
|
||
AddControl(CreateCfgCheck(windowSection, "边框职业色", 360, -162,
|
||
function() return EnsureDB().borderClassColor == true end,
|
||
function(checked) EnsureDB().borderClassColor = (checked == true) end,
|
||
function() SFrames.Chat:ApplyConfig(); SFrames.Chat:RefreshConfigFrame() end
|
||
))
|
||
AddControl(CreateCfgCheck(windowSection, "显示等级", 360, -182,
|
||
function() return EnsureDB().showPlayerLevel ~= false end,
|
||
function(checked) EnsureDB().showPlayerLevel = (checked == true) end,
|
||
function() SFrames.Chat:RefreshConfigFrame() end
|
||
))
|
||
|
||
CreateCfgButton(windowSection, "重置位置", 16, -198, 110, 22, function()
|
||
SFrames.Chat:ResetPosition()
|
||
end)
|
||
CreateCfgButton(windowSection, "解锁", 132, -198, 110, 22, function()
|
||
if SFrames and SFrames.UnlockFrames then SFrames:UnlockFrames() end
|
||
end)
|
||
CreateCfgButton(windowSection, "锁定", 248, -198, 110, 22, function()
|
||
if SFrames and SFrames.LockFrames then SFrames:LockFrames() end
|
||
end)
|
||
|
||
local posLabel = windowSection:CreateFontString(nil, "OVERLAY")
|
||
posLabel:SetFont(fontPath, 13, "OUTLINE")
|
||
posLabel:SetPoint("TOPLEFT", windowSection, "TOPLEFT", 16, -151)
|
||
posLabel:SetText("输入框位置:")
|
||
posLabel:SetTextColor(CFG_THEME.text[1], CFG_THEME.text[2], CFG_THEME.text[3])
|
||
|
||
CreateCfgButton(windowSection, "底部", 90, -148, 50, 20, function() EnsureDB().editBoxPosition = "bottom"; SFrames.Chat:StyleEditBox() end)
|
||
CreateCfgButton(windowSection, "顶部", 144, -148, 50, 20, function() EnsureDB().editBoxPosition = "top"; SFrames.Chat:StyleEditBox() end)
|
||
CreateCfgButton(windowSection, "自由拖动 (Alt)", 198, -148, 100, 20, function()
|
||
EnsureDB().editBoxPosition = "free"
|
||
SFrames.Chat:StyleEditBox()
|
||
DEFAULT_CHAT_FRAME:AddMessage("|cffffd100Nanami-UI:|r 按住 Alt 键可以自由拖动输入框。")
|
||
end)
|
||
|
||
local tabSection = CreateCfgSection(panel, "标签", 10, -268, 520, 118, fontPath)
|
||
self.cfgCurrentTabText = tabSection:CreateFontString(nil, "OVERLAY")
|
||
self.cfgCurrentTabText:SetFont(fontPath, 11, "OUTLINE")
|
||
self.cfgCurrentTabText:SetPoint("TOPLEFT", tabSection, "TOPLEFT", 14, -28)
|
||
self.cfgCurrentTabText:SetText("当前标签: " .. self:GetConfigFrameActiveTabName())
|
||
self.cfgCurrentTabText:SetText("当前标签: " .. self:GetConfigFrameActiveTabName())
|
||
|
||
CreateCfgButton(tabSection, "上一个", 14, -48, 92, 22, function()
|
||
SFrames.Chat:StepTab(-1)
|
||
SFrames.Chat:RefreshConfigFrame()
|
||
end)
|
||
CreateCfgButton(tabSection, "下一个", 112, -48, 92, 22, function()
|
||
SFrames.Chat:StepTab(1)
|
||
SFrames.Chat:RefreshConfigFrame()
|
||
end)
|
||
CreateCfgButton(tabSection, "新建", 210, -48, 92, 22, function()
|
||
SFrames.Chat:PromptNewTab()
|
||
SFrames.Chat:RefreshConfigFrame()
|
||
end)
|
||
self.cfgDeleteTabButton = CreateCfgButton(tabSection, "删除", 308, -48, 92, 22, function()
|
||
SFrames.Chat:DeleteActiveTab()
|
||
SFrames.Chat:RefreshConfigFrame()
|
||
end)
|
||
|
||
CreateCfgButton(tabSection, "AI", 406, -48, 92, 22, function()
|
||
SFrames.Chat:OpenTranslateConfigFrame()
|
||
end)
|
||
|
||
self.cfgRenameBox = CreateCfgEditBox(tabSection, 14, -80, 188, 20,
|
||
function() return SFrames.Chat:GetConfigFrameActiveTabName() end,
|
||
function(text)
|
||
SFrames.Chat:RenameActiveTab(text)
|
||
SFrames.Chat:RefreshConfigFrame()
|
||
end
|
||
)
|
||
CreateCfgButton(tabSection, "重命名", 210, -80, 92, 22, function()
|
||
SFrames.Chat:RenameActiveTab(SFrames.Chat.cfgRenameBox:GetText() or "")
|
||
SFrames.Chat:RefreshConfigFrame()
|
||
end)
|
||
|
||
local filterSection = CreateCfgSection(panel, "消息过滤(当前标签)", 10, -392, 520, 186, fontPath)
|
||
for i = 1, table.getn(FILTER_DEFS) do
|
||
local def = FILTER_DEFS[i]
|
||
local col = math.mod(i - 1, 2)
|
||
local row = math.floor((i - 1) / 2)
|
||
local x = 14 + col * 240
|
||
local y = -28 - row * 24
|
||
|
||
AddControl(CreateCfgCheck(filterSection, def.label, x, y,
|
||
function()
|
||
local tab = SFrames.Chat:GetActiveTab()
|
||
return tab and tab.filters and tab.filters[def.key] ~= false
|
||
end,
|
||
function(checked)
|
||
SFrames.Chat:SetActiveTabFilter(def.key, checked)
|
||
end,
|
||
function()
|
||
SFrames.Chat:ApplyConfig()
|
||
SFrames.Chat:RefreshConfigFrame()
|
||
end
|
||
))
|
||
end
|
||
|
||
local channelSection = CreateCfgSection(panel, "频道过滤(当前标签)", 10, -584, 520, 172, fontPath)
|
||
self.cfgChannelChecks = {}
|
||
self.cfgChannelHint = channelSection:CreateFontString(nil, "OVERLAY")
|
||
self.cfgChannelHint:SetFont(fontPath, 10, "OUTLINE")
|
||
self.cfgChannelHint:SetPoint("BOTTOMLEFT", channelSection, "BOTTOMLEFT", 14, 8)
|
||
self.cfgChannelHint:SetTextColor(0.84, 0.8, 0.86)
|
||
self.cfgChannelHint:SetText("已加入频道:")
|
||
|
||
local maxChannelChecks = 15
|
||
for i = 1, maxChannelChecks do
|
||
local slot = i
|
||
local col = math.mod(i - 1, 3)
|
||
local row = math.floor((i - 1) / 3)
|
||
local x = 14 + col * 165
|
||
local y = -24 - row * 24
|
||
|
||
local cb = CreateFrame("CheckButton", NextConfigWidget("ChannelCheck"), channelSection, "UICheckButtonTemplate")
|
||
cb:SetWidth(20)
|
||
cb:SetHeight(20)
|
||
cb:SetPoint("TOPLEFT", channelSection, "TOPLEFT", x, y)
|
||
StyleCfgCheck(cb)
|
||
|
||
local label = _G[cb:GetName() .. "Text"]
|
||
if label then
|
||
label:SetText("")
|
||
label:SetWidth(140)
|
||
label:SetTextColor(CFG_THEME.text[1], CFG_THEME.text[2], CFG_THEME.text[3])
|
||
end
|
||
|
||
cb:SetScript("OnClick", function()
|
||
local name = this.channelName
|
||
if not name or name == "" then return end
|
||
SFrames.Chat:SetActiveTabChannelFilter(name, this:GetChecked() and true or false)
|
||
SFrames.Chat:ApplyConfig()
|
||
SFrames.Chat:RefreshConfigFrame()
|
||
end)
|
||
|
||
cb.Refresh = function()
|
||
local channels = SFrames.Chat:GetJoinedChannels()
|
||
local info = channels[slot]
|
||
if info then
|
||
cb.channelName = info.name
|
||
if label then label:SetText(ShortText(info.name, 24)) end
|
||
cb:SetChecked(SFrames.Chat:GetTabChannelFilter(SFrames.Chat:GetActiveTabIndex(), info.name) and true or false)
|
||
cb:Show()
|
||
else
|
||
cb.channelName = nil
|
||
cb:SetChecked(false)
|
||
if label then label:SetText("") end
|
||
cb:Hide()
|
||
end
|
||
end
|
||
|
||
AddControl(cb)
|
||
table.insert(self.cfgChannelChecks, cb)
|
||
end
|
||
|
||
|
||
local okBtn = CreateCfgButton(panel, "保存", 115, -730, 140, 26, function()
|
||
SFrames.Chat.configFrame:Hide()
|
||
end)
|
||
StyleCfgButton(okBtn)
|
||
AddBtnIcon(okBtn, "save")
|
||
|
||
local reloadBtn = CreateCfgButton(panel, "保存并重载", 285, -730, 140, 26, function()
|
||
ReloadUI()
|
||
end)
|
||
StyleCfgButton(reloadBtn)
|
||
AddBtnIcon(reloadBtn, "save")
|
||
|
||
panel.controls = controls
|
||
self.configControls = controls
|
||
self.configFrame = panel
|
||
end
|
||
|
||
function SFrames.Chat:OpenConfigFrame()
|
||
self:EnsureConfigFrame()
|
||
if not self.configFrame then return end
|
||
self:RefreshConfigFrame()
|
||
self.configFrame:Show()
|
||
self.configFrame:Raise()
|
||
end
|
||
|
||
function SFrames.Chat:ToggleConfigFrame()
|
||
self:EnsureConfigFrame()
|
||
if not self.configFrame then return end
|
||
if self.configFrame:IsShown() then
|
||
self.configFrame:Hide()
|
||
else
|
||
self:OpenConfigFrame()
|
||
end
|
||
end
|
||
|
||
local CONFIG_PAGE_ORDER = {
|
||
{ key = "window", label = "窗口", title = "聊天窗口", desc = "尺寸、缩放、边框和输入框位置。", icon = "settings" },
|
||
{ key = "tabs", label = "标签", title = "标签管理", desc = "切换、重命名、新建和删除聊天标签。", icon = "chat" },
|
||
{ key = "filters", label = "过滤", title = "消息过滤", desc = "为当前标签设置消息类型和频道接收规则。", icon = "settings" },
|
||
{ key = "translate", label = "AI翻译", title = "AI 翻译", desc = "为当前标签配置自动翻译范围和频道翻译。", icon = "ai" },
|
||
{ key = "hc", label = "硬核设置", title = "硬核生存选项", desc = "全局硬核控制、死亡通报过滤及等级限制。", icon = "skull" },
|
||
}
|
||
|
||
local CONFIG_PAGE_MAP = {}
|
||
for i = 1, table.getn(CONFIG_PAGE_ORDER) do
|
||
local info = CONFIG_PAGE_ORDER[i]
|
||
CONFIG_PAGE_MAP[info.key] = info
|
||
end
|
||
|
||
local function GetConfigPageInfo(pageKey)
|
||
return CONFIG_PAGE_MAP[pageKey] or CONFIG_PAGE_MAP.window
|
||
end
|
||
|
||
local function GetEditBoxPositionText(mode)
|
||
if mode == "top" then return "顶部" end
|
||
if mode == "free" then return "自由拖动" end
|
||
return "底部"
|
||
end
|
||
|
||
local function SetConfigNavButtonActive(btn, active)
|
||
if not btn then return end
|
||
local bg = CFG_THEME.buttonBg
|
||
local border = CFG_THEME.buttonBorder
|
||
local text = CFG_THEME.buttonText
|
||
if active then
|
||
bg = { 0.32, 0.16, 0.26, 0.98 }
|
||
border = CFG_THEME.btnHoverBd or { 0.80, 0.48, 0.64, 0.98 }
|
||
text = { 1, 0.92, 0.96 }
|
||
end
|
||
if btn.SetBackdropColor then
|
||
btn:SetBackdropColor(bg[1], bg[2], bg[3], bg[4])
|
||
end
|
||
if btn.SetBackdropBorderColor then
|
||
btn:SetBackdropBorderColor(border[1], border[2], border[3], border[4])
|
||
end
|
||
local fs = btn.GetFontString and btn:GetFontString()
|
||
if fs then
|
||
fs:SetTextColor(text[1], text[2], text[3])
|
||
end
|
||
end
|
||
|
||
function SFrames.Chat:RefreshConfigNavButtons()
|
||
if not self.configNavButtons then return end
|
||
local activePage = self.configActivePage or "window"
|
||
for key, btn in pairs(self.configNavButtons) do
|
||
SetConfigNavButtonActive(btn, key == activePage)
|
||
end
|
||
end
|
||
|
||
function SFrames.Chat:ShowConfigPage(pageKey)
|
||
if not self.configFrame then return end
|
||
local info = GetConfigPageInfo(pageKey)
|
||
self.configActivePage = info.key
|
||
|
||
if self.configPages then
|
||
for key, page in pairs(self.configPages) do
|
||
if page then
|
||
if key == info.key then
|
||
page:Show()
|
||
else
|
||
page:Hide()
|
||
end
|
||
end
|
||
end
|
||
end
|
||
|
||
if self.configPageTitle then
|
||
self.configPageTitle:SetText(info.title or "")
|
||
end
|
||
if self.configPageDesc then
|
||
self.configPageDesc:SetText(info.desc or "")
|
||
end
|
||
|
||
self:RefreshConfigNavButtons()
|
||
end
|
||
|
||
function SFrames.Chat:RefreshConfigFrame()
|
||
if not self.configFrame then return end
|
||
|
||
if not self.configActivePage then
|
||
self.configActivePage = "window"
|
||
end
|
||
|
||
if self.configSidebarTabText then
|
||
self.configSidebarTabText:SetText(self:GetConfigFrameActiveTabName())
|
||
end
|
||
|
||
local pageInfo = GetConfigPageInfo(self.configActivePage)
|
||
if self.configPageTitle then
|
||
self.configPageTitle:SetText(pageInfo.title or "")
|
||
end
|
||
if self.configPageDesc then
|
||
self.configPageDesc:SetText(pageInfo.desc or "")
|
||
end
|
||
|
||
if self.cfgCurrentTabText then
|
||
self.cfgCurrentTabText:SetText("当前标签: " .. self:GetConfigFrameActiveTabName())
|
||
end
|
||
if self.cfgFilterTabText then
|
||
self.cfgFilterTabText:SetText("过滤目标: " .. self:GetConfigFrameActiveTabName())
|
||
end
|
||
if self.cfgTranslateTabText then
|
||
self.cfgTranslateTabText:SetText("翻译目标: " .. self:GetConfigFrameActiveTabName())
|
||
end
|
||
|
||
if self.cfgChannelHint then
|
||
local channels = self:GetJoinedChannels()
|
||
self.cfgChannelHint:SetText("已加入频道: " .. tostring(table.getn(channels)))
|
||
end
|
||
if self.cfgTranslateChannelHint then
|
||
local channels = self:GetJoinedChannels()
|
||
self.cfgTranslateChannelHint:SetText("已加入频道: " .. tostring(table.getn(channels)))
|
||
end
|
||
|
||
if self.cfgRenameBox and self.cfgRenameBox.Refresh then
|
||
self.cfgRenameBox:Refresh()
|
||
end
|
||
|
||
if self.cfgDeleteTabButton then
|
||
local protected = self:IsTabProtected(self:GetActiveTabIndex())
|
||
if protected and self.cfgDeleteTabButton.Disable then
|
||
self.cfgDeleteTabButton:Disable()
|
||
elseif (not protected) and self.cfgDeleteTabButton.Enable then
|
||
self.cfgDeleteTabButton:Enable()
|
||
end
|
||
end
|
||
|
||
if self.cfgTabProtectedText then
|
||
if self:IsTabProtected(self:GetActiveTabIndex()) then
|
||
self.cfgTabProtectedText:SetText("当前标签受保护,不能删除。")
|
||
self.cfgTabProtectedText:SetTextColor(0.95, 0.72, 0.72)
|
||
else
|
||
self.cfgTabProtectedText:SetText("当前标签可自由调整过滤和翻译设置。")
|
||
self.cfgTabProtectedText:SetTextColor(0.72, 0.88, 0.76)
|
||
end
|
||
end
|
||
|
||
if self.cfgInputModeText then
|
||
self.cfgInputModeText:SetText("当前输入框位置: " .. GetEditBoxPositionText(EnsureDB().editBoxPosition))
|
||
end
|
||
|
||
if self.cfgWindowSummaryText then
|
||
local db = EnsureDB()
|
||
self.cfgWindowSummaryText:SetText("当前尺寸: " .. tostring(db.width) .. " x " .. tostring(db.height) .. " 缩放: " .. string.format("%.2f", db.scale) .. " 背景: " .. string.format("%.0f%%", (db.bgAlpha or DEFAULTS.bgAlpha) * 100))
|
||
end
|
||
|
||
if self.cfgTranslateStatusText then
|
||
if self:CanUseAutoTranslateAPI() then
|
||
self.cfgTranslateStatusText:SetText("STranslate API 已就绪,自动翻译可用。")
|
||
self.cfgTranslateStatusText:SetTextColor(0.72, 0.9, 0.78)
|
||
else
|
||
self.cfgTranslateStatusText:SetText("未检测到可用的 STranslate API,开启后也不会发起翻译请求。")
|
||
self.cfgTranslateStatusText:SetTextColor(0.95, 0.76, 0.62)
|
||
end
|
||
end
|
||
|
||
if self.configControls then
|
||
for i = 1, table.getn(self.configControls) do
|
||
local ctrl = self.configControls[i]
|
||
if ctrl and ctrl.Refresh then
|
||
ctrl:Refresh()
|
||
end
|
||
end
|
||
end
|
||
|
||
self:RefreshConfigNavButtons()
|
||
end
|
||
|
||
function SFrames.Chat:RefreshTranslateConfigFrame()
|
||
self:RefreshConfigFrame()
|
||
end
|
||
|
||
function SFrames.Chat:EnsureTranslateConfigFrame()
|
||
self:EnsureConfigFrame()
|
||
end
|
||
|
||
function SFrames.Chat:OpenTranslateConfigFrame()
|
||
self:OpenConfigFrame("translate")
|
||
end
|
||
|
||
function SFrames.Chat:ToggleTranslateConfigFrame()
|
||
self:ToggleConfigFrame("translate")
|
||
end
|
||
|
||
function SFrames.Chat:EnsureConfigFrame()
|
||
if self.configFrame then return end
|
||
|
||
local fontPath = (SFrames and SFrames.GetFont and SFrames:GetFont()) or "Fonts\\ARIALN.TTF"
|
||
local panel = CreateFrame("Frame", "SFramesChatConfigPanel", UIParent)
|
||
panel:SetWidth(780)
|
||
panel:SetHeight(630)
|
||
panel:SetPoint("CENTER", UIParent, "CENTER", 120, 0)
|
||
panel:SetMovable(true)
|
||
panel:EnableMouse(true)
|
||
panel:SetClampedToScreen(true)
|
||
panel:SetFrameStrata("DIALOG")
|
||
panel:SetFrameLevel(150)
|
||
panel:RegisterForDrag("LeftButton")
|
||
panel:SetScript("OnDragStart", function() this:StartMoving() end)
|
||
panel:SetScript("OnDragStop", function() this:StopMovingOrSizing() end)
|
||
|
||
table.insert(UISpecialFrames, "SFramesChatConfigPanel")
|
||
|
||
if SFrames and SFrames.CreateBackdrop then
|
||
SFrames:CreateBackdrop(panel)
|
||
else
|
||
panel:SetBackdrop({
|
||
bgFile = "Interface\\DialogFrame\\UI-DialogBox-Background",
|
||
edgeFile = "Interface\\Buttons\\WHITE8X8",
|
||
tile = true, tileSize = 32, edgeSize = 1,
|
||
insets = { left = 1, right = 1, top = 1, bottom = 1 },
|
||
})
|
||
end
|
||
panel:SetBackdropColor(CFG_THEME.panelBg[1], CFG_THEME.panelBg[2], CFG_THEME.panelBg[3], CFG_THEME.panelBg[4])
|
||
panel:SetBackdropBorderColor(CFG_THEME.panelBorder[1], CFG_THEME.panelBorder[2], CFG_THEME.panelBorder[3], CFG_THEME.panelBorder[4])
|
||
|
||
local title = panel:CreateFontString(nil, "OVERLAY")
|
||
title:SetFont(fontPath, 15, "OUTLINE")
|
||
title:SetPoint("TOPLEFT", panel, "TOPLEFT", 18, -14)
|
||
title:SetText("Nanami 聊天设置")
|
||
title:SetTextColor(CFG_THEME.title[1], CFG_THEME.title[2], CFG_THEME.title[3])
|
||
|
||
local closeBtn = CreateFrame("Button", nil, panel, "UIPanelCloseButton")
|
||
closeBtn:SetPoint("TOPRIGHT", panel, "TOPRIGHT", -4, -4)
|
||
|
||
local sidebar = CreateCfgSection(panel, "导航", 12, -42, 154, 532, fontPath)
|
||
|
||
local sideLabel = sidebar:CreateFontString(nil, "OVERLAY")
|
||
sideLabel:SetFont(fontPath, 10, "OUTLINE")
|
||
sideLabel:SetPoint("TOPLEFT", sidebar, "TOPLEFT", 12, -32)
|
||
sideLabel:SetText("当前标签")
|
||
sideLabel:SetTextColor(0.72, 0.72, 0.78)
|
||
|
||
self.configSidebarTabText = sidebar:CreateFontString(nil, "OVERLAY")
|
||
self.configSidebarTabText:SetFont(fontPath, 14, "OUTLINE")
|
||
self.configSidebarTabText:SetPoint("TOPLEFT", sidebar, "TOPLEFT", 12, -50)
|
||
self.configSidebarTabText:SetText(self:GetConfigFrameActiveTabName())
|
||
self.configSidebarTabText:SetTextColor(0.96, 0.94, 0.98)
|
||
|
||
local sideTip = sidebar:CreateFontString(nil, "OVERLAY")
|
||
sideTip:SetFont(fontPath, 10, "OUTLINE")
|
||
sideTip:SetPoint("BOTTOMLEFT", sidebar, "BOTTOMLEFT", 12, 14)
|
||
sideTip:SetWidth(130)
|
||
sideTip:SetJustifyH("LEFT")
|
||
sideTip:SetText("把窗口、标签、过滤和翻译拆成独立页面,避免一屏堆满。")
|
||
sideTip:SetTextColor(0.72, 0.72, 0.78)
|
||
|
||
self.configPageTitle = panel:CreateFontString(nil, "OVERLAY")
|
||
self.configPageTitle:SetFont(fontPath, 14, "OUTLINE")
|
||
self.configPageTitle:SetPoint("TOPLEFT", panel, "TOPLEFT", 184, -48)
|
||
self.configPageTitle:SetText("")
|
||
self.configPageTitle:SetTextColor(CFG_THEME.title[1], CFG_THEME.title[2], CFG_THEME.title[3])
|
||
|
||
self.configPageDesc = panel:CreateFontString(nil, "OVERLAY")
|
||
self.configPageDesc:SetFont(fontPath, 10, "OUTLINE")
|
||
self.configPageDesc:SetPoint("TOPLEFT", panel, "TOPLEFT", 184, -68)
|
||
self.configPageDesc:SetText("")
|
||
self.configPageDesc:SetTextColor(0.78, 0.78, 0.84)
|
||
|
||
self.configNavButtons = {}
|
||
local navY = -88
|
||
for i = 1, table.getn(CONFIG_PAGE_ORDER) do
|
||
local info = CONFIG_PAGE_ORDER[i]
|
||
local btn = CreateCfgButton(sidebar, info.label, 12, navY, 130, 28, function()
|
||
SFrames.Chat:ShowConfigPage(info.key)
|
||
SFrames.Chat:RefreshConfigFrame()
|
||
end)
|
||
if info.icon then AddBtnIcon(btn, info.icon, nil, "left") end
|
||
btn.pageKey = info.key
|
||
local oldLeave = btn:GetScript("OnLeave")
|
||
btn:SetScript("OnLeave", function()
|
||
if oldLeave then oldLeave() end
|
||
if this.pageKey and SFrames and SFrames.Chat and SFrames.Chat.configActivePage == this.pageKey then
|
||
SetConfigNavButtonActive(this, true)
|
||
end
|
||
end)
|
||
self.configNavButtons[info.key] = btn
|
||
navY = navY - 36
|
||
end
|
||
|
||
local content = CreateFrame("Frame", nil, panel)
|
||
content:SetPoint("TOPLEFT", panel, "TOPLEFT", 184, -92)
|
||
content:SetWidth(584)
|
||
content:SetHeight(484)
|
||
panel.content = content
|
||
|
||
local controls = {}
|
||
local function AddControl(ctrl)
|
||
table.insert(controls, ctrl)
|
||
return ctrl
|
||
end
|
||
|
||
local pages = {}
|
||
local function CreatePage(key)
|
||
local page = CreateFrame("Frame", nil, content)
|
||
page:SetAllPoints(content)
|
||
page:Hide()
|
||
pages[key] = page
|
||
return page
|
||
end
|
||
|
||
local windowPage = CreatePage("window")
|
||
do
|
||
local appearance = CreateCfgSection(windowPage, "窗口外观", 0, 0, 584, 274, fontPath)
|
||
AddControl(CreateCfgSlider(appearance, "宽度", 16, -46, 260, 320, 900, 1,
|
||
function() return EnsureDB().width end,
|
||
function(v) EnsureDB().width = v end,
|
||
function(v) return tostring(math.floor(v + 0.5)) end,
|
||
function() SFrames.Chat:ApplyConfig(); SFrames.Chat:RefreshConfigFrame() end
|
||
))
|
||
AddControl(CreateCfgSlider(appearance, "高度", 308, -46, 260, 120, 460, 1,
|
||
function() return EnsureDB().height end,
|
||
function(v) EnsureDB().height = v end,
|
||
function(v) return tostring(math.floor(v + 0.5)) end,
|
||
function() SFrames.Chat:ApplyConfig(); SFrames.Chat:RefreshConfigFrame() end
|
||
))
|
||
AddControl(CreateCfgSlider(appearance, "缩放", 16, -108, 260, 0.75, 1.40, 0.05,
|
||
function() return EnsureDB().scale end,
|
||
function(v) EnsureDB().scale = v end,
|
||
function(v) return string.format("%.2f", v) end,
|
||
function() SFrames.Chat:ApplyConfig(); SFrames.Chat:RefreshConfigFrame() end
|
||
))
|
||
AddControl(CreateCfgSlider(appearance, "字号", 308, -108, 260, 10, 18, 1,
|
||
function() return EnsureDB().fontSize end,
|
||
function(v) EnsureDB().fontSize = v end,
|
||
function(v) return tostring(math.floor(v + 0.5)) end,
|
||
function() SFrames.Chat:ApplyConfig(); SFrames.Chat:RefreshConfigFrame() end
|
||
))
|
||
AddControl(CreateCfgSlider(appearance, "背景透明度", 16, -170, 260, 0, 1, 0.05,
|
||
function() return EnsureDB().bgAlpha end,
|
||
function(v) EnsureDB().bgAlpha = v end,
|
||
function(v) return string.format("%.0f%%", v * 100) end,
|
||
function() SFrames.Chat:ApplyConfig(); SFrames.Chat:RefreshConfigFrame() end
|
||
))
|
||
AddControl(CreateCfgCheck(appearance, "启用聊天", 16, -218,
|
||
function() return EnsureDB().enable ~= false end,
|
||
function(checked)
|
||
local oldState = EnsureDB().enable ~= false
|
||
if oldState ~= checked then
|
||
EnsureDB().enable = (checked == true)
|
||
if StaticPopup_Show then
|
||
StaticPopup_Show("SFRAMES_CHAT_RELOAD_PROMPT")
|
||
else
|
||
ReloadUI()
|
||
end
|
||
end
|
||
end,
|
||
function() SFrames.Chat:RefreshConfigFrame() end
|
||
))
|
||
AddControl(CreateCfgCheck(appearance, "显示边框", 144, -218,
|
||
function() return EnsureDB().showBorder ~= false end,
|
||
function(checked) EnsureDB().showBorder = (checked == true) end,
|
||
function() SFrames.Chat:ApplyConfig(); SFrames.Chat:RefreshConfigFrame() end
|
||
))
|
||
AddControl(CreateCfgCheck(appearance, "边框职业色", 288, -218,
|
||
function() return EnsureDB().borderClassColor == true end,
|
||
function(checked) EnsureDB().borderClassColor = (checked == true) end,
|
||
function() SFrames.Chat:ApplyConfig(); SFrames.Chat:RefreshConfigFrame() end
|
||
))
|
||
AddControl(CreateCfgCheck(appearance, "显示等级", 432, -218,
|
||
function() return EnsureDB().showPlayerLevel ~= false end,
|
||
function(checked) EnsureDB().showPlayerLevel = (checked == true) end,
|
||
function() SFrames.Chat:RefreshConfigFrame() end
|
||
))
|
||
|
||
self.cfgWindowSummaryText = appearance:CreateFontString(nil, "OVERLAY")
|
||
self.cfgWindowSummaryText:SetFont(fontPath, 10, "OUTLINE")
|
||
self.cfgWindowSummaryText:SetPoint("BOTTOMLEFT", appearance, "BOTTOMLEFT", 16, 10)
|
||
self.cfgWindowSummaryText:SetTextColor(0.74, 0.74, 0.8)
|
||
|
||
local inputSection = CreateCfgSection(windowPage, "输入框", 0, -290, 584, 114, fontPath)
|
||
self.cfgInputModeText = inputSection:CreateFontString(nil, "OVERLAY")
|
||
self.cfgInputModeText:SetFont(fontPath, 11, "OUTLINE")
|
||
self.cfgInputModeText:SetPoint("TOPLEFT", inputSection, "TOPLEFT", 16, -30)
|
||
self.cfgInputModeText:SetTextColor(CFG_THEME.text[1], CFG_THEME.text[2], CFG_THEME.text[3])
|
||
self.cfgInputModeText:SetText("")
|
||
|
||
CreateCfgButton(inputSection, "底部", 16, -52, 92, 22, function()
|
||
EnsureDB().editBoxPosition = "bottom"
|
||
SFrames.Chat:StyleEditBox()
|
||
SFrames.Chat:RefreshConfigFrame()
|
||
end)
|
||
CreateCfgButton(inputSection, "顶部", 114, -52, 92, 22, function()
|
||
EnsureDB().editBoxPosition = "top"
|
||
SFrames.Chat:StyleEditBox()
|
||
SFrames.Chat:RefreshConfigFrame()
|
||
end)
|
||
CreateCfgButton(inputSection, "自由拖动", 212, -52, 108, 22, function()
|
||
EnsureDB().editBoxPosition = "free"
|
||
SFrames.Chat:StyleEditBox()
|
||
SFrames.Chat:RefreshConfigFrame()
|
||
DEFAULT_CHAT_FRAME:AddMessage("|cffffd100Nanami-UI:|r 按住 Alt 可拖动输入框。")
|
||
end)
|
||
|
||
local inputTip = inputSection:CreateFontString(nil, "OVERLAY")
|
||
inputTip:SetFont(fontPath, 10, "OUTLINE")
|
||
inputTip:SetPoint("BOTTOMLEFT", inputSection, "BOTTOMLEFT", 16, 10)
|
||
inputTip:SetWidth(540)
|
||
inputTip:SetJustifyH("LEFT")
|
||
inputTip:SetText("建议优先使用顶部或底部模式;自由拖动适合特殊布局。")
|
||
inputTip:SetTextColor(0.74, 0.74, 0.8)
|
||
|
||
local actionSection = CreateCfgSection(windowPage, "窗口操作", 0, -398, 584, 96, fontPath)
|
||
CreateCfgButton(actionSection, "重置位置", 16, -32, 108, 24, function()
|
||
SFrames.Chat:ResetPosition()
|
||
SFrames.Chat:RefreshConfigFrame()
|
||
end)
|
||
CreateCfgButton(actionSection, "解锁", 132, -32, 108, 24, function()
|
||
if SFrames and SFrames.UnlockFrames then SFrames:UnlockFrames() end
|
||
end)
|
||
CreateCfgButton(actionSection, "锁定", 248, -32, 108, 24, function()
|
||
if SFrames and SFrames.LockFrames then SFrames:LockFrames() end
|
||
end)
|
||
CreateCfgButton(actionSection, "重置私聊图标位置", 364, -32, 160, 24, function()
|
||
if SFramesDB then SFramesDB.whisperBtnPos = nil end
|
||
if SFrames and SFrames.Chat and SFrames.Chat.frame then
|
||
local f = SFrames.Chat.frame
|
||
if f.whisperButton and f.configButton then
|
||
f.whisperButton:ClearAllPoints()
|
||
f.whisperButton:SetPoint("RIGHT", f.configButton, "LEFT", -6, 0)
|
||
end
|
||
end
|
||
SFrames.Chat:RefreshConfigFrame()
|
||
end)
|
||
end
|
||
|
||
local tabsPage = CreatePage("tabs")
|
||
do
|
||
local tabSection = CreateCfgSection(tabsPage, "当前标签", 0, 0, 584, 118, fontPath)
|
||
self.cfgCurrentTabText = tabSection:CreateFontString(nil, "OVERLAY")
|
||
self.cfgCurrentTabText:SetFont(fontPath, 12, "OUTLINE")
|
||
self.cfgCurrentTabText:SetPoint("TOPLEFT", tabSection, "TOPLEFT", 16, -30)
|
||
self.cfgCurrentTabText:SetTextColor(CFG_THEME.text[1], CFG_THEME.text[2], CFG_THEME.text[3])
|
||
|
||
CreateCfgButton(tabSection, "上一个", 16, -54, 92, 22, function()
|
||
SFrames.Chat:StepTab(-1)
|
||
SFrames.Chat:RefreshConfigFrame()
|
||
end)
|
||
CreateCfgButton(tabSection, "下一个", 114, -54, 92, 22, function()
|
||
SFrames.Chat:StepTab(1)
|
||
SFrames.Chat:RefreshConfigFrame()
|
||
end)
|
||
CreateCfgButton(tabSection, "新建", 212, -54, 92, 22, function()
|
||
SFrames.Chat:PromptNewTab()
|
||
SFrames.Chat:RefreshConfigFrame()
|
||
end)
|
||
self.cfgDeleteTabButton = CreateCfgButton(tabSection, "删除", 310, -54, 92, 22, function()
|
||
SFrames.Chat:DeleteActiveTab()
|
||
SFrames.Chat:RefreshConfigFrame()
|
||
end)
|
||
CreateCfgButton(tabSection, "前往过滤", 408, -54, 112, 22, function()
|
||
SFrames.Chat:ShowConfigPage("filters")
|
||
SFrames.Chat:RefreshConfigFrame()
|
||
end)
|
||
|
||
local renameSection = CreateCfgSection(tabsPage, "重命名", 0, -134, 584, 84, fontPath)
|
||
self.cfgRenameBox = CreateCfgEditBox(renameSection, 16, -38, 250, 20,
|
||
function() return SFrames.Chat:GetConfigFrameActiveTabName() end,
|
||
function(text)
|
||
SFrames.Chat:RenameActiveTab(text)
|
||
SFrames.Chat:RefreshConfigFrame()
|
||
end
|
||
)
|
||
CreateCfgButton(renameSection, "应用名称", 280, -36, 108, 22, function()
|
||
SFrames.Chat:RenameActiveTab(SFrames.Chat.cfgRenameBox:GetText() or "")
|
||
SFrames.Chat:RefreshConfigFrame()
|
||
end)
|
||
|
||
local infoSection = CreateCfgSection(tabsPage, "状态与快捷入口", 0, -234, 584, 116, fontPath)
|
||
self.cfgTabProtectedText = infoSection:CreateFontString(nil, "OVERLAY")
|
||
self.cfgTabProtectedText:SetFont(fontPath, 11, "OUTLINE")
|
||
self.cfgTabProtectedText:SetPoint("TOPLEFT", infoSection, "TOPLEFT", 16, -30)
|
||
self.cfgTabProtectedText:SetWidth(540)
|
||
self.cfgTabProtectedText:SetJustifyH("LEFT")
|
||
|
||
CreateCfgButton(infoSection, "消息过滤", 16, -66, 110, 22, function()
|
||
SFrames.Chat:ShowConfigPage("filters")
|
||
SFrames.Chat:RefreshConfigFrame()
|
||
end)
|
||
local aiBtn = CreateCfgButton(infoSection, "AI 翻译", 132, -66, 110, 22, function()
|
||
SFrames.Chat:ShowConfigPage("translate")
|
||
SFrames.Chat:RefreshConfigFrame()
|
||
end)
|
||
AddBtnIcon(aiBtn, "ai")
|
||
|
||
local infoTip = infoSection:CreateFontString(nil, "OVERLAY")
|
||
infoTip:SetFont(fontPath, 10, "OUTLINE")
|
||
infoTip:SetPoint("BOTTOMLEFT", infoSection, "BOTTOMLEFT", 16, 12)
|
||
infoTip:SetWidth(540)
|
||
infoTip:SetJustifyH("LEFT")
|
||
infoTip:SetText("默认综合/战斗标签可能受保护。自定义标签建议先设置频道过滤,再配置 AI 翻译。")
|
||
infoTip:SetTextColor(0.74, 0.74, 0.8)
|
||
end
|
||
|
||
local filtersPage = CreatePage("filters")
|
||
do
|
||
local headerSection = CreateCfgSection(filtersPage, "当前过滤目标", 0, 0, 584, 92, fontPath)
|
||
self.cfgFilterTabText = headerSection:CreateFontString(nil, "OVERLAY")
|
||
self.cfgFilterTabText:SetFont(fontPath, 12, "OUTLINE")
|
||
self.cfgFilterTabText:SetPoint("TOPLEFT", headerSection, "TOPLEFT", 16, -30)
|
||
self.cfgFilterTabText:SetTextColor(CFG_THEME.text[1], CFG_THEME.text[2], CFG_THEME.text[3])
|
||
|
||
CreateCfgButton(headerSection, "上一个", 16, -52, 92, 22, function()
|
||
SFrames.Chat:StepTab(-1)
|
||
SFrames.Chat:RefreshConfigFrame()
|
||
end)
|
||
CreateCfgButton(headerSection, "下一个", 114, -52, 92, 22, function()
|
||
SFrames.Chat:StepTab(1)
|
||
SFrames.Chat:RefreshConfigFrame()
|
||
end)
|
||
CreateCfgButton(headerSection, "标签管理", 212, -52, 108, 22, function()
|
||
SFrames.Chat:ShowConfigPage("tabs")
|
||
SFrames.Chat:RefreshConfigFrame()
|
||
end)
|
||
|
||
local filterSection = CreateCfgSection(filtersPage, "消息与频道接收", 0, -108, 584, 380, fontPath)
|
||
|
||
CreateCfgButton(filterSection, "全选", 16, -26, 60, 20, function()
|
||
for _, def in ipairs(FILTER_DEFS) do
|
||
SFrames.Chat:SetActiveTabFilter(def.key, true)
|
||
end
|
||
local channels = SFrames.Chat:GetJoinedChannels()
|
||
for i = 1, table.getn(channels) do
|
||
SFrames.Chat:SetActiveTabChannelFilter(channels[i].name, true)
|
||
end
|
||
SFrames.Chat:ApplyConfig()
|
||
SFrames.Chat:RefreshConfigFrame()
|
||
end)
|
||
CreateCfgButton(filterSection, "取消全选", 84, -26, 75, 20, function()
|
||
for _, def in ipairs(FILTER_DEFS) do
|
||
SFrames.Chat:SetActiveTabFilter(def.key, false)
|
||
end
|
||
local channels = SFrames.Chat:GetJoinedChannels()
|
||
for i = 1, table.getn(channels) do
|
||
SFrames.Chat:SetActiveTabChannelFilter(channels[i].name, false)
|
||
end
|
||
SFrames.Chat:ApplyConfig()
|
||
SFrames.Chat:RefreshConfigFrame()
|
||
end)
|
||
CreateCfgButton(filterSection, "反选", 167, -26, 60, 20, function()
|
||
local actIdx = SFrames.Chat:GetActiveTabIndex()
|
||
local tab = SFrames.Chat:GetActiveTab()
|
||
for _, def in ipairs(FILTER_DEFS) do
|
||
local state = tab and tab.filters and tab.filters[def.key] ~= false
|
||
SFrames.Chat:SetActiveTabFilter(def.key, not state)
|
||
end
|
||
local channels = SFrames.Chat:GetJoinedChannels()
|
||
for i = 1, table.getn(channels) do
|
||
local state = SFrames.Chat:GetTabChannelFilter(actIdx, channels[i].name)
|
||
SFrames.Chat:SetActiveTabChannelFilter(channels[i].name, not state)
|
||
end
|
||
SFrames.Chat:ApplyConfig()
|
||
SFrames.Chat:RefreshConfigFrame()
|
||
end)
|
||
|
||
local nextIndex = 0
|
||
for i = 1, table.getn(FILTER_DEFS) do
|
||
local def = FILTER_DEFS[i]
|
||
local col = math.mod(nextIndex, 3)
|
||
local row = math.floor(nextIndex / 3)
|
||
local x = 16 + col * 182
|
||
local y = -60 - row * 24
|
||
|
||
AddControl(CreateCfgCheck(filterSection, def.label, x, y,
|
||
function()
|
||
local tab = SFrames.Chat:GetActiveTab()
|
||
return tab and tab.filters and tab.filters[def.key] ~= false
|
||
end,
|
||
function(checked)
|
||
SFrames.Chat:SetActiveTabFilter(def.key, checked)
|
||
end,
|
||
function()
|
||
SFrames.Chat:ApplyConfig()
|
||
SFrames.Chat:RefreshConfigFrame()
|
||
end
|
||
))
|
||
nextIndex = nextIndex + 1
|
||
end
|
||
|
||
-- Channel sub-header: separator, hint, refresh button
|
||
local chSepRow = math.ceil(nextIndex / 3)
|
||
local chSepY = -60 - chSepRow * 24
|
||
|
||
local chSepLine = filterSection:CreateTexture(nil, "ARTWORK")
|
||
chSepLine:SetTexture(1, 1, 1, 0.15)
|
||
chSepLine:SetPoint("TOPLEFT", filterSection, "TOPLEFT", 16, chSepY + 6)
|
||
chSepLine:SetWidth(540)
|
||
chSepLine:SetHeight(1)
|
||
|
||
local chSepLabel = filterSection:CreateFontString(nil, "OVERLAY")
|
||
chSepLabel:SetFont(fontPath, 11, "OUTLINE")
|
||
chSepLabel:SetPoint("TOPLEFT", filterSection, "TOPLEFT", 16, chSepY - 4)
|
||
chSepLabel:SetText("频道过滤")
|
||
chSepLabel:SetTextColor(CFG_THEME.title[1], CFG_THEME.title[2], CFG_THEME.title[3])
|
||
|
||
self.cfgChannelHint = filterSection:CreateFontString(nil, "OVERLAY")
|
||
self.cfgChannelHint:SetFont(fontPath, 10, "OUTLINE")
|
||
self.cfgChannelHint:SetPoint("TOPLEFT", filterSection, "TOPLEFT", 120, chSepY - 5)
|
||
self.cfgChannelHint:SetTextColor(0.84, 0.80, 0.86)
|
||
self.cfgChannelHint:SetText("已加入频道:")
|
||
|
||
CreateCfgButton(filterSection, "刷新频道列表", 420, chSepY - 2, 100, 18, function()
|
||
SFrames.Chat:RefreshConfigFrame()
|
||
end)
|
||
|
||
nextIndex = (chSepRow + 1) * 3
|
||
|
||
self.cfgChannelChecks = {}
|
||
for i = 1, 15 do
|
||
local slot = i
|
||
local col = math.mod(nextIndex, 3)
|
||
local row = math.floor(nextIndex / 3)
|
||
local x = 16 + col * 182
|
||
local y = -60 - row * 24
|
||
|
||
local cb = CreateFrame("CheckButton", NextConfigWidget("ChannelCheck"), filterSection, "UICheckButtonTemplate")
|
||
cb:SetWidth(20)
|
||
cb:SetHeight(20)
|
||
cb:SetPoint("TOPLEFT", filterSection, "TOPLEFT", x, y)
|
||
StyleCfgCheck(cb)
|
||
|
||
local label = _G[cb:GetName() .. "Text"]
|
||
if label then
|
||
label:SetText("")
|
||
label:SetWidth(150)
|
||
label:SetTextColor(CFG_THEME.text[1], CFG_THEME.text[2], CFG_THEME.text[3])
|
||
end
|
||
|
||
cb:SetScript("OnClick", function()
|
||
local name = this.channelName
|
||
if not name or name == "" then return end
|
||
SFrames.Chat:SetActiveTabChannelFilter(name, this:GetChecked() and true or false)
|
||
SFrames.Chat:ApplyConfig()
|
||
SFrames.Chat:RefreshConfigFrame()
|
||
end)
|
||
|
||
cb.Refresh = function()
|
||
local channels = SFrames.Chat:GetJoinedChannels()
|
||
local info = channels[slot]
|
||
if info then
|
||
cb.channelName = info.name
|
||
if label then label:SetText(ShortText(info.name, 24)) end
|
||
cb:SetChecked(SFrames.Chat:GetTabChannelFilter(SFrames.Chat:GetActiveTabIndex(), info.name) and true or false)
|
||
cb:Show()
|
||
else
|
||
cb.channelName = nil
|
||
cb:SetChecked(false)
|
||
if label then label:SetText("") end
|
||
cb:Hide()
|
||
end
|
||
end
|
||
|
||
AddControl(cb)
|
||
table.insert(self.cfgChannelChecks, cb)
|
||
nextIndex = nextIndex + 1
|
||
end
|
||
end
|
||
|
||
local translatePage = CreatePage("translate")
|
||
do
|
||
local headerSection = CreateCfgSection(translatePage, "当前翻译目标", 0, 0, 584, 104, fontPath)
|
||
self.cfgTranslateTabText = headerSection:CreateFontString(nil, "OVERLAY")
|
||
self.cfgTranslateTabText:SetFont(fontPath, 12, "OUTLINE")
|
||
self.cfgTranslateTabText:SetPoint("TOPLEFT", headerSection, "TOPLEFT", 16, -30)
|
||
self.cfgTranslateTabText:SetTextColor(CFG_THEME.text[1], CFG_THEME.text[2], CFG_THEME.text[3])
|
||
|
||
self.cfgTranslateStatusText = headerSection:CreateFontString(nil, "OVERLAY")
|
||
self.cfgTranslateStatusText:SetFont(fontPath, 10, "OUTLINE")
|
||
self.cfgTranslateStatusText:SetPoint("TOPLEFT", headerSection, "TOPLEFT", 16, -50)
|
||
self.cfgTranslateStatusText:SetWidth(540)
|
||
self.cfgTranslateStatusText:SetJustifyH("LEFT")
|
||
|
||
CreateCfgButton(headerSection, "上一个", 16, -72, 92, 22, function()
|
||
SFrames.Chat:StepTab(-1)
|
||
SFrames.Chat:RefreshConfigFrame()
|
||
end)
|
||
CreateCfgButton(headerSection, "下一个", 114, -72, 92, 22, function()
|
||
SFrames.Chat:StepTab(1)
|
||
SFrames.Chat:RefreshConfigFrame()
|
||
end)
|
||
CreateCfgButton(headerSection, "频道过滤", 212, -72, 108, 22, function()
|
||
SFrames.Chat:ShowConfigPage("filters")
|
||
SFrames.Chat:RefreshConfigFrame()
|
||
end)
|
||
|
||
local filterSection = CreateCfgSection(translatePage, "消息与频道翻译", 0, -120, 584, 360, fontPath)
|
||
|
||
CreateCfgButton(filterSection, "全选", 16, -26, 60, 20, function()
|
||
for _, key in ipairs(TRANSLATE_FILTER_ORDER) do
|
||
SFrames.Chat:SetActiveTabTranslateFilter(key, true)
|
||
end
|
||
local channels = SFrames.Chat:GetJoinedChannels()
|
||
for i = 1, table.getn(channels) do
|
||
SFrames.Chat:SetActiveTabChannelTranslateFilter(channels[i].name, true)
|
||
end
|
||
SFrames.Chat:RefreshConfigFrame()
|
||
end)
|
||
CreateCfgButton(filterSection, "取消全选", 84, -26, 75, 20, function()
|
||
for _, key in ipairs(TRANSLATE_FILTER_ORDER) do
|
||
SFrames.Chat:SetActiveTabTranslateFilter(key, false)
|
||
end
|
||
local channels = SFrames.Chat:GetJoinedChannels()
|
||
for i = 1, table.getn(channels) do
|
||
SFrames.Chat:SetActiveTabChannelTranslateFilter(channels[i].name, false)
|
||
end
|
||
SFrames.Chat:RefreshConfigFrame()
|
||
end)
|
||
CreateCfgButton(filterSection, "反选", 167, -26, 60, 20, function()
|
||
local actIdx = SFrames.Chat:GetActiveTabIndex()
|
||
for _, key in ipairs(TRANSLATE_FILTER_ORDER) do
|
||
local state = SFrames.Chat:GetTabTranslateFilter(actIdx, key)
|
||
SFrames.Chat:SetActiveTabTranslateFilter(key, not state)
|
||
end
|
||
local channels = SFrames.Chat:GetJoinedChannels()
|
||
for i = 1, table.getn(channels) do
|
||
local state = SFrames.Chat:GetTabChannelTranslateFilter(actIdx, channels[i].name)
|
||
SFrames.Chat:SetActiveTabChannelTranslateFilter(channels[i].name, not state)
|
||
end
|
||
SFrames.Chat:RefreshConfigFrame()
|
||
end)
|
||
|
||
local nextIndex = 0
|
||
for i = 1, table.getn(TRANSLATE_FILTER_ORDER) do
|
||
local key = TRANSLATE_FILTER_ORDER[i]
|
||
local col = math.mod(nextIndex, 3)
|
||
local row = math.floor(nextIndex / 3)
|
||
local x = 16 + col * 182
|
||
local y = -60 - row * 24
|
||
|
||
AddControl(CreateCfgCheck(filterSection, GetFilterLabel(key), x, y,
|
||
function()
|
||
return SFrames.Chat:GetTabTranslateFilter(SFrames.Chat:GetActiveTabIndex(), key)
|
||
end,
|
||
function(checked)
|
||
SFrames.Chat:SetActiveTabTranslateFilter(key, checked)
|
||
end,
|
||
function()
|
||
SFrames.Chat:RefreshConfigFrame()
|
||
end
|
||
))
|
||
nextIndex = nextIndex + 1
|
||
end
|
||
|
||
-- Channel sub-header for translate page
|
||
local tchSepRow = math.ceil(nextIndex / 3)
|
||
local tchSepY = -60 - tchSepRow * 24
|
||
|
||
local tchSepLine = filterSection:CreateTexture(nil, "ARTWORK")
|
||
tchSepLine:SetTexture(1, 1, 1, 0.15)
|
||
tchSepLine:SetPoint("TOPLEFT", filterSection, "TOPLEFT", 16, tchSepY + 6)
|
||
tchSepLine:SetWidth(540)
|
||
tchSepLine:SetHeight(1)
|
||
|
||
local tchSepLabel = filterSection:CreateFontString(nil, "OVERLAY")
|
||
tchSepLabel:SetFont(fontPath, 11, "OUTLINE")
|
||
tchSepLabel:SetPoint("TOPLEFT", filterSection, "TOPLEFT", 16, tchSepY - 4)
|
||
tchSepLabel:SetText("频道翻译")
|
||
tchSepLabel:SetTextColor(CFG_THEME.title[1], CFG_THEME.title[2], CFG_THEME.title[3])
|
||
|
||
self.cfgTranslateChannelHint = filterSection:CreateFontString(nil, "OVERLAY")
|
||
self.cfgTranslateChannelHint:SetFont(fontPath, 10, "OUTLINE")
|
||
self.cfgTranslateChannelHint:SetPoint("TOPLEFT", filterSection, "TOPLEFT", 120, tchSepY - 5)
|
||
self.cfgTranslateChannelHint:SetTextColor(0.84, 0.80, 0.86)
|
||
self.cfgTranslateChannelHint:SetText("已加入频道:")
|
||
|
||
CreateCfgButton(filterSection, "刷新频道列表", 420, tchSepY - 2, 100, 18, function()
|
||
SFrames.Chat:RefreshConfigFrame()
|
||
end)
|
||
|
||
nextIndex = (tchSepRow + 1) * 3
|
||
|
||
self.translateChannelChecks = {}
|
||
for i = 1, 15 do
|
||
local slot = i
|
||
local col = math.mod(nextIndex, 3)
|
||
local row = math.floor(nextIndex / 3)
|
||
local x = 16 + col * 182
|
||
local y = -60 - row * 24
|
||
|
||
local cb = CreateFrame("CheckButton", NextConfigWidget("TranslateChannelCheck"), filterSection, "UICheckButtonTemplate")
|
||
cb:SetWidth(20)
|
||
cb:SetHeight(20)
|
||
cb:SetPoint("TOPLEFT", filterSection, "TOPLEFT", x, y)
|
||
StyleCfgCheck(cb)
|
||
|
||
local label = _G[cb:GetName() .. "Text"]
|
||
if label then
|
||
label:SetText("")
|
||
label:SetWidth(150)
|
||
label:SetTextColor(CFG_THEME.text[1], CFG_THEME.text[2], CFG_THEME.text[3])
|
||
end
|
||
|
||
cb:SetScript("OnClick", function()
|
||
local name = this.channelName
|
||
if not name or name == "" then return end
|
||
SFrames.Chat:SetActiveTabChannelTranslateFilter(name, this:GetChecked() and true or false)
|
||
SFrames.Chat:RefreshConfigFrame()
|
||
end)
|
||
|
||
cb.Refresh = function()
|
||
local channels = SFrames.Chat:GetJoinedChannels()
|
||
local info = channels[slot]
|
||
if info then
|
||
cb.channelName = info.name
|
||
if label then
|
||
label:SetText(ShortText(info.name, 24))
|
||
label:SetTextColor(CFG_THEME.text[1], CFG_THEME.text[2], CFG_THEME.text[3])
|
||
end
|
||
cb:SetChecked(SFrames.Chat:GetTabChannelTranslateFilter(SFrames.Chat:GetActiveTabIndex(), info.name) and true or false)
|
||
cb:Enable()
|
||
cb:Show()
|
||
else
|
||
cb.channelName = nil
|
||
cb:SetChecked(false)
|
||
if label then label:SetText("") end
|
||
cb:Hide()
|
||
end
|
||
end
|
||
|
||
AddControl(cb)
|
||
table.insert(self.translateChannelChecks, cb)
|
||
nextIndex = nextIndex + 1
|
||
end
|
||
end
|
||
|
||
local hcPage = CreatePage("hc")
|
||
do
|
||
local hcControls = CreateCfgSection(hcPage, "硬核生存服务器专属", 0, 0, 584, 182, fontPath)
|
||
|
||
AddControl(CreateCfgCheck(hcControls, "全局彻底关闭硬核频道接收", 16, -30,
|
||
function() return EnsureDB().hcGlobalDisable == true end,
|
||
function(checked) EnsureDB().hcGlobalDisable = (checked == true) end,
|
||
function() SFrames.Chat:RefreshConfigFrame() end
|
||
))
|
||
|
||
local hcTip = hcControls:CreateFontString(nil, "OVERLAY")
|
||
hcTip:SetFont(fontPath, 10, "OUTLINE")
|
||
hcTip:SetPoint("TOPLEFT", hcControls, "TOPLEFT", 16, -56)
|
||
hcTip:SetWidth(540)
|
||
hcTip:SetJustifyH("LEFT")
|
||
hcTip:SetText("彻底无视HC频道的强制聊天推送。勾选后,所有标签都不会收到硬核频道内容。")
|
||
hcTip:SetTextColor(0.8, 0.7, 0.7)
|
||
|
||
AddControl(CreateCfgCheck(hcControls, "全局屏蔽玩家死亡/满级信息", 16, -86,
|
||
function() return EnsureDB().hcDeathDisable == true end,
|
||
function(checked) EnsureDB().hcDeathDisable = (checked == true) end,
|
||
function() SFrames.Chat:RefreshConfigFrame() end
|
||
))
|
||
|
||
local deathTip = hcControls:CreateFontString(nil, "OVERLAY")
|
||
deathTip:SetFont(fontPath, 10, "OUTLINE")
|
||
deathTip:SetPoint("TOPLEFT", hcControls, "TOPLEFT", 16, -112)
|
||
deathTip:SetWidth(540)
|
||
deathTip:SetJustifyH("LEFT")
|
||
deathTip:SetText("关闭那些“某某在XX级死亡”的系统提示。")
|
||
deathTip:SetTextColor(0.8, 0.7, 0.7)
|
||
|
||
AddControl(CreateCfgSlider(hcControls, "最低死亡通报等级", 340, -82, 210, 0, 60, 1,
|
||
function() return EnsureDB().hcDeathLevelMin or 0 end,
|
||
function(v) EnsureDB().hcDeathLevelMin = v end,
|
||
function(v) return (v == 0) and "所有击杀" or (tostring(v) .. " 级及以上") end,
|
||
function() SFrames.Chat:RefreshConfigFrame() end
|
||
))
|
||
end
|
||
|
||
local close = CreateCfgButton(panel, "保存", 430, -588, 150, 28, function()
|
||
SFrames.Chat.configFrame:Hide()
|
||
local db = EnsureDB()
|
||
|
||
-- Send Hardcore specific commands on Save
|
||
if SFrames.Chat.initialHcGlobalDisable ~= nil and db.hcGlobalDisable ~= SFrames.Chat.initialHcGlobalDisable then
|
||
SendChatMessage(".hcc", "SAY")
|
||
SFrames.Chat.initialHcGlobalDisable = db.hcGlobalDisable
|
||
end
|
||
|
||
if db.hcDeathDisable then
|
||
SendChatMessage(".hcm 60", "SAY")
|
||
elseif db.hcDeathLevelMin then
|
||
SendChatMessage(".hcm " .. tostring(db.hcDeathLevelMin), "SAY")
|
||
else
|
||
SendChatMessage(".hcm 0", "SAY")
|
||
end
|
||
end)
|
||
StyleCfgButton(close)
|
||
AddBtnIcon(close, "save")
|
||
|
||
local reload = CreateCfgButton(panel, "保存并重载", 598, -588, 150, 28, function()
|
||
ReloadUI()
|
||
end)
|
||
StyleCfgButton(reload)
|
||
AddBtnIcon(reload, "save")
|
||
|
||
self.configControls = controls
|
||
self.configPages = pages
|
||
self.configFrame = panel
|
||
self.translateConfigFrame = panel
|
||
|
||
-- Auto-refresh channel list while panel is visible
|
||
local channelPollTimer = 0
|
||
local lastChannelCount = 0
|
||
panel:SetScript("OnUpdate", function()
|
||
channelPollTimer = channelPollTimer + (arg1 or 0)
|
||
if channelPollTimer >= 3 then
|
||
channelPollTimer = 0
|
||
if not this:IsShown() then return end
|
||
local channels = SFrames.Chat:GetJoinedChannels()
|
||
local n = table.getn(channels)
|
||
if n ~= lastChannelCount then
|
||
lastChannelCount = n
|
||
SFrames.Chat:RefreshConfigFrame()
|
||
end
|
||
end
|
||
end)
|
||
|
||
self:ShowConfigPage(self.configActivePage or "window")
|
||
end
|
||
|
||
function SFrames.Chat:OpenConfigFrame(pageKey)
|
||
self:EnsureConfigFrame()
|
||
if not self.configFrame then return end
|
||
self.initialHcGlobalDisable = EnsureDB().hcGlobalDisable
|
||
self:ShowConfigPage(pageKey or self.configActivePage or "window")
|
||
self:RefreshConfigFrame()
|
||
self.configFrame:Show()
|
||
self.configFrame:Raise()
|
||
end
|
||
|
||
function SFrames.Chat:ToggleConfigFrame(pageKey)
|
||
self:EnsureConfigFrame()
|
||
if not self.configFrame then return end
|
||
|
||
local targetPage = pageKey or self.configActivePage or "window"
|
||
if self.configFrame:IsShown() then
|
||
if pageKey and self.configActivePage ~= targetPage then
|
||
self:ShowConfigPage(targetPage)
|
||
self:RefreshConfigFrame()
|
||
self.configFrame:Raise()
|
||
else
|
||
self.configFrame:Hide()
|
||
end
|
||
else
|
||
self:OpenConfigFrame(targetPage)
|
||
end
|
||
end
|
||
|
||
function SFrames.Chat:HandleSlash(input)
|
||
local text = Trim(input)
|
||
local _, _, cmd, rest = string.find(text, "^(%S*)%s*(.-)$")
|
||
cmd = string.lower(cmd or "")
|
||
rest = rest or ""
|
||
|
||
if cmd == "" or cmd == "ui" or cmd == "config" or cmd == "panel" then
|
||
self:ToggleConfigFrame()
|
||
return
|
||
end
|
||
|
||
if cmd == "help" then
|
||
self:PrintHelp()
|
||
return
|
||
end
|
||
|
||
if cmd == "reset" then
|
||
self:ResetPosition()
|
||
return
|
||
end
|
||
|
||
if cmd == "size" then
|
||
local _, _, w, h = string.find(rest, "^(%d+)%s+(%d+)$")
|
||
w = tonumber(w)
|
||
h = tonumber(h)
|
||
if not w or not h then
|
||
if SFrames and SFrames.Print then
|
||
SFrames:Print("闂備焦妞垮鍧楀礉瀹ュ洦鍏? /nui chat size <width> <height>")
|
||
end
|
||
return
|
||
end
|
||
self:SetWindowSize(w, h)
|
||
if SFrames and SFrames.Print then
|
||
SFrames:Print("闂備胶鍘у畷顒勬晝閵堝桅濠㈣泛顭ù鏍煕閳╁喚娈曟俊娴嬪亾闂? " .. tostring(math.floor(w + 0.5)) .. "x" .. tostring(math.floor(h + 0.5)))
|
||
end
|
||
return
|
||
end
|
||
|
||
if cmd == "scale" then
|
||
local v = tonumber(rest)
|
||
if not v then
|
||
if SFrames and SFrames.Print then
|
||
SFrames:Print("闂備焦妞垮鍧楀礉瀹ュ洦鍏? /nui chat scale <0.75-1.4>")
|
||
end
|
||
return
|
||
end
|
||
self:SetWindowScale(v)
|
||
if SFrames and SFrames.Print then
|
||
SFrames:Print("闂備胶鍘у畷顒勬晝閵堝桅濠㈣泛顭ù鏍煕閳╁啰鎳呯紒杈ㄥ哺閺岋繝鍩€? " .. string.format("%.2f", Clamp(v, 0.75, 1.4)))
|
||
end
|
||
return
|
||
end
|
||
|
||
if cmd == "font" then
|
||
local v = tonumber(rest)
|
||
if not v then
|
||
if SFrames and SFrames.Print then
|
||
SFrames:Print("闂備焦妞垮鍧楀礉瀹ュ洦鍏? /nui chat font <10-18>")
|
||
end
|
||
return
|
||
end
|
||
self:SetFontSize(v)
|
||
if SFrames and SFrames.Print then
|
||
SFrames:Print("闂備胶鍘у畷顒勬晝閵堝桅濠㈣泛澶囬崑鎾斥槈濞嗗秳娌紓? " .. tostring(math.floor(Clamp(v, 10, 18) + 0.5)))
|
||
end
|
||
return
|
||
end
|
||
|
||
if cmd == "tab" then
|
||
local _, _, sub, subArgs = string.find(rest, "^(%S*)%s*(.-)$")
|
||
sub = string.lower(sub or "")
|
||
subArgs = subArgs or ""
|
||
|
||
if sub == "new" then
|
||
if Trim(subArgs) == "" then
|
||
self:PromptNewTab()
|
||
else
|
||
self:AddTab(subArgs)
|
||
end
|
||
return
|
||
elseif sub == "del" or sub == "delete" then
|
||
self:DeleteActiveTab()
|
||
return
|
||
elseif sub == "next" then
|
||
self:StepTab(1)
|
||
return
|
||
elseif sub == "prev" then
|
||
self:StepTab(-1)
|
||
return
|
||
elseif sub == "rename" then
|
||
local ok = self:RenameActiveTab(subArgs)
|
||
if (not ok) and SFrames and SFrames.Print then
|
||
SFrames:Print("闂備焦妞垮鍧楀礉瀹ュ洦鍏? /nui chat tab rename <name>")
|
||
end
|
||
return
|
||
else
|
||
local idx = tonumber(sub)
|
||
if idx then
|
||
self:SetActiveTab(idx)
|
||
return
|
||
end
|
||
end
|
||
|
||
if SFrames and SFrames.Print then
|
||
SFrames:Print("闂備焦妞垮鍧楀礉瀹ュ洦鍏? /nui chat tab new|del|next|prev|rename|<index>")
|
||
end
|
||
return
|
||
end
|
||
|
||
if cmd == "filters" then
|
||
self:PrintFilters()
|
||
return
|
||
end
|
||
|
||
if cmd == "filter" then
|
||
local _, _, key, state = string.find(rest, "^(%S+)%s*(%S*)$")
|
||
key = string.lower(key or "")
|
||
state = string.lower(state or "")
|
||
|
||
local matched = nil
|
||
for i = 1, table.getn(FILTER_DEFS) do
|
||
local def = FILTER_DEFS[i]
|
||
if key == def.key then
|
||
matched = def.key
|
||
break
|
||
end
|
||
end
|
||
|
||
if not matched then
|
||
if SFrames and SFrames.Print then
|
||
SFrames:Print("闂備礁鎼悧婊勭閻愮儤鍋傞柍鈺佸暞娴溿倝鏌涢妷锝呭濠殿喓鍨归—鍐Χ閸涱垳鏆涢梺閫炲苯澧柛濠佺矙椤㈡岸顢楁担鐟邦€撻柣鐘充航閸斿秹寮?/nui chat filters")
|
||
end
|
||
return
|
||
end
|
||
|
||
local enabled = nil
|
||
if state == "on" or state == "1" or state == "true" then
|
||
enabled = true
|
||
elseif state == "off" or state == "0" or state == "false" then
|
||
enabled = false
|
||
end
|
||
|
||
if enabled == nil then
|
||
if SFrames and SFrames.Print then
|
||
SFrames:Print("闂備焦妞垮鍧楀礉瀹ュ洦鍏? /nui chat filter <key> on|off")
|
||
end
|
||
return
|
||
end
|
||
|
||
self:SetActiveTabFilter(matched, enabled)
|
||
self:ApplyConfig()
|
||
if SFrames and SFrames.Print then
|
||
SFrames:Print("闂佸搫顦弲娑樏洪敃鍌氱?" .. matched .. " = " .. BoolText(enabled))
|
||
end
|
||
return
|
||
end
|
||
|
||
if SFrames and SFrames.Print then
|
||
SFrames:Print("闂備礁鎼悧婊勭閻愮儤鍋傞柨鐔哄У閸ゅ倸鈹戦悩鎻掆偓鑸电椤栫偞鐓曟繛鎴炵懃閻忓﹪鏌熼煬鎻掆偓婵囦繆閹绢喖纾兼繝鍨姇濞堝弶绻涙潏鍓хК婵炲娲熷?/nui chat help")
|
||
end
|
||
end
|
||
|
||
function SFrames.Chat:CreateContainer()
|
||
if self.frame then return end
|
||
|
||
local f = CreateFrame("Frame", "SFramesChatContainer", UIParent)
|
||
f:SetWidth(DEFAULTS.width)
|
||
f:SetHeight(DEFAULTS.height)
|
||
f:SetPoint("BOTTOMLEFT", UIParent, "BOTTOMLEFT", 30, 30)
|
||
f:SetMovable(true)
|
||
f:EnableMouse(true)
|
||
f:RegisterForDrag("LeftButton")
|
||
f:SetClampedToScreen(true)
|
||
f:SetFrameStrata("LOW")
|
||
f:SetResizable(true)
|
||
if f.SetMinResize then f:SetMinResize(320, 120) end
|
||
if f.SetMaxResize then f:SetMaxResize(900, 460) end
|
||
|
||
f:SetScript("OnDragStart", function()
|
||
if IsAltKeyDown() or (SFrames and SFrames.isUnlocked) then
|
||
this:StartMoving()
|
||
end
|
||
end)
|
||
|
||
f:SetScript("OnDragStop", function()
|
||
this:StopMovingOrSizing()
|
||
if SFrames and SFrames.Chat then
|
||
SFrames.Chat:SavePosition()
|
||
end
|
||
end)
|
||
|
||
f:SetScript("OnSizeChanged", function()
|
||
if SFrames and SFrames.Chat then
|
||
SFrames.Chat:SaveSizeFromFrame()
|
||
SFrames.Chat:RefreshChatBounds()
|
||
SFrames.Chat:RefreshTabButtons()
|
||
end
|
||
end)
|
||
|
||
f:SetBackdrop({
|
||
bgFile = "Interface\\Tooltips\\UI-Tooltip-Background",
|
||
edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
|
||
tile = true, tileSize = 16, edgeSize = 14,
|
||
insets = { left = 3, right = 3, top = 3, bottom = 3 },
|
||
})
|
||
f:SetBackdropColor(CFG_THEME.panelBg[1], CFG_THEME.panelBg[2], CFG_THEME.panelBg[3], 0.9)
|
||
f:SetBackdropBorderColor(CFG_THEME.panelBorder[1], CFG_THEME.panelBorder[2], CFG_THEME.panelBorder[3], 0.95)
|
||
local chatShadow = CreateFrame("Frame", nil, f)
|
||
chatShadow:SetPoint("TOPLEFT", f, "TOPLEFT", -5, 5)
|
||
chatShadow:SetPoint("BOTTOMRIGHT", f, "BOTTOMRIGHT", 5, -5)
|
||
chatShadow:SetFrameLevel(math.max(f:GetFrameLevel() - 1, 0))
|
||
chatShadow:SetBackdrop({
|
||
bgFile = "Interface\\Tooltips\\UI-Tooltip-Background",
|
||
edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
|
||
tile = true, tileSize = 16, edgeSize = 16,
|
||
insets = { left = 4, right = 4, top = 4, bottom = 4 },
|
||
})
|
||
chatShadow:SetBackdropColor(0, 0, 0, 0.55)
|
||
chatShadow:SetBackdropBorderColor(0, 0, 0, 0.4)
|
||
|
||
local topGlow = f:CreateTexture(nil, "BACKGROUND")
|
||
topGlow:SetTexture("Interface\\Buttons\\WHITE8X8")
|
||
topGlow:SetPoint("TOPLEFT", f, "TOPLEFT", 1, -1)
|
||
topGlow:SetPoint("TOPRIGHT", f, "TOPRIGHT", -1, -1)
|
||
topGlow:SetHeight(20)
|
||
topGlow:SetVertexColor(1, 0.58, 0.82, 0.2)
|
||
topGlow:Hide()
|
||
f.topGlow = topGlow
|
||
|
||
local topLine = f:CreateTexture(nil, "BORDER")
|
||
topLine:SetTexture("Interface\\Buttons\\WHITE8X8")
|
||
topLine:SetPoint("TOPLEFT", f, "TOPLEFT", 1, -22)
|
||
topLine:SetPoint("TOPRIGHT", f, "TOPRIGHT", -1, -22)
|
||
topLine:SetHeight(1)
|
||
topLine:SetVertexColor(1, 0.69, 0.88, 0.85)
|
||
topLine:Hide()
|
||
f.topLine = topLine
|
||
|
||
local title = CreateFont(f, 11, "LEFT")
|
||
title:SetPoint("TOPLEFT", f, "TOPLEFT", 26, -7)
|
||
title:SetText("Nanami")
|
||
title:SetTextColor(1, 0.82, 0.93)
|
||
f.title = title
|
||
local configButton = CreateFrame("Button", nil, f)
|
||
configButton:SetWidth(14)
|
||
configButton:SetHeight(14)
|
||
configButton:SetPoint("TOPRIGHT", f, "TOPRIGHT", -8, -7)
|
||
configButton:SetHitRectInsets(-4, -4, -4, -4)
|
||
configButton:SetFrameStrata("HIGH")
|
||
configButton:SetFrameLevel(f:GetFrameLevel() + 20)
|
||
configButton:RegisterForClicks("LeftButtonUp")
|
||
|
||
local cfgIcon = SFrames:CreateIcon(configButton, "settings", 12)
|
||
cfgIcon:SetPoint("CENTER", configButton, "CENTER", 0, 0)
|
||
configButton.cfgIcon = cfgIcon
|
||
|
||
configButton:SetScript("OnClick", function()
|
||
if SFrames and SFrames.Chat then
|
||
SFrames.Chat:ToggleConfigFrame()
|
||
end
|
||
end)
|
||
configButton:SetScript("OnEnter", function()
|
||
GameTooltip:SetOwner(this, "ANCHOR_TOP")
|
||
GameTooltip:ClearLines()
|
||
GameTooltip:AddLine("聊天设置", 1, 0.84, 0.94)
|
||
GameTooltip:AddLine("打开聊天配置面板", 0.85, 0.85, 0.85)
|
||
GameTooltip:Show()
|
||
end)
|
||
configButton:SetScript("OnLeave", function()
|
||
GameTooltip:Hide()
|
||
end)
|
||
f.configButton = configButton
|
||
|
||
local whisperButton = CreateFrame("Button", nil, f)
|
||
whisperButton:SetFrameStrata("HIGH")
|
||
whisperButton:SetWidth(14)
|
||
whisperButton:SetHeight(14)
|
||
if SFramesDB and SFramesDB.whisperBtnPos then
|
||
local pos = SFramesDB.whisperBtnPos
|
||
whisperButton:SetPoint(pos.p, f, pos.rp, pos.x, pos.y)
|
||
else
|
||
whisperButton:SetPoint("RIGHT", configButton, "LEFT", -6, 0)
|
||
end
|
||
whisperButton:SetHitRectInsets(-4, -4, -4, -4)
|
||
whisperButton:SetFrameLevel(f:GetFrameLevel() + 20)
|
||
whisperButton:RegisterForClicks("LeftButtonUp")
|
||
whisperButton:RegisterForDrag("LeftButton")
|
||
whisperButton:SetMovable(true)
|
||
whisperButton:SetScript("OnDragStart", function()
|
||
if IsAltKeyDown() then
|
||
this:StartMoving()
|
||
end
|
||
end)
|
||
whisperButton:SetScript("OnDragStop", function()
|
||
this:StopMovingOrSizing()
|
||
if not SFramesDB then SFramesDB = {} end
|
||
-- Convert to position relative to parent frame f to ensure correct restore after reload
|
||
local absX, absY = this:GetCenter()
|
||
local fX, fY = f:GetCenter()
|
||
local fW, fH = f:GetWidth(), f:GetHeight()
|
||
local relX = absX - fX
|
||
local relY = absY - fY
|
||
SFramesDB.whisperBtnPos = { p = "CENTER", rp = "CENTER", x = relX, y = relY }
|
||
end)
|
||
|
||
if SFrames and SFrames.CreateBackdrop then
|
||
SFrames:CreateBackdrop(whisperButton)
|
||
elseif whisperButton.SetBackdrop then
|
||
whisperButton:SetBackdrop({
|
||
bgFile = "Interface\\Buttons\\WHITE8X8",
|
||
edgeFile = "Interface\\Buttons\\WHITE8X8",
|
||
tile = false, edgeSize = 1,
|
||
insets = { left = 1, right = 1, top = 1, bottom = 1 },
|
||
})
|
||
end
|
||
if whisperButton.SetBackdropColor then
|
||
whisperButton:SetBackdropColor(0.1, 0.08, 0.12, 0.9)
|
||
end
|
||
if whisperButton.SetBackdropBorderColor then
|
||
whisperButton:SetBackdropBorderColor(0.5, 0.8, 1.0, 0.92)
|
||
end
|
||
|
||
local whisperIcon = SFrames:CreateIcon(whisperButton, "chat", 12)
|
||
whisperIcon:SetDrawLayer("ARTWORK")
|
||
whisperIcon:SetPoint("CENTER", whisperButton, "CENTER", 0, 0)
|
||
whisperIcon:SetVertexColor(0.6, 0.85, 1, 0.95)
|
||
|
||
local flashFrame = CreateFrame("Frame", nil, whisperButton)
|
||
flashFrame:SetAllPoints(whisperButton)
|
||
flashFrame:SetFrameLevel(whisperButton:GetFrameLevel() + 1)
|
||
local flashTex = flashFrame:CreateTexture(nil, "OVERLAY")
|
||
flashTex:SetTexture("Interface\\Buttons\\WHITE8X8")
|
||
flashTex:SetAllPoints(flashFrame)
|
||
flashTex:SetVertexColor(1, 0.8, 0.2, 0.4)
|
||
flashTex:SetBlendMode("ADD")
|
||
flashFrame.tex = flashTex
|
||
flashFrame:Hide()
|
||
whisperButton.flashFrame = flashFrame
|
||
|
||
flashFrame:SetScript("OnUpdate", function()
|
||
if not this:IsShown() then return end
|
||
this.elapsed = (this.elapsed or 0) + arg1
|
||
local alpha = math.abs(math.sin(this.elapsed * 4)) * 0.5 + 0.1
|
||
this.tex:SetAlpha(alpha)
|
||
end)
|
||
|
||
whisperButton:SetScript("OnClick", function()
|
||
if SFrames and SFrames.Whisper then
|
||
SFrames.Whisper:Toggle()
|
||
end
|
||
this.hasUnread = false
|
||
if this.flashFrame then this.flashFrame:Hide() end
|
||
end)
|
||
whisperButton:SetScript("OnEnter", function()
|
||
if this.SetBackdropColor then this:SetBackdropColor(0.16, 0.12, 0.2, 0.96) end
|
||
GameTooltip:SetOwner(this, "ANCHOR_TOP")
|
||
GameTooltip:ClearLines()
|
||
GameTooltip:AddLine("私聊对话管理器", 0.6, 0.8, 1)
|
||
if this.hasUnread then
|
||
GameTooltip:AddLine("你有未读的私聊消息!", 1, 0.8, 0.2)
|
||
end
|
||
GameTooltip:AddLine("Alt + 拖动 可移动图标", 0.6, 0.6, 0.6)
|
||
GameTooltip:Show()
|
||
end)
|
||
whisperButton:SetScript("OnLeave", function()
|
||
if this.SetBackdropColor then this:SetBackdropColor(0.1, 0.08, 0.12, 0.9) end
|
||
GameTooltip:Hide()
|
||
end)
|
||
|
||
f.whisperButton = whisperButton
|
||
|
||
local hint = CreateFont(f, 10, "RIGHT")
|
||
hint:SetPoint("RIGHT", whisperButton, "LEFT", -8, 0)
|
||
hint:SetText("")
|
||
hint:SetTextColor(0.86, 0.78, 0.85)
|
||
hint:Hide()
|
||
f.hint = hint
|
||
|
||
local leftCat = SFrames:CreateIcon(f, "logo", 14)
|
||
leftCat:SetDrawLayer("OVERLAY")
|
||
leftCat:SetPoint("TOPLEFT", f, "TOPLEFT", 8, -5)
|
||
leftCat:SetVertexColor(1, 0.82, 0.9, 0.8)
|
||
f.leftCat = leftCat
|
||
|
||
local watermark = SFrames:CreateIcon(f, "logo", 62)
|
||
watermark:SetDrawLayer("BACKGROUND")
|
||
watermark:SetPoint("BOTTOMRIGHT", f, "BOTTOMRIGHT", -8, 8)
|
||
watermark:SetVertexColor(1, 0.78, 0.9, 0.08)
|
||
f.watermark = watermark
|
||
|
||
local shade = f:CreateTexture(nil, "BACKGROUND")
|
||
shade:SetTexture("Interface\\Buttons\\WHITE8X8")
|
||
shade:SetVertexColor(0, 0, 0, 0.2)
|
||
shade:SetPoint("TOPLEFT", f, "TOPLEFT", 8, -30)
|
||
shade:SetPoint("BOTTOMRIGHT", f, "BOTTOMRIGHT", -8, 8)
|
||
f.innerShade = shade
|
||
|
||
local tabBar = CreateFrame("Frame", nil, f)
|
||
tabBar:SetPoint("LEFT", title, "RIGHT", 10, -1)
|
||
tabBar:SetPoint("RIGHT", configButton, "LEFT", -8, -1)
|
||
tabBar:SetHeight(18)
|
||
f.tabBar = tabBar
|
||
|
||
local inner = CreateFrame("Frame", nil, f)
|
||
inner:SetPoint("TOPLEFT", f, "TOPLEFT", 10, -30)
|
||
inner:SetPoint("BOTTOMRIGHT", f, "BOTTOMRIGHT", -20, 8)
|
||
f.inner = inner
|
||
|
||
local fontPath = (SFrames and SFrames.GetFont and SFrames:GetFont()) or "Fonts\\ARIALN.TTF"
|
||
|
||
local scrollUpBtn = CreateFrame("Button", nil, f)
|
||
scrollUpBtn:SetWidth(16)
|
||
scrollUpBtn:SetHeight(16)
|
||
scrollUpBtn:SetPoint("TOPRIGHT", f, "TOPRIGHT", -4, -30)
|
||
scrollUpBtn:SetFrameStrata("HIGH")
|
||
local upTxt = scrollUpBtn:CreateFontString(nil, "OVERLAY")
|
||
upTxt:SetFont(fontPath, 11, "OUTLINE")
|
||
upTxt:SetPoint("CENTER", scrollUpBtn, "CENTER", 1, 1)
|
||
upTxt:SetText("▲")
|
||
upTxt:SetTextColor(0.5, 0.55, 0.6)
|
||
scrollUpBtn:SetScript("OnEnter", function() upTxt:SetTextColor(0.9, 0.85, 1) end)
|
||
scrollUpBtn:SetScript("OnLeave", function() upTxt:SetTextColor(0.5, 0.55, 0.6) end)
|
||
scrollUpBtn:SetScript("OnClick", function()
|
||
if SFrames.Chat.chatFrame then
|
||
SFrames.Chat.chatFrame:ScrollToTop()
|
||
end
|
||
end)
|
||
|
||
local scrollDownBtn = CreateFrame("Button", nil, f)
|
||
scrollDownBtn:SetWidth(16)
|
||
scrollDownBtn:SetHeight(16)
|
||
scrollDownBtn:SetPoint("BOTTOMRIGHT", f, "BOTTOMRIGHT", -4, 20)
|
||
scrollDownBtn:SetFrameStrata("HIGH")
|
||
local dnTxt = scrollDownBtn:CreateFontString(nil, "OVERLAY")
|
||
dnTxt:SetFont(fontPath, 11, "OUTLINE")
|
||
dnTxt:SetPoint("CENTER", scrollDownBtn, "CENTER", 1, -1)
|
||
dnTxt:SetText("▼")
|
||
dnTxt:SetTextColor(0.5, 0.55, 0.6)
|
||
scrollDownBtn:SetScript("OnEnter", function() dnTxt:SetTextColor(0.9, 0.85, 1) end)
|
||
scrollDownBtn:SetScript("OnLeave", function() dnTxt:SetTextColor(0.5, 0.55, 0.6) end)
|
||
scrollDownBtn:SetScript("OnClick", function()
|
||
if SFrames.Chat.chatFrame then
|
||
SFrames.Chat.chatFrame:ScrollToBottom()
|
||
end
|
||
end)
|
||
|
||
local scrollTrack = f:CreateTexture(nil, "BACKGROUND")
|
||
scrollTrack:SetTexture("Interface\\Buttons\\WHITE8X8")
|
||
scrollTrack:SetPoint("TOP", scrollUpBtn, "BOTTOM", 0, -2)
|
||
scrollTrack:SetPoint("BOTTOM", scrollDownBtn, "TOP", 0, 2)
|
||
scrollTrack:SetWidth(4)
|
||
scrollTrack:SetVertexColor(0.18, 0.19, 0.22, 0.9)
|
||
|
||
local resize = CreateFrame("Button", nil, f)
|
||
resize:SetWidth(16)
|
||
resize:SetHeight(16)
|
||
resize:SetPoint("BOTTOMRIGHT", f, "BOTTOMRIGHT", -1, 1)
|
||
resize:RegisterForDrag("LeftButton")
|
||
resize:EnableMouse(true)
|
||
resize:SetFrameStrata("HIGH")
|
||
|
||
local resizeTex = resize:CreateTexture(nil, "ARTWORK")
|
||
resizeTex:SetTexture("Interface\\ChatFrame\\UI-ChatIM-SizeGrabber-Up")
|
||
resizeTex:SetAllPoints(resize)
|
||
resizeTex:SetVertexColor(1, 0.74, 0.88, 0.92)
|
||
|
||
resize:SetScript("OnEnter", function()
|
||
resizeTex:SetVertexColor(1, 0.86, 0.94, 1)
|
||
end)
|
||
resize:SetScript("OnLeave", function()
|
||
resizeTex:SetVertexColor(1, 0.74, 0.88, 0.92)
|
||
end)
|
||
resize:SetScript("OnMouseDown", function()
|
||
if not (IsAltKeyDown() or (SFrames and SFrames.isUnlocked)) then return end
|
||
f:StartSizing("BOTTOMRIGHT")
|
||
end)
|
||
resize:SetScript("OnMouseUp", function()
|
||
f:StopMovingOrSizing()
|
||
if SFrames and SFrames.Chat then
|
||
SFrames.Chat:SaveSizeFromFrame()
|
||
SFrames.Chat:ApplyConfig()
|
||
end
|
||
end)
|
||
|
||
f.resizeHandle = resize
|
||
self.frame = f
|
||
|
||
if not self.hiddenConfigButton then
|
||
local hiddenConfigButton = CreateFrame("Button", "SFramesChatHiddenConfigButton", UIParent, "UIPanelButtonTemplate")
|
||
hiddenConfigButton:SetWidth(74)
|
||
hiddenConfigButton:SetHeight(22)
|
||
hiddenConfigButton:SetPoint("BOTTOMLEFT", UIParent, "BOTTOMLEFT", 14, 132)
|
||
hiddenConfigButton:SetFrameStrata("DIALOG")
|
||
hiddenConfigButton:SetFrameLevel(220)
|
||
hiddenConfigButton:SetText("Chat Set")
|
||
hiddenConfigButton:SetScript("OnClick", function()
|
||
if SFrames and SFrames.Chat then
|
||
SFrames.Chat:ToggleConfigFrame()
|
||
end
|
||
end)
|
||
hiddenConfigButton:SetScript("OnEnter", function()
|
||
GameTooltip:SetOwner(this, "ANCHOR_TOPLEFT")
|
||
GameTooltip:ClearLines()
|
||
GameTooltip:AddLine("Chat Settings", 1, 0.84, 0.94)
|
||
GameTooltip:AddLine("Shown while chat UI is hidden.", 0.86, 0.86, 0.86)
|
||
GameTooltip:AddLine("Click to open Nanami chat config.", 0.86, 0.86, 0.86)
|
||
GameTooltip:Show()
|
||
end)
|
||
hiddenConfigButton:SetScript("OnLeave", function()
|
||
GameTooltip:Hide()
|
||
end)
|
||
StyleCfgButton(hiddenConfigButton)
|
||
hiddenConfigButton:Hide()
|
||
self.hiddenConfigButton = hiddenConfigButton
|
||
end
|
||
|
||
local db = EnsureDB()
|
||
local saved = SFramesDB and SFramesDB.Positions and SFramesDB.Positions["ChatFrame"]
|
||
if saved then
|
||
f:ClearAllPoints()
|
||
f:SetPoint(saved.point, UIParent, saved.relativePoint, saved.xOfs, saved.yOfs)
|
||
else
|
||
f:SetPoint("BOTTOMLEFT", UIParent, "BOTTOMLEFT", 30, 30)
|
||
end
|
||
f:SetWidth(Clamp(db.width, 320, 900))
|
||
f:SetHeight(Clamp(db.height, 120, 460))
|
||
|
||
-- Background alpha: always show at configured bgAlpha
|
||
local bgA = Clamp(db.bgAlpha or DEFAULTS.bgAlpha, 0, 1)
|
||
f:SetBackdropColor(CFG_THEME.panelBg[1], CFG_THEME.panelBg[2], CFG_THEME.panelBg[3], bgA)
|
||
end
|
||
|
||
function SFrames.Chat:HideDefaultChrome()
|
||
for _, objectName in ipairs(HIDDEN_OBJECTS) do
|
||
ForceHide(_G[objectName])
|
||
end
|
||
end
|
||
|
||
function SFrames.Chat:HideTabChrome()
|
||
local maxWindows = tonumber(NUM_CHAT_WINDOWS) or 7
|
||
|
||
for i = 1, maxWindows do
|
||
local prefix = "ChatFrame" .. i
|
||
local elements = {
|
||
prefix .. "Tab",
|
||
prefix .. "ButtonFrame",
|
||
prefix .. "UpButton",
|
||
prefix .. "DownButton",
|
||
prefix .. "BottomButton",
|
||
prefix .. "TabLeft",
|
||
prefix .. "TabMiddle",
|
||
prefix .. "TabRight",
|
||
prefix .. "TabSelectedLeft",
|
||
prefix .. "TabSelectedMiddle",
|
||
prefix .. "TabSelectedRight",
|
||
prefix .. "TabHighlightLeft",
|
||
prefix .. "TabHighlightMiddle",
|
||
prefix .. "TabHighlightRight",
|
||
prefix .. "TabFlash",
|
||
prefix .. "TabGlow",
|
||
}
|
||
|
||
for _, objectName in ipairs(elements) do
|
||
ForceHide(_G[objectName])
|
||
end
|
||
|
||
local text = _G[prefix .. "TabText"]
|
||
if text then
|
||
ForceInvisible(text)
|
||
text.Show = Dummy
|
||
end
|
||
end
|
||
|
||
local dock = _G["GeneralDockManager"]
|
||
if dock then
|
||
if dock.SetAlpha then dock:SetAlpha(0) end
|
||
if dock.EnableMouse then dock:EnableMouse(false) end
|
||
end
|
||
end
|
||
|
||
function SFrames.Chat:IsManagedChatWindow(chatFrame)
|
||
if not chatFrame then return false end
|
||
local name = chatFrame.GetName and chatFrame:GetName()
|
||
if type(name) ~= "string" then return false end
|
||
if not string.find(name, "^ChatFrame%d+$") then return false end
|
||
if self.frame and self.frame.inner and chatFrame.GetParent and chatFrame:GetParent() == self.frame.inner then
|
||
return true
|
||
end
|
||
if self.GetTabIndexForChatFrame and self:GetTabIndexForChatFrame(chatFrame) then
|
||
return true
|
||
end
|
||
return false
|
||
end
|
||
|
||
function SFrames.Chat:HookWindowDragBlocker()
|
||
if self.dragBlockerHooked then return end
|
||
|
||
local function ResolveObjectToChatFrame(object)
|
||
local probe = object
|
||
local steps = 0
|
||
while probe and steps < 10 do
|
||
if probe.chatFrame then
|
||
probe = probe.chatFrame
|
||
end
|
||
|
||
if probe and probe.GetName then
|
||
local name = probe:GetName()
|
||
if type(name) == "string" then
|
||
local _, _, idx = string.find(name, "^ChatFrame(%d+)Tab$")
|
||
if idx then
|
||
local cf = _G["ChatFrame" .. idx]
|
||
if cf then
|
||
return cf
|
||
end
|
||
end
|
||
if string.find(name, "^ChatFrame%d+$") then
|
||
return probe
|
||
end
|
||
end
|
||
end
|
||
|
||
if not (probe and probe.GetParent) then
|
||
break
|
||
end
|
||
probe = probe:GetParent()
|
||
steps = steps + 1
|
||
end
|
||
return nil
|
||
end
|
||
|
||
local function ResolveTarget(frame)
|
||
local direct = ResolveObjectToChatFrame(frame)
|
||
if direct then return direct end
|
||
return ResolveObjectToChatFrame(this)
|
||
end
|
||
|
||
local function Wrap(name)
|
||
local orig = _G[name]
|
||
if type(orig) ~= "function" then return false end
|
||
_G[name] = function(frame, a, b, c, d, e)
|
||
local target = ResolveTarget(frame)
|
||
if SFrames and SFrames.Chat and SFrames.Chat.IsManagedChatWindow and SFrames.Chat:IsManagedChatWindow(target) then
|
||
if target and target.StopMovingOrSizing then
|
||
target:StopMovingOrSizing()
|
||
end
|
||
return
|
||
end
|
||
return orig(frame, a, b, c, d, e)
|
||
end
|
||
return true
|
||
end
|
||
|
||
local hookedAny = false
|
||
if Wrap("FCF_StartWindowDrag") then hookedAny = true end
|
||
if Wrap("FCF_StartMoving") then hookedAny = true end
|
||
if Wrap("FCF_StartDrag") then hookedAny = true end
|
||
if Wrap("FloatingChatFrame_OnMouseDown") then hookedAny = true end
|
||
|
||
if hookedAny then
|
||
self.dragBlockerHooked = true
|
||
end
|
||
end
|
||
|
||
function SFrames.Chat:EnforceChatWindowLock(chatFrame)
|
||
if not chatFrame then return end
|
||
if FCF_SetLocked then
|
||
pcall(function() FCF_SetLocked(chatFrame, 1) end)
|
||
end
|
||
if FCF_SetWindowLocked then
|
||
pcall(function() FCF_SetWindowLocked(chatFrame, 1) end)
|
||
end
|
||
if chatFrame.SetMovable then
|
||
chatFrame:SetMovable(false)
|
||
end
|
||
if not chatFrame.sfRegisterForDragBlocked and chatFrame.RegisterForDrag then
|
||
pcall(function() chatFrame:RegisterForDrag("Button4") end)
|
||
chatFrame.sfRegisterForDragBlocked = true
|
||
chatFrame.sfOriginalRegisterForDrag = chatFrame.RegisterForDrag
|
||
chatFrame.RegisterForDrag = function() end
|
||
end
|
||
if not chatFrame.sfStartMovingBlocked and chatFrame.StartMoving then
|
||
chatFrame.sfStartMovingBlocked = true
|
||
chatFrame.StartMoving = function(frame)
|
||
if frame and frame.StopMovingOrSizing then
|
||
frame:StopMovingOrSizing()
|
||
end
|
||
end
|
||
end
|
||
if not chatFrame.sfStartSizingBlocked and chatFrame.StartSizing then
|
||
chatFrame.sfStartSizingBlocked = true
|
||
chatFrame.StartSizing = function(frame)
|
||
if frame and frame.StopMovingOrSizing then
|
||
frame:StopMovingOrSizing()
|
||
end
|
||
end
|
||
end
|
||
if chatFrame.SetScript then
|
||
chatFrame:SetScript("OnMouseDown", function()
|
||
if this and this.StopMovingOrSizing then
|
||
this:StopMovingOrSizing()
|
||
end
|
||
if (IsAltKeyDown() or (SFrames and SFrames.isUnlocked)) and SFrames.Chat and SFrames.Chat.frame then
|
||
SFrames.Chat.frame:StartMoving()
|
||
SFrames.Chat._contentDragging = true
|
||
end
|
||
end)
|
||
chatFrame:SetScript("OnMouseUp", function()
|
||
if SFrames.Chat and SFrames.Chat._contentDragging and SFrames.Chat.frame then
|
||
SFrames.Chat.frame:StopMovingOrSizing()
|
||
SFrames.Chat:SavePosition()
|
||
SFrames.Chat._contentDragging = nil
|
||
end
|
||
if this and this.StopMovingOrSizing then
|
||
this:StopMovingOrSizing()
|
||
end
|
||
end)
|
||
chatFrame:SetScript("OnDragStart", function()
|
||
if this and this.StopMovingOrSizing then
|
||
this:StopMovingOrSizing()
|
||
end
|
||
end)
|
||
chatFrame:SetScript("OnDragStop", function()
|
||
if SFrames.Chat and SFrames.Chat._contentDragging and SFrames.Chat.frame then
|
||
SFrames.Chat.frame:StopMovingOrSizing()
|
||
SFrames.Chat:SavePosition()
|
||
SFrames.Chat._contentDragging = nil
|
||
end
|
||
if this and this.StopMovingOrSizing then
|
||
this:StopMovingOrSizing()
|
||
end
|
||
end)
|
||
end
|
||
if chatFrame.SetUserPlaced then
|
||
chatFrame:SetUserPlaced(false)
|
||
end
|
||
if chatFrame.StopMovingOrSizing then
|
||
chatFrame:StopMovingOrSizing()
|
||
end
|
||
end
|
||
|
||
function SFrames.Chat:StartPositionEnforcer()
|
||
if self._positionEnforcerRunning then return end
|
||
self._positionEnforcerRunning = true
|
||
if not self._positionEnforcer then
|
||
self._positionEnforcer = CreateFrame("Frame")
|
||
end
|
||
local enforcer = self._positionEnforcer
|
||
enforcer.elapsed = 0
|
||
enforcer:SetScript("OnUpdate", function()
|
||
this.elapsed = this.elapsed + arg1
|
||
if this.elapsed < 0.3 then return end
|
||
this.elapsed = 0
|
||
if not (SFrames and SFrames.Chat and SFrames.Chat.frame and SFrames.Chat.frame.inner and SFrames.Chat.frame:IsShown()) then
|
||
return
|
||
end
|
||
local inner = SFrames.Chat.frame.inner
|
||
local maxWindows = tonumber(NUM_CHAT_WINDOWS) or 7
|
||
for i = 1, maxWindows do
|
||
local cf = _G["ChatFrame" .. tostring(i)]
|
||
if cf and SFrames.Chat:IsManagedChatWindow(cf) then
|
||
local needsFix = false
|
||
if cf.GetParent and cf:GetParent() ~= inner then
|
||
cf:SetParent(inner)
|
||
needsFix = true
|
||
end
|
||
if cf.GetPoint then
|
||
local point, relativeTo, relativePoint, xOfs, yOfs = cf:GetPoint(1)
|
||
if point ~= "TOPLEFT" or relativeTo ~= inner or relativePoint ~= "TOPLEFT" or (xOfs and xOfs ~= 0) or (yOfs and yOfs ~= 0) then
|
||
needsFix = true
|
||
end
|
||
if cf.GetNumPoints and cf:GetNumPoints() ~= 1 then
|
||
needsFix = true
|
||
end
|
||
end
|
||
if needsFix then
|
||
cf:ClearAllPoints()
|
||
cf:SetPoint("TOPLEFT", inner, "TOPLEFT", 0, 0)
|
||
SFrames.Chat:EnforceChatWindowLock(cf)
|
||
SFrames.Chat:RefreshChatBounds()
|
||
end
|
||
end
|
||
end
|
||
end)
|
||
end
|
||
|
||
function SFrames.Chat:RefreshChatBounds()
|
||
if not (self.chatFrame and self.frame and self.frame.inner) then return end
|
||
|
||
-- In WoW 1.12, GetWidth/GetHeight can return 0 for frames sized entirely by anchors.
|
||
-- ChatFrame internal scrolling breaks if its explicit size is 0, causing it to render
|
||
-- only 1 line at the bottom. We manually calculate the intended size.
|
||
local cfg = self:GetConfig()
|
||
local parentWidth = self.frame:GetWidth() or cfg.width or 400
|
||
local parentHeight = self.frame:GetHeight() or cfg.height or 200
|
||
|
||
local width = parentWidth - (cfg.sidePadding * 2)
|
||
local height = parentHeight - cfg.topPadding - cfg.bottomPadding
|
||
|
||
if width < 80 or height < 10 then return end
|
||
|
||
self.chatFrame:SetWidth(width + 1)
|
||
self.chatFrame:SetWidth(width)
|
||
self.chatFrame:SetHeight(height + 1)
|
||
self.chatFrame:SetHeight(height)
|
||
if self.chatFrame.UpdateScrollRegion then
|
||
pcall(function() self.chatFrame:UpdateScrollRegion() end)
|
||
end
|
||
end
|
||
|
||
function SFrames.Chat:StartStabilizer()
|
||
if not self.stabilizer then
|
||
self.stabilizer = CreateFrame("Frame")
|
||
end
|
||
|
||
local stabilize = self.stabilizer
|
||
stabilize.elapsed = 0
|
||
stabilize.total = 0
|
||
|
||
stabilize:SetScript("OnUpdate", function()
|
||
this.elapsed = this.elapsed + arg1
|
||
this.total = this.total + arg1
|
||
|
||
if this.elapsed >= 0.12 then
|
||
this.elapsed = 0
|
||
if SFrames and SFrames.Chat then
|
||
SFrames.Chat:HideDefaultChrome()
|
||
SFrames.Chat:HideTabChrome()
|
||
SFrames.Chat:RefreshTabButtons()
|
||
-- Only refresh bounds if anchors actually drifted (avoids +1/-1 flicker)
|
||
local chat = SFrames.Chat
|
||
if chat.chatFrame and chat.frame and chat.frame.inner then
|
||
local inner = chat.frame.inner
|
||
local point, relativeTo = chat.chatFrame:GetPoint(1)
|
||
if point ~= "TOPLEFT" or relativeTo ~= inner then
|
||
chat:ReanchorChatFrames()
|
||
chat:RefreshChatBounds()
|
||
end
|
||
end
|
||
end
|
||
end
|
||
|
||
if this.total >= 2 then
|
||
this:SetScript("OnUpdate", nil)
|
||
end
|
||
end)
|
||
end
|
||
|
||
function SFrames.Chat:EnsureChromeWatcher()
|
||
if not self.chromeWatcher then
|
||
self.chromeWatcher = CreateFrame("Frame")
|
||
end
|
||
|
||
local watcher = self.chromeWatcher
|
||
watcher.elapsed = 0
|
||
watcher.remaining = math.max(watcher.remaining or 0, 2.5)
|
||
if watcher.sfRunning then
|
||
return
|
||
end
|
||
|
||
watcher.sfRunning = true
|
||
watcher:SetScript("OnUpdate", function()
|
||
this.elapsed = this.elapsed + arg1
|
||
this.remaining = (this.remaining or 0) - arg1
|
||
|
||
if this.elapsed < 0.25 then
|
||
if this.remaining <= 0 then
|
||
this.sfRunning = false
|
||
this:SetScript("OnUpdate", nil)
|
||
end
|
||
return
|
||
end
|
||
this.elapsed = 0
|
||
|
||
if not (SFrames and SFrames.Chat and SFrames.Chat.frame and SFrames.Chat.frame:IsShown()) then
|
||
if this.remaining <= 0 then
|
||
this.sfRunning = false
|
||
this:SetScript("OnUpdate", nil)
|
||
end
|
||
return
|
||
end
|
||
|
||
local didReanchor = false
|
||
SFrames.Chat:HideDefaultChrome()
|
||
SFrames.Chat:HideTabChrome()
|
||
local maxWindows = tonumber(NUM_CHAT_WINDOWS) or 7
|
||
for i = 1, maxWindows do
|
||
local cf = _G["ChatFrame" .. tostring(i)]
|
||
if cf then
|
||
SFrames.Chat:EnforceChatWindowLock(cf)
|
||
if SFrames.Chat:IsManagedChatWindow(cf) and SFrames.Chat.frame and SFrames.Chat.frame.inner then
|
||
if cf.GetParent and cf:GetParent() ~= SFrames.Chat.frame.inner then
|
||
cf:SetParent(SFrames.Chat.frame.inner)
|
||
didReanchor = true
|
||
end
|
||
if cf.GetPoint then
|
||
local point, relativeTo, relativePoint, xOfs, yOfs = cf:GetPoint(1)
|
||
local badAnchor = (point ~= "TOPLEFT" or relativeTo ~= SFrames.Chat.frame.inner or relativePoint ~= "TOPLEFT" or xOfs ~= 0 or yOfs ~= 0)
|
||
if badAnchor or (cf.GetNumPoints and cf:GetNumPoints() ~= 2) then
|
||
cf:ClearAllPoints()
|
||
cf:SetPoint("TOPLEFT", SFrames.Chat.frame.inner, "TOPLEFT", 0, 0)
|
||
cf:SetPoint("BOTTOMRIGHT", SFrames.Chat.frame.inner, "BOTTOMRIGHT", 0, 0)
|
||
didReanchor = true
|
||
end
|
||
end
|
||
end
|
||
end
|
||
end
|
||
if SFrames.Chat.chatFrame and SFrames.Chat.frame and SFrames.Chat.frame.inner then
|
||
local cf = SFrames.Chat.chatFrame
|
||
if cf.GetParent and cf:GetParent() ~= SFrames.Chat.frame.inner then
|
||
cf:SetParent(SFrames.Chat.frame.inner)
|
||
didReanchor = true
|
||
end
|
||
if cf.GetPoint then
|
||
local point, relativeTo, relativePoint, xOfs, yOfs = cf:GetPoint(1)
|
||
local badAnchor = (point ~= "TOPLEFT" or relativeTo ~= SFrames.Chat.frame.inner or relativePoint ~= "TOPLEFT" or xOfs ~= 0 or yOfs ~= 0)
|
||
if badAnchor or (cf.GetNumPoints and cf:GetNumPoints() ~= 2) then
|
||
cf:ClearAllPoints()
|
||
cf:SetPoint("TOPLEFT", SFrames.Chat.frame.inner, "TOPLEFT", 0, 0)
|
||
cf:SetPoint("BOTTOMRIGHT", SFrames.Chat.frame.inner, "BOTTOMRIGHT", 0, 0)
|
||
didReanchor = true
|
||
end
|
||
end
|
||
end
|
||
|
||
if didReanchor then
|
||
SFrames.Chat:RefreshChatBounds()
|
||
end
|
||
|
||
if this.remaining <= 0 then
|
||
this.sfRunning = false
|
||
this:SetScript("OnUpdate", nil)
|
||
end
|
||
end)
|
||
end
|
||
|
||
function SFrames.Chat:ReanchorChatFrames()
|
||
if not (self.frame and self.frame.inner) then return end
|
||
local inner = self.frame.inner
|
||
local maxWindows = tonumber(NUM_CHAT_WINDOWS) or 7
|
||
for i = 1, maxWindows do
|
||
local cf = _G["ChatFrame" .. tostring(i)]
|
||
if cf and self:IsManagedChatWindow(cf) then
|
||
if cf.GetParent and cf:GetParent() ~= inner then
|
||
cf:SetParent(inner)
|
||
end
|
||
local point, relativeTo, relativePoint, xOfs, yOfs = cf:GetPoint(1)
|
||
if point ~= "TOPLEFT" or relativeTo ~= inner or relativePoint ~= "TOPLEFT" or xOfs ~= 0 or yOfs ~= 0 then
|
||
cf:ClearAllPoints()
|
||
cf:SetPoint("TOPLEFT", inner, "TOPLEFT", 0, 0)
|
||
cf:SetPoint("BOTTOMRIGHT", inner, "BOTTOMRIGHT", 0, 0)
|
||
end
|
||
end
|
||
end
|
||
self:HideDefaultChrome()
|
||
self:HideTabChrome()
|
||
end
|
||
|
||
function SFrames.Chat:ApplyChatFrameBaseStyle(chatFrame, isCombat)
|
||
if not chatFrame then return end
|
||
if chatFrame.SetFrameStrata then
|
||
chatFrame:SetFrameStrata("MEDIUM")
|
||
end
|
||
if chatFrame.SetFrameLevel and self.frame and self.frame.inner then
|
||
chatFrame:SetFrameLevel((self.frame.inner:GetFrameLevel() or self.frame:GetFrameLevel() or 1) + 6)
|
||
end
|
||
if chatFrame.SetAlpha then chatFrame:SetAlpha(1) end
|
||
chatFrame:SetFading(false)
|
||
if chatFrame.SetTimeVisible then
|
||
chatFrame:SetTimeVisible(999999)
|
||
end
|
||
if chatFrame.EnableMouseWheel then
|
||
chatFrame:EnableMouseWheel(true)
|
||
if not chatFrame.sfScrollHooked then
|
||
chatFrame.sfScrollHooked = true
|
||
chatFrame:SetScript("OnMouseWheel", function()
|
||
if arg1 > 0 then
|
||
if IsShiftKeyDown() then
|
||
this:ScrollToTop()
|
||
else
|
||
this:ScrollUp()
|
||
this:ScrollUp()
|
||
this:ScrollUp()
|
||
end
|
||
elseif arg1 < 0 then
|
||
if IsShiftKeyDown() then
|
||
this:ScrollToBottom()
|
||
else
|
||
this:ScrollDown()
|
||
this:ScrollDown()
|
||
this:ScrollDown()
|
||
end
|
||
end
|
||
end)
|
||
end
|
||
end
|
||
chatFrame:SetJustifyH("LEFT")
|
||
if chatFrame.SetSpacing then chatFrame:SetSpacing(1) end
|
||
if chatFrame.SetMaxLines then
|
||
chatFrame:SetMaxLines((isCombat and 4096) or 1024)
|
||
end
|
||
if chatFrame.SetHyperlinksEnabled then chatFrame:SetHyperlinksEnabled(1) end
|
||
if chatFrame.SetIndentedWordWrap then chatFrame:SetIndentedWordWrap(false) end
|
||
if chatFrame.SetShadowOffset then chatFrame:SetShadowOffset(1, -1) end
|
||
if chatFrame.SetShadowColor then chatFrame:SetShadowColor(0, 0, 0, 0.92) end
|
||
|
||
self:EnforceChatWindowLock(chatFrame)
|
||
if not chatFrame.sfDragLockHooked and chatFrame.HookScript then
|
||
chatFrame.sfDragLockHooked = true
|
||
chatFrame:HookScript("OnDragStart", function()
|
||
chatFrame:StopMovingOrSizing()
|
||
end)
|
||
chatFrame:HookScript("OnDragStop", function()
|
||
chatFrame:StopMovingOrSizing()
|
||
end)
|
||
end
|
||
|
||
-- Force disable fading completely
|
||
chatFrame:SetFading(false)
|
||
if chatFrame.SetTimeVisible then
|
||
chatFrame:SetTimeVisible(999999)
|
||
end
|
||
if not chatFrame.sfFadingHooked then
|
||
chatFrame.sfFadingHooked = true
|
||
chatFrame.SetFading = function() end
|
||
chatFrame.SetTimeVisible = function() end
|
||
end
|
||
end
|
||
|
||
function SFrames.Chat:GetChatFrameForTab(tab)
|
||
if not tab then return ChatFrame1, false end
|
||
if tab.kind == "combat" and ChatFrame2 then
|
||
return ChatFrame2, true
|
||
end
|
||
|
||
local index = 1
|
||
local db = EnsureDB()
|
||
for i=1, table.getn(db.tabs) do
|
||
if db.tabs[i] == tab then
|
||
index = i
|
||
break
|
||
end
|
||
end
|
||
|
||
local maxWindows = tonumber(NUM_CHAT_WINDOWS) or 7
|
||
if index > maxWindows then index = maxWindows end
|
||
|
||
local cfName = "ChatFrame" .. tostring(index)
|
||
local cf = _G[cfName]
|
||
|
||
if not cf then
|
||
return ChatFrame1, false
|
||
end
|
||
|
||
return cf, false
|
||
end
|
||
|
||
function SFrames.Chat:EnsureCombatLogFrame()
|
||
if not ChatFrame2 then return end
|
||
|
||
if (not self.combatLogAddonLoaded) and LoadAddOn and IsAddOnLoaded then
|
||
if not IsAddOnLoaded("Blizzard_CombatLog") then
|
||
local loadable = false
|
||
if GetAddOnInfo then
|
||
local name, _, _, enabled, lod, reason = GetAddOnInfo("Blizzard_CombatLog")
|
||
loadable = name and name ~= "" and reason ~= "MISSING" and reason ~= "DISABLED"
|
||
end
|
||
if loadable then
|
||
local ok = pcall(LoadAddOn, "Blizzard_CombatLog")
|
||
if not ok then
|
||
DEFAULT_CHAT_FRAME:AddMessage("|cffff9900Nanami-UI:|r Blizzard_CombatLog not available, skipping.")
|
||
end
|
||
end
|
||
end
|
||
self.combatLogAddonLoaded = true
|
||
end
|
||
|
||
if FCF_SetCombatLog then
|
||
pcall(function()
|
||
FCF_SetCombatLog(ChatFrame2)
|
||
end)
|
||
end
|
||
|
||
if FCF_SetWindowName then
|
||
pcall(function()
|
||
FCF_SetWindowName(ChatFrame2, DEFAULT_COMBAT_TAB_NAME)
|
||
end)
|
||
end
|
||
|
||
if ChatFrame2.SetFading then ChatFrame2:SetFading(false) end
|
||
if ChatFrame2.SetMaxLines then ChatFrame2:SetMaxLines(4096) end
|
||
self.combatLogPrepared = true
|
||
end
|
||
|
||
function SFrames.Chat:SetupChatFrameForTab(index)
|
||
local tab = self:GetTab(index)
|
||
if not tab then return end
|
||
if not (self.frame and self.frame.inner) then return end
|
||
|
||
local chatFrame, isCombat = self:GetChatFrameForTab(tab)
|
||
if not chatFrame then return end
|
||
if isCombat then
|
||
self:EnsureCombatLogFrame()
|
||
end
|
||
|
||
if not self.chatUndockedFrames then
|
||
self.chatUndockedFrames = {}
|
||
end
|
||
if not self.chatFrameToTabIndex then
|
||
self.chatFrameToTabIndex = {}
|
||
end
|
||
local frameID = chatFrame.GetID and chatFrame:GetID() or 1
|
||
if not self.chatUndockedFrames[frameID] and FCF_UnDockFrame then
|
||
pcall(function() FCF_UnDockFrame(chatFrame) end)
|
||
self.chatUndockedFrames[frameID] = true
|
||
end
|
||
self.chatFrameToTabIndex[chatFrame] = index
|
||
|
||
chatFrame:ClearAllPoints()
|
||
chatFrame:SetParent(self.frame.inner)
|
||
chatFrame:SetPoint("TOPLEFT", self.frame.inner, "TOPLEFT", 0, 0)
|
||
|
||
local cfg = self:GetConfig()
|
||
local width = (self.frame:GetWidth() or cfg.width or 400) - (cfg.sidePadding * 2)
|
||
local height = (self.frame:GetHeight() or cfg.height or 180) - cfg.topPadding - cfg.bottomPadding
|
||
if width < 80 then width = 80 end
|
||
if height < 10 then height = 10 end
|
||
chatFrame:SetWidth(width)
|
||
chatFrame:SetHeight(height)
|
||
|
||
self:ApplyChatFrameBaseStyle(chatFrame, isCombat)
|
||
if chatFrame.SetUserPlaced then chatFrame:SetUserPlaced(false) end
|
||
if chatFrame.SetMaxLines then chatFrame:SetMaxLines((isCombat and 4096) or 1024) end
|
||
if chatFrame.UpdateScrollRegion then pcall(function() chatFrame:UpdateScrollRegion() end) end
|
||
end
|
||
|
||
function SFrames.Chat:SwitchActiveChatFrame(tab)
|
||
if not (self.frame and self.frame.inner) then return end
|
||
|
||
local activeChatFrame, isCombat = self:GetChatFrameForTab(tab)
|
||
if not activeChatFrame then return end
|
||
|
||
local maxWindows = tonumber(NUM_CHAT_WINDOWS) or 7
|
||
for i = 1, maxWindows do
|
||
local cf = _G["ChatFrame"..tostring(i)]
|
||
if cf then
|
||
self:EnforceChatWindowLock(cf)
|
||
if cf == activeChatFrame then
|
||
if cf.SetAlpha then cf:SetAlpha(1) end
|
||
if cf.EnableMouse then cf:EnableMouse(true) end
|
||
if cf.SetFrameLevel and self.frame and self.frame.inner then
|
||
cf:SetFrameLevel((self.frame.inner:GetFrameLevel() or self.frame:GetFrameLevel() or 1) + 6)
|
||
end
|
||
cf:Show()
|
||
self.chatFrame = cf
|
||
self.chatFrameIsCombat = isCombat
|
||
self:RefreshChatBounds()
|
||
else
|
||
cf:Hide()
|
||
if cf.SetAlpha then cf:SetAlpha(0) end
|
||
if cf.EnableMouse then cf:EnableMouse(false) end
|
||
if cf.SetFrameLevel and self.frame and self.frame.inner then
|
||
cf:SetFrameLevel(self.frame.inner:GetFrameLevel() or 1)
|
||
end
|
||
end
|
||
end
|
||
end
|
||
end
|
||
|
||
function SFrames.Chat:AttachChatFrame()
|
||
if not (self.frame and self.frame.inner and ChatFrame1) then return end
|
||
self:SwitchActiveChatFrame(self:GetActiveTab())
|
||
end
|
||
|
||
function SFrames.Chat:RestoreCachedMessages(targetChatFrame)
|
||
if not targetChatFrame then return end
|
||
local cache = EnsureDB().messageCache
|
||
if not cache or table.getn(cache) == 0 then return end
|
||
|
||
-- Find which frame index this is
|
||
local targetIdx = nil
|
||
for fi = 1, 7 do
|
||
if targetChatFrame == _G["ChatFrame" .. fi] then
|
||
targetIdx = fi
|
||
break
|
||
end
|
||
end
|
||
|
||
local origAM = targetChatFrame.origAddMessage or targetChatFrame.AddMessage
|
||
if not origAM then return end
|
||
|
||
for idx = 1, table.getn(cache) do
|
||
local entry = cache[idx]
|
||
if entry and entry.text then
|
||
local frameIdx = entry.frame or 1
|
||
-- Only restore messages that belong to this frame
|
||
if frameIdx == targetIdx then
|
||
local msgID = entry.id or 0
|
||
if not SFrames.Chat.MessageHistory then
|
||
SFrames.Chat.MessageHistory = {}
|
||
end
|
||
SFrames.Chat.MessageHistory[msgID] = entry.text
|
||
local modified = "|cff888888|Hsfchat:" .. msgID .. "|h[+]|h|r " .. entry.text
|
||
origAM(targetChatFrame, modified, entry.r, entry.g, entry.b)
|
||
end
|
||
end
|
||
end
|
||
end
|
||
|
||
function SFrames.Chat:ApplyAllTabsSetup()
|
||
local db = EnsureDB()
|
||
self.chatFrameToTabIndex = {}
|
||
for i = 1, table.getn(db.tabs) do
|
||
self:SetupChatFrameForTab(i)
|
||
self:ApplyTabFilters(i)
|
||
self:ApplyTabChannels(i)
|
||
end
|
||
-- Restore cached messages after filters cleared the frames
|
||
for i = 1, table.getn(db.tabs) do
|
||
local tab = db.tabs[i]
|
||
if tab then
|
||
local chatFrame = self:GetChatFrameForTab(tab)
|
||
if chatFrame then
|
||
self:RestoreCachedMessages(chatFrame)
|
||
end
|
||
end
|
||
end
|
||
self.cacheRestorePrimed = true
|
||
self:SwitchActiveChatFrame(self:GetActiveTab())
|
||
end
|
||
|
||
function SFrames.Chat:ApplyTabFilters(index)
|
||
local tab, idx = self:GetTab(index)
|
||
if not tab then return end
|
||
|
||
local chatFrame, isCombat = self:GetChatFrameForTab(tab)
|
||
if not chatFrame then return end
|
||
|
||
if tab.kind == "combat" and isCombat then
|
||
return
|
||
end
|
||
if not tab.filters then return end
|
||
|
||
for _, group in ipairs(ALL_MESSAGE_GROUPS) do
|
||
pcall(function()
|
||
ChatFrame_RemoveMessageGroup(chatFrame, group)
|
||
end)
|
||
end
|
||
|
||
local applied = {}
|
||
local anyEnabled = false
|
||
for _, def in ipairs(FILTER_DEFS) do
|
||
if tab.filters[def.key] ~= false then
|
||
anyEnabled = true
|
||
local groups = FILTER_GROUPS[def.key] or {}
|
||
for _, group in ipairs(groups) do
|
||
if not applied[group] then
|
||
applied[group] = true
|
||
pcall(function()
|
||
ChatFrame_AddMessageGroup(chatFrame, group)
|
||
end)
|
||
end
|
||
end
|
||
end
|
||
end
|
||
|
||
if not anyEnabled then
|
||
tab.filters = CopyTable(DEFAULT_FILTERS)
|
||
applied = {}
|
||
for _, def in ipairs(FILTER_DEFS) do
|
||
if tab.filters[def.key] ~= false then
|
||
local groups = FILTER_GROUPS[def.key] or {}
|
||
for _, group in ipairs(groups) do
|
||
if not applied[group] then
|
||
applied[group] = true
|
||
pcall(function()
|
||
ChatFrame_AddMessageGroup(chatFrame, group)
|
||
end)
|
||
end
|
||
end
|
||
end
|
||
end
|
||
self:NotifyConfigUI()
|
||
end
|
||
end
|
||
|
||
function SFrames.Chat:ApplyTabChannels(index)
|
||
local tab, idx = self:GetTab(index)
|
||
if not tab then return end
|
||
|
||
local chatFrame, isCombat = self:GetChatFrameForTab(tab)
|
||
if not chatFrame then return end
|
||
if tab.kind == "combat" and isCombat then return end
|
||
|
||
local channels = self:GetJoinedChannels()
|
||
self:ApplyBlockedChannelsGlobally(channels)
|
||
local addSupported = (ChatFrame_AddChannel ~= nil)
|
||
local removeSupported = (ChatFrame_RemoveChannel ~= nil)
|
||
if not (addSupported or removeSupported) then return end
|
||
if table.getn(channels) == 0 then return end
|
||
|
||
for i = 1, table.getn(channels) do
|
||
local name = channels[i].name
|
||
local enabled = self:GetTabChannelFilter(idx, name)
|
||
if enabled then
|
||
if addSupported then
|
||
pcall(function()
|
||
ChatFrame_AddChannel(chatFrame, name)
|
||
end)
|
||
end
|
||
else
|
||
if removeSupported then
|
||
pcall(function()
|
||
ChatFrame_RemoveChannel(chatFrame, name)
|
||
end)
|
||
end
|
||
end
|
||
end
|
||
end
|
||
|
||
function SFrames.Chat:ApplyAllTabChannels()
|
||
local db = EnsureDB()
|
||
for i = 1, table.getn(db.tabs) do
|
||
self:ApplyTabChannels(i)
|
||
end
|
||
end
|
||
|
||
function SFrames.Chat:ApplyBlockedChannelsGlobally(channels)
|
||
if not ChatFrame_RemoveChannel then return end
|
||
channels = channels or self:GetJoinedChannels()
|
||
if table.getn(channels) == 0 then return end
|
||
|
||
local maxWindows = tonumber(NUM_CHAT_WINDOWS) or 7
|
||
local db = EnsureDB()
|
||
for i = 1, table.getn(channels) do
|
||
local info = channels[i]
|
||
local name = info and info.name
|
||
if IsIgnoredChannelByDefault(name) then
|
||
local anyTabEnabled = false
|
||
for t = 1, table.getn(db.tabs) do
|
||
if self:GetTabChannelFilter(t, name) then
|
||
anyTabEnabled = true
|
||
break
|
||
end
|
||
end
|
||
if not anyTabEnabled then
|
||
for w = 1, maxWindows do
|
||
local frame = _G["ChatFrame" .. tostring(w)]
|
||
if frame then
|
||
pcall(function()
|
||
ChatFrame_RemoveChannel(frame, name)
|
||
end)
|
||
end
|
||
end
|
||
end
|
||
end
|
||
end
|
||
end
|
||
|
||
function SFrames.Chat:EnsureChannelMessageFilter()
|
||
return
|
||
end
|
||
|
||
function SFrames.Chat:EnsureAddMessageChannelFilter()
|
||
return
|
||
end
|
||
|
||
function SFrames.Chat:StartBlockedChannelWatcher()
|
||
local watcher = self.blockedChannelWatcher
|
||
if watcher and type(watcher) ~= "boolean" and watcher.SetScript then
|
||
watcher:SetScript("OnUpdate", nil)
|
||
end
|
||
self.blockedChannelWatcher = true
|
||
end
|
||
|
||
function SFrames.Chat:RefreshTabButtons()
|
||
if not (self.frame and self.frame.tabBar) then return end
|
||
|
||
local db = EnsureDB()
|
||
local tabs = db.tabs
|
||
local count = table.getn(tabs)
|
||
local activeIndex = self:GetActiveTabIndex()
|
||
local cfg = self:GetConfig()
|
||
local showBorder = (cfg.showBorder ~= false)
|
||
local borderR, borderG, borderB = self:GetBorderColorRGB()
|
||
local inactiveBorderA = showBorder and 0.72 or 0
|
||
local activeBorderA = showBorder and 0.95 or 0
|
||
local addBorderA = showBorder and 0.9 or 0
|
||
|
||
if not self.tabButtons then self.tabButtons = {} end
|
||
for i = 1, table.getn(self.tabButtons) do
|
||
self.tabButtons[i]:Hide()
|
||
end
|
||
|
||
local function EnsureButtonSkin(btn)
|
||
if btn.sfSkinned then return end
|
||
|
||
btn:SetBackdrop({
|
||
bgFile = "Interface\\Tooltips\\UI-Tooltip-Background",
|
||
edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
|
||
tile = true, tileSize = 16, edgeSize = 12,
|
||
insets = { left = 2, right = 2, top = 2, bottom = 2 },
|
||
})
|
||
|
||
if btn.SetBackdropColor then
|
||
btn:SetBackdropColor(CFG_THEME.panelBg[1], CFG_THEME.panelBg[2], CFG_THEME.panelBg[3], 0.92)
|
||
end
|
||
if btn.SetBackdropBorderColor then
|
||
btn:SetBackdropBorderColor(borderR, borderG, borderB, inactiveBorderA)
|
||
end
|
||
|
||
local bg = btn:CreateTexture(nil, "BACKGROUND")
|
||
bg:SetTexture("Interface\\Tooltips\\UI-Tooltip-Background")
|
||
bg:SetPoint("TOPLEFT", btn, "TOPLEFT", 3, -3)
|
||
bg:SetPoint("BOTTOMRIGHT", btn, "BOTTOMRIGHT", -3, 3)
|
||
bg:SetVertexColor(1, 0.62, 0.84, 0.12)
|
||
btn.sfBg = bg
|
||
|
||
local fontPath = (SFrames and SFrames.GetFont and SFrames:GetFont()) or "Fonts\\ARIALN.TTF"
|
||
local label = btn:CreateFontString(nil, "OVERLAY")
|
||
label:SetFont(fontPath, 9, "OUTLINE")
|
||
label:SetPoint("CENTER", btn, "CENTER", 0, 0)
|
||
label:SetTextColor(0.92, 0.84, 0.9)
|
||
btn.sfText = label
|
||
|
||
btn.sfSkinned = true
|
||
end
|
||
|
||
if not self.addTabButton then
|
||
local addBtn = CreateFrame("Button", nil, self.frame.tabBar)
|
||
addBtn:SetHeight(18)
|
||
addBtn:SetWidth(20)
|
||
addBtn:RegisterForClicks("LeftButtonUp")
|
||
EnsureButtonSkin(addBtn)
|
||
addBtn.sfText:SetText("+")
|
||
addBtn:SetScript("OnClick", function()
|
||
if SFrames and SFrames.Chat then
|
||
SFrames.Chat:PromptNewTab()
|
||
end
|
||
end)
|
||
addBtn:SetScript("OnMouseDown", function()
|
||
if this.sfBg then this.sfBg:SetVertexColor(1, 0.4, 0.7, 0.6) end
|
||
if this.SetBackdropColor then this:SetBackdropColor(CFG_THEME.btnDownBg[1], CFG_THEME.btnDownBg[2], CFG_THEME.btnDownBg[3], 0.96) end
|
||
end)
|
||
addBtn:SetScript("OnMouseUp", function()
|
||
if MouseIsOver and MouseIsOver(this) then
|
||
if this.sfBg then this.sfBg:SetVertexColor(1, 0.7, 0.9, 0.3) end
|
||
if this.SetBackdropColor then this:SetBackdropColor(CFG_THEME.btnHoverBg[1], CFG_THEME.btnHoverBg[2], CFG_THEME.btnHoverBg[3], 0.96) end
|
||
else
|
||
if this.sfBg then this.sfBg:SetVertexColor(1, 0.62, 0.84, 0.2) end
|
||
if this.SetBackdropColor then this:SetBackdropColor(CFG_THEME.panelBg[1], CFG_THEME.panelBg[2], CFG_THEME.panelBg[3], 0.94) end
|
||
end
|
||
end)
|
||
addBtn:SetScript("OnEnter", function()
|
||
if this.sfBg then this.sfBg:SetVertexColor(1, 0.7, 0.9, 0.3) end
|
||
if this.SetBackdropColor then this:SetBackdropColor(CFG_THEME.btnHoverBg[1], CFG_THEME.btnHoverBg[2], CFG_THEME.btnHoverBg[3], 0.96) end
|
||
GameTooltip:SetOwner(this, "ANCHOR_TOP")
|
||
GameTooltip:ClearLines()
|
||
GameTooltip:AddLine("New Tab", 1, 0.84, 0.94)
|
||
GameTooltip:AddLine("Create chat tab", 0.85, 0.85, 0.85)
|
||
GameTooltip:Show()
|
||
end)
|
||
addBtn:SetScript("OnLeave", function()
|
||
if this.sfBg then this.sfBg:SetVertexColor(1, 0.62, 0.84, 0.2) end
|
||
if this.SetBackdropColor then this:SetBackdropColor(CFG_THEME.panelBg[1], CFG_THEME.panelBg[2], CFG_THEME.panelBg[3], 0.94) end
|
||
GameTooltip:Hide()
|
||
end)
|
||
self.addTabButton = addBtn
|
||
else
|
||
EnsureButtonSkin(self.addTabButton)
|
||
if self.addTabButton.sfText then self.addTabButton.sfText:SetText("+") end
|
||
end
|
||
|
||
local gap = 3
|
||
local addWidth = 20
|
||
local barWidth = self.frame.tabBar:GetWidth() or 0
|
||
if barWidth <= 0 then
|
||
barWidth = (self.frame:GetWidth() or DEFAULTS.width) - 180
|
||
end
|
||
local available = barWidth - addWidth - gap * count - 2
|
||
if available < 20 then available = 20 end
|
||
local buttonWidth = math.floor(available / math.max(1, count))
|
||
buttonWidth = Clamp(buttonWidth, 14, 96)
|
||
local maxChars = math.max(3, math.floor((buttonWidth - 8) / 5))
|
||
|
||
local x = 0
|
||
for i = 1, count do
|
||
local tab = tabs[i]
|
||
local btn = self.tabButtons[i]
|
||
if not btn then
|
||
btn = CreateFrame("Button", nil, self.frame.tabBar)
|
||
btn:SetHeight(18)
|
||
btn:RegisterForClicks("LeftButtonUp", "RightButtonUp")
|
||
self.tabButtons[i] = btn
|
||
end
|
||
|
||
EnsureButtonSkin(btn)
|
||
|
||
btn:ClearAllPoints()
|
||
btn:SetPoint("LEFT", self.frame.tabBar, "LEFT", x, 0)
|
||
btn:SetWidth(buttonWidth)
|
||
if btn.sfText then
|
||
btn.sfText:SetText(ShortText(tab.name or ("Tab" .. tostring(i)), maxChars))
|
||
end
|
||
|
||
local idx = i
|
||
btn:SetScript("OnClick", function()
|
||
if not (SFrames and SFrames.Chat) then return end
|
||
if arg1 == "RightButton" then
|
||
SFrames.Chat:OpenTabContextMenu(idx)
|
||
else
|
||
SFrames.Chat:SetActiveTab(idx)
|
||
end
|
||
end)
|
||
|
||
btn:SetScript("OnEnter", function()
|
||
if idx ~= activeIndex then
|
||
if this.sfBg then this.sfBg:SetVertexColor(1, 0.68, 0.89, 0.25) end
|
||
if this.sfText then this.sfText:SetTextColor(0.92, 0.84, 0.92) end
|
||
if this.SetBackdropColor then this:SetBackdropColor(CFG_THEME.panelBg[1], CFG_THEME.panelBg[2], CFG_THEME.panelBg[3], 0.92) end
|
||
this:SetAlpha(0.96)
|
||
end
|
||
GameTooltip:SetOwner(this, "ANCHOR_TOP")
|
||
GameTooltip:ClearLines()
|
||
GameTooltip:AddLine(tab.name or ("Tab" .. tostring(idx)), 1, 0.84, 0.94)
|
||
GameTooltip:AddLine("Left: switch", 0.82, 0.82, 0.82)
|
||
GameTooltip:AddLine("Right: menu", 1, 0.68, 0.79)
|
||
GameTooltip:Show()
|
||
end)
|
||
btn:SetScript("OnLeave", function()
|
||
if idx ~= activeIndex then
|
||
if this.sfBg then this.sfBg:SetVertexColor(1, 0.62, 0.84, 0.06) end
|
||
if this.sfText then this.sfText:SetTextColor(0.72, 0.64, 0.72) end
|
||
if this.SetBackdropColor then this:SetBackdropColor(CFG_THEME.panelBg[1], CFG_THEME.panelBg[2], CFG_THEME.panelBg[3], 0.78) end
|
||
this:SetAlpha(0.82)
|
||
end
|
||
GameTooltip:Hide()
|
||
end)
|
||
|
||
if idx == activeIndex then
|
||
local aBg = CFG_THEME.tabActiveBg or CFG_THEME.buttonActiveBg or CFG_THEME.btnBg
|
||
local aBd = CFG_THEME.tabActiveBorder or CFG_THEME.buttonActiveBorder
|
||
if btn.SetBackdropColor then btn:SetBackdropColor(aBg[1], aBg[2], aBg[3], 0.98) end
|
||
if btn.SetBackdropBorderColor then
|
||
if aBd then
|
||
btn:SetBackdropBorderColor(aBd[1], aBd[2], aBd[3], 1)
|
||
else
|
||
btn:SetBackdropBorderColor(borderR, borderG, borderB, 1)
|
||
end
|
||
end
|
||
if btn.sfBg then btn.sfBg:SetVertexColor(1, 0.64, 0.86, 0.45) end
|
||
if btn.sfText then btn.sfText:SetTextColor(1, 1, 1) end
|
||
btn:SetAlpha(1)
|
||
else
|
||
if btn.SetBackdropColor then btn:SetBackdropColor(CFG_THEME.panelBg[1], CFG_THEME.panelBg[2], CFG_THEME.panelBg[3], 0.78) end
|
||
if btn.SetBackdropBorderColor then btn:SetBackdropBorderColor(borderR, borderG, borderB, inactiveBorderA) end
|
||
if btn.sfBg then btn.sfBg:SetVertexColor(1, 0.62, 0.84, 0.06) end
|
||
if btn.sfText then btn.sfText:SetTextColor(0.72, 0.64, 0.72) end
|
||
btn:SetAlpha(0.82)
|
||
end
|
||
|
||
btn:Show()
|
||
x = x + buttonWidth + gap
|
||
end
|
||
|
||
self.addTabButton:ClearAllPoints()
|
||
self.addTabButton:SetPoint("LEFT", self.frame.tabBar, "LEFT", x, 0)
|
||
if self.addTabButton.SetBackdropColor then self.addTabButton:SetBackdropColor(CFG_THEME.panelBg[1], CFG_THEME.panelBg[2], CFG_THEME.panelBg[3], 0.94) end
|
||
if self.addTabButton.SetBackdropBorderColor then self.addTabButton:SetBackdropBorderColor(borderR, borderG, borderB, addBorderA) end
|
||
if self.addTabButton.sfBg then self.addTabButton.sfBg:SetVertexColor(1, 0.62, 0.84, 0.2) end
|
||
if self.addTabButton.sfText then self.addTabButton.sfText:SetTextColor(1, 0.9, 0.98) end
|
||
self.addTabButton:Show()
|
||
end
|
||
|
||
|
||
|
||
function SFrames.Chat:StyleChatFont()
|
||
if not (SFrames and SFrames.GetFont) then return end
|
||
local cfg = self:GetConfig()
|
||
local outline = (SFrames and SFrames.Media and SFrames.Media.fontOutline) or "OUTLINE"
|
||
local fontPath = SFrames:GetFont()
|
||
|
||
local styled = {}
|
||
local function Apply(frame)
|
||
if not (frame and frame.SetFont) then return end
|
||
if styled[frame] then return end
|
||
styled[frame] = true
|
||
pcall(function()
|
||
frame:SetFont(fontPath, cfg.fontSize, outline)
|
||
end)
|
||
end
|
||
|
||
if ChatFontNormal and ChatFontNormal.SetFont then
|
||
pcall(function()
|
||
ChatFontNormal:SetFont(fontPath, cfg.fontSize, outline)
|
||
end)
|
||
end
|
||
|
||
Apply(self.chatFrame)
|
||
local maxWindows = tonumber(NUM_CHAT_WINDOWS) or 7
|
||
for i = 1, maxWindows do
|
||
local cf = _G["ChatFrame"..tostring(i)]
|
||
if cf then Apply(cf) end
|
||
end
|
||
end
|
||
|
||
function SFrames.Chat:HookChatEditColoring()
|
||
if ChatEdit_UpdateHeader and not self._colorHooked then
|
||
local orig = ChatEdit_UpdateHeader
|
||
ChatEdit_UpdateHeader = function(editBox)
|
||
orig(editBox)
|
||
|
||
-- Re-apply insets to prevent buttons overlapping text and header
|
||
local header = _G[editBox:GetName().."Header"]
|
||
local hw = (header and header:GetWidth()) or 0
|
||
if editBox and editBox.SetTextInsets then
|
||
-- Left: 20px (for cat icon) + header width + 5px padding
|
||
-- Right: 45px (for right shortcut buttons)
|
||
editBox:SetTextInsets(hw + 25, 45, 0, 0)
|
||
end
|
||
|
||
local ctype = editBox.chatType
|
||
if not ctype and editBox.GetAttribute then
|
||
ctype = editBox:GetAttribute("chatType")
|
||
end
|
||
|
||
local info = ChatTypeInfo[ctype]
|
||
if SFrames and SFrames.Chat and SFrames.Chat.editBackdrop then
|
||
if info then
|
||
SFrames.Chat.editBackdrop:SetBackdropBorderColor(info.r, info.g, info.b, 1)
|
||
if SFrames.Chat.editBackdrop.topLine then
|
||
SFrames.Chat.editBackdrop.topLine:SetVertexColor(info.r, info.g, info.b, 0.85)
|
||
end
|
||
if SFrames.Chat.editBackdrop.catIcon then
|
||
SFrames.Chat.editBackdrop.catIcon:SetVertexColor(info.r, info.g, info.b, 0.9)
|
||
end
|
||
else
|
||
-- Default coloring
|
||
local cfg = SFrames.Chat:GetConfig()
|
||
local r, g, b = SFrames.Chat:GetBorderColorRGB()
|
||
SFrames.Chat.editBackdrop:SetBackdropBorderColor(r, g, b, (cfg.showBorder ~= false) and 0.98 or 0)
|
||
if SFrames.Chat.editBackdrop.topLine then
|
||
SFrames.Chat.editBackdrop.topLine:SetVertexColor(r, g, b, 0.85)
|
||
end
|
||
if SFrames.Chat.editBackdrop.catIcon then
|
||
SFrames.Chat.editBackdrop.catIcon:SetVertexColor(1, 0.84, 0.94, 0.9)
|
||
end
|
||
end
|
||
end
|
||
end
|
||
self._colorHooked = true
|
||
end
|
||
end
|
||
|
||
function SFrames.Chat:StyleEditBox()
|
||
if not self.frame then return end
|
||
|
||
local editBox = ChatFrameEditBox or ChatFrame1EditBox
|
||
if not editBox then return end
|
||
self.editBox = editBox
|
||
|
||
local cfg = self:GetConfig()
|
||
local outline = (SFrames and SFrames.Media and SFrames.Media.fontOutline) or "OUTLINE"
|
||
local fontPath = "Fonts\\ARIALN.TTF"
|
||
if SFrames and SFrames.GetFont then
|
||
local customFont = SFrames:GetFont()
|
||
if type(customFont) == "string" and customFont ~= "" then
|
||
fontPath = customFont
|
||
end
|
||
end
|
||
local editText = _G[editBox:GetName() .. "Text"]
|
||
|
||
local ename = editBox:GetName()
|
||
if ename then
|
||
local suffixes = {"Left", "Mid", "Middle", "Right", "FocusLeft", "FocusMid", "FocusMiddle", "FocusRight", "Focus"}
|
||
for _, suf in ipairs(suffixes) do
|
||
local tex = _G[ename .. suf]
|
||
if tex then
|
||
tex:SetTexture(nil)
|
||
tex:Hide()
|
||
tex.Show = Dummy
|
||
end
|
||
end
|
||
end
|
||
|
||
for _, texName in ipairs(EDITBOX_TEXTURES) do
|
||
local tex = _G[texName]
|
||
if tex then
|
||
tex:SetTexture(nil)
|
||
tex:Hide()
|
||
tex.Show = Dummy
|
||
end
|
||
end
|
||
|
||
if editBox.SetBackdrop then
|
||
editBox:SetBackdrop(nil)
|
||
end
|
||
local regions = { editBox:GetRegions() }
|
||
for _, region in ipairs(regions) do
|
||
if region and region:GetObjectType() == "Texture" and region ~= editText then
|
||
local layer = region:GetDrawLayer()
|
||
if layer == "BACKGROUND" or layer == "BORDER" then
|
||
region:SetTexture(nil)
|
||
region:Hide()
|
||
end
|
||
end
|
||
end
|
||
|
||
-- We will anchor editBox slightly later inside editBackdrop, but we set its basic properties here.
|
||
editBox:ClearAllPoints()
|
||
editBox:SetHeight(20)
|
||
if editBox.SetFrameStrata then editBox:SetFrameStrata("DIALOG") end
|
||
if editBox.SetFrameLevel then editBox:SetFrameLevel((self.frame:GetFrameLevel() or 1) + 20) end
|
||
if editBox.SetAltArrowKeyMode then editBox:SetAltArrowKeyMode(false) end
|
||
-- Let WoW manage text insets via ChatEdit_UpdateHeader dynamically
|
||
-- if editBox.SetTextInsets then editBox:SetTextInsets(20, 6, 0, 0) end
|
||
if editBox.SetJustifyH then editBox:SetJustifyH("LEFT") end
|
||
|
||
local header = _G[editBox:GetName() .. "Header"]
|
||
if header then
|
||
header:ClearAllPoints()
|
||
header:SetPoint("LEFT", editBox, "LEFT", 20, 0)
|
||
header:Show()
|
||
end
|
||
|
||
local function ApplyFontStyle(target)
|
||
if not target then return end
|
||
if target.SetFont then
|
||
pcall(function()
|
||
target:SetFont(fontPath, cfg.fontSize, outline)
|
||
end)
|
||
end
|
||
if target.SetFontObject and ChatFontNormal then
|
||
target:SetFontObject(ChatFontNormal)
|
||
end
|
||
if target.SetTextColor then target:SetTextColor(1, 1, 1) end
|
||
if target.SetShadowColor then target:SetShadowColor(0, 0, 0, 1) end
|
||
if target.SetShadowOffset then target:SetShadowOffset(1, -1) end
|
||
if target.SetAlpha then target:SetAlpha(1) end
|
||
end
|
||
|
||
local function ApplyEditTextStyle()
|
||
ApplyFontStyle(editBox)
|
||
ApplyFontStyle(editText)
|
||
if header then ApplyFontStyle(header) end
|
||
if editText and editText.SetDrawLayer then editText:SetDrawLayer("OVERLAY", 7) end
|
||
if editText and editText.SetParent then editText:SetParent(editBox) end
|
||
if editText and editText.Show then editText:Show() end
|
||
end
|
||
ApplyEditTextStyle()
|
||
|
||
if not self.editBackdrop then
|
||
local bg = CreateFrame("Frame", "SFramesChatEditBackdrop", self.frame)
|
||
bg:SetFrameStrata("DIALOG")
|
||
bg:SetFrameLevel(editBox:GetFrameLevel() - 1)
|
||
bg:SetBackdrop({
|
||
bgFile = "Interface\\Tooltips\\UI-Tooltip-Background",
|
||
edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
|
||
tile = true, tileSize = 16, edgeSize = 14,
|
||
insets = { left = 3, right = 3, top = 3, bottom = 3 },
|
||
})
|
||
bg:SetBackdropColor(CFG_THEME.panelBg[1], CFG_THEME.panelBg[2], CFG_THEME.panelBg[3], 0.96)
|
||
bg:SetBackdropBorderColor(CFG_THEME.panelBorder[1], CFG_THEME.panelBorder[2], CFG_THEME.panelBorder[3], 0.98)
|
||
|
||
local icon = SFrames:CreateIcon(bg, "logo", 13)
|
||
icon:SetPoint("LEFT", bg, "LEFT", 4, 0)
|
||
icon:SetVertexColor(1, 0.84, 0.94, 0.9)
|
||
bg.catIcon = icon
|
||
|
||
local line = bg:CreateTexture(nil, "OVERLAY")
|
||
line:SetTexture("Interface\\Buttons\\WHITE8X8")
|
||
line:SetPoint("TOPLEFT", bg, "TOPLEFT", 1, -1)
|
||
line:SetPoint("TOPRIGHT", bg, "TOPRIGHT", -1, -1)
|
||
line:SetHeight(1)
|
||
line:SetVertexColor(1, 0.76, 0.9, 0.85)
|
||
line:Hide()
|
||
bg.topLine = line
|
||
|
||
self.editBackdrop = bg
|
||
end
|
||
|
||
-- Add shortcut buttons inside the edit backdrop's right side.
|
||
local buttonParent = self.editBackdrop or self.frame
|
||
local btnLevel = (buttonParent:GetFrameLevel() or editBox:GetFrameLevel() or 1) + 3
|
||
if not self.rollButton then
|
||
local rb = CreateFrame("Button", "SFramesChatRollButton", buttonParent)
|
||
rb:SetWidth(18)
|
||
rb:SetHeight(18)
|
||
rb:SetFrameStrata("DIALOG")
|
||
rb:SetFrameLevel(btnLevel)
|
||
|
||
local tex = rb:CreateTexture(nil, "ARTWORK")
|
||
tex:SetTexture("Interface\\Buttons\\UI-GroupLoot-Dice-Up")
|
||
tex:SetTexCoord(0.06, 0.94, 0.06, 0.94)
|
||
tex:SetAllPoints(rb)
|
||
rb:SetNormalTexture(tex)
|
||
|
||
rb:SetScript("OnClick", function() RandomRoll(1, 100) end)
|
||
rb:SetScript("OnEnter", function()
|
||
GameTooltip:SetOwner(this, "ANCHOR_TOP")
|
||
GameTooltip:SetText("掷骰子 (1-100)")
|
||
GameTooltip:Show()
|
||
end)
|
||
rb:SetScript("OnLeave", function() GameTooltip:Hide() end)
|
||
self.rollButton = rb
|
||
end
|
||
|
||
if not self.emoteButton then
|
||
local eb = CreateFrame("Button", "SFramesChatEmoteButton", buttonParent)
|
||
eb:SetWidth(18)
|
||
eb:SetHeight(18)
|
||
eb:SetFrameStrata("DIALOG")
|
||
eb:SetFrameLevel(btnLevel)
|
||
|
||
local tex = eb:CreateTexture(nil, "ARTWORK")
|
||
tex:SetTexture("Interface\\Icons\\Ability_Warrior_BattleShout")
|
||
tex:SetTexCoord(0.08, 0.92, 0.08, 0.92)
|
||
tex:SetAllPoints(eb)
|
||
eb:SetNormalTexture(tex)
|
||
|
||
eb:SetScript("OnClick", function()
|
||
if not SFrames.Chat._emoteDropdown then
|
||
local dd = CreateFrame("Frame", "SFramesChatEmoteDropdown", UIParent, "UIDropDownMenuTemplate")
|
||
SFrames.Chat._emoteDropdown = dd
|
||
end
|
||
UIDropDownMenu_Initialize(SFrames.Chat._emoteDropdown, function()
|
||
local emotes = {
|
||
{ text = "/大笑", cmd = "LAUGH" },
|
||
{ text = "/微笑", cmd = "SMILE" },
|
||
{ text = "/挥手", cmd = "WAVE" },
|
||
{ text = "/跳舞", cmd = "DANCE" },
|
||
{ text = "/鞠躬", cmd = "BOW" },
|
||
{ text = "/欢呼", cmd = "CHEER" },
|
||
{ text = "/感谢", cmd = "THANK" },
|
||
{ text = "/惊讶", cmd = "GASP" },
|
||
{ text = "/眨眼", cmd = "WINK" },
|
||
{ text = "/叹气", cmd = "SIGH" },
|
||
{ text = "/哭泣", cmd = "CRY" },
|
||
{ text = "/生气", cmd = "ANGRY" },
|
||
{ text = "/飞吻", cmd = "KISS" },
|
||
{ text = "/鼓掌", cmd = "APPLAUD"},
|
||
{ text = "/点头", cmd = "YES" },
|
||
{ text = "/摇头", cmd = "NO" },
|
||
{ text = "/强壮", cmd = "FLEX" },
|
||
{ text = "/哭喊", cmd = "WHINE" },
|
||
}
|
||
for _, e in ipairs(emotes) do
|
||
local info = NewDropDownInfo()
|
||
info.text = e.text
|
||
info.notCheckable = 1
|
||
local cmd = e.cmd
|
||
info.func = function()
|
||
DoEmote(cmd)
|
||
end
|
||
UIDropDownMenu_AddButton(info)
|
||
end
|
||
end, "MENU")
|
||
ToggleDropDownMenu(1, nil, SFrames.Chat._emoteDropdown, "SFramesChatEmoteButton", 0, 0)
|
||
end)
|
||
eb:SetScript("OnEnter", function()
|
||
GameTooltip:SetOwner(this, "ANCHOR_TOP")
|
||
GameTooltip:SetText("表情动作")
|
||
GameTooltip:Show()
|
||
end)
|
||
eb:SetScript("OnLeave", function() GameTooltip:Hide() end)
|
||
self.emoteButton = eb
|
||
end
|
||
|
||
-- Re-parent in case buttons were created by an older version.
|
||
self.rollButton:SetParent(self.editBackdrop)
|
||
self.emoteButton:SetParent(self.editBackdrop)
|
||
if self.rollButton.SetFrameLevel then
|
||
self.rollButton:SetFrameLevel((editBox:GetFrameLevel() or 1) + 2)
|
||
end
|
||
if self.emoteButton.SetFrameLevel then
|
||
self.emoteButton:SetFrameLevel((editBox:GetFrameLevel() or 1) + 2)
|
||
end
|
||
|
||
-- Re-anchor buttons every call so they track editBackdrop position
|
||
self.rollButton:ClearAllPoints()
|
||
self.rollButton:SetPoint("RIGHT", self.editBackdrop, "RIGHT", -4, 0)
|
||
self.emoteButton:ClearAllPoints()
|
||
self.emoteButton:SetPoint("RIGHT", self.rollButton, "LEFT", -2, 0)
|
||
|
||
local function SaveFreeEditBoxPosition()
|
||
if EnsureDB().editBoxPosition ~= "free" then return end
|
||
if not self.editBackdrop then return end
|
||
local x = self.editBackdrop:GetLeft()
|
||
local y = self.editBackdrop:GetBottom()
|
||
if x and y then
|
||
EnsureDB().editBoxX = x
|
||
EnsureDB().editBoxY = y
|
||
end
|
||
end
|
||
|
||
self.editBackdrop:ClearAllPoints()
|
||
if cfg.editBoxPosition == "bottom" then
|
||
self.editBackdrop:SetPoint("TOPLEFT", self.frame, "BOTTOMLEFT", cfg.sidePadding - 4, -5)
|
||
self.editBackdrop:SetPoint("TOPRIGHT", self.frame, "BOTTOMRIGHT", -(cfg.sidePadding - 4), -5)
|
||
elseif cfg.editBoxPosition == "free" then
|
||
self.editBackdrop:SetPoint("BOTTOMLEFT", UIParent, "BOTTOMLEFT", cfg.editBoxX or 0, cfg.editBoxY or 200)
|
||
self.editBackdrop:SetWidth(cfg.width - cfg.sidePadding * 2 + 8)
|
||
else -- "top" or default
|
||
self.editBackdrop:SetPoint("BOTTOMLEFT", self.frame, "TOPLEFT", cfg.sidePadding - 4, 5)
|
||
self.editBackdrop:SetPoint("BOTTOMRIGHT", self.frame, "TOPRIGHT", -(cfg.sidePadding - 4), 5)
|
||
end
|
||
self.editBackdrop:SetHeight(26)
|
||
|
||
self.editBackdrop:EnableMouse(true)
|
||
self.editBackdrop:SetMovable(true)
|
||
if self.editBackdrop.SetClampedToScreen then self.editBackdrop:SetClampedToScreen(true) end
|
||
self.editBackdrop:RegisterForDrag("LeftButton")
|
||
self.editBackdrop:SetScript("OnDragStart", function()
|
||
if IsAltKeyDown() and EnsureDB().editBoxPosition == "free" then
|
||
this:StartMoving()
|
||
end
|
||
end)
|
||
self.editBackdrop:SetScript("OnDragStop", function()
|
||
this:StopMovingOrSizing()
|
||
SaveFreeEditBoxPosition()
|
||
end)
|
||
|
||
editBox:SetParent(self.editBackdrop)
|
||
editBox:ClearAllPoints()
|
||
editBox:SetPoint("TOPLEFT", self.editBackdrop, "TOPLEFT", 4, -3)
|
||
editBox:SetPoint("BOTTOMRIGHT", self.editBackdrop, "BOTTOMRIGHT", -45, 3)
|
||
editBox:EnableMouse(true)
|
||
self.editBackdrop:SetBackdropColor(CFG_THEME.panelBg[1], CFG_THEME.panelBg[2], CFG_THEME.panelBg[3], 0.96)
|
||
local borderR, borderG, borderB = self:GetBorderColorRGB()
|
||
self.editBackdrop:SetBackdropBorderColor(borderR, borderG, borderB, (cfg.showBorder ~= false) and 0.98 or 0)
|
||
if self.editBackdrop.topLine then
|
||
self.editBackdrop.topLine:Hide()
|
||
end
|
||
|
||
if not editBox.sfNanamiHooked then
|
||
local oldOnShow = editBox:GetScript("OnShow")
|
||
local oldOnHide = editBox:GetScript("OnHide")
|
||
local oldOnTextChanged = editBox:GetScript("OnTextChanged")
|
||
|
||
editBox:SetScript("OnShow", function()
|
||
if oldOnShow then oldOnShow() end
|
||
ApplyEditTextStyle()
|
||
if SFrames and SFrames.Chat and SFrames.Chat.editBackdrop then
|
||
SFrames.Chat.editBackdrop:Show()
|
||
end
|
||
end)
|
||
|
||
editBox:SetScript("OnHide", function()
|
||
if oldOnHide then oldOnHide() end
|
||
if SFrames and SFrames.Chat and SFrames.Chat.editBackdrop then
|
||
SFrames.Chat.editBackdrop:Hide()
|
||
end
|
||
end)
|
||
|
||
editBox:SetScript("OnTextChanged", function()
|
||
if oldOnTextChanged then oldOnTextChanged() end
|
||
ApplyEditTextStyle()
|
||
end)
|
||
|
||
local origShow = editBox.Show
|
||
editBox.Show = function(self)
|
||
if SFrames and SFrames.Chat and SFrames.Chat.editBackdrop then
|
||
SFrames.Chat.editBackdrop:Show()
|
||
end
|
||
if origShow then origShow(self) end
|
||
end
|
||
|
||
local origHide = editBox.Hide
|
||
editBox.Hide = function(self)
|
||
if SFrames and SFrames.Chat and SFrames.Chat.editBackdrop then
|
||
SFrames.Chat.editBackdrop:Hide()
|
||
end
|
||
if origHide then origHide(self) end
|
||
end
|
||
|
||
editBox.sfNanamiHooked = true
|
||
end
|
||
|
||
if not editBox.sfAltFreeDragHooked then
|
||
local oldMouseDown = editBox:GetScript("OnMouseDown")
|
||
local oldMouseUp = editBox:GetScript("OnMouseUp")
|
||
local oldDragStart = editBox:GetScript("OnDragStart")
|
||
local oldDragStop = editBox:GetScript("OnDragStop")
|
||
|
||
editBox:RegisterForDrag("LeftButton")
|
||
editBox:SetScript("OnMouseDown", function()
|
||
if oldMouseDown then oldMouseDown() end
|
||
if IsAltKeyDown() and EnsureDB().editBoxPosition == "free" and SFrames.Chat and SFrames.Chat.editBackdrop and not SFrames.Chat._editBoxAltDragging then
|
||
SFrames.Chat._editBoxAltDragging = true
|
||
SFrames.Chat.editBackdrop:StartMoving()
|
||
end
|
||
end)
|
||
editBox:SetScript("OnMouseUp", function()
|
||
if oldMouseUp then oldMouseUp() end
|
||
if SFrames.Chat and SFrames.Chat._editBoxAltDragging and SFrames.Chat.editBackdrop then
|
||
SFrames.Chat.editBackdrop:StopMovingOrSizing()
|
||
SFrames.Chat._editBoxAltDragging = nil
|
||
SaveFreeEditBoxPosition()
|
||
end
|
||
end)
|
||
editBox:SetScript("OnDragStart", function()
|
||
if oldDragStart then oldDragStart() end
|
||
if IsAltKeyDown() and EnsureDB().editBoxPosition == "free" and SFrames.Chat and SFrames.Chat.editBackdrop and not SFrames.Chat._editBoxAltDragging then
|
||
SFrames.Chat._editBoxAltDragging = true
|
||
SFrames.Chat.editBackdrop:StartMoving()
|
||
end
|
||
end)
|
||
editBox:SetScript("OnDragStop", function()
|
||
if oldDragStop then oldDragStop() end
|
||
if SFrames.Chat and SFrames.Chat.editBackdrop then
|
||
SFrames.Chat.editBackdrop:StopMovingOrSizing()
|
||
SFrames.Chat._editBoxAltDragging = nil
|
||
SaveFreeEditBoxPosition()
|
||
end
|
||
end)
|
||
editBox.sfAltFreeDragHooked = true
|
||
end
|
||
|
||
if editBox:IsShown() then
|
||
self.editBackdrop:Show()
|
||
else
|
||
self.editBackdrop:Hide()
|
||
end
|
||
end
|
||
|
||
function SFrames.Chat:ApplyFrameBorderStyle()
|
||
if not self.frame then return end
|
||
|
||
local cfg = self:GetConfig()
|
||
local showBorder = (cfg.showBorder ~= false)
|
||
local borderR, borderG, borderB = self:GetBorderColorRGB()
|
||
if showBorder then
|
||
local alpha = (SFrames and SFrames.isUnlocked) and 1 or 0.95
|
||
self.frame:SetBackdropBorderColor(borderR, borderG, borderB, alpha)
|
||
else
|
||
self.frame:SetBackdropBorderColor(borderR, borderG, borderB, 0)
|
||
end
|
||
|
||
if self.frame.topLine then
|
||
self.frame.topLine:Hide()
|
||
end
|
||
if self.frame.topGlow then
|
||
self.frame.topGlow:Hide()
|
||
end
|
||
end
|
||
|
||
function SFrames.Chat:SetUnlocked(unlocked)
|
||
if not self.frame then return end
|
||
if self.frame.hint then
|
||
self.frame.hint:SetText("")
|
||
self.frame.hint:Hide()
|
||
end
|
||
self:ApplyFrameBorderStyle()
|
||
if not unlocked then
|
||
self:ApplyAllTabsSetup()
|
||
self:RefreshChatBounds()
|
||
end
|
||
end
|
||
|
||
function SFrames.Chat:ApplyConfig()
|
||
if not self.frame then return end
|
||
|
||
self:HookWindowDragBlocker()
|
||
|
||
local db = EnsureDB()
|
||
local cfg = self:GetConfig()
|
||
|
||
if cfg.enable == false then
|
||
if self.hiddenConfigButton then
|
||
self.hiddenConfigButton:Show()
|
||
end
|
||
self.frame:Hide()
|
||
if self.editBackdrop then self.editBackdrop:Hide() end
|
||
return
|
||
end
|
||
|
||
if self.hiddenConfigButton then
|
||
self.hiddenConfigButton:Hide()
|
||
end
|
||
self.frame:Show()
|
||
self.frame:SetWidth(cfg.width)
|
||
self.frame:SetHeight(cfg.height)
|
||
self.frame:SetScale(cfg.scale)
|
||
|
||
db.width = cfg.width
|
||
db.height = cfg.height
|
||
db.scale = cfg.scale
|
||
db.fontSize = cfg.fontSize
|
||
db.showBorder = cfg.showBorder
|
||
db.borderClassColor = cfg.borderClassColor
|
||
db.sidePadding = cfg.sidePadding
|
||
db.topPadding = cfg.topPadding
|
||
db.bottomPadding = cfg.bottomPadding
|
||
|
||
if self.frame.tabBar and self.frame.title and self.frame.configButton then
|
||
self.frame.tabBar:ClearAllPoints()
|
||
self.frame.tabBar:SetPoint("LEFT", self.frame.title, "RIGHT", 10, -1)
|
||
self.frame.tabBar:SetPoint("RIGHT", self.frame.configButton, "LEFT", -8, -1)
|
||
self.frame.tabBar:SetHeight(18)
|
||
end
|
||
|
||
if self.frame.inner then
|
||
self.frame.inner:ClearAllPoints()
|
||
self.frame.inner:SetPoint("TOPLEFT", self.frame, "TOPLEFT", cfg.sidePadding, -cfg.topPadding)
|
||
self.frame.inner:SetPoint("BOTTOMRIGHT", self.frame, "BOTTOMRIGHT", -cfg.sidePadding, cfg.bottomPadding)
|
||
end
|
||
|
||
if self.frame.innerShade then
|
||
self.frame.innerShade:ClearAllPoints()
|
||
self.frame.innerShade:SetPoint("TOPLEFT", self.frame, "TOPLEFT", cfg.sidePadding - 2, -cfg.topPadding + 2)
|
||
self.frame.innerShade:SetPoint("BOTTOMRIGHT", self.frame, "BOTTOMRIGHT", -cfg.sidePadding + 2, cfg.bottomPadding + 2)
|
||
end
|
||
|
||
local bgA = Clamp(cfg.bgAlpha or DEFAULTS.bgAlpha, 0, 1)
|
||
self.frame:SetBackdropColor(CFG_THEME.panelBg[1], CFG_THEME.panelBg[2], CFG_THEME.panelBg[3], bgA)
|
||
|
||
self:AttachChatFrame()
|
||
self:HideDefaultChrome()
|
||
self:HideTabChrome()
|
||
self:StyleChatFont()
|
||
self:RefreshTabButtons()
|
||
self:ApplyAllTabsSetup()
|
||
self:StyleEditBox()
|
||
self:RefreshChatBounds()
|
||
self:EnsureChromeWatcher()
|
||
self:StartStabilizer()
|
||
self:SetUnlocked(SFrames and SFrames.isUnlocked)
|
||
self:RefreshConfigFrame()
|
||
end
|
||
|
||
function SFrames.Chat:Initialize()
|
||
EnsureDB()
|
||
self:CreateContainer()
|
||
self:HookWindowDragBlocker()
|
||
self:ApplyConfig()
|
||
self:StartPositionEnforcer()
|
||
self:EnsureAddMessageChannelFilter()
|
||
self:EnsureChannelMessageFilter()
|
||
self:ApplyBlockedChannelsGlobally()
|
||
self:StartBlockedChannelWatcher()
|
||
self:HookChatEditColoring()
|
||
|
||
-- Hook ChatFrame_MessageEventHandler to suppress ignored channels (lft/lfg/etc)
|
||
if ChatFrame_MessageEventHandler and not SFrames.Chat._lftHooked then
|
||
local origHandler = ChatFrame_MessageEventHandler
|
||
ChatFrame_MessageEventHandler = function(frameArg, eventArg)
|
||
local frame = frameArg or this
|
||
local ev = eventArg or event
|
||
|
||
if ev == "CHAT_MSG_CHANNEL" or ev == "CHAT_MSG_CHANNEL_JOIN" or ev == "CHAT_MSG_CHANNEL_LEAVE" or ev == "CHAT_MSG_CHANNEL_NOTICE" then
|
||
local chanName = GetChannelNameFromMessageEvent(arg4, arg8, arg9, arg2)
|
||
if chanName and chanName ~= "" then
|
||
if ev == "CHAT_MSG_CHANNEL" or ev == "CHAT_MSG_CHANNEL_JOIN" then
|
||
TrackDiscoveredChannel(chanName)
|
||
elseif ev == "CHAT_MSG_CHANNEL_LEAVE" then
|
||
UntrackDiscoveredChannel(chanName)
|
||
end
|
||
end
|
||
if frame and frame.GetName then
|
||
local frameName = frame:GetName()
|
||
if type(frameName) == "string" and string.find(frameName, "^ChatFrame%d+$") then
|
||
local matchedTabIdx = SFrames.Chat:GetTabIndexForChatFrame(frame)
|
||
if matchedTabIdx then
|
||
local tab = EnsureDB().tabs[matchedTabIdx]
|
||
local allowChannelMessages = not (tab.filters and tab.filters.channel == false)
|
||
if not allowChannelMessages or not SFrames.Chat:GetTabChannelFilter(matchedTabIdx, chanName) then
|
||
return
|
||
end
|
||
end
|
||
end
|
||
end
|
||
end
|
||
|
||
return origHandler(frame, ev)
|
||
end
|
||
SFrames.Chat._lftHooked = true
|
||
end
|
||
|
||
-- Direct event-based auto-translation hook (always installed as primary translation trigger)
|
||
if not SFrames.Chat._directTranslateHooked then
|
||
SFrames.Chat._directTranslateHooked = true
|
||
local translateEvFrame = CreateFrame("Frame", "SFramesChatTranslateEvents", UIParent)
|
||
for evName, _ in pairs(TRANSLATE_EVENT_FILTERS) do
|
||
translateEvFrame:RegisterEvent(evName)
|
||
end
|
||
translateEvFrame:SetScript("OnEvent", function()
|
||
if not (SFrames and SFrames.Chat) then return end
|
||
local filterKey = GetTranslateFilterKeyForEvent(event)
|
||
if not filterKey then return end
|
||
local messageText = arg1
|
||
if type(messageText) ~= "string" or messageText == "" then return end
|
||
|
||
local channelName = nil
|
||
if filterKey == "channel" then
|
||
channelName = GetChannelNameFromMessageEvent(arg4, arg8, arg9, arg2)
|
||
-- Note: do NOT skip ignored channels here — user may have explicitly
|
||
-- enabled them (e.g. hc/hardcore). ShouldAutoTranslateForTab will
|
||
-- correctly return false if the channel is not enabled for this tab.
|
||
end
|
||
|
||
local db = EnsureDB()
|
||
local translated = false
|
||
for i = 1, table.getn(db.tabs) do
|
||
if not translated and SFrames.Chat:ShouldAutoTranslateForTab(i, filterKey, channelName) then
|
||
local tab = db.tabs[i]
|
||
if tab and type(tab.id) == "number" then
|
||
local cleanText = CleanTextForTranslation(messageText)
|
||
if cleanText ~= "" then
|
||
local tabId = tab.id
|
||
local senderName = arg2
|
||
SFrames.Chat:RequestAutoTranslation(cleanText, function(result, err)
|
||
if result and result ~= "" then
|
||
SFrames.Chat:AppendAutoTranslatedLine(tabId, filterKey, channelName, cleanText, result, senderName)
|
||
end
|
||
end)
|
||
translated = true
|
||
end
|
||
end
|
||
end
|
||
end
|
||
end)
|
||
end
|
||
|
||
SFrames:RegisterEvent("PLAYER_ENTERING_WORLD", function()
|
||
if SFrames and SFrames.Chat then
|
||
SFrames.Chat:ApplyConfig()
|
||
SFrames.Chat:EnsureAddMessageChannelFilter()
|
||
SFrames.Chat:ApplyBlockedChannelsGlobally()
|
||
end
|
||
LoadPersistentClassCache()
|
||
if IsInGuild and IsInGuild() and GuildRoster then
|
||
GuildRoster()
|
||
end
|
||
SFrames:RefreshClassColorCache()
|
||
end)
|
||
|
||
-- 团队成员变化时更新职业缓存
|
||
SFrames:RegisterEvent("PARTY_MEMBERS_CHANGED", function() SFrames:RefreshClassColorCache() end)
|
||
SFrames:RegisterEvent("RAID_ROSTER_UPDATE", function() SFrames:RefreshClassColorCache() end)
|
||
SFrames:RegisterEvent("FRIENDLIST_UPDATE", function() SFrames:RefreshClassColorCache() end)
|
||
SFrames:RegisterEvent("GUILD_ROSTER_UPDATE", function() SFrames:RefreshClassColorCache() end)
|
||
SFrames:RegisterEvent("WHO_LIST_UPDATE", function() SFrames:RefreshClassColorCache() end)
|
||
|
||
-- 监听公会/队伍聊天,未命中缓存时即时刷新名单
|
||
if not SFrames.Chat._classRefreshHooked then
|
||
SFrames.Chat._classRefreshHooked = true
|
||
local classRefreshFrame = CreateFrame("Frame", "SFramesChatClassRefresh", UIParent)
|
||
classRefreshFrame:RegisterEvent("CHAT_MSG_GUILD")
|
||
classRefreshFrame:RegisterEvent("CHAT_MSG_OFFICER")
|
||
classRefreshFrame:RegisterEvent("CHAT_MSG_PARTY")
|
||
classRefreshFrame:RegisterEvent("CHAT_MSG_RAID")
|
||
classRefreshFrame:RegisterEvent("CHAT_MSG_RAID_LEADER")
|
||
classRefreshFrame:SetScript("OnEvent", function()
|
||
local sender = arg2
|
||
if sender and sender ~= "" and not SFrames.PlayerClassColorCache[sender] then
|
||
if event == "CHAT_MSG_GUILD" or event == "CHAT_MSG_OFFICER" then
|
||
if GuildRoster then GuildRoster() end
|
||
else
|
||
SFrames:RefreshClassColorCache()
|
||
end
|
||
end
|
||
end)
|
||
end
|
||
|
||
SFrames:RegisterEvent("UPDATE_CHAT_WINDOWS", function()
|
||
if SFrames and SFrames.Chat then
|
||
if EnsureDB().enable == false then return end
|
||
SFrames.Chat:HideDefaultChrome()
|
||
SFrames.Chat:HideTabChrome()
|
||
SFrames.Chat:RefreshChatBounds()
|
||
SFrames.Chat:RefreshTabButtons()
|
||
SFrames.Chat:StyleEditBox()
|
||
SFrames.Chat:EnsureAddMessageChannelFilter()
|
||
SFrames.Chat:ApplyBlockedChannelsGlobally()
|
||
SFrames.Chat:RefreshConfigFrame()
|
||
end
|
||
end)
|
||
|
||
SFrames:RegisterEvent("UPDATE_CHAT_COLOR", function()
|
||
if SFrames and SFrames.Chat then
|
||
if EnsureDB().enable == false then return end
|
||
SFrames.Chat:StyleChatFont()
|
||
SFrames.Chat:StyleEditBox()
|
||
end
|
||
end)
|
||
|
||
if not SFrames.Chat._msgHooked then
|
||
SFrames.Chat._msgHooked = true
|
||
|
||
local MAX_CACHE = 200
|
||
|
||
-- Initialize from persistent storage
|
||
local db = EnsureDB()
|
||
if type(db.messageCache) ~= "table" then
|
||
db.messageCache = {}
|
||
end
|
||
|
||
-- MessageHistory is the live runtime lookup (msgID -> raw text), seeded from cache
|
||
SFrames.Chat.MessageHistory = {}
|
||
SFrames.Chat.MessageSenders = {}
|
||
SFrames.Chat.MessageIndex = 0
|
||
|
||
-- Seed runtime lookup from persistent cache
|
||
for i = 1, table.getn(db.messageCache) do
|
||
local entry = db.messageCache[i]
|
||
if entry and entry.id and entry.text then
|
||
SFrames.Chat.MessageHistory[entry.id] = entry.text
|
||
if entry.id > SFrames.Chat.MessageIndex then
|
||
SFrames.Chat.MessageIndex = entry.id
|
||
end
|
||
end
|
||
end
|
||
|
||
-- Helper: save a message to the persistent cache ring buffer
|
||
local function PersistMessage(msgID, text, r, g, b, frameIndex)
|
||
local cache = EnsureDB().messageCache
|
||
if not cache then
|
||
EnsureDB().messageCache = {}
|
||
cache = EnsureDB().messageCache
|
||
end
|
||
table.insert(cache, {
|
||
id = msgID,
|
||
text = text,
|
||
r = r,
|
||
g = g,
|
||
b = b,
|
||
frame = frameIndex or 1,
|
||
time = date("%H:%M:%S"),
|
||
})
|
||
-- Trim to MAX_CACHE
|
||
while table.getn(cache) > MAX_CACHE do
|
||
table.remove(cache, 1)
|
||
end
|
||
end
|
||
|
||
for i = 1, 7 do
|
||
local cf = _G["ChatFrame" .. i]
|
||
if cf and cf.AddMessage then
|
||
local origAddMessage = cf.AddMessage
|
||
cf.origAddMessage = origAddMessage
|
||
cf.AddMessage = function(self, text, r, g, b, alpha, holdTime)
|
||
if not text or text == "" then
|
||
origAddMessage(self, text, r, g, b, alpha, holdTime)
|
||
return
|
||
end
|
||
if string.sub(text, 1, 10) ~= "|Hsfchat:" and string.sub(text, 1, 12) ~= "|cff888888|H" then
|
||
local db = EnsureDB()
|
||
-- Universal catch for Turtle WoW custom chat channels like [硬核]
|
||
local chanName = GetChannelNameFromChatLine(text)
|
||
|
||
if chanName and IsIgnoredChannelByDefault(chanName) then
|
||
-- Global HC kill switch override check
|
||
if db.hcGlobalDisable then
|
||
local lowerChan = string.lower(chanName)
|
||
if string.find(lowerChan, "hc") or string.find(chanName, "硬核") or string.find(lowerChan, "hardcore") then
|
||
return
|
||
end
|
||
end
|
||
|
||
local frameName = self:GetName()
|
||
local matchedTabIdx = nil
|
||
if type(frameName) == "string" and string.find(frameName, "^ChatFrame%d+$") then
|
||
matchedTabIdx = SFrames.Chat:GetTabIndexForChatFrame(self)
|
||
if not matchedTabIdx then
|
||
local _, _, frameNumStr = string.find(frameName, "^ChatFrame(%d+)$")
|
||
local frameNum = tonumber(frameNumStr)
|
||
if frameNum then
|
||
local db = EnsureDB()
|
||
if frameNum <= table.getn(db.tabs) then
|
||
matchedTabIdx = frameNum
|
||
end
|
||
end
|
||
end
|
||
end
|
||
if matchedTabIdx then
|
||
local tab = EnsureDB().tabs[matchedTabIdx]
|
||
if tab then
|
||
if not SFrames.Chat:GetTabChannelFilter(matchedTabIdx, chanName) then
|
||
return
|
||
end
|
||
-- If it passed channel filter, allow translation for these bypassed channels
|
||
local shouldTranslate = SFrames.Chat:GetTabChannelTranslateFilter(matchedTabIdx, chanName)
|
||
if shouldTranslate then
|
||
local cleanText = CleanTextForTranslation(text)
|
||
-- Remove the channel prefix from translation text to be clean
|
||
cleanText = string.gsub(cleanText, "^%[.-%]%s*", "")
|
||
-- It might also have [PlayerName]:
|
||
local _, _, senderName = string.find(cleanText, "^%[([^%]]+)%]:%s*")
|
||
if senderName then
|
||
cleanText = string.gsub(cleanText, "^%[[^%]]+%]:%s*", "")
|
||
end
|
||
if cleanText ~= "" then
|
||
local tabId = tab.id
|
||
SFrames.Chat:RequestAutoTranslation(cleanText, function(result)
|
||
if result and result ~= "" then
|
||
SFrames.Chat:AppendAutoTranslatedLine(tabId, "channel", chanName, cleanText, result, senderName)
|
||
end
|
||
end)
|
||
end
|
||
end
|
||
end
|
||
else
|
||
return
|
||
end
|
||
end
|
||
|
||
-- Hardcore Death Event Overrides
|
||
if db.hcDeathDisable or (db.hcDeathLevelMin and db.hcDeathLevelMin > 1) then
|
||
local deathLvl = ParseHardcoreDeathMessage(text)
|
||
if deathLvl then
|
||
if db.hcDeathDisable then return end
|
||
if db.hcDeathLevelMin and deathLvl < db.hcDeathLevelMin then return end
|
||
end
|
||
end
|
||
|
||
SFrames.Chat.MessageIndex = SFrames.Chat.MessageIndex + 1
|
||
local msgID = SFrames.Chat.MessageIndex
|
||
|
||
-- Store in runtime lookup
|
||
SFrames.Chat.MessageHistory[msgID] = text
|
||
|
||
-- Persist to SavedVariables
|
||
local frameIdx = nil
|
||
for fi = 1, 7 do
|
||
if self == _G["ChatFrame" .. fi] then
|
||
frameIdx = fi
|
||
break
|
||
end
|
||
end
|
||
PersistMessage(msgID, text, r, g, b, frameIdx)
|
||
|
||
-- Apply class color to player names in message
|
||
local coloredText = ColorPlayerNamesInText(text)
|
||
-- Insert the clickable button [+] at the beginning
|
||
local modifiedText = "|cff888888|Hsfchat:" .. msgID .. "|h[+]|h|r " .. coloredText
|
||
origAddMessage(self, modifiedText, r, g, b, alpha, holdTime)
|
||
|
||
-- Legacy auto-translate disabled; handled by per-tab routing above.
|
||
if false then
|
||
if self == ChatFrame1 and not string.find(text, "%[翻译%]") then
|
||
if string.find(text, "%[.-硬核.-%]") or string.find(string.lower(text), "%[.-hc.-%]") or string.find(string.lower(text), "%[.-hardcore.-%]") then
|
||
local cleanText = string.gsub(text, "|c%x%x%x%x%x%x%x%x", "")
|
||
cleanText = string.gsub(cleanText, "|r", "")
|
||
cleanText = string.gsub(cleanText, "|H.-|h(.-)|h", "%1")
|
||
|
||
if _G.STranslateAPI and _G.STranslateAPI.IsReady and _G.STranslateAPI.IsReady() then
|
||
_G.STranslateAPI.Translate(cleanText, "auto", "zh", function(result, err, meta)
|
||
if result then
|
||
DEFAULT_CHAT_FRAME:AddMessage("|cff00ffff[翻译] |cffffff00" .. tostring(result) .. "|r")
|
||
end
|
||
end, "Nanami-UI")
|
||
elseif _G.STranslate and _G.STranslate.SendIO then
|
||
_G.STranslate:SendIO(cleanText, "IN", "auto", "zh")
|
||
end
|
||
end
|
||
end
|
||
end
|
||
else
|
||
origAddMessage(self, text, r, g, b, alpha, holdTime)
|
||
end
|
||
end
|
||
end
|
||
end
|
||
|
||
-- Restore cached messages after a short delay so chat frames are fully ready
|
||
if not SFrames.Chat._cacheRestored then
|
||
SFrames.Chat._cacheRestored = true
|
||
local restoreFrame = CreateFrame("Frame", nil, UIParent)
|
||
restoreFrame.elapsed = 0
|
||
restoreFrame:SetScript("OnUpdate", function()
|
||
this.elapsed = this.elapsed + arg1
|
||
if this.elapsed < 1.5 then return end
|
||
this:SetScript("OnUpdate", nil)
|
||
this:Hide()
|
||
|
||
if SFrames.Chat.cacheRestorePrimed then
|
||
return
|
||
end
|
||
|
||
local cache = EnsureDB().messageCache
|
||
if not cache or table.getn(cache) == 0 then return end
|
||
|
||
-- Split cache restoration across multiple frames to prevent WoW from freezing
|
||
local currentIdx = 1
|
||
local totalCount = table.getn(cache)
|
||
|
||
this:SetScript("OnUpdate", function()
|
||
local chunkEnd = math.min(currentIdx + 15, totalCount)
|
||
for idx = currentIdx, chunkEnd do
|
||
local entry = cache[idx]
|
||
if entry and entry.text then
|
||
local frameIdx = entry.frame or 1
|
||
local cf = _G["ChatFrame" .. frameIdx]
|
||
if not cf then cf = ChatFrame1 end
|
||
if cf and cf.AddMessage then
|
||
local msgID = entry.id or 0
|
||
SFrames.Chat.MessageHistory[msgID] = entry.text
|
||
local modified = "|cff888888|Hsfchat:" .. msgID .. "|h[+]|h|r " .. entry.text
|
||
-- Call with the raw origAddMessage to avoid re-persisting
|
||
local origAM = cf.origAddMessage or cf.AddMessage
|
||
origAM(cf, modified, entry.r, entry.g, entry.b)
|
||
end
|
||
end
|
||
end
|
||
currentIdx = chunkEnd + 1
|
||
if currentIdx > totalCount then
|
||
this:SetScript("OnUpdate", nil)
|
||
this:Hide()
|
||
SFrames.Chat.cacheRestorePrimed = true
|
||
end
|
||
end)
|
||
end)
|
||
end
|
||
end
|
||
|
||
if not SFrames.Chat._itemRefHooked then
|
||
SFrames.Chat._itemRefHooked = true
|
||
local origSetItemRef = SetItemRef
|
||
SetItemRef = function(link, text, button)
|
||
if link and string.sub(link, 1, 7) == "sfchat:" then
|
||
local msgID = tonumber(string.sub(link, 8))
|
||
if msgID and SFrames.Chat.MessageHistory[msgID] then
|
||
local messageText = SFrames.Chat.MessageHistory[msgID]
|
||
local sender = nil
|
||
|
||
-- Check MessageSenders lookup first (for AI translated messages)
|
||
if SFrames.Chat.MessageSenders and SFrames.Chat.MessageSenders[msgID] then
|
||
sender = SFrames.Chat.MessageSenders[msgID]
|
||
else
|
||
-- Attempt to extract sender name from raw text like: |Hplayer:Name|h[Name]|h or [Name]:
|
||
local _, _, extractedName = string.find(messageText, "|Hplayer:(.-)|h")
|
||
if extractedName then
|
||
sender = string.gsub(extractedName, ":.*", "")
|
||
else
|
||
_, _, sender = string.find(messageText, "%[(.-)%]")
|
||
end
|
||
end
|
||
-- `this` inside SetItemRef is usually the ChatFrame that was clicked
|
||
local clickedFrame = this
|
||
if type(clickedFrame) ~= "table" or not clickedFrame.AddMessage then
|
||
local activeIdx = SFrames.Chat:GetActiveTabIndex()
|
||
local tab = SFrames.Chat:GetTab(activeIdx)
|
||
clickedFrame = tab and SFrames.Chat:GetChatFrameForTab(tab) or DEFAULT_CHAT_FRAME
|
||
end
|
||
|
||
SFrames.Chat:OpenMessageContextMenu(msgID, messageText, sender, clickedFrame)
|
||
end
|
||
return
|
||
end
|
||
if origSetItemRef then
|
||
origSetItemRef(link, text, button)
|
||
end
|
||
end
|
||
end
|
||
end
|
||
|
||
function SFrames.Chat:ShowCopyDialog(text)
|
||
if not self.copyFrame then
|
||
local f = CreateFrame("Frame", "SFramesChatCopyFrame", UIParent)
|
||
f:SetWidth(400)
|
||
f:SetHeight(150)
|
||
f:SetPoint("CENTER", UIParent, "CENTER", 0, 100)
|
||
f:SetFrameStrata("FULLSCREEN_DIALOG")
|
||
f:EnableMouse(true)
|
||
f:SetMovable(true)
|
||
f:RegisterForDrag("LeftButton")
|
||
f:SetScript("OnDragStart", function() this:StartMoving() end)
|
||
f:SetScript("OnDragStop", function() this:StopMovingOrSizing() end)
|
||
|
||
if SFrames and SFrames.CreateBackdrop then
|
||
SFrames:CreateBackdrop(f)
|
||
else
|
||
f:SetBackdrop({
|
||
bgFile = "Interface\\DialogFrame\\UI-DialogBox-Background",
|
||
edgeFile = "Interface\\Buttons\\WHITE8X8",
|
||
tile = true, tileSize = 32, edgeSize = 1,
|
||
insets = { left = 1, right = 1, top = 1, bottom = 1 },
|
||
})
|
||
end
|
||
f:SetBackdropColor(0.08, 0.07, 0.1, 0.96)
|
||
f:SetBackdropBorderColor(0.5, 0.5, 0.5, 0.8)
|
||
|
||
local close = CreateFrame("Button", nil, f, "UIPanelCloseButton")
|
||
close:SetPoint("TOPRIGHT", f, "TOPRIGHT", -2, -2)
|
||
|
||
local l = f:CreateFontString(nil, "OVERLAY")
|
||
l:SetFont("Fonts\\ARIALN.TTF", 12, "OUTLINE")
|
||
l:SetPoint("TOPLEFT", f, "TOPLEFT", 10, -10)
|
||
l:SetText("复制聊天内容 (Ctrl+C)")
|
||
|
||
local sf = CreateFrame("ScrollFrame", "SFramesChatCopyScroll", f, "UIPanelScrollFrameTemplate")
|
||
sf:SetPoint("TOPLEFT", f, "TOPLEFT", 10, -30)
|
||
sf:SetPoint("BOTTOMRIGHT", f, "BOTTOMRIGHT", -30, 10)
|
||
|
||
local edit = CreateFrame("EditBox", "SFramesChatCopyEdit", sf)
|
||
edit:SetWidth(350)
|
||
edit:SetHeight(90)
|
||
edit:SetMultiLine(true)
|
||
edit:SetFont("Fonts\\ARIALN.TTF", 12, "OUTLINE")
|
||
edit:SetAutoFocus(false)
|
||
edit:SetScript("OnEscapePressed", function() f:Hide() end)
|
||
sf:SetScrollChild(edit)
|
||
|
||
f.edit = edit
|
||
self.copyFrame = f
|
||
end
|
||
|
||
local cleanText = string.gsub(text, "|c%x%x%x%x%x%x%x%x", "")
|
||
cleanText = string.gsub(cleanText, "|r", "")
|
||
cleanText = string.gsub(cleanText, "|H.-|h(.-)|h", "%1")
|
||
|
||
self.copyFrame.edit:SetText(cleanText)
|
||
self.copyFrame:Show()
|
||
self.copyFrame.edit:HighlightText()
|
||
self.copyFrame.edit:SetFocus()
|
||
end
|
||
|
||
function SFrames.Chat:OpenMessageContextMenu(msgID, text, sender, targetFrame)
|
||
if not self._msgDropdown then
|
||
self._msgDropdown = CreateFrame("Frame", "SFramesChatMessageDropdown", UIParent, "UIDropDownMenuTemplate")
|
||
self._msgDropdown:SetFrameStrata("FULLSCREEN_DIALOG")
|
||
end
|
||
UIDropDownMenu_Initialize(self._msgDropdown, function()
|
||
local level = UIDROPDOWNMENU_MENU_LEVEL or 1
|
||
|
||
local cleanText = string.gsub(text, "|c%x%x%x%x%x%x%x%x", "")
|
||
cleanText = string.gsub(cleanText, "|r", "")
|
||
cleanText = string.gsub(cleanText, "|H.-|h(.-)|h", "%1")
|
||
|
||
if level == 2 and UIDROPDOWNMENU_MENU_VALUE == "STranslate_Langs" then
|
||
local langs = nil
|
||
if _G.STranslateAPI and _G.STranslateAPI.GetSupportedLanguages then
|
||
langs = _G.STranslateAPI.GetSupportedLanguages()
|
||
end
|
||
|
||
if type(langs) ~= "table" or table.getn(langs) == 0 then
|
||
langs = {
|
||
{ code = "zh", name = "中文" },
|
||
{ code = "en", name = "英语" },
|
||
{ code = "es", name = "西班牙语" },
|
||
{ code = "fr", name = "法语" },
|
||
{ code = "pl", name = "波兰语" },
|
||
{ code = "de", name = "德语" },
|
||
{ code = "ru", name = "俄语" },
|
||
{ code = "ko", name = "韩语" },
|
||
}
|
||
end
|
||
|
||
local langNames = {
|
||
zh = "中文",
|
||
en = "英语",
|
||
es = "西班牙语",
|
||
fr = "法语",
|
||
pl = "波兰语",
|
||
de = "德语",
|
||
ru = "俄语",
|
||
ko = "韩语",
|
||
ja = "日语",
|
||
auto = "自动检测"
|
||
}
|
||
|
||
for _, lang in ipairs(langs) do
|
||
local info = NewDropDownInfo()
|
||
info.text = langNames[lang.code] or lang.name or lang.code
|
||
info.notCheckable = 1
|
||
|
||
-- Capture the primitive value correctly for Lua 5.0 loops
|
||
local currentLangCode = lang.code
|
||
|
||
info.func = function()
|
||
local outputFrame = targetFrame or DEFAULT_CHAT_FRAME
|
||
if _G.STranslateAPI and _G.STranslateAPI.IsReady and _G.STranslateAPI.IsReady() then
|
||
_G.STranslateAPI.Translate(cleanText, "auto", currentLangCode, function(result, err, meta)
|
||
if err then
|
||
outputFrame:AddMessage("|cffff3333[Nanami-UI] 翻译失败:|r " .. tostring(err))
|
||
return
|
||
end
|
||
if not result then return end
|
||
outputFrame:AddMessage("|cff00ffff[翻译] |cffffff00" .. tostring(result) .. "|r")
|
||
end, "Nanami-UI")
|
||
elseif _G.STranslate and _G.STranslate.SendIO then
|
||
_G.STranslate:SendIO(cleanText, "IN", "auto", currentLangCode)
|
||
end
|
||
CloseDropDownMenus()
|
||
end
|
||
UIDropDownMenu_AddButton(info, level)
|
||
end
|
||
return
|
||
end
|
||
|
||
if level == 1 then
|
||
if sender and sender ~= "" then
|
||
local replySender = sender
|
||
local infoReply = NewDropDownInfo()
|
||
infoReply.text = "回复 " .. replySender
|
||
infoReply.notCheckable = 1
|
||
infoReply.func = function()
|
||
if ChatFrameEditBox then
|
||
ChatFrameEditBox:Show()
|
||
ChatFrameEditBox:SetText("/w " .. replySender .. " ")
|
||
ChatFrameEditBox:SetFocus()
|
||
end
|
||
CloseDropDownMenus()
|
||
end
|
||
UIDropDownMenu_AddButton(infoReply, level)
|
||
|
||
local infoName = NewDropDownInfo()
|
||
infoName.text = "复制玩家姓名"
|
||
infoName.notCheckable = 1
|
||
infoName.func = function()
|
||
SFrames.Chat:ShowCopyDialog(replySender)
|
||
end
|
||
UIDropDownMenu_AddButton(infoName, level)
|
||
end
|
||
|
||
local info = NewDropDownInfo()
|
||
info.text = "复制内容"
|
||
info.notCheckable = 1
|
||
info.func = function()
|
||
SFrames.Chat:ShowCopyDialog(text)
|
||
end
|
||
UIDropDownMenu_AddButton(info, level)
|
||
|
||
if (_G.STranslateAPI and _G.STranslateAPI.IsReady and _G.STranslateAPI.IsReady()) or (_G.STranslate and _G.STranslate.SendIO) then
|
||
local t = NewDropDownInfo()
|
||
t.text = "翻译为..."
|
||
t.hasArrow = 1
|
||
t.value = "STranslate_Langs"
|
||
t.notCheckable = 1
|
||
UIDropDownMenu_AddButton(t, level)
|
||
end
|
||
end
|
||
end, "MENU")
|
||
ToggleDropDownMenu(1, nil, self._msgDropdown, "cursor", 3, -3)
|
||
end
|
||
SFrames:RegisterEvent("CHANNEL_UI_UPDATE", function()
|
||
if SFrames and SFrames.Chat then
|
||
if EnsureDB().enable == false then return end
|
||
SFrames.Chat:ApplyBlockedChannelsGlobally()
|
||
SFrames.Chat:RefreshConfigFrame()
|
||
local delayFrame = CreateFrame("Frame")
|
||
local elapsed = 0
|
||
delayFrame:SetScript("OnUpdate", function()
|
||
elapsed = elapsed + (arg1 or 0)
|
||
if elapsed >= 0.5 then
|
||
this:SetScript("OnUpdate", nil)
|
||
if SFrames and SFrames.Chat then
|
||
SFrames.Chat:RefreshConfigFrame()
|
||
end
|
||
end
|
||
end)
|
||
end
|
||
end)
|
||
|
||
SFrames:RegisterEvent("CHAT_MSG_CHANNEL_NOTICE", function()
|
||
if SFrames and SFrames.Chat then
|
||
if EnsureDB().enable == false then return end
|
||
-- Track channel join/leave from notice events (arg9 = channel name)
|
||
local noticeName = arg9 or arg4 or ""
|
||
if noticeName ~= "" then
|
||
if arg1 == "YOU_JOINED" then
|
||
TrackDiscoveredChannel(noticeName)
|
||
elseif arg1 == "YOU_LEFT" then
|
||
UntrackDiscoveredChannel(noticeName)
|
||
end
|
||
end
|
||
SFrames.Chat:ApplyBlockedChannelsGlobally()
|
||
SFrames.Chat:RefreshConfigFrame()
|
||
-- Delayed re-refresh: GetChannelList() may lag behind the event
|
||
local delayFrame = CreateFrame("Frame")
|
||
local elapsed = 0
|
||
delayFrame:SetScript("OnUpdate", function()
|
||
elapsed = elapsed + (arg1 or 0)
|
||
if elapsed >= 0.5 then
|
||
this:SetScript("OnUpdate", nil)
|
||
if SFrames and SFrames.Chat then
|
||
SFrames.Chat:RefreshConfigFrame()
|
||
end
|
||
end
|
||
end)
|
||
end
|
||
end)
|
||
|
||
SFrames:RegisterEvent("ZONE_CHANGED", function()
|
||
if SFrames and SFrames.Chat then
|
||
if EnsureDB().enable == false then return end
|
||
SFrames.Chat:ApplyBlockedChannelsGlobally()
|
||
SFrames.Chat:RefreshConfigFrame()
|
||
SFrames.Chat:StartStabilizer()
|
||
end
|
||
end)
|
||
|
||
SFrames:RegisterEvent("ZONE_CHANGED_INDOORS", function()
|
||
if SFrames and SFrames.Chat then
|
||
if EnsureDB().enable == false then return end
|
||
SFrames.Chat:ApplyBlockedChannelsGlobally()
|
||
SFrames.Chat:RefreshConfigFrame()
|
||
SFrames.Chat:StartStabilizer()
|
||
end
|
||
end)
|
||
|
||
SFrames:RegisterEvent("ZONE_CHANGED_NEW_AREA", function()
|
||
if SFrames and SFrames.Chat then
|
||
if EnsureDB().enable == false then return end
|
||
SFrames.Chat:ApplyBlockedChannelsGlobally()
|
||
SFrames.Chat:RefreshConfigFrame()
|
||
end
|
||
end)
|
||
|
||
local function ChatCombatReanchor()
|
||
if SFrames and SFrames.Chat then
|
||
if EnsureDB().enable == false then return end
|
||
SFrames.Chat:ReanchorChatFrames()
|
||
end
|
||
end
|
||
SFrames:RegisterEvent("PLAYER_REGEN_DISABLED", ChatCombatReanchor)
|
||
SFrames:RegisterEvent("PLAYER_REGEN_ENABLED", ChatCombatReanchor)
|
||
|