Files
Claude ec9fe7819b ForeverUI : fenêtre sociale en combat, croix sécurisée et /ignore
Les onglets sécurisés protègent FriendsFrame en permanence (avant :
dès que Blizzard_RaidUI se chargeait, au premier raid ou champ de
bataille). Ce qui le cachait ou le plaçait depuis le code de l'addon
était alors refusé en combat :
- la croix clique celle du client (action « click »), qui ferme la
  fenêtre comme le client ;
- Contrôle de guilde et Recherche de raid refusent en combat avec le
  message du client, au lieu d'une action bloquée ;
- la fenêtre de contrôle n'est plus cachée par notre code en combat :
  le client la cache lui-même ;
- FriendsFrame ne prend plus la souris dès le chargement.

/ignore et /friends sans nom ouvrent de nouveau le bon sous-onglet, et
l'onglet Amis redemande la liste au serveur comme celui du client.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KxRZNHkq453dyUMcWsyUf1
2026-10-02 00:43:25 +00:00

782 lines
29 KiB
Lua

-- ForeverUI: the Guild page of the Social window (client tab 3), after WotLK GuildFrame,
-- with its member detail, guild information and event log panes.
-- The member list follows camelot's Communities Roster view without the Note column;
-- the note shows in the member detail and in the tooltip.
local ForeverUI = ForeverUI or {}
_G.ForeverUI = ForeverUI
local S = ForeverUI.Social
if not S or not S.registerPage then
return
end
local Gu = {}
S.Guild = Gu
local SEP = string.char(92)
local txt = S.txt
local P = {
frameBoxY1 = -60, frameBoxBottom = 88, headerX = 4, headerY = -4, gap = -2,
rowH = 20, textH = 12,
columns = { { "LEVEL", 40, "level" }, { "CLASS", 45, "class" }, { "NAME", 100, "name" },
{ "ZONE", 100, "zone" }, { "RANK", 0, "rank" } },
-- The last column reaches the list edge; fillW: its width before anchoring.
-- Without a scroll bar, list and headers keep their left margin on the right:
-- list at -5, last column at +1 from the list edge.
noteRight = -6, noteRightAlone = 1, fillW = 60, listX2 = -22, listX2NoBar = -5,
strip = "Interface" .. SEP .. "ForeverUI" .. SEP .. "guildframe" .. SEP .. "guildframe",
stripCoords = { 0.36230469, 0.38183594, 0.95898438, 0.99804688 },
bar = "Interface" .. SEP .. "FriendsFrame" .. SEP .. "UI-FriendsFrame-HighlightBar",
classes = "Interface" .. SEP .. "Glues" .. SEP .. "CharacterCreate" .. SEP .. "UI-CharacterCreate-Classes",
leader = "Interface" .. SEP .. "GroupFrame" .. SEP .. "UI-Group-LeaderIcon",
absent = "Interface" .. SEP .. "FriendsFrame" .. SEP .. "StatusIcon-Away",
busy = "Interface" .. SEP .. "FriendsFrame" .. SEP .. "StatusIcon-DnD",
nameW = 95,
totalsX = 64, totalsY = -40, offlineRight = -12, offlineY = -33,
motdX = 14, motdBottom = 32, motdH = 36,
buttonBottom = 4, buttonRight = -6,
gray = 0.5, grayText = 0.65,
}
local function active(b, yes)
if b.Activate then b:Activate(yes) elseif yes then b:Enable() else b:Disable() end
end
-- The client's RecentTimeDate if present, else the same rule.
local function fromIndex(year, month, day, hour)
if RecentTimeDate then return RecentTimeDate(year, month, day, hour) end
if year and year > 0 then return string.format(txt("LASTONLINE_YEARS"), year) end
if month and month > 0 then return string.format(txt("LASTONLINE_MONTHS"), month) end
if day and day > 0 then return string.format(txt("LASTONLINE_DAYS"), day) end
if hour and hour > 0 then return string.format(txt("LASTONLINE_HOURS"), hour) end
return txt("LASTONLINE_MINS")
end
-- IsTruncated, missing in 3.3.5: true if a fixed-width font string is too narrow.
local function isTruncated(fs)
local l = fs:GetWidth()
return l and l > 0 and fs:GetStringWidth() > l + 0.5
end
local function classColor(file)
local c = file and RAID_CLASS_COLORS and RAID_CLASS_COLORS[file] or NORMAL_FONT_COLOR
return c.r, c.g, c.b
end
-- ---------- Member list
-- Builds a roster row (CommunitiesMemberListEntryTemplate). l: row button.
local function createRow(l)
l:RegisterForClicks("LeftButtonUp", "RightButtonUp")
l:SetNormalTexture(P.strip)
local n = l:GetNormalTexture()
n:SetTexCoord(P.stripCoords[1], P.stripCoords[2], P.stripCoords[3], P.stripCoords[4])
n:ClearAllPoints()
n:SetAllPoints(l)
l:SetHighlightTexture(P.bar)
local s = l:GetHighlightTexture()
s:SetBlendMode("ADD")
s:ClearAllPoints()
s:SetAllPoints(l)
local function field(parent, width)
local fs = parent:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
fs:SetJustifyH("LEFT")
fs:SetHeight(P.textH)
if width then fs:SetWidth(width) end
return fs
end
-- Level centered on its column: the Level header spans -1 to 39 from the list edge
-- (camelot left-aligns it at 4).
l.level = field(l, P.columns[1][2])
l.level:SetPoint("LEFT", l, "LEFT", -1, 0)
l.level:SetJustifyH("CENTER")
-- Class icon at its camelot place (4 + 40 + 8), centered under Class.
l.className = l:CreateTexture(nil, "OVERLAY")
l.className:SetTexture(P.classes)
l.className:SetWidth(16)
l.className:SetHeight(16)
l.className:SetPoint("LEFT", l, "LEFT", 52, 0)
-- NameFrame: presence, name, rank icon.
local nf = CreateFrame("Frame", nil, l)
nf:SetHeight(20)
nf:SetWidth(P.nameW)
nf:SetPoint("LEFT", l.className, "RIGHT", 18, 0)
l.nameFrame = nf
l.presence = nf:CreateTexture(nil, "OVERLAY")
l.presence:SetWidth(16)
l.presence:SetHeight(16)
l.presence:SetPoint("LEFT", nf, "LEFT", 0, 0)
l.name = field(nf)
l.rankIcon = nf:CreateTexture(nil, "OVERLAY")
l.rankIcon:SetWidth(12)
l.rankIcon:SetHeight(12)
l.zone = field(l, 90)
l.zone:SetPoint("LEFT", nf, "RIGHT", 8, 0)
-- Rank fills the rest of the row (no Note column).
l.rank = field(l)
l.rank:SetPoint("LEFT", l.zone, "RIGHT", 7, 0)
l.rank:SetPoint("RIGHT", l, "RIGHT", -4, 0)
l:SetScript("OnClick", function(self, button) Gu.click(self, button) end)
-- CommunitiesMemberListEntryMixin:OnEnter, Roster view. Without a Note column,
-- a member with a note also opens the tooltip.
l:SetScript("OnEnter", function(self)
local name, rank, _, level, className, zone, note, _, online = GetGuildRosterInfo(self.index)
local hasNote = note and note ~= ""
if not (hasNote or isTruncated(self.name) or isTruncated(self.rank) or isTruncated(self.zone)) then return end
local n = NORMAL_FONT_COLOR or { r = 1, g = 0.82, b = 0 }
GameTooltip:SetOwner(self, "ANCHOR_RIGHT")
GameTooltip:AddLine(name)
GameTooltip:AddLine(rank or "")
if level and className then
GameTooltip:AddLine(string.format(txt("FRIENDS_LEVEL_TEMPLATE"), level, className), 1, 1, 1, true)
end
if online and zone and zone ~= "" then
GameTooltip:AddLine(zone, 1, 1, 1, true)
end
if note and note ~= "" then
GameTooltip:AddLine(txt("NOTE_COLON") .. " " .. note, n.r, n.g, n.b, true)
end
GameTooltip:Show()
end)
l:SetScript("OnLeave", function() GameTooltip:Hide() end)
end
-- UpdateNameFrame: name after the presence icon, rank icon after the name.
local function updateNameFrame(l)
local icons, offset = 0, 0
local presence = l.presence:IsShown()
l.name:ClearAllPoints()
if presence then
icons = icons + 20
l.name:SetPoint("LEFT", l.presence, "RIGHT", 0, 0)
offset = l.presence:GetWidth()
else
l.name:SetPoint("LEFT", l.nameFrame, "LEFT", 0, 0)
end
if l.rankIcon:IsShown() then
icons = icons + (presence and 20 or 25)
end
local nameWidth = P.nameW - icons
l.name:SetWidth(nameWidth)
local text = l.name:GetStringWidth()
l.rankIcon:ClearAllPoints()
l.rankIcon:SetPoint("LEFT", l.nameFrame, "LEFT", math.min(text, nameWidth) + offset, 0)
end
-- CommunitiesMemberListEntryMixin: SetMember, UpdateRank, UpdatePresence,
-- RefreshExpandedColumns. 3.3.5 has no community roles: the rank icon only marks
-- the guild master.
local function populateRow(l, i)
local name, rank, rankIndex, level, _, zone, _, _, online, status, file = GetGuildRosterInfo(i)
l.name:SetText(name)
l.level:SetText(level or "")
local tc = file and CLASS_ICON_TCOORDS and CLASS_ICON_TCOORDS[file]
if tc then
l.className:SetTexCoord(tc[1], tc[2], tc[3], tc[4])
l.className:Show()
else
l.className:Hide()
end
local fields = { l.level, l.zone, l.rank }
if online then
l.name:SetTextColor(classColor(file))
for _, fs in ipairs(fields) do fs:SetTextColor(1, 1, 1) end
if status == CHAT_FLAG_AFK then
l.presence:SetTexture(FRIENDS_TEXTURE_AFK or P.absent)
l.presence:Show()
elseif status == CHAT_FLAG_DND then
l.presence:SetTexture(FRIENDS_TEXTURE_DND or P.busy)
l.presence:Show()
else
l.presence:Hide()
end
l.zone:SetText(zone or "")
else
l.presence:Hide()
l.name:SetTextColor(P.gray, P.gray, P.gray)
for _, fs in ipairs(fields) do fs:SetTextColor(P.gray, P.gray, P.gray) end
l.zone:SetText(fromIndex(GetGuildRosterLastOnline(i)))
end
l.rank:SetText(rank or "")
if rankIndex == 0 then
l.rankIcon:SetTexture(P.leader)
l.rankIcon:Show()
else
l.rankIcon:Hide()
end
updateNameFrame(l)
if GetGuildRosterSelection() == i then l:LockHighlight() else l:UnlockHighlight() end
end
-- ---------- Member detail
local D = {}
-- Label and value line of the detail. caption: text key; y: offset from the top.
local function detailField(a, caption, y)
local l = a:CreateFontString(nil, "ARTWORK", "GameFontNormalSmall")
l:SetPoint("TOPLEFT", a, "TOPLEFT", 18, y)
l:SetText(txt(caption))
local v = a:CreateFontString(nil, "ARTWORK", "GameFontHighlightSmall")
v:SetPoint("LEFT", l, "RIGHT", 6, 0)
v:SetPoint("RIGHT", a, "RIGHT", -18, 0)
v:SetJustifyH("LEFT")
v:SetHeight(12)
return l, v
end
-- A note: clickable inset, text grayed when it cannot be edited.
-- popup: StaticPopup that edits it.
local function detailNote(a, caption, popup)
local l = a:CreateFontString(nil, "ARTWORK", "GameFontNormalSmall")
l:SetText(txt(caption))
local e = ForeverUI.CreateInset(a)
e:SetHeight(40)
e:SetPoint("TOPLEFT", l, "BOTTOMLEFT", -2, -3)
e:SetPoint("RIGHT", a, "RIGHT", -16, 0)
e:EnableMouse(true)
local t = e:CreateFontString(nil, "ARTWORK", "GameFontHighlightSmall")
t:SetPoint("TOPLEFT", e, "TOPLEFT", 6, -5)
t:SetPoint("BOTTOMRIGHT", e, "BOTTOMRIGHT", -6, 5)
t:SetJustifyH("LEFT")
t:SetJustifyV("TOP")
if t.SetWordWrap then t:SetWordWrap(true) end
e:SetScript("OnMouseUp", function(self)
if self.editable then StaticPopup_Show(popup) end
end)
return l, e, t
end
local function createDetail()
local a = S.createPopup("ForeverUIGuildMemberDetail", 232, 290)
D.frame = a
D.level = a:CreateFontString(nil, "ARTWORK", "GameFontHighlightSmall")
D.level:SetPoint("TOPLEFT", a, "TOPLEFT", 18, -30)
D.zoneLabel, D.zone = detailField(a, "ZONE_COLON", -48)
D.rankLabel, D.rank = detailField(a, "RANK_COLON", -64)
D.rank:SetPoint("RIGHT", a, "RIGHT", -52, 0)
D.lastSeenLabel, D.found = detailField(a, "LAST_ONLINE_COLON", -80)
-- Promote / demote: camelot scroll bar arrows.
local function arrow(atlas, hover, onClick)
local f = CreateFrame("Button", nil, a)
f:SetWidth(17)
f:SetHeight(11)
local e = ForeverUI.AtlasEntry(atlas)
f:SetNormalTexture(e and e[1] or "")
local n = f:GetNormalTexture()
if n then ForeverUI.SetAtlas(n, atlas, true) n:SetAllPoints(f) end
f:SetHighlightTexture(e and e[1] or "")
local h = f:GetHighlightTexture()
if h then ForeverUI.SetAtlas(h, hover, true) h:SetAllPoints(f) end
f:SetScript("OnClick", onClick)
return f
end
D.promote = arrow("minimal-scrollbar-arrow-top-c60", "minimal-scrollbar-arrow-top-over-c60", function(self)
GuildPromote(GuildFrame.selectedName)
PlaySound("UChatScrollButton")
self:Disable()
end)
D.promote:SetPoint("LEFT", D.rank, "RIGHT", 4, 6)
D.demote = arrow("minimal-scrollbar-arrow-bottom-c60", "minimal-scrollbar-arrow-bottom-over-c60", function(self)
GuildDemote(GuildFrame.selectedName)
PlaySound("UChatScrollButton")
self:Disable()
end)
D.demote:SetPoint("TOP", D.promote, "BOTTOM", 0, -2)
D.noteLabel, D.note, D.noteText = detailNote(a, "NOTE_COLON", "SET_GUILDPLAYERNOTE")
D.noteLabel:SetPoint("TOPLEFT", a, "TOPLEFT", 18, -102)
D.officerLabel, D.officer, D.officerText = detailNote(a, "OFFICER_NOTE_COLON", "SET_GUILDOFFICERNOTE")
D.officerLabel:SetPoint("TOPLEFT", D.note, "BOTTOMLEFT", 2, -10)
D.remove = S.button(a, txt("REMOVE"), 100)
D.remove:SetPoint("BOTTOMLEFT", a, "BOTTOMLEFT", 12, 12)
D.remove:SetScript("OnClick", function() StaticPopup_Show("REMOVE_GUILDMEMBER") end)
D.inviteButton = S.button(a, txt("GROUP_INVITE"), 100)
D.inviteButton:SetPoint("LEFT", D.remove, "RIGHT", 4, 0)
D.inviteButton:SetScript("OnClick", function() InviteUnit(GuildFrame.selectedName) end)
a:HookScript("OnHide", function()
-- Closing the detail clears the selection (the client resets it to 0).
if Gu.closeSize then return end
Gu.selection(0)
end)
end
-- GuildStatus_Update, for the detail pane.
function Gu.updateDetail()
local a = D.frame
if not a or not a:IsShown() then return end
local i = GetGuildRosterSelection()
if not i or i == 0 then a:Hide() return end
local name, rank, rankIndex, level, className, zone, note, officer, online = GetGuildRosterInfo(i)
local _, _, myRank = GetGuildInfo("player")
myRank = myRank or 0
local maxRank = (GuildControlGetNumRanks and GuildControlGetNumRanks() or 1) - 1
a.title:SetText(name or "")
D.level:SetText(string.format(txt("FRIENDS_LEVEL_TEMPLATE"), level or 0, className or ""))
D.zone:SetText(zone)
D.rank:SetText(rank)
D.found:SetText(online and txt("GUILD_ONLINE_LABEL") or fromIndex(GetGuildRosterLastOnline(i)))
-- Public note
D.note.editable = CanEditPublicNote()
if D.note.editable then
if not note or note == "" then note = txt("GUILD_NOTE_EDITLABEL") end
D.noteText:SetTextColor(1, 1, 1)
else
D.noteText:SetTextColor(P.grayText, P.grayText, P.grayText)
end
D.noteText:SetText(note or "")
-- Officer note
if CanViewOfficerNote() then
D.officer.editable = CanEditOfficerNote()
if D.officer.editable then
if not officer or officer == "" then officer = txt("GUILD_OFFICERNOTE_EDITLABEL") end
D.officerText:SetTextColor(1, 1, 1)
else
D.officerText:SetTextColor(P.grayText, P.grayText, P.grayText)
end
D.officerText:SetText(officer or "")
D.officerLabel:Show()
D.officer:Show()
a:SetHeight(290)
else
D.officerLabel:Hide()
D.officer:Hide()
a:SetHeight(230)
end
-- Promote, demote, remove, invite
local canPromote = CanGuildPromote() and rankIndex and rankIndex > 1 and rankIndex > (myRank + 1)
local canDemote = CanGuildDemote() and rankIndex and rankIndex >= 1 and rankIndex > myRank and rankIndex ~= maxRank
if canPromote then D.promote:Enable() else D.promote:Disable() end
if canDemote then D.demote:Enable() else D.demote:Disable() end
if canPromote or canDemote then
D.promote:Show()
D.demote:Show()
else
D.promote:Hide()
D.demote:Hide()
end
active(D.remove, CanGuildRemove() and rankIndex and rankIndex >= 1 and rankIndex > myRank)
active(D.inviteButton, UnitName("player") ~= name and online and true or false)
end
-- Sets the client selection and what its popups read (GuildFrame.selectedName).
function Gu.selection(i)
SetGuildRosterSelection(i or 0)
if GuildFrame then
GuildFrame.selectedGuildMember = i or 0
GuildFrame.selectedName = (i and i > 0) and GetGuildRosterInfo(i) or nil
end
if Gu.list then Gu.list:Update() end
end
-- FriendsFrameGuildStatusButton_OnClick
function Gu.click(l, button)
local i = l.index
if button == "RightButton" then
local name, _, _, _, _, _, _, _, online = GetGuildRosterInfo(i)
ForeverUI.UnitMenu.open(FriendsFrame_ShowDropdown, name, online)
return
end
PlaySound("igMainMenuOptionCheckBoxOn")
if D.frame:IsShown() and GetGuildRosterSelection() == i then
Gu.selection(0)
D.frame:Hide()
else
Gu.selection(i)
D.frame:Show()
Gu.updateDetail()
end
end
-- ---------- Guild information
local I = {}
local function createInfo()
local a = S.createPopup("ForeverUIGuildInfoFrame", 300, 300)
a.title:SetText(txt("GUILD_INFORMATION"))
I.frame = a
local e = ForeverUI.CreateInset(a)
e:SetPoint("TOPLEFT", a, "TOPLEFT", 12, -30)
e:SetPoint("BOTTOMRIGHT", a, "BOTTOMRIGHT", -12, 44)
local scrollFrame = CreateFrame("ScrollFrame", "ForeverUIGuildInfoScroll", e)
scrollFrame:SetPoint("TOPLEFT", e, "TOPLEFT", 6, -6)
scrollFrame:SetPoint("BOTTOMRIGHT", e, "BOTTOMRIGHT", -6, 6)
local b = CreateFrame("EditBox", "ForeverUIGuildInfoEditBox", scrollFrame)
b:SetMultiLine(true)
b:SetAutoFocus(false)
b:SetMaxLetters(500)
-- Full width of the scroll frame (300 - 2 x 12 - 2 x 6): there is no scroll bar here.
b:SetWidth(264)
b:SetHeight(200)
b:SetFontObject(GameFontHighlightSmall)
b:SetScript("OnEscapePressed", function(self) self:ClearFocus() end)
scrollFrame:SetScrollChild(b)
scrollFrame:EnableMouse(true)
scrollFrame:SetScript("OnMouseUp", function()
if CanEditGuildInfo() then b:SetFocus() else b:ClearFocus() end
end)
I.input = b
I.accept = S.button(a, txt("ACCEPT"), 90)
-- Left to right: Log, then Accept and Close against the right edge.
I.accept:SetScript("OnClick", function()
SetGuildInfoText(b:GetText())
if GuildInfoFrame then GuildInfoFrame.cachedText = b:GetText() end
I.savedText = b:GetText()
GuildRoster()
a:Hide()
end)
local close = S.button(a, txt("CLOSE"), 90)
close:SetPoint("BOTTOMRIGHT", a, "BOTTOMRIGHT", -12, 14)
I.accept:SetPoint("RIGHT", close, "LEFT", -4, 0)
close:SetScript("OnClick", function() a:Hide() end)
local questLog = S.button(a, txt("GUILD_EVENT_LOG"), 70)
questLog:SetPoint("BOTTOMLEFT", a, "BOTTOMLEFT", 12, 14)
questLog:SetScript("OnClick", function() Gu.toggleQuestLog() end)
Gu.infoButtons = { questLog = questLog, accept = I.accept, close = close }
-- GuildInfoTextBackground's OnShow
a:HookScript("OnShow", function()
local text = I.savedText or GetGuildInfoText() or ""
if CanEditGuildInfo() then
b:SetText(text ~= "" and text or txt("GUILD_INFO_EDITLABEL"))
b:SetTextColor(1, 1, 1)
b:EnableMouse(true)
active(I.accept, true)
else
b:SetText(text)
b:SetTextColor(P.grayText, P.grayText, P.grayText)
b:EnableMouse(false)
active(I.accept, false)
end
end)
end
-- ---------- Event log
local J = {}
-- Text of guild event i, followed by its age.
local function eventLogLine(i)
local type, j1, j2, rank, year, month, day, hour = GetGuildEventInfo(i)
j1 = j1 or txt("UNKNOWN")
j2 = j2 or txt("UNKNOWN")
local msg
if type == "invite" then msg = string.format(txt("GUILDEVENT_TYPE_INVITE"), j1, j2)
elseif type == "join" then msg = string.format(txt("GUILDEVENT_TYPE_JOIN"), j1)
elseif type == "promote" then msg = string.format(txt("GUILDEVENT_TYPE_PROMOTE"), j1, j2, rank)
elseif type == "demote" then msg = string.format(txt("GUILDEVENT_TYPE_DEMOTE"), j1, j2, rank)
elseif type == "remove" then msg = string.format(txt("GUILDEVENT_TYPE_REMOVE"), j1, j2)
elseif type == "quit" then msg = string.format(txt("GUILDEVENT_TYPE_QUIT"), j1)
end
if not msg then return "" end
return msg .. "|cff009999 " .. string.format(txt("GUILD_BANK_LOG_TIME"), fromIndex(year, month, day, hour)) .. "|r"
end
local function createEventLog()
local a = S.createPopup("ForeverUIGuildEventLog", 400, 420)
a.title:SetText(txt("GUILD_EVENT_LOG"))
J.frame = a
local e = ForeverUI.CreateInset(a)
e:SetPoint("TOPLEFT", a, "TOPLEFT", 12, -30)
e:SetPoint("BOTTOMRIGHT", a, "BOTTOMRIGHT", -12, 44)
J.list = S.createList(e, "ForeverUIGuildEventList", 14, function(l)
l.text = l:CreateFontString(nil, "ARTWORK", "GameFontHighlightSmall")
l.text:SetPoint("LEFT", l, "LEFT", 4, 0)
l.text:SetPoint("RIGHT", l, "RIGHT", -4, 0)
l.text:SetJustifyH("LEFT")
l.text:SetHeight(12)
end, function(l, n)
-- Most recent first, like the client's loop.
l.text:SetText(eventLogLine((GetNumGuildEvents() or 0) - n + 1))
end)
-- Without a scroll bar the list sits 4 from the inset edge, as on the left.
J.list:FollowBar({ "TOPLEFT", e, "TOPLEFT", 4, -4 }, { "BOTTOMRIGHT", e, "BOTTOMRIGHT", -16, 4 }, -4)
local close = S.button(a, txt("CLOSE"), 140)
close:SetPoint("BOTTOM", a, "BOTTOM", 0, 14)
close:SetScript("OnClick", function() a:Hide() end)
a:HookScript("OnShow", function()
QueryGuildEventLog()
J.list:Update(GetNumGuildEvents() or 0)
end)
end
function Gu.toggleQuestLog()
if J.frame:IsShown() then J.frame:Hide() else J.frame:Show() end
end
-- ---------- Update
function Gu.update()
if not Gu.list then return end
-- List: shown members (no offline ones if the box is unchecked); total: all members.
local shownItems = GetNumGuildMembers() or 0
local total = GetNumGuildMembers(true) or shownItems
local online = 0
for i = 1, shownItems do
if select(9, GetGuildRosterInfo(i)) then online = online + 1 end
end
Gu.totals:SetText(string.format(txt("GUILD_TOTAL"), total) .. " " .. string.format(txt("GUILD_TOTALONLINE"), online))
Gu.offlineCheck:SetChecked(GetGuildRosterShowOffline() and true or false)
Gu.list:Update(shownItems)
-- Message of the day
Gu.motd:SetText(CURRENT_GUILD_MOTD or (GetGuildRosterMOTD and GetGuildRosterMOTD()) or "")
if CanEditMOTD() then
Gu.motd:SetTextColor(1, 1, 1)
Gu.motdZone:EnableMouse(true)
else
Gu.motd:SetTextColor(P.grayText, P.grayText, P.grayText)
Gu.motdZone:EnableMouse(false)
end
active(Gu.control, IsGuildLeader() and true or false)
active(Gu.add, CanGuildInvite() and true or false)
Gu.updateDetail()
end
local function title()
local guild, rank = GetGuildInfo("player")
if guild then return string.format(txt("GUILD_TITLE_TEMPLATE"), rank or "", guild) end
return ""
end
-- ---------- Guild control window
local CONTROL_CHECKBOXES = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 15, 16, 17 }
Gu.CONTROL_TOP = 24
-- Reskins the client's GuildControlPopupFrame once. gc: that frame.
function Gu.skinControl(gc)
if gc.foreverSkinApplied then return end
gc.foreverSkinApplied = true
-- MacroPopup background: six unnamed textures.
for _, r in ipairs({ gc:GetRegions() }) do
local f = r.GetTexture and r:GetTexture()
if type(f) == "string" and string.find(string.lower(f), "macropopup", 1, true) then
r:SetAlpha(0)
r:Hide()
end
end
-- The skin reaches 24 above the frame: the client content starts 15 below the top
-- (GUILDCONTROL_SELECTRANK) and the metal bar would cover it. The skin draws under the
-- content, its metal in front. Size set by hand: UpdatePanelCorners reads it.
local skin = CreateFrame("Frame", nil, gc)
skin:SetWidth(gc:GetWidth() > 0 and gc:GetWidth() or 320)
skin:SetHeight((gc:GetHeight() > 0 and gc:GetHeight() or 457) + Gu.CONTROL_TOP)
skin:SetPoint("TOPLEFT", gc, "TOPLEFT", 0, Gu.CONTROL_TOP)
skin:SetFrameLevel(math.max(0, gc:GetFrameLevel() - 1))
ForeverUI.SetPanelArt(skin, { topLeftCorner = "ui-frame-metal-cornertopleft", level = 25 })
gc.foreverSkin = skin
local metal = skin.foreverSkinLayer or skin
local banner = CreateFrame("Frame", nil, gc)
banner:SetAllPoints(skin)
banner:SetFrameLevel(metal:GetFrameLevel() + 1)
local title = banner:CreateFontString(nil, "OVERLAY", "GameFontNormal")
title:SetPoint("TOP", skin, "TOP", 0, -6)
title:SetText(txt("GUILDCONTROL"))
gc.foreverTitle = title
local closeButton = CreateFrame("Button", nil, gc)
closeButton:SetWidth(24)
closeButton:SetHeight(24)
closeButton:SetFrameLevel(metal:GetFrameLevel() + 2)
closeButton:SetPoint("TOPRIGHT", skin, "TOPRIGHT", 1, 0)
for _, state in ipairs({
{ "SetNormalTexture", "GetNormalTexture", "redbutton-exit" },
{ "SetPushedTexture", "GetPushedTexture", "redbutton-exit-pressed" },
{ "SetHighlightTexture", "GetHighlightTexture", "redbutton-highlight" },
}) do
local e = ForeverUI.AtlasEntry(state[3])
closeButton[state[1]](closeButton, e and e[1] or "")
local t = closeButton[state[2]](closeButton)
if t then
ForeverUI.SetAtlas(t, state[3], true)
t:ClearAllPoints()
t:SetAllPoints(closeButton)
if state[3] == "redbutton-highlight" then t:SetBlendMode("ADD") end
end
end
closeButton:SetScript("OnClick", function() gc:Hide() end)
gc.foreverCloseButton = closeButton
-- Checkboxes
for _, i in ipairs(CONTROL_CHECKBOXES) do
local c = _G["GuildControlPopupFrameCheckbox" .. i]
if c then S.skinCell(c) end
end
for _, n in ipairs({ "GuildControlTabPermissionsViewTab", "GuildControlTabPermissionsDepositItems",
"GuildControlTabPermissionsUpdateText" }) do
if _G[n] then S.skinCell(_G[n]) end
end
-- Edit boxes
for _, n in ipairs({ "GuildControlPopupFrameEditBox", "GuildControlWithdrawGoldEditBox",
"GuildControlWithdrawItemsEditBox" }) do
if _G[n] then S.skinInput(_G[n]) end
end
-- Bank permissions frame: camelot inset instead of the tooltip backdrop.
local tp = _G["GuildControlPopupFrameTabPermissions"]
if tp then
if tp.SetBackdrop then tp:SetBackdrop(nil) end
ForeverUI.DecorateInset(tp)
end
end
-- ---------- Build
-- Builds the Guild page. frame: page frame inside the Social window.
local function build(frame)
local frameBox = ForeverUI.CreateInset(frame, "ForeverUIGuildInset")
frameBox:SetPoint("TOPLEFT", S.frame, "TOPLEFT", 4, P.frameBoxY1)
frameBox:SetPoint("BOTTOMRIGHT", S.frame, "BOTTOMRIGHT", -6, P.frameBoxBottom)
-- Headers: GUILD_COLUMN_INFO without Note, Rank up to the edge.
Gu.headers = {}
local previous
for i, col in ipairs(P.columns) do
local h = S.createHeader(frameBox, "ForeverUIGuildColumn" .. i, col[2] > 0 and col[2] or P.fillW, txt(col[1]), function(self)
SortGuildRoster(self.sort)
end)
h.sort = col[3]
if previous then
h:SetPoint("LEFT", previous, "RIGHT", P.gap, 0)
else
h:SetPoint("TOPLEFT", frameBox, "TOPLEFT", P.headerX, P.headerY)
end
Gu.headers[i] = h
previous = h
end
Gu.list = S.createList(frameBox, "ForeverUIGuildList", P.rowH, createRow, populateRow)
-- Without a scroll bar, the list and the Rank column reach the edge.
local placeAnchors = Gu.list.PlaceAnchors
function Gu.list:PlaceAnchors()
placeAnchors(self)
Gu.headers[#P.columns]:SetPoint("BOTTOMRIGHT", self, "TOPRIGHT",
self.hasBar and P.noteRight or P.noteRightAlone, 1)
end
Gu.list:FollowBar({ "TOPLEFT", Gu.headers[1], "BOTTOMLEFT", 1, -1 },
{ "BOTTOMRIGHT", frameBox, "BOTTOMRIGHT", P.listX2, 4 }, P.listX2NoBar)
Gu.totals = frame:CreateFontString(nil, "ARTWORK", "GameFontNormalSmall")
Gu.totals:SetPoint("TOPLEFT", S.frame, "TOPLEFT", P.totalsX, P.totalsY)
-- Show offline checkbox (ShowOfflineButton), top right.
local outside = S.createCheckbox(frame, "ForeverUIGuildShowOffline", txt("SHOW_OFFLINE_MEMBERS"))
outside:SetPoint("TOPRIGHT", S.frame, "TOPRIGHT", P.offlineRight - outside.text:GetStringWidth() - 2, P.offlineY)
-- As GuildFrameLFGButton's OnClick: clear the selection (roster indexes change with the
-- filter), toggle the filter and redraw now instead of waiting for GUILD_ROSTER_UPDATE.
outside:SetScript("OnClick", function(self)
Gu.selection(0)
if self:GetChecked() then
PlaySound("igMainMenuOptionCheckBoxOff")
else
PlaySound("igMainMenuOptionCheckBoxOn")
end
SetGuildRosterShowOffline(self:GetChecked() and true or false)
Gu.update()
end)
Gu.offlineCheck = outside
-- Message of the day
local motdCaption = frame:CreateFontString(nil, "ARTWORK", "GameFontNormalSmall")
motdCaption:SetPoint("BOTTOMLEFT", S.frame, "BOTTOMLEFT", P.motdX, P.motdBottom + P.motdH + 2)
motdCaption:SetText(txt("GUILD_MOTD_LABEL"))
local zone = CreateFrame("Button", "ForeverUIGuildMOTD", frame)
zone:SetPoint("BOTTOMLEFT", S.frame, "BOTTOMLEFT", P.motdX, P.motdBottom)
zone:SetPoint("BOTTOMRIGHT", S.frame, "BOTTOMRIGHT", -P.motdX, P.motdBottom)
zone:SetHeight(P.motdH)
local motd = zone:CreateFontString(nil, "ARTWORK", "GameFontHighlightSmall")
motd:SetAllPoints(zone)
motd:SetJustifyH("LEFT")
motd:SetJustifyV("TOP")
if motd.SetWordWrap then motd:SetWordWrap(true) end
zone:SetScript("OnClick", function() StaticPopup_Show("SET_GUILDMOTD") end)
Gu.motd, Gu.motdZone = motd, zone
-- The three bottom buttons
Gu.info = S.button(frame, txt("GUILD_INFORMATION"), 130)
Gu.info:SetPoint("BOTTOMRIGHT", S.frame, "BOTTOMRIGHT", P.buttonRight, P.buttonBottom)
Gu.info:SetScript("OnClick", function()
if I.frame:IsShown() then I.frame:Hide() else I.frame:Show() end
end)
Gu.add = S.button(frame, txt("ADDMEMBER"), 110)
Gu.add:SetPoint("RIGHT", Gu.info, "LEFT", -2, 0)
Gu.add:SetScript("OnClick", function() StaticPopup_Show("ADD_GUILDMEMBER") end)
Gu.control = S.button(frame, txt("GUILDCONTROL"), 120)
Gu.control:SetPoint("RIGHT", Gu.add, "LEFT", -2, 0)
Gu.control:SetScript("OnClick", function()
local gc = GuildControlPopupFrame
if not gc then return end
-- Its OnShow and OnHide set FriendsFrame's attributes and place it (UpdateUIPanelPositions):
-- refused to our code in combat, FriendsFrame being protected by the secure tabs.
if InCombatLockdown() then
UIErrorsFrame:AddMessage(ERR_NOT_IN_COMBAT, 1.0, 0.1, 0.1, 1.0)
return
end
if gc:IsShown() then
gc:Hide()
else
if S.popup then S.popup:Hide() end
if GuildControlPopupFrame_Initialize and not gc.initialized then
GuildControlPopupFrame_Initialize()
end
gc:Show()
end
end)
createDetail()
createInfo()
createEventLog()
-- The client's guild control window (parent UIParent) is kept: its functions drive the server.
-- It is moved to the right of our window on each show; anchored to GuildFrame, it would go
-- off screen with it.
if GuildControlPopupFrame then
Gu.skinControl(GuildControlPopupFrame)
GuildControlPopupFrame:HookScript("OnShow", function(self)
if S.popup then S.popup:Hide() end
self:ClearAllPoints()
self:SetPoint("TOPLEFT", S.frame, "TOPRIGHT", 12, -Gu.CONTROL_TOP)
end)
end
-- The client GuildFrame stays shown, off screen and transparent: UnitPopup offers Promote to
-- Guildmaster and Leave Guild, and the client refreshes the roster, only while it is shown.
if GuildFrame then
S.keep("GuildFrame")
GuildFrame:ClearAllPoints()
GuildFrame:SetPoint("TOPLEFT", UIParent, "TOPLEFT", -5000, 5000)
GuildFrame:SetWidth(384)
GuildFrame:SetHeight(512)
GuildFrame:SetAlpha(0)
if GuildFrame.EnableMouse then GuildFrame:EnableMouse(false) end
end
end
S.registerPage(3, {
build = build,
title = title,
update = Gu.update,
showRegion = function(initial)
if initial then GuildRoster() end
end,
hide = function()
Gu.closeSize = true
for _, a in ipairs({ D.frame, I.frame, J.frame }) do
if a then a:Hide() end
end
-- In combat the client hides it (tab OnClick, FriendsFrame_OnHide): its OnHide, run from
-- ours, would be refused (see Gu.control).
if GuildControlPopupFrame and not InCombatLockdown() then GuildControlPopupFrame:Hide() end
Gu.closeSize = nil
end,
})
local listener = CreateFrame("Frame")
for _, ev in ipairs({ "GUILD_ROSTER_UPDATE", "PLAYER_GUILD_UPDATE", "GUILD_MOTD", "GUILD_EVENT_LOG_UPDATE" }) do
listener:RegisterEvent(ev)
end
listener:SetScript("OnEvent", function(self, ev)
if ev == "GUILD_ROSTER_UPDATE" then I.savedText = nil end
if ev == "GUILD_EVENT_LOG_UPDATE" then
if J.frame and J.frame:IsShown() then J.list:Update(GetNumGuildEvents() or 0) end
return
end
if Gu.list and Gu.list:IsVisible() then Gu.update() end
end)