diff --git a/Core/ChatLog.lua b/Core/ChatLog.lua new file mode 100644 index 0000000..7e98833 --- /dev/null +++ b/Core/ChatLog.lua @@ -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 diff --git a/Freewood-OctoWoW-Core.toc b/Freewood-OctoWoW-Core.toc index 8096998..026bbfb 100644 --- a/Freewood-OctoWoW-Core.toc +++ b/Freewood-OctoWoW-Core.toc @@ -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 diff --git a/Locales.lua b/Locales.lua index 36c9bc0..a6cd008 100644 --- a/Locales.lua +++ b/Locales.lua @@ -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.", diff --git a/UI/ChannelsTab.lua b/UI/ChannelsTab.lua index 05a56ac..e659dee 100644 --- a/UI/ChannelsTab.lua +++ b/UI/ChannelsTab.lua @@ -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, })