0.1.229: Kanaele-Reiter als Chatfenster: Kategorien, Chatblasen mit Klassenbild und Level, Log fuer Service-Kanaele, Gilde/Gruppe/Fluestern, 7-Tage-Verlauf, Rechtsklick-Menue, Kopierfeld [ungeprueft]

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016onJCSHLwhs9udhC9ZLzk9
This commit is contained in:
Lacustat
2026-11-01 23:57:14 +01:00
co-authored by Claude Sonnet 5.5
parent 442709319e
commit ae241c5dfb
4 changed files with 1154 additions and 145 deletions
+409
View File
@@ -0,0 +1,409 @@
-- Freewood-OctoWoW-Core: Chatverlauf (Kanaele, Gilde, Gruppe, Fluestern) bis 7 Tage, Klasse/Level-Merker, /who-Abfrage.
-- ALLES UNGEPRUEFT im Spiel. Alle Spielaufrufe sind geschuetzt.
local ADDON, FW = ...
local G = _G
local CL = {}
FW.ChatLog = CL
CL.version = 0
CL.unread = {}
CL.guild = {} -- online Gildenmitglieder { {name,level,class,zone} }
CL.viewKey = nil -- vom Reiter gesetzt: aktuell angezeigter Chat
CL.viewVisible = false
local KEEP = 7 * 86400
local CAP_CH, CAP_OTHER = 300, 1000
local PLAYER_KEEP = 30 * 86400
local PLAYER_MAX = 3000
local function Now() return (G.time and G.time()) or 0 end
local function DB()
local d = FW.chardb
if type(d) ~= "table" then return nil end
if type(d.chat) ~= "table" then d.chat = {} end
local c = d.chat
if type(c.lines) ~= "table" then c.lines = {} end
if type(c.open) ~= "table" then c.open = {} end
return c
end
local function PDB()
local d = G.FreewoodDB
if type(d) ~= "table" then return nil end
if type(d.chatPlayers) ~= "table" then d.chatPlayers = {} end
return d.chatPlayers
end
------------------------------------------------------------------------
-- Klassen
------------------------------------------------------------------------
local TOKENS = {
warrior = "WARRIOR", krieger = "WARRIOR", kriegerin = "WARRIOR",
mage = "MAGE", magier = "MAGE", magierin = "MAGE",
rogue = "ROGUE", schurke = "ROGUE", schurkin = "ROGUE",
druid = "DRUID", druide = "DRUID", druidin = "DRUID",
hunter = "HUNTER", ["jäger"] = "HUNTER", ["jägerin"] = "HUNTER", jaeger = "HUNTER",
shaman = "SHAMAN", schamane = "SHAMAN", schamanin = "SHAMAN",
priest = "PRIEST", priester = "PRIEST", priesterin = "PRIEST",
warlock = "WARLOCK", hexenmeister = "WARLOCK", hexenmeisterin = "WARLOCK",
paladin = "PALADIN",
}
function CL.ClassToken(c)
if type(c) ~= "string" or c == "" then return nil end
return TOKENS[string.lower(c)]
end
CL.ICON_COORDS = {
WARRIOR = { 0, 0.25, 0, 0.25 }, MAGE = { 0.25, 0.5, 0, 0.25 },
ROGUE = { 0.5, 0.75, 0, 0.25 }, DRUID = { 0.75, 1, 0, 0.25 },
HUNTER = { 0, 0.25, 0.25, 0.5 }, SHAMAN = { 0.25, 0.5, 0.25, 0.5 },
PRIEST = { 0.5, 0.75, 0.25, 0.5 }, WARLOCK = { 0.75, 1, 0.25, 0.5 },
PALADIN = { 0, 0.25, 0.5, 0.75 },
}
CL.ICON_FILE = "Interface\\WorldStateFrame\\Icons-Classes"
local FALLBACK_COLORS = {
WARRIOR = { 0.78, 0.61, 0.43 }, MAGE = { 0.41, 0.8, 0.94 }, ROGUE = { 1, 0.96, 0.41 },
DRUID = { 1, 0.49, 0.04 }, HUNTER = { 0.67, 0.83, 0.45 }, SHAMAN = { 0, 0.44, 0.87 },
PRIEST = { 1, 1, 1 }, WARLOCK = { 0.58, 0.51, 0.79 }, PALADIN = { 0.96, 0.55, 0.73 },
}
function CL.ClassColor(token)
local c = token and G.RAID_CLASS_COLORS and G.RAID_CLASS_COLORS[token]
if c and c.r then return c.r, c.g, c.b end
local f = token and FALLBACK_COLORS[token]
if f then return f[1], f[2], f[3] end
return 0.9, 0.89, 0.84
end
------------------------------------------------------------------------
-- Spielerdaten (Klasse, Level) merken
------------------------------------------------------------------------
function CL.Note(name, class, level)
if type(name) ~= "string" or name == "" then return end
local tok = CL.ClassToken(class) or (type(class) == "string" and FALLBACK_COLORS[class] and class) or nil
local lv = tonumber(level)
if not tok and not lv then return end
local db = PDB()
if not db then return end
local r = db[name]
if not r then r = {}; db[name] = r end
local changed = false
if tok and r.c ~= tok then r.c = tok; changed = true end
if lv and lv > 0 and r.l ~= lv then r.l = lv; changed = true end
r.t = Now()
if changed then CL.version = CL.version + 1 end
end
function CL.Info(name)
local db = PDB()
local r = db and db[name]
if r then return r.c, r.l end
return nil, nil
end
local function NoteUnit(unit)
if not (G.UnitExists and G.UnitExists(unit) and G.UnitIsPlayer and G.UnitIsPlayer(unit)) then return end
local name = G.UnitName(unit)
local _, tok = G.UnitClass(unit)
CL.Note(name, tok, G.UnitLevel(unit))
end
function CL.ScanGroup()
local out = {}
local function Add(unit)
if G.UnitExists and G.UnitExists(unit) then
local name = G.UnitName(unit)
local _, tok = G.UnitClass(unit)
local lv = G.UnitLevel(unit)
CL.Note(name, tok, lv)
out[table.getn(out) + 1] = { name = name, class = tok, level = lv }
end
end
local raid = (G.GetNumRaidMembers and G.GetNumRaidMembers()) or 0
if raid > 0 then
local i = 1
while i <= raid do Add("raid" .. i); i = i + 1 end
else
Add("player")
local i = 1
while i <= 4 do Add("party" .. i); i = i + 1 end
end
return out
end
------------------------------------------------------------------------
-- Gilde
------------------------------------------------------------------------
local lastGuildReq = 0
function CL.RequestGuild()
local now = (G.GetTime and G.GetTime()) or 0
if now - lastGuildReq < 20 then return end
lastGuildReq = now
if G.IsInGuild and G.IsInGuild() and G.GuildRoster then pcall(G.GuildRoster) end
end
function CL.RefreshGuild()
local out = {}
if G.IsInGuild and G.IsInGuild() and G.GetNumGuildMembers then
local n = G.GetNumGuildMembers() or 0
local i = 1
while i <= n do
local name, rank, ri, level, class, zone, note, onote, online = G.GetGuildRosterInfo(i)
if name then
CL.Note(name, class, level)
if online then out[table.getn(out) + 1] = { name = name, level = level, class = CL.ClassToken(class), zone = zone } end
end
i = i + 1
end
table.sort(out, function(a, b) return a.name < b.name end)
end
CL.guild = out
CL.version = CL.version + 1
end
------------------------------------------------------------------------
-- /who im Hintergrund (nur wenn Klasse/Level unbekannt)
------------------------------------------------------------------------
local whoQueue, whoTried, whoCur = {}, {}, nil
local whoSent, whoLast = 0, 0
local WHO_GAP, WHO_TIMEOUT, WHO_RETRY = 6, 5, 600
function CL.Ask(name, force)
if type(name) ~= "string" or name == "" then return end
local c, l = CL.Info(name)
if c and l and not force then return end
local now = Now()
if whoTried[name] and now - whoTried[name] < WHO_RETRY and not force then return end
local k = 1
while k <= table.getn(whoQueue) do
if whoQueue[k] == name then return end
k = k + 1
end
if table.getn(whoQueue) >= 25 then return end
whoQueue[table.getn(whoQueue) + 1] = name
end
local function WhoDone()
whoCur = nil
local ff = G.FriendsFrame
if ff and ff.RegisterEvent then pcall(ff.RegisterEvent, ff, "WHO_LIST_UPDATE") end
if G.SetWhoToUI then pcall(G.SetWhoToUI, 0) end
end
local function WhoSend(name)
whoCur = name
whoTried[name] = Now()
whoSent = (G.GetTime and G.GetTime()) or 0
whoLast = whoSent
local ff = G.FriendsFrame
if ff and ff.UnregisterEvent then pcall(ff.UnregisterEvent, ff, "WHO_LIST_UPDATE") end
if G.SetWhoToUI then pcall(G.SetWhoToUI, 1) end
if G.SendWho then pcall(G.SendWho, "n-" .. name) end
end
local pump = CreateFrame("Frame")
pump:SetScript("OnUpdate", function()
local now = (G.GetTime and G.GetTime()) or 0
if whoCur and now - whoSent > WHO_TIMEOUT then WhoDone() end
if not whoCur and table.getn(whoQueue) > 0 and now - whoLast >= WHO_GAP then
local name = whoQueue[1]
local n = table.getn(whoQueue)
local k = 1
while k < n do whoQueue[k] = whoQueue[k + 1]; k = k + 1 end
whoQueue[n] = nil
WhoSend(name)
end
end)
local function OnWho()
local n = (G.GetNumWhoResults and G.GetNumWhoResults()) or 0
local i = 1
while i <= n do
local name, guild, level, race, class = G.GetWhoInfo(i)
if name then CL.Note(name, class, level) end
i = i + 1
end
if whoCur then WhoDone() end
end
------------------------------------------------------------------------
-- Verlauf
------------------------------------------------------------------------
function CL.Lines(key)
local c = DB()
return c and c.lines[key] or nil
end
function CL.Add(key, sender, text, out)
local c = DB()
if not c or not key then return end
local list = c.lines[key]
if not list then list = {}; c.lines[key] = list end
local n = table.getn(list) + 1
list[n] = { t = Now(), s = sender, m = text, o = out and 1 or nil }
local cap = (string.sub(key, 1, 2) == "c:") and CAP_CH or CAP_OTHER
if n > cap + 50 then
local new, i, j = {}, n - cap + 1, 1
while i <= n do new[j] = list[i]; i = i + 1; j = j + 1 end
c.lines[key] = new
end
if not (CL.viewVisible and CL.viewKey == key) then
CL.unread[key] = (CL.unread[key] or 0) + 1
end
if sender and not out then CL.Ask(sender) end
CL.version = CL.version + 1
end
function CL.Prune()
local c = DB()
if c then
local limit = Now() - KEEP
for key, list in pairs(c.lines) do
local n = table.getn(list)
local first = 1
while first <= n and (list[first].t or 0) < limit do first = first + 1 end
if first > 1 then
local new, j, i = {}, 1, first
while i <= n do new[j] = list[i]; i = i + 1; j = j + 1 end
if table.getn(new) == 0 then c.lines[key] = nil else c.lines[key] = new end
end
end
for name, t in pairs(c.open) do
if type(t) ~= "number" or t < limit then c.open[name] = nil end
end
end
local p = PDB()
if p then
local lim = Now() - PLAYER_KEEP
local cnt = 0
for name, r in pairs(p) do
if (r.t or 0) < lim then p[name] = nil else cnt = cnt + 1 end
end
if cnt > PLAYER_MAX then
for name in pairs(p) do p[name] = nil; cnt = cnt - 1; if cnt <= PLAYER_MAX then break end end
end
end
end
-- geoeffnete Fluesterchats (Name -> letzte Zeit)
function CL.OpenWhispers()
local c = DB()
local out = {}
if c then
for name, t in pairs(c.open) do out[table.getn(out) + 1] = { name = name, t = t } end
table.sort(out, function(a, b) return a.t > b.t end)
end
return out
end
function CL.OpenWhisper(name)
local c = DB()
if c and name then c.open[name] = Now(); CL.version = CL.version + 1 end
end
function CL.CloseWhisper(name)
local c = DB()
if c and name then c.open[name] = nil; CL.version = CL.version + 1 end
end
------------------------------------------------------------------------
-- Ereignisse (ohne Owner registrieren: sonst rutscht der Owner als erstes Argument rein)
------------------------------------------------------------------------
local function IsDataChannel(name)
return type(name) == "string" and string.find(name, "^Freewood") ~= nil
end
function CL.ChannelKey(num, name)
local Ch = FW.Channels
local e = Ch and tonumber(num) and Ch.byNum[tonumber(num)]
local nm = (e and e.name) or name
if type(nm) ~= "string" or nm == "" then return nil, nil end
return "c:" .. nm, nm
end
FW:RegisterEvent("CHAT_MSG_CHANNEL", function(text, sender, lang, chanStr, target, flags, zone, num, cname)
local key, nm = CL.ChannelKey(num, cname)
if not key or IsDataChannel(nm) or IsDataChannel(cname) then return end
CL.Add(key, sender, text, sender == G.UnitName("player"))
end)
local function GuildMsg(text, sender)
CL.Add("guild", sender, text, sender == G.UnitName("player"))
end
FW:RegisterEvent("CHAT_MSG_GUILD", GuildMsg)
FW:RegisterEvent("CHAT_MSG_OFFICER", GuildMsg)
local function GroupMsg(text, sender)
CL.Add("group", sender, text, sender == G.UnitName("player"))
end
FW:RegisterEvent("CHAT_MSG_PARTY", GroupMsg)
FW:RegisterEvent("CHAT_MSG_RAID", GroupMsg)
FW:RegisterEvent("CHAT_MSG_RAID_LEADER", GroupMsg)
FW:RegisterEvent("CHAT_MSG_RAID_WARNING", GroupMsg)
FW:RegisterEvent("CHAT_MSG_WHISPER", function(text, sender)
if not sender then return end
CL.OpenWhisper(sender)
CL.Add("w:" .. sender, sender, text, false)
end)
FW:RegisterEvent("CHAT_MSG_WHISPER_INFORM", function(text, target)
if not target then return end
CL.OpenWhisper(target)
CL.Add("w:" .. target, G.UnitName("player"), text, true)
end)
FW:RegisterEvent("GUILD_ROSTER_UPDATE", function() CL.RefreshGuild() end)
FW:RegisterEvent("WHO_LIST_UPDATE", OnWho)
FW:RegisterEvent("PLAYER_TARGET_CHANGED", function() NoteUnit("target") end)
FW:RegisterEvent("UPDATE_MOUSEOVER_UNIT", function() NoteUnit("mouseover") end)
FW:RegisterEvent("PARTY_MEMBERS_CHANGED", function() CL.ScanGroup() end)
FW:RegisterEvent("PLAYER_ENTERING_WORLD", function()
CL.Prune()
CL.RequestGuild()
end)
------------------------------------------------------------------------
-- Senden
------------------------------------------------------------------------
-- kind: "channel" (target = Kanalnummer), "guild", "group", "whisper" (target = Name)
function CL.Send(kind, target, text)
if type(text) ~= "string" or text == "" or not G.SendChatMessage then return end
if kind == "channel" then
pcall(G.SendChatMessage, text, "CHANNEL", nil, target)
elseif kind == "guild" then
pcall(G.SendChatMessage, text, "GUILD")
elseif kind == "group" then
local raid = (G.GetNumRaidMembers and G.GetNumRaidMembers()) or 0
pcall(G.SendChatMessage, text, raid > 0 and "RAID" or "PARTY")
elseif kind == "whisper" then
pcall(G.SendChatMessage, text, "WHISPER", nil, target)
end
end
-- Klartext (Links/Farbcodes entfernen) fuers Kopieren
function CL.Plain(text)
local t = tostring(text or "")
t = string.gsub(t, "|H.-|h(.-)|h", "%1")
t = string.gsub(t, "|c%x%x%x%x%x%x%x%x", "")
t = string.gsub(t, "|r", "")
return t
end
function CL.FirstUrl(text)
local t = CL.Plain(text)
local _, _, u = string.find(t, "(https?://%S+)")
if not u then _, _, u = string.find(t, "(www%.%S+)") end
return u
end
+2 -1
View File
@@ -3,7 +3,7 @@
## Notes: Base addon of the Freewood family: data storage, UI kit, export/import and the main window.
## Notes-deDE: Basis-Addon der Freewood-Familie: Datenspeicher, UI-Bausteine, Export/Import und Hauptfenster.
## Author: Lacustat
## Version: 0.1.228
## Version: 0.1.229
## SavedVariables: FreewoodDB
## SavedVariablesPerCharacter: FreewoodCharDB
@@ -17,6 +17,7 @@ UI\MainFrame.lua
UI\MainWindow.lua
UI\Tabs.lua
Core\Channels.lua
Core\ChatLog.lua
UI\ChannelsTab.lua
Core\Tooltips.lua
Core\TooltipInfo.lua
+50
View File
@@ -56,6 +56,31 @@ FW:AddLocale("enUS", {
TAB_EXCHANGE = "Exchange",
TAB_SETTINGS = "Settings",
TAB_CHANNELS = "Channels",
CHAT_CAT_STD = "OctoWoW Std.",
CHAT_CAT_OTHER = "Other channels",
CHAT_CAT_GUILD = "Guild",
CHAT_CAT_GROUP = "Group",
CHAT_CAT_WHISPER = "Whispers",
CHAT_CAT_SERVICE = "Addon service channels",
CHAT_GUILD = "Guild chat",
CHAT_GROUP = "Group chat",
CHAT_INPUT = "Write a message...",
CHAT_SEND = "Send",
CHAT_EMPTY = "No messages yet.",
CHAT_MEMBERS_HDR = "Members",
CHAT_ONLINE_HDR = "Online",
CHAT_DATA = "Data channel of the Freewood modules (protocol data, no chat). The members are shown on the right.",
CHAT_COPY_MSG = "Message",
CHAT_COPY_LINK = "Link",
CHAT_COPY_HINT = "Ctrl+C copies, Esc closes.",
CHAT_MENU_FRIEND = "Add friend",
CHAT_MENU_WHO = "Look up (/who)",
CHAT_MENU_COPY = "Copy name",
CHAT_NOGUILD = "You are not in a guild.",
CHAT_NOGROUP = "You are not in a group.",
CHAT_NOWHISPER = "No open whispers. A whisper appears in the list when someone writes to you.",
CHAT_CLOSE_WHISPER = "Close whisper",
CHAT_KEPT = "Chats are kept up to 7 days.",
CHAN_REFRESH = "Refresh",
CHAN_SEARCH = "Find player in all channels...",
CHAN_NONE = "You are not in any channel.",
@@ -172,6 +197,31 @@ FW:AddLocale("deDE", {
TAB_EXCHANGE = "Austausch",
TAB_SETTINGS = "Einstellungen",
TAB_CHANNELS = "Kanäle",
CHAT_CAT_STD = "OctoWoW Std.",
CHAT_CAT_OTHER = "Weitere Kanäle",
CHAT_CAT_GUILD = "Gilde",
CHAT_CAT_GROUP = "Gruppe",
CHAT_CAT_WHISPER = "Flüstern",
CHAT_CAT_SERVICE = "Addon Service Channel",
CHAT_GUILD = "Gildenchat",
CHAT_GROUP = "Gruppenchat",
CHAT_INPUT = "Nachricht schreiben...",
CHAT_SEND = "Senden",
CHAT_EMPTY = "Noch keine Nachrichten.",
CHAT_MEMBERS_HDR = "Mitglieder",
CHAT_ONLINE_HDR = "Online",
CHAT_DATA = "Datenkanal der Freewood-Module (Protokolldaten, kein Chat). Die Mitglieder siehst du rechts.",
CHAT_COPY_MSG = "Nachricht",
CHAT_COPY_LINK = "Link",
CHAT_COPY_HINT = "Strg+C kopiert, Esc schließt.",
CHAT_MENU_FRIEND = "Freund hinzufügen",
CHAT_MENU_WHO = "Nachschlagen (/who)",
CHAT_MENU_COPY = "Namen kopieren",
CHAT_NOGUILD = "Du bist in keiner Gilde.",
CHAT_NOGROUP = "Du bist in keiner Gruppe.",
CHAT_NOWHISPER = "Keine offenen Flüsterchats. Sobald dich jemand anflüstert, erscheint er hier in der Liste.",
CHAT_CLOSE_WHISPER = "Flüsterchat schließen",
CHAT_KEPT = "Chats werden bis zu 7 Tage gespeichert.",
CHAN_REFRESH = "Aktualisieren",
CHAN_SEARCH = "Spieler in allen Kanälen suchen...",
CHAN_NONE = "Du bist in keinem Kanal.",
+693 -144
View File
@@ -1,218 +1,767 @@
-- Freewood-OctoWoW-Core: Reiter "Kanaele" (Chat-Kanaele und ihre Mitglieder). UNGEPRUEFT im Spiel.
-- Links die Kanaele, rechts die Mitglieder des gewaehlten Kanals oder die Suchtreffer ueber alle Kanaele.
-- Freewood-OctoWoW-Core: Reiter "Kanaele" als Chatfenster (Kategorien links, Chat Mitte, Mitglieder rechts).
-- ALLES UNGEPRUEFT im Spiel. Datenschicht: Core/Channels.lua (Mitglieder) und Core/ChatLog.lua (Verlauf).
local ADDON, FW = ...
local UI = FW.UI
local Ch = FW.Channels
local CL = FW.ChatLog
local ROW = 20
local MAX_ROWS = 300
local LEFT_W, RIGHT_W = 220, 220
local ROW = 24
local MAX_BUBBLES = 60
local LOG_LINES = 200
local LOGO = "Interface\\AddOns\\Freewood-OctoWoW-Core\\Media\\FreeWood_Logo.tga"
local QMARK = "Interface\\Icons\\INV_Misc_QuestionMark"
local function MakeRows(holder)
local rows = {}
function rows.Get(i)
local r = rows[i]
if not r then
r = CreateFrame("Button", nil, holder.child)
r:SetHeight(ROW)
r:SetPoint("TOPLEFT", holder.child, "TOPLEFT", 0, -(i - 1) * ROW)
r:SetPoint("TOPRIGHT", holder.child, "TOPRIGHT", 0, -(i - 1) * ROW)
r.sel = r:CreateTexture(nil, "BACKGROUND")
r.sel:SetAllPoints()
r.sel:SetColorTexture(UI.Color("bronze", 0.35))
r.sel:Hide()
r.hl = r:CreateTexture(nil, "HIGHLIGHT")
r.hl:SetAllPoints()
r.hl:SetColorTexture(UI.Color("gold", 0.15))
r.text = UI.CreateLabel(r, "", { size = 12, justify = "LEFT" })
r.text:SetPoint("LEFT", r, "LEFT", 6, 0)
r.text:SetPoint("RIGHT", r, "RIGHT", -4, 0)
rows[i] = r
end
return r
end
function rows.HideFrom(i)
local k = i
while rows[k] do rows[k]:Hide(); k = k + 1 end
end
return rows
local ICONS = {
general = "Interface\\Icons\\INV_Misc_Note_01",
localdefense = "Interface\\Icons\\INV_Shield_06",
world = "Interface\\Icons\\INV_Misc_Map_01",
lookingforgroup = "Interface\\Icons\\INV_Misc_GroupLooking",
lft = "Interface\\Icons\\INV_Misc_GroupNeedMore",
}
local ICON_GUILD = "Interface\\Icons\\INV_Shirt_GuildTabard_01"
local ICON_GROUP = "Interface\\Icons\\Spell_Holy_PrayerOfHealing"
local ICON_WHISPER = "Interface\\Icons\\INV_Letter_02"
local STD = { general = true, localdefense = true, world = true, lookingforgroup = true,
allgemein = true, lokaleverteidigung = true }
local function Hex(r, g, b)
return string.format("|cff%02x%02x%02x", math.floor(r * 255 + 0.5), math.floor(g * 255 + 0.5), math.floor(b * 255 + 0.5))
end
local function BuildChannels(content)
local function SetClassIcon(tex, token)
local c = token and CL.ICON_COORDS[token]
if c then
tex:SetTexture(CL.ICON_FILE)
tex:SetTexCoord(c[1], c[2], c[3], c[4])
else
tex:SetTexture(QMARK)
tex:SetTexCoord(0.08, 0.92, 0.08, 0.92)
end
end
local function SetIcon(tex, path)
tex:SetTexture(path or QMARK)
tex:SetTexCoord(0.08, 0.92, 0.08, 0.92)
end
local G_date = _G.date
local function TimeText(t)
if not (t and G_date) then return "" end
local ok, s = pcall(G_date, "%H:%M", t)
return ok and s or ""
end
local function Build(content)
local L = FW.L
local f = CreateFrame("Frame", nil, content)
f:SetAllPoints(content)
local selNum, selPlayer, seenVersion = nil, nil, -1
local selKey = nil
local entries = {}
local search = ""
local lastAuto = 0
local seen = { cl = -1, ch = -1, w = 0 }
local stick = true
local lastH, lastViewH = 0, 0
-- links: Kanalliste
local title = UI.CreateLabel(f, L.TAB_CHANNELS, { font = "heading", size = 15, color = "gold" })
title:SetPoint("TOPLEFT", f, "TOPLEFT", 0, 0)
local refresh = UI.CreateButton(f, L.CHAN_REFRESH, 110, 24, function()
Ch.RefreshAll()
------------------------------------------------------------------
-- Kopierfeld und Menue (gemeinsam)
------------------------------------------------------------------
local copy = UI.CreatePanel(UIParent, "bgDeep", "gold")
copy:SetSize(560, 96)
copy:SetPoint("CENTER", UIParent, "CENTER", 0, 120)
copy:SetFrameStrata("FULLSCREEN_DIALOG")
copy:Hide()
local copyHint = UI.CreateLabel(copy, L.CHAT_COPY_HINT, { size = 11, color = "textDim" })
copyHint:SetPoint("TOPLEFT", copy, "TOPLEFT", 12, -8)
local cbox = UI.CreatePanel(copy, "bg", "bronze")
cbox:SetPoint("TOPLEFT", copy, "TOPLEFT", 10, -28)
cbox:SetPoint("TOPRIGHT", copy, "TOPRIGHT", -10, -28)
cbox:SetHeight(26)
local cedit = CreateFrame("EditBox", nil, cbox)
cedit:SetAutoFocus(false)
cedit:SetPoint("TOPLEFT", cbox, "TOPLEFT", 6, -3)
cedit:SetPoint("BOTTOMRIGHT", cbox, "BOTTOMRIGHT", -6, 3)
cedit:SetMaxLetters(600)
if not cedit:SetFont(UI.GetFontPath("body"), 12, "") then cedit:SetFont("Fonts\\FRIZQT__.TTF", 12, "") end
cedit:SetTextColor(UI.Color("text"))
cedit:SetScript("OnEscapePressed", function() copy:Hide() end)
local copyMsg, copyUrl
local bMsg = UI.CreateButton(copy, L.CHAT_COPY_MSG, 100, 24, function()
if copyMsg then cedit:SetText(copyMsg); cedit:SetFocus(); cedit:HighlightText() end
end)
refresh:SetPoint("TOPLEFT", f, "TOPLEFT", 100, 2)
local left = UI.CreateScrollFrame(f)
left:SetPoint("TOPLEFT", f, "TOPLEFT", 0, -34)
left:SetPoint("BOTTOMLEFT", f, "BOTTOMLEFT", 0, 0)
left:SetWidth(220)
local lrows = MakeRows(left)
bMsg:SetPoint("BOTTOMLEFT", copy, "BOTTOMLEFT", 10, 8)
local bUrl = UI.CreateButton(copy, L.CHAT_COPY_LINK, 100, 24, function()
if copyUrl then cedit:SetText(copyUrl); cedit:SetFocus(); cedit:HighlightText() end
end)
bUrl:SetPoint("LEFT", bMsg, "RIGHT", 6, 0)
local bX = UI.CreateButton(copy, "X", 30, 24, function() copy:Hide() end)
bX:SetPoint("BOTTOMRIGHT", copy, "BOTTOMRIGHT", -10, 8)
-- rechts: Suche, Status, Mitglieder, Aktionsleiste
local function ShowCopy(text, url)
copyMsg, copyUrl = text, url
cedit:SetText(text or "")
if url then bUrl:Show() else bUrl:Hide() end
copy:Show()
cedit:SetFocus()
cedit:HighlightText()
end
local menu, menuCatcher, menuTitle
local menuName
local selectKey, Redraw
local input
local function HideMenu()
if menu then menu:Hide() end
if menuCatcher then menuCatcher:Hide() end
end
local function BuildMenu()
menuCatcher = CreateFrame("Button", nil, UIParent)
menuCatcher:SetAllPoints(UIParent)
menuCatcher:SetFrameStrata("FULLSCREEN_DIALOG")
menuCatcher:RegisterForClicks("LeftButtonUp", "RightButtonUp")
menuCatcher:SetScript("OnClick", HideMenu)
menuCatcher:Hide()
menu = UI.CreatePanel(UIParent, "bgDeep", "gold")
menu:SetSize(180, 28 + 6 * 24 + 8)
menu:SetFrameStrata("FULLSCREEN_DIALOG")
menu:SetFrameLevel(menuCatcher:GetFrameLevel() + 5)
menu:Hide()
menuTitle = UI.CreateLabel(menu, "", { size = 12, color = "gold" })
menuTitle:SetPoint("TOPLEFT", menu, "TOPLEFT", 10, -8)
local acts = {
{ L.CHAN_WHISPER, function(n)
CL.OpenWhisper(n)
if selectKey then selectKey("w:" .. n) end
if input then input:SetFocus() end
end },
{ L.CHAN_INVITE, function(n) Ch.Invite(n) end },
{ L.CHAN_IGNORE, function(n) Ch.Ignore(n) end },
{ L.CHAT_MENU_FRIEND, function(n) if _G.AddFriend then pcall(_G.AddFriend, n) end end },
{ L.CHAT_MENU_WHO, function(n) CL.Ask(n, true) end },
{ L.CHAT_MENU_COPY, function(n) ShowCopy(n, nil) end },
}
local i = 1
while i <= 6 do
local a = acts[i]
local b = UI.CreateButton(menu, a[1], 164, 22, function()
local n = menuName
HideMenu()
if n then a[2](n) end
end)
b:SetPoint("TOPLEFT", menu, "TOPLEFT", 8, -28 - (i - 1) * 24)
i = i + 1
end
end
local function ShowMenu(name)
if not name or name == "" then return end
if not menu then BuildMenu() end
menuName = name
menuTitle:SetText(name)
local x, y = GetCursorPosition()
local sc = UIParent:GetEffectiveScale()
menu:ClearAllPoints()
menu:SetPoint("TOPLEFT", UIParent, "BOTTOMLEFT", x / sc, y / sc)
menuCatcher:Show()
menu:Show()
end
------------------------------------------------------------------
-- Linke Spalte: Kategorien und Chats
------------------------------------------------------------------
local left = UI.CreateScrollFrame(f)
left:SetPoint("TOPLEFT", f, "TOPLEFT", 0, 0)
left:SetPoint("BOTTOMLEFT", f, "BOTTOMLEFT", 0, 0)
left:SetWidth(LEFT_W)
local lrows = {}
local function LeftRow(i)
local r = lrows[i]
if not r then
r = CreateFrame("Button", nil, left.child)
r:SetHeight(ROW)
r:SetPoint("TOPLEFT", left.child, "TOPLEFT", 0, -(i - 1) * ROW)
r:SetPoint("TOPRIGHT", left.child, "TOPRIGHT", 0, -(i - 1) * ROW)
r:RegisterForClicks("LeftButtonUp")
r.sel = r:CreateTexture(nil, "BACKGROUND")
r.sel:SetAllPoints()
r.sel:SetColorTexture(UI.Color("bronze", 0.35))
r.hl = r:CreateTexture(nil, "HIGHLIGHT")
r.hl:SetAllPoints()
r.hl:SetColorTexture(UI.Color("gold", 0.15))
r.icon = r:CreateTexture(nil, "ARTWORK")
r.icon:SetSize(18, 18)
r.icon:SetPoint("LEFT", r, "LEFT", 8, 0)
r.text = UI.CreateLabel(r, "", { size = 12, justify = "LEFT" })
r.text:SetPoint("LEFT", r.icon, "RIGHT", 6, 0)
r.text:SetPoint("RIGHT", r, "RIGHT", -26, 0)
r.x = CreateFrame("Button", nil, r)
r.x:SetSize(18, 18)
r.x:SetPoint("RIGHT", r, "RIGHT", -4, 0)
r.xt = UI.CreateLabel(r.x, "x", { size = 13, color = "textDim" })
r.xt:SetPoint("CENTER", r.x, "CENTER", 0, 0)
r.x:SetScript("OnEnter", function() r.xt:SetTextColor(UI.Color("danger")) end)
r.x:SetScript("OnLeave", function() r.xt:SetTextColor(UI.Color("textDim")) end)
lrows[i] = r
end
return r
end
local function BuildEntries()
local std, other, service = {}, {}, {}
local k = 1
while k <= table.getn(Ch.list) do
local e = Ch.list[k]
local low = string.lower(e.name)
local en = { key = "c:" .. e.name, kind = "channel", num = e.num, label = e.num .. ". " .. e.name,
count = e.stamp and e.count or nil, e = e }
if string.find(e.name, "^Freewood") then
en.mode = "data"; en.logo = true
service[table.getn(service) + 1] = en
elseif low == "lft" then
en.mode = "log"; en.icon = ICONS.lft
service[table.getn(service) + 1] = en
elseif STD[low] then
en.mode = "bubble"; en.icon = ICONS[low] or ICONS.general
std[table.getn(std) + 1] = en
else
en.mode = "bubble"; en.icon = ICONS[low] or ICONS.general
other[table.getn(other) + 1] = en
end
k = k + 1
end
local out = {}
local function Head(t) out[table.getn(out) + 1] = { head = t } end
local function Add(en) out[table.getn(out) + 1] = en end
if table.getn(std) > 0 then
Head(L.CHAT_CAT_STD)
k = 1; while k <= table.getn(std) do Add(std[k]); k = k + 1 end
end
if table.getn(other) > 0 then
Head(L.CHAT_CAT_OTHER)
k = 1; while k <= table.getn(other) do Add(other[k]); k = k + 1 end
end
Head(L.CHAT_CAT_GUILD)
Add({ key = "guild", kind = "guild", mode = "bubble", label = L.CHAT_GUILD, icon = ICON_GUILD,
count = table.getn(CL.guild) })
Head(L.CHAT_CAT_GROUP)
Add({ key = "group", kind = "group", mode = "bubble", label = L.CHAT_GROUP, icon = ICON_GROUP })
Head(L.CHAT_CAT_WHISPER)
local ws = CL.OpenWhispers()
k = 1
while k <= table.getn(ws) do
Add({ key = "w:" .. ws[k].name, kind = "whisper", mode = "bubble", label = ws[k].name,
icon = ICON_WHISPER, closable = true, name = ws[k].name })
k = k + 1
end
if table.getn(ws) == 0 then out[table.getn(out) + 1] = { note = L.CHAT_NOWHISPER } end
if table.getn(service) > 0 then
Head(L.CHAT_CAT_SERVICE)
k = 1; while k <= table.getn(service) do Add(service[k]); k = k + 1 end
end
return out
end
local function EntryByKey(key)
local k = 1
while k <= table.getn(entries) do
if entries[k].key == key then return entries[k] end
k = k + 1
end
return nil
end
local function FirstEntry()
local k = 1
while k <= table.getn(entries) do
if entries[k].key then return entries[k] end
k = k + 1
end
return nil
end
local function DrawLeft()
entries = BuildEntries()
if not selKey or not EntryByKey(selKey) then
local fe = FirstEntry()
selKey = fe and fe.key or nil
stick = true
end
local n = table.getn(entries)
local i = 1
local y = 0
while i <= n do
local en = entries[i]
local r = LeftRow(i)
r:ClearAllPoints()
r:SetPoint("TOPLEFT", left.child, "TOPLEFT", 0, -y)
r:SetPoint("TOPRIGHT", left.child, "TOPRIGHT", 0, -y)
r.x:Hide()
if en.head then
r:SetHeight(26)
r.sel:Hide(); r.hl:Hide(); r.icon:Hide()
r.text:ClearAllPoints()
r.text:SetPoint("LEFT", r, "LEFT", 8, -4)
r.text:SetPoint("RIGHT", r, "RIGHT", -4, -4)
r.text:SetText(Hex(UI.Color("gold")) .. string.upper(en.head) .. "|r")
r:SetScript("OnClick", nil)
y = y + 26
elseif en.note then
r:SetHeight(44)
r.sel:Hide(); r.hl:Hide(); r.icon:Hide()
r.text:ClearAllPoints()
r.text:SetPoint("LEFT", r, "LEFT", 8, 0)
r.text:SetPoint("RIGHT", r, "RIGHT", -8, 0)
r.text:SetText("|cff9a978c" .. en.note .. "|r")
r:SetScript("OnClick", nil)
y = y + 44
else
r:SetHeight(ROW)
r.hl:Show(); r.icon:Show()
r.text:ClearAllPoints()
r.text:SetPoint("LEFT", r.icon, "RIGHT", 6, 0)
r.text:SetPoint("RIGHT", r, "RIGHT", en.closable and -26 or -6, 0)
if en.logo then
r.icon:SetTexture(LOGO); r.icon:SetTexCoord(0, 1, 0, 1)
else
SetIcon(r.icon, en.icon)
end
local txt = en.label
if en.count then txt = txt .. " |cff9a978c(" .. en.count .. ")|r" end
local un = CL.unread[en.key]
if un and un > 0 and en.key ~= selKey then txt = txt .. " |cffc89b3c+" .. un .. "|r" end
r.text:SetText(txt)
if en.key == selKey then r.sel:Show() else r.sel:Hide() end
local key = en.key
r:SetScript("OnClick", function() selectKey(key) end)
if en.closable then
local nm = en.name
r.x:Show()
r.x:SetScript("OnClick", function()
CL.CloseWhisper(nm)
if selKey == "w:" .. nm then selKey = nil end
Redraw()
end)
end
y = y + ROW
end
r:Show()
i = i + 1
end
while lrows[i] do lrows[i]:Hide(); i = i + 1 end
left:SetContentHeight(math.max(1, y))
end
------------------------------------------------------------------
-- Mitte: Kopf, Chat (Blasen / Log), Eingabe
------------------------------------------------------------------
local head = UI.CreateLabel(f, "", { font = "heading", size = 15, color = "gold", justify = "LEFT" })
head:SetPoint("TOPLEFT", f, "TOPLEFT", LEFT_W + 14, -2)
head:SetPoint("TOPRIGHT", f, "TOPRIGHT", -(RIGHT_W + 14), -2)
local sub = UI.CreateLabel(f, "", { size = 11, color = "textDim", justify = "LEFT" })
sub:SetPoint("TOPLEFT", f, "TOPLEFT", LEFT_W + 14, -24)
sub:SetPoint("TOPRIGHT", f, "TOPRIGHT", -(RIGHT_W + 14), -24)
local chat = UI.CreateScrollFrame(f)
chat:SetPoint("TOPLEFT", f, "TOPLEFT", LEFT_W + 14, -44)
chat:SetPoint("BOTTOMRIGHT", f, "BOTTOMRIGHT", -(RIGHT_W + 14), 40)
local logArea = UI.CreateTextArea(f)
logArea:SetPoint("TOPLEFT", f, "TOPLEFT", LEFT_W + 14, -44)
logArea:SetPoint("BOTTOMRIGHT", f, "BOTTOMRIGHT", -(RIGHT_W + 14), 40)
logArea:Hide()
local empty = UI.CreateLabel(f, "", { size = 12, color = "textDim", justify = "LEFT", justifyV = "TOP" })
empty:SetPoint("TOPLEFT", f, "TOPLEFT", LEFT_W + 24, -56)
empty:SetPoint("TOPRIGHT", f, "TOPRIGHT", -(RIGHT_W + 24), -56)
empty:SetHeight(60)
local measure = f:CreateFontString(nil, "OVERLAY")
UI.SetFont(measure, "body", 12)
measure:Hide()
local ibox = UI.CreatePanel(f, "bgDeep", "bronze")
ibox:SetHeight(28)
ibox:SetPoint("BOTTOMLEFT", f, "BOTTOMLEFT", LEFT_W + 14, 6)
ibox:SetPoint("BOTTOMRIGHT", f, "BOTTOMRIGHT", -(RIGHT_W + 14 + 96), 6)
input = CreateFrame("EditBox", nil, ibox)
input:SetAutoFocus(false)
input:SetPoint("TOPLEFT", ibox, "TOPLEFT", 8, -3)
input:SetPoint("BOTTOMRIGHT", ibox, "BOTTOMRIGHT", -8, 3)
input:SetMaxLetters(255)
if not input:SetFont(UI.GetFontPath("body"), 12, "") then input:SetFont("Fonts\\FRIZQT__.TTF", 12, "") end
input:SetTextColor(UI.Color("text"))
local ihint = UI.CreateLabel(ibox, L.CHAT_INPUT, { size = 12, color = "textDim" })
ihint:SetPoint("LEFT", ibox, "LEFT", 10, 0)
local function DoSend()
local txt = input:GetText() or ""
local en = selKey and EntryByKey(selKey)
if txt == "" or not en then return end
if en.kind == "channel" then CL.Send("channel", en.num, txt)
elseif en.kind == "guild" then CL.Send("guild", nil, txt)
elseif en.kind == "group" then CL.Send("group", nil, txt)
elseif en.kind == "whisper" then CL.Send("whisper", en.name, txt) end
input:SetText("")
end
input:SetScript("OnEnterPressed", function() DoSend(); input:SetFocus() end)
input:SetScript("OnEscapePressed", function(self) self:ClearFocus() end)
input:SetScript("OnTextChanged", function(self)
if (self:GetText() or "") == "" then ihint:Show() else ihint:Hide() end
end)
local sendBtn = UI.CreateButton(f, L.CHAT_SEND, 88, 28, DoSend)
sendBtn:SetPoint("BOTTOMRIGHT", f, "BOTTOMRIGHT", -(RIGHT_W + 14), 6)
-- Blasen-Pool
local bubbles = {}
local function Bubble(i)
local b = bubbles[i]
if b then return b end
b = CreateFrame("Frame", nil, chat.child)
b:SetHeight(60)
b.avBtn = CreateFrame("Button", nil, b)
b.avBtn:SetSize(36, 36)
b.avBtn:RegisterForClicks("LeftButtonUp", "RightButtonUp")
b.av = b.avBtn:CreateTexture(nil, "ARTWORK")
b.av:SetAllPoints()
b.lvl = UI.CreateLabel(b, "", { size = 11, color = "textDim" })
b.panel = CreateFrame("Button", nil, b)
b.panel:RegisterForClicks("LeftButtonUp", "RightButtonUp")
b.bg = b.panel:CreateTexture(nil, "BACKGROUND")
b.bg:SetAllPoints()
b.hl = b.panel:CreateTexture(nil, "HIGHLIGHT")
b.hl:SetAllPoints()
b.hl:SetColorTexture(1, 1, 1, 0.05)
b.name = UI.CreateLabel(b.panel, "", { size = 12, justify = "LEFT" })
b.name:SetPoint("TOPLEFT", b.panel, "TOPLEFT", 10, -7)
b.time = UI.CreateLabel(b.panel, "", { size = 10, color = "textDim", justify = "RIGHT" })
b.time:SetPoint("TOPRIGHT", b.panel, "TOPRIGHT", -8, -8)
b.text = UI.CreateLabel(b.panel, "", { size = 12, justify = "LEFT", justifyV = "TOP" })
b.text:SetPoint("TOPLEFT", b.panel, "TOPLEFT", 10, -24)
bubbles[i] = b
return b
end
local function Plain(t) return CL.Plain(t) end
local function DrawBubbles(key)
local lines = CL.Lines(key) or {}
local n = table.getn(lines)
local first = math.max(1, n - MAX_BUBBLES + 1)
local areaW = chat.child:GetWidth()
if not areaW or areaW < 200 then areaW = 560 end
seen.w = areaW
local maxBub = math.floor(areaW * 0.68)
local atBottom = stick or (chat.scroll:GetVerticalScroll() >= lastH - lastViewH - 8)
local y = 6
local idx = 0
local i = first
while i <= n do
local ln = lines[i]
idx = idx + 1
local b = Bubble(idx)
local own = ln.o ~= nil
local who = ln.s or "?"
local cls, lv = CL.Info(who)
SetClassIcon(b.av, cls)
b.lvl:SetText(lv and tostring(lv) or "?")
local r, g, bl = CL.ClassColor(cls)
b.name:SetTextColor(r, g, bl)
b.name:SetText(who)
b.time:SetText(TimeText(ln.t))
local txt = ln.m or ""
measure:SetText(Plain(txt))
local tw = measure:GetStringWidth() or 0
if tw <= 0 then tw = maxBub end
local nameW = (b.name:GetStringWidth() or 60) + 50
local pw = math.floor(math.max(math.min(math.max(tw, nameW) + 22, maxBub), 110))
b.text:SetWidth(pw - 20)
b.text:SetText(txt)
local th = (b.text.GetStringHeight and b.text:GetStringHeight()) or b.text:GetHeight() or 14
if not th or th < 14 then th = 14 end
local ph = math.floor(th + 38)
b.panel:SetSize(pw, ph)
if own then b.bg:SetColorTexture(0.13, 0.27, 0.46, 0.95) else b.bg:SetColorTexture(UI.Color("card", 0.95)) end
local rowH = math.max(ph, 56)
b:SetSize(areaW - 8, rowH)
b:ClearAllPoints()
b:SetPoint("TOPLEFT", chat.child, "TOPLEFT", 0, -y)
b.avBtn:ClearAllPoints(); b.lvl:ClearAllPoints(); b.panel:ClearAllPoints()
if own then
b.avBtn:SetPoint("TOPRIGHT", b, "TOPRIGHT", -6, 0)
b.lvl:SetPoint("TOP", b.avBtn, "BOTTOM", 0, -1)
b.panel:SetPoint("TOPRIGHT", b.avBtn, "TOPLEFT", -8, 0)
else
b.avBtn:SetPoint("TOPLEFT", b, "TOPLEFT", 6, 0)
b.lvl:SetPoint("TOP", b.avBtn, "BOTTOM", 0, -1)
b.panel:SetPoint("TOPLEFT", b.avBtn, "TOPRIGHT", 8, 0)
end
local msg, sender = Plain(txt), who
local url = CL.FirstUrl(txt)
b.avBtn:SetScript("OnClick", function() ShowMenu(sender) end)
b.panel:SetScript("OnClick", function(_, btn)
btn = btn or arg1
if btn == "RightButton" then ShowMenu(sender) else ShowCopy(msg, url) end
end)
b:Show()
y = y + rowH + 8
i = i + 1
end
idx = idx + 1
while bubbles[idx] do bubbles[idx]:Hide(); idx = idx + 1 end
local viewH = chat.scroll:GetHeight() or 300
chat:SetContentHeight(math.max(1, y))
lastH, lastViewH = y, viewH
if atBottom then
chat.scroll:SetVerticalScroll(math.max(0, y - viewH))
chat.UpdateScroll()
stick = false
end
return n
end
local function DrawLog(key)
local lines = CL.Lines(key) or {}
local n = table.getn(lines)
local first = math.max(1, n - LOG_LINES + 1)
local parts = {}
local i = first
while i <= n do
local ln = lines[i]
parts[table.getn(parts) + 1] = "[" .. TimeText(ln.t) .. "] " .. (ln.s or "?") .. ": " .. Plain(ln.m or "")
i = i + 1
end
if not logArea.edit:HasFocus() then
local text = table.concat(parts, "\n")
if logArea.lastText ~= text then
logArea.lastText = text
logArea:SetText(text)
if C_Timer and C_Timer.After then
C_Timer.After(0.15, function()
local h = logArea.child:GetHeight() or 0
local v = logArea.scroll:GetHeight() or 0
logArea.scroll:SetVerticalScroll(math.max(0, h - v))
logArea.UpdateScroll()
end)
end
end
end
return n
end
local function DrawCenter()
local en = selKey and EntryByKey(selKey)
if not en then
head:SetText(""); sub:SetText(""); empty:SetText("")
chat:Hide(); logArea:Hide(); ibox:Hide(); sendBtn:Hide()
return
end
local title = en.label
if en.kind == "whisper" then
local c, lv = CL.Info(en.name)
title = en.name
sub:SetText((lv and (tostring(lv)) or "?") .. " - " .. L.CHAT_KEPT)
else
sub:SetText(L.CHAT_KEPT)
end
head:SetText(title)
local canWrite = en.mode ~= "data"
if canWrite then ibox:Show(); sendBtn:Show() else ibox:Hide(); sendBtn:Hide() end
if en.mode == "data" then
chat:Hide(); logArea:Hide()
empty:SetText(L.CHAT_DATA)
elseif en.mode == "log" then
chat:Hide(); logArea:Show()
local n = DrawLog(en.key)
if n == 0 then empty:SetText(L.CHAT_EMPTY) else empty:SetText("") end
else
logArea:Hide(); chat:Show()
local n = DrawBubbles(en.key)
if n == 0 then empty:SetText(L.CHAT_EMPTY) else empty:SetText("") end
end
if en.kind == "guild" and not (IsInGuild and IsInGuild()) then empty:SetText(L.CHAT_NOGUILD) end
end
------------------------------------------------------------------
-- Rechte Spalte: Suche und Mitglieder
------------------------------------------------------------------
local box = UI.CreatePanel(f, "bgDeep", "bronze")
box:SetHeight(26)
box:SetPoint("TOPLEFT", f, "TOPLEFT", 236, -2)
box:SetPoint("TOPRIGHT", f, "TOPRIGHT", 0, -2)
box:SetWidth(RIGHT_W)
local edit = CreateFrame("EditBox", nil, box)
edit:SetAutoFocus(false)
edit:SetPoint("TOPLEFT", box, "TOPLEFT", 8, -2)
edit:SetPoint("BOTTOMRIGHT", box, "BOTTOMRIGHT", -8, 2)
edit:SetMaxLetters(40)
if not edit:SetFont(UI.GetFontPath("body"), 12, "") then
edit:SetFont("Fonts\\FRIZQT__.TTF", 12, "")
end
if not edit:SetFont(UI.GetFontPath("body"), 12, "") then edit:SetFont("Fonts\\FRIZQT__.TTF", 12, "") end
edit:SetTextColor(UI.Color("text"))
edit:SetScript("OnEscapePressed", function(self) self:ClearFocus() end)
local hint = UI.CreateLabel(box, L.CHAN_SEARCH, { size = 12, color = "textDim" })
hint:SetPoint("LEFT", box, "LEFT", 10, 0)
local hint = UI.CreateLabel(box, L.CHAN_SEARCH, { size = 11, color = "textDim" })
hint:SetPoint("LEFT", box, "LEFT", 8, 0)
hint:SetPoint("RIGHT", box, "RIGHT", -4, 0)
local status = UI.CreateLabel(f, "", { size = 11, color = "textDim", justify = "LEFT" })
status:SetPoint("TOPLEFT", f, "TOPLEFT", 236, -34)
status:SetPoint("TOPRIGHT", f, "TOPRIGHT", 0, -34)
local rhead = UI.CreateLabel(f, "", { size = 11, color = "textDim", justify = "LEFT" })
rhead:SetPoint("TOPRIGHT", f, "TOPRIGHT", 0, -34)
rhead:SetWidth(RIGHT_W)
local refresh = UI.CreateButton(f, L.CHAN_REFRESH, RIGHT_W, 24, function()
Ch.RefreshAll()
CL.RequestGuild()
end)
refresh:SetPoint("BOTTOMRIGHT", f, "BOTTOMRIGHT", 0, 6)
local right = UI.CreateScrollFrame(f)
right:SetPoint("TOPLEFT", f, "TOPLEFT", 236, -54)
right:SetPoint("TOPRIGHT", f, "TOPRIGHT", 0, -54)
right:SetPoint("BOTTOMRIGHT", f, "BOTTOMRIGHT", 0, 36)
local rrows = MakeRows(right)
local selLabel = UI.CreateLabel(f, L.CHAN_SELECT, { size = 12, color = "textDim", justify = "LEFT" })
selLabel:SetPoint("BOTTOMLEFT", f, "BOTTOMLEFT", 236, 10)
selLabel:SetWidth(200)
local function Act(fn)
return function() if selPlayer then fn(selPlayer) end end
end
local bW = UI.CreateButton(f, L.CHAN_WHISPER, 90, 24, Act(Ch.Whisper))
local bI = UI.CreateButton(f, L.CHAN_INVITE, 90, 24, Act(Ch.Invite))
local bG = UI.CreateButton(f, L.CHAN_IGNORE, 90, 24, Act(Ch.Ignore))
bG:SetPoint("BOTTOMRIGHT", f, "BOTTOMRIGHT", 0, 4)
bI:SetPoint("RIGHT", bG, "LEFT", -6, 0)
bW:SetPoint("RIGHT", bI, "LEFT", -6, 0)
local function Redraw()
seenVersion = Ch.version
-- Kanalliste
local n = table.getn(Ch.list)
local k = 1
while k <= n do
local e = Ch.list[k]
local r = lrows.Get(k)
local num = e.num
r.text:SetText(e.num .. ". " .. e.name .. " |cff8c8a84(" .. (e.stamp and e.count or "?") .. ")|r")
if num == selNum then r.sel:Show() else r.sel:Hide() end
r:SetScript("OnClick", function()
selNum = num
selPlayer = nil
Redraw()
end)
r:Show()
k = k + 1
right:SetWidth(RIGHT_W)
local rrows = {}
local function RightRow(i)
local r = rrows[i]
if not r then
r = CreateFrame("Button", nil, right.child)
r:SetHeight(22)
r:SetPoint("TOPLEFT", right.child, "TOPLEFT", 0, -(i - 1) * 22)
r:SetPoint("TOPRIGHT", right.child, "TOPRIGHT", 0, -(i - 1) * 22)
r:RegisterForClicks("LeftButtonUp", "RightButtonUp")
r.hl = r:CreateTexture(nil, "HIGHLIGHT")
r.hl:SetAllPoints()
r.hl:SetColorTexture(UI.Color("gold", 0.15))
r.icon = r:CreateTexture(nil, "ARTWORK")
r.icon:SetSize(16, 16)
r.icon:SetPoint("LEFT", r, "LEFT", 4, 0)
r.text = UI.CreateLabel(r, "", { size = 12, justify = "LEFT" })
r.text:SetPoint("LEFT", r.icon, "RIGHT", 6, 0)
r.text:SetPoint("RIGHT", r, "RIGHT", -4, 0)
rrows[i] = r
end
lrows.HideFrom(n + 1)
left:SetContentHeight(math.max(1, n * ROW))
return r
end
-- rechts
local items = {} -- { name=, extra= }
local function MembersOf(en)
local out = {}
if not en then return out end
if en.kind == "channel" and en.e then
local k = 1
while k <= table.getn(en.e.members) do
out[table.getn(out) + 1] = { name = en.e.members[k] }
k = k + 1
end
elseif en.kind == "guild" then
local k = 1
while k <= table.getn(CL.guild) do
local g = CL.guild[k]
out[table.getn(out) + 1] = { name = g.name, class = g.class, level = g.level }
k = k + 1
end
elseif en.kind == "group" then
out = CL.ScanGroup()
elseif en.kind == "whisper" then
out[1] = { name = en.name }
end
return out
end
local function DrawRight()
local en = selKey and EntryByKey(selKey)
local items = {}
if search ~= "" then
local hits = Ch.Find(search, MAX_ROWS)
local hits = Ch.Find(search, 300)
local h = 1
while h <= table.getn(hits) do
items[h] = { name = hits[h].name, extra = table.concat(hits[h].chans, ", ") }
h = h + 1
end
if table.getn(items) == 0 then
status:SetText(L.CHAN_NO_HITS)
elseif Ch.moreFound then
status:SetText(L.CHAN_MORE)
else
status:SetText("")
end
rhead:SetText(table.getn(items) == 0 and L.CHAN_NO_HITS or (Ch.moreFound and L.CHAN_MORE or ""))
Ch.moreFound = nil
else
local e = selNum and Ch.byNum[selNum]
if n == 0 then
status:SetText(L.CHAN_NONE)
elseif not e then
status:SetText(L.CHAN_SELECT)
else
local txt = e.num .. ". " .. e.name
if e.stamp then
txt = txt .. " - " .. string.format(L.CHAN_MEMBERS, tostring(e.count))
else
txt = txt .. " - " .. L.CHAN_NOT_QUERIED
end
if string.find(e.name, "^Freewood") then txt = txt .. " (" .. L.CHAN_HIDDEN .. ")" end
local pend = Ch.Pending()
if pend > 0 then txt = txt .. " - " .. string.format(L.CHAN_PENDING, tostring(pend)) end
status:SetText(txt)
local m = 1
while m <= table.getn(e.members) and m <= MAX_ROWS do
items[m] = { name = e.members[m] }
m = m + 1
end
end
items = MembersOf(en)
local title = (en and en.kind == "guild") and L.CHAT_ONLINE_HDR or L.CHAT_MEMBERS_HDR
rhead:SetText(title .. " (" .. table.getn(items) .. ")")
if en and en.kind == "group" and table.getn(items) <= 1 then rhead:SetText(L.CHAT_NOGROUP) end
end
local cnt = table.getn(items)
local cnt = math.min(table.getn(items), 400)
local i = 1
while i <= cnt do
local it = items[i]
local r = rrows.Get(i)
local r = RightRow(i)
local nm = it.name
if it.extra then
r.text:SetText(nm .. " |cff8c8a84" .. it.extra .. "|r")
else
r.text:SetText(nm)
local cls, lv = it.class, it.level
if not cls or not lv then
local c2, l2 = CL.Info(nm)
cls = cls or c2; lv = lv or l2
end
if nm == selPlayer then r.sel:Show() else r.sel:Hide() end
r:SetScript("OnClick", function()
selPlayer = nm
selLabel:SetText(nm)
Redraw()
end)
SetClassIcon(r.icon, cls)
local cr, cg, cb = CL.ClassColor(cls)
local txt = Hex(cr, cg, cb) .. nm .. "|r"
if lv then txt = txt .. " |cff9a978c" .. lv .. "|r" end
if it.extra then txt = txt .. " |cff9a978c" .. it.extra .. "|r" end
r.text:SetText(txt)
r:SetScript("OnClick", function() ShowMenu(nm) end)
r:Show()
i = i + 1
end
rrows.HideFrom(cnt + 1)
right:SetContentHeight(math.max(1, cnt * ROW))
while rrows[i] do rrows[i]:Hide(); i = i + 1 end
right:SetContentHeight(math.max(1, cnt * 22))
end
edit:SetScript("OnTextChanged", function(self)
search = self:GetText() or ""
if search == "" then hint:Show() else hint:Hide() end
right:ScrollToTop()
Redraw()
DrawRight()
end)
------------------------------------------------------------------
-- Zusammenspiel
------------------------------------------------------------------
Redraw = function()
seen.cl, seen.ch = CL.version, Ch.version
DrawLeft()
CL.viewKey = selKey
if selKey then CL.unread[selKey] = nil end
DrawCenter()
DrawRight()
end
selectKey = function(key)
selKey = key
stick = true
CL.unread[key] = nil
Redraw()
end
f.Refresh = function()
CL.viewVisible = true
if Ch.dirty or table.getn(Ch.list) == 0 then
Ch.dirty = nil
Ch.RefreshAll()
else
Ch.Rescan()
end
if not selNum and Ch.list[1] then selNum = Ch.list[1].num end
CL.RequestGuild()
stick = true
Redraw()
end
-- Aenderungen der Datenschicht nachziehen (hoechstens 2x pro Sekunde)
f:SetScript("OnHide", function()
CL.viewVisible = false
HideMenu()
copy:Hide()
end)
local acc = 0
f:SetScript("OnUpdate", function(_, el)
acc = acc + (el or arg1 or 0)
if acc < 0.5 then return end
if acc < 0.4 then return end
acc = 0
if f:IsShown() and seenVersion ~= Ch.version then Redraw() end
if not f:IsShown() then return end
local w = chat.child:GetWidth()
if seen.cl ~= CL.version or seen.ch ~= Ch.version or (w and math.abs(w - seen.w) > 4) then
Redraw()
end
end)
return f
@@ -221,6 +770,6 @@ end
UI:RegisterTab("channels", {
label = function() return FW.L.TAB_CHANNELS end,
order = 115,
build = BuildChannels,
build = Build,
onShow = function(f) f.Refresh() end,
})