Files
pfUI/modules/combatlog_config.lua
T
Brues 187e057923 Add the Combat Log settings window and 4.3.4's default filters
"Settings" in the Combat Log tab's menu opens Blizzard's Combat Log page,
rebuilt in Lua with pfUI widgets: the filter list with copy, add and
delete, and the Message Sources, Message Types, Colors, Formatting and
Settings tabs. Combat Log Defaults restores the default filters. Closing
the window refills the log only when the active filter changed.

The log now records while ChatFrame2 is hidden, so switching tabs no
longer loses history.

The default filters are 4.3.4's: Self, Everything, What happened to me?
and Kills. Classic Era's My actions left out auras.
2026-10-30 03:02:42 -05:00

1006 lines
39 KiB
Lua

-- Combat Log settings: the Combat Log page of Classic Era's ChatConfigFrame
-- (Blizzard_ChatFrame/Classic/ChatConfigFrame), built with pfUI's widgets.
-- The combatlog module opens it from the Combat Log tab's menu and hands it
-- the filter state as `log`.
setfenv(1, pfUI:GetEnvironment())
pfUI.combatlog = {}
-- ChatConfigFrame's eventList states for the subtypes of an unchecked group
local GRAY_CHECKED = 1
local UNCHECKED_ENABLED = 2
local UNCHECKED_DISABLED = 3
local SCHOOL_MASK_FIRE = 4
local SCHOOL_MASK_FROST = 16
-- ChatConfigFrame's cap on saved filters
local MAX_COMBATLOG_FILTERS = 20
local FILTER_ROWS = 4
local FILTER_ROW_HEIGHT = 16
local function Resolve(value)
if type(value) == "function" then return value() end
return value
end
local function SetCheck(check, checked)
check:SetChecked(checked and 1 or nil)
end
local function SetCheckEnabled(check, enabled)
if enabled then
check:Enable()
check.text:SetTextColor(1, 1, 1)
else
check:Disable()
check.text:SetTextColor(.5, .5, .5)
end
end
-- ChatConfigCheckButtonTemplate and ChatConfigSmallCheckButtonTemplate
local function CreateCheck(parent, text, tooltip, small)
local check = CreateFrame("CheckButton", nil, parent, "UICheckButtonTemplate")
SkinCheckbox(check, small and 12 or 14)
check.text = check:CreateFontString(nil, "OVERLAY", small and "GameFontHighlightSmall" or "GameFontHighlight")
check.text:SetPoint("LEFT", check, "RIGHT", 2, 0)
check.text:SetText(text)
check:SetHitRectInsets(0, small and -70 or -145, 0, 0)
check.tooltip = tooltip
check:SetScript("OnClick", function(self)
local checked = self:GetChecked()
if self.func then self.func(self, checked) end
PlaySound(checked and SOUNDKIT.IG_MAINMENU_OPTION_CHECKBOX_ON or SOUNDKIT.IG_MAINMENU_OPTION_CHECKBOX_OFF)
end)
check:SetScript("OnEnter", function(self)
if self.tooltip then
GameTooltip:SetOwner(self, "ANCHOR_RIGHT")
GameTooltip:SetText(self.tooltip, 1, 1, 1, 1, 1)
GameTooltip:Show()
end
end)
check:SetScript("OnLeave", function()
GameTooltip:Hide()
end)
return check
end
-- ChatConfigBoxWithHeaderTemplate
local function CreateBox(parent, title)
local box = CreateFrame("Frame", nil, parent)
CreateBackdrop(box, nil, nil, .75)
if title then
box.title = box:CreateFontString(nil, "OVERLAY", "GameFontNormal")
box.title:SetPoint("BOTTOMLEFT", box, "TOPLEFT", 0, 4)
box.title:SetText(title)
end
return box
end
local function CreateSwatch(parent)
local swatch = CreateFrame("Button", nil, parent)
swatch:SetSize(24, 12)
CreateBackdrop(swatch)
swatch.prev = swatch.backdrop:CreateTexture(nil, "OVERLAY")
swatch.prev:SetAllPoints(swatch)
if swatch.backdrop_shadow then swatch.backdrop_shadow:Hide() end
return swatch
end
local function CreateButton(parent, text, width)
local button = CreateFrame("Button", nil, parent, "UIPanelButtonTemplate")
SkinButton(button)
button:SetSize(width, 22)
button:SetText(text)
return button
end
function pfUI.combatlog.CreateConfig(log)
local selected -- CHATCONFIG_SELECTED_FILTER
local selectedIndex -- ChatConfigCombatSettingsFilters.selectedFilter
local changed -- the log's active filter was edited; it reapplies on close
local Update -- refreshes every page from the selected filter
-- the log reapplies its filter on close only after an edit to that filter,
-- so closing the window doesn't clear what the log holds
local function Edited()
local filters = log.GetFilters()
if selected == filters.filters[filters.currentFilter] then changed = true end
Update()
end
--
-- The filter model (ChatConfigFrame's helpers). A filter's first part holds
-- the event list and the source flags. Its second part holds the dest flags,
-- unless it names units by GUID; a one-part filter keeps both in its first.
--
local function UsesGUID(direction)
local flags = selected.filters[1][direction == "SOURCE" and "sourceFlags" or "destFlags"]
for k in pairs(flags or {}) do
if type(k) == "string" then return true end
end
return false
end
local function DestFilter()
if UsesGUID("SOURCE") or UsesGUID("DEST") then return selected.filters[1] end
return selected.filters[2] or selected.filters[1]
end
-- no flag table means every unit; unchecking one starts from all of them
local function AllFlags()
return {
[COMBATLOG_FILTER_MINE] = true, [COMBATLOG_FILTER_MY_PET] = true,
[COMBATLOG_FILTER_FRIENDLY_UNITS] = true, [COMBATLOG_FILTER_HOSTILE_PLAYERS] = true,
[COMBATLOG_FILTER_HOSTILE_UNITS] = true, [COMBATLOG_FILTER_NEUTRAL_UNITS] = true,
[COMBATLOG_FILTER_UNKNOWN_UNITS] = true,
}
end
local function IsMessageDoneBy(flag)
local flags = selected.filters[1].sourceFlags
return not flags or flags[flag]
end
local function IsMessageDoneTo(flag)
local flags = DestFilter().destFlags
return not flags or flags[flag]
end
local function ToggleMessageSource(checked, flag)
local filter = selected.filters[1]
filter.sourceFlags = filter.sourceFlags or AllFlags()
filter.sourceFlags[flag] = checked and true or false
end
local function ToggleMessageDest(checked, flag)
local filter = DestFilter()
filter.destFlags = filter.destFlags or AllFlags()
filter.destFlags[flag] = checked and true or false
end
local function GetMessageTypeState(messageType)
if type(messageType) == "table" then messageType = messageType[1] end
return selected.filters[1].eventList[messageType]
end
-- only the first type counts; a subtype's types go on and off together
local function HasMessageType(messageType)
local state = GetMessageTypeState(messageType)
return state and state ~= UNCHECKED_ENABLED and state ~= UNCHECKED_DISABLED
end
local function ToggleMessageType(checked, ...)
local eventList = selected.filters[1].eventList
for _, messageType in pairs({ ... }) do
eventList[messageType] = checked and true or false
end
end
-- A group shows checked unless its first subtype with a state is parked.
local function HasMessageTypeGroup(group)
for _, sub in ipairs(group.subTypes) do
local state = GetMessageTypeState(sub.type)
if state == GRAY_CHECKED or state == UNCHECKED_DISABLED then
return false
elseif state then
return true
end
end
return false
end
-- Unchecking a group parks its subtypes' states, so checking it again
-- restores the ones that were on.
local function ToggleMessageTypeGroup(checked, group)
local eventList = selected.filters[1].eventList
for _, sub in ipairs(group.subTypes) do
local state = GetMessageTypeState(sub.type)
local on = state == true or state == GRAY_CHECKED
local value
if checked then
value = on and true or UNCHECKED_ENABLED
else
value = on and GRAY_CHECKED or UNCHECKED_DISABLED
end
local types = type(sub.type) == "table" and sub.type or { sub.type }
for _, messageType in pairs(types) do
eventList[messageType] = value
end
end
end
--
-- The checkbox tables (COMBAT_CONFIG_MESSAGESOURCES_*, _MESSAGETYPES_*).
-- Enchants and pet loyalty are left out: ClassicAPI never logs them.
--
local function Source(text, flag, tooltip)
return {
text = text,
checked = function() return IsMessageDoneBy(flag) end,
hidden = function() return UsesGUID("SOURCE") end,
func = function(self, checked) ToggleMessageSource(checked, flag) end,
tooltip = tooltip,
}
end
local function Dest(text, flag, tooltip)
return {
text = text,
checked = function() return IsMessageDoneTo(flag) end,
hidden = function() return UsesGUID("DEST") end,
func = function(self, checked) ToggleMessageDest(checked, flag) end,
tooltip = tooltip,
}
end
local MESSAGESOURCES_BY = {
{
text = function() return UsesGUID("SOURCE") and COMBATLOG_FILTER_STRING_CUSTOM_UNIT or COMBATLOG_FILTER_STRING_ME end,
checked = function() return UsesGUID("SOURCE") or IsMessageDoneBy(COMBATLOG_FILTER_MINE) end,
disabled = function() return UsesGUID("SOURCE") end,
func = function(self, checked) ToggleMessageSource(checked, COMBATLOG_FILTER_MINE) end,
tooltip = FILTER_BY_ME_COMBATLOG_TOOLTIP,
},
Source(COMBATLOG_FILTER_STRING_MY_PET, COMBATLOG_FILTER_MY_PET, FILTER_BY_PET_COMBATLOG_TOOLTIP),
Source(COMBATLOG_FILTER_STRING_FRIENDLY_UNITS, COMBATLOG_FILTER_FRIENDLY_UNITS, FILTER_BY_FRIENDS_COMBATLOG_TOOLTIP),
Source(COMBATLOG_FILTER_STRING_HOSTILE_PLAYERS, COMBATLOG_FILTER_HOSTILE_PLAYERS, FILTER_BY_HOSTILE_PLAYERS_COMBATLOG_TOOLTIP),
Source(COMBATLOG_FILTER_STRING_HOSTILE_UNITS, COMBATLOG_FILTER_HOSTILE_UNITS, FILTER_BY_ENEMIES_COMBATLOG_TOOLTIP),
Source(COMBATLOG_FILTER_STRING_NEUTRAL_UNITS, COMBATLOG_FILTER_NEUTRAL_UNITS, FILTER_BY_NEUTRAL_COMBATLOG_TOOLTIP),
Source(COMBATLOG_FILTER_STRING_UNKNOWN_UNITS, COMBATLOG_FILTER_UNKNOWN_UNITS, FILTER_BY_UNKNOWN_COMBATLOG_TOOLTIP),
}
local MESSAGESOURCES_TO = {
{
text = function() return UsesGUID("DEST") and COMBATLOG_FILTER_STRING_CUSTOM_UNIT or COMBATLOG_FILTER_STRING_ME end,
checked = function() return UsesGUID("DEST") or IsMessageDoneTo(COMBATLOG_FILTER_MINE) end,
disabled = function() return UsesGUID("DEST") end,
func = function(self, checked) ToggleMessageDest(checked, COMBATLOG_FILTER_MINE) end,
tooltip = FILTER_TO_ME_COMBATLOG_TOOLTIP,
},
Dest(COMBATLOG_FILTER_STRING_MY_PET, COMBATLOG_FILTER_MY_PET, FILTER_TO_PET_COMBATLOG_TOOLTIP),
Dest(COMBATLOG_FILTER_STRING_FRIENDLY_UNITS, COMBATLOG_FILTER_FRIENDLY_UNITS, FILTER_TO_FRIENDS_COMBATLOG_TOOLTIP),
Dest(COMBATLOG_FILTER_STRING_HOSTILE_PLAYERS, COMBATLOG_FILTER_HOSTILE_PLAYERS, FILTER_TO_HOSTILE_PLAYERS_COMBATLOG_TOOLTIP),
Dest(COMBATLOG_FILTER_STRING_HOSTILE_UNITS, COMBATLOG_FILTER_HOSTILE_UNITS, FILTER_TO_HOSTILE_COMBATLOG_TOOLTIP),
Dest(COMBATLOG_FILTER_STRING_NEUTRAL_UNITS, COMBATLOG_FILTER_NEUTRAL_UNITS, FILTER_TO_NEUTRAL_COMBATLOG_TOOLTIP),
Dest(COMBATLOG_FILTER_STRING_UNKNOWN_UNITS, COMBATLOG_FILTER_UNKNOWN_UNITS, FILTER_TO_UNKNOWN_COMBATLOG_TOOLTIP),
}
-- a subtype that turns its event types on and off together
local function Type(text, tooltip, ...)
local types = { ... }
return {
text = text,
type = types,
checked = function() return HasMessageType(types) end,
func = function(self, checked) ToggleMessageType(checked, unpack(types)) end,
tooltip = tooltip,
}
end
local AURA_TYPES = { "SPELL_AURA_APPLIED", "SPELL_AURA_APPLIED_DOSE", "SPELL_AURA_REMOVED", "SPELL_AURA_REMOVED_DOSE", "SPELL_AURA_REFRESH" }
-- buffs and debuffs share the aura events; those stop only when both are off
local function Aura(text, tooltip, setting, other)
return {
text = text,
type = AURA_TYPES,
checked = function() return not selected.settings[setting] end,
func = function(self, checked)
selected.settings[setting] = not checked
if checked or selected.settings[other] then
ToggleMessageType(checked, unpack(AURA_TYPES))
end
end,
tooltip = tooltip,
}
end
local MESSAGETYPES_LEFT = {
{
text = MELEE, tooltip = MELEE_COMBATLOG_TOOLTIP,
subTypes = {
Type(DAMAGE, SWING_DAMAGE_COMBATLOG_TOOLTIP, "SWING_DAMAGE"),
Type(MISSES, SWING_MISSED_COMBATLOG_TOOLTIP, "SWING_MISSED"),
},
},
{
text = RANGED, tooltip = RANGED_COMBATLOG_TOOLTIP,
subTypes = {
Type(DAMAGE, RANGE_DAMAGE_COMBATLOG_TOOLTIP, "RANGE_DAMAGE"),
Type(MISSES, RANGE_MISSED_COMBATLOG_TOOLTIP, "RANGE_MISSED"),
},
},
{
text = AURAS, tooltip = AURAS_COMBATLOG_TOOLTIP,
subTypes = {
Aura(BENEFICIAL, BENEFICIAL_AURA_COMBATLOG_TOOLTIP, "hideBuffs", "hideDebuffs"),
Aura(HOSTILE, HARMFUL_AURA_COMBATLOG_TOOLTIP, "hideDebuffs", "hideBuffs"),
Type(DISPELS, DISPEL_AURA_COMBATLOG_TOOLTIP, "SPELL_DISPEL", "SPELL_DISPEL_FAILED", "SPELL_STOLEN"),
},
},
{
text = PERIODIC, tooltip = SPELL_PERIODIC_COMBATLOG_TOOLTIP,
subTypes = {
Type(DAMAGE, SPELL_PERIODIC_DAMAGE_COMBATLOG_TOOLTIP, "SPELL_PERIODIC_DAMAGE"),
Type(MISSES, SPELL_PERIODIC_MISSED_COMBATLOG_TOOLTIP, "SPELL_PERIODIC_MISSED"),
Type(HEALS, SPELL_PERIODIC_HEAL_COMBATLOG_TOOLTIP, "SPELL_PERIODIC_HEAL"),
Type(OTHER, SPELL_PERIODIC_OTHER_COMBATLOG_TOOLTIP, "SPELL_PERIODIC_DRAIN", "SPELL_PERIODIC_LEECH"),
},
},
}
local MESSAGETYPES_RIGHT = {
{
text = SPELLS, tooltip = SPELLS_COMBATLOG_TOOLTIP,
subTypes = {
Type(DAMAGE, SPELL_DAMAGE_COMBATLOG_TOOLTIP, "SPELL_DAMAGE"),
Type(MISSES, SPELL_MISSED_COMBATLOG_TOOLTIP, "SPELL_MISSED"),
Type(HEALS, SPELL_HEAL_COMBATLOG_TOOLTIP, "SPELL_HEAL"),
Type(POWER_GAINS, POWER_GAINS_COMBATLOG_TOOLTIP, "SPELL_ENERGIZE", "SPELL_PERIODIC_ENERGIZE"),
Type(DRAINS, SPELL_DRAIN_COMBATLOG_TOOLTIP, "SPELL_DRAIN", "SPELL_LEECH"),
Type(INTERRUPTS, SPELL_INTERRUPT_COMBATLOG_TOOLTIP, "SPELL_INTERRUPT"),
Type(SPECIAL, SPELL_INSTAKILL_COMBATLOG_TOOLTIP, "SPELL_INSTAKILL", "SPELL_DURABILITY_DAMAGE", "SPELL_DURABILITY_DAMAGE_ALL"),
Type(EXTRA_ATTACKS, SPELL_EXTRA_ATTACKS_COMBATLOG_TOOLTIP, "SPELL_EXTRA_ATTACKS"),
Type(SUMMONS, SPELL_SUMMON_COMBATLOG_TOOLTIP, "SPELL_SUMMON"),
Type(RESURRECT, SPELL_RESURRECT_COMBATLOG_TOOLTIP, "SPELL_RESURRECT"),
},
},
{
text = SPELL_CASTING, tooltip = SPELL_CASTING_COMBATLOG_TOOLTIP,
subTypes = {
Type(START, SPELL_CAST_START_COMBATLOG_TOOLTIP, "SPELL_CAST_START"),
Type(SUCCESS, SPELL_CAST_SUCCESS_COMBATLOG_TOOLTIP, "SPELL_CAST_SUCCESS"),
Type(FAILURES, SPELL_CAST_FAILED_COMBATLOG_TOOLTIP, "SPELL_CAST_FAILED"),
},
},
}
local MESSAGETYPES_MISC = {
Type(DAMAGE_SHIELD, DAMAGE_SHIELD_COMBATLOG_TOOLTIP, "DAMAGE_SHIELD", "DAMAGE_SHIELD_MISSED"),
Type(ENVIRONMENTAL_DAMAGE, ENVIRONMENTAL_DAMAGE_COMBATLOG_TOOLTIP, "ENVIRONMENTAL_DAMAGE"),
Type(KILLS, KILLS_COMBATLOG_TOOLTIP, "PARTY_KILL"),
Type(DEATHS, DEATHS_COMBATLOG_TOOLTIP, "UNIT_DIED", "UNIT_DESTROYED", "UNIT_DISSIPATES"),
}
local UNIT_COLORS = {
{ text = COMBATLOG_FILTER_STRING_ME, flag = COMBATLOG_FILTER_MINE },
{ text = COMBATLOG_FILTER_STRING_MY_PET, flag = COMBATLOG_FILTER_MY_PET },
{ text = COMBATLOG_FILTER_STRING_FRIENDLY_UNITS, flag = COMBATLOG_FILTER_FRIENDLY_UNITS },
{ text = COMBATLOG_FILTER_STRING_HOSTILE_UNITS, flag = COMBATLOG_FILTER_HOSTILE_UNITS },
{ text = COMBATLOG_FILTER_STRING_HOSTILE_PLAYERS, flag = COMBATLOG_FILTER_HOSTILE_PLAYERS },
{ text = COMBATLOG_FILTER_STRING_NEUTRAL_UNITS, flag = COMBATLOG_FILTER_NEUTRAL_UNITS },
{ text = COMBATLOG_FILTER_STRING_UNKNOWN_UNITS, flag = COMBATLOG_FILTER_UNKNOWN_UNITS },
}
--
-- Checkbox builders (ChatConfig_CreateCheckboxes, _CreateTieredCheckboxes)
--
local function CreateCheckboxList(parent, title, entries)
local box = CreateBox(parent, title)
box:SetWidth(200)
box.entries = entries
box.checks = {}
for i, entry in ipairs(entries) do
local check = CreateCheck(box, nil, entry.tooltip)
local func = entry.func
check.func = function(self, checked) func(self, checked) Edited() end
box.checks[i] = check
end
return box
end
-- hidden entries close up, and the box fits the rest
local function UpdateCheckboxList(box)
local shown = 0
for i, entry in ipairs(box.entries) do
local check = box.checks[i]
if Resolve(entry.hidden) then
check:Hide()
else
check:ClearAllPoints()
check:SetPoint("TOPLEFT", box, "TOPLEFT", 8, -6 - shown * 22)
check.text:SetText(Resolve(entry.text))
SetCheck(check, Resolve(entry.checked))
SetCheckEnabled(check, not Resolve(entry.disabled))
check:Show()
shown = shown + 1
end
end
box:SetHeight(shown * 22 + 8)
end
-- a checkbox per group with its subtypes in two columns under it; an entry
-- without subtypes is a plain checkbox
local function CreateTieredList(parent, entries, small)
local list = CreateFrame("Frame", nil, parent)
list:SetWidth(220)
list.entries = entries
list.checks = {}
local y = 0
for i, entry in ipairs(entries) do
local check = CreateCheck(list, entry.text, entry.tooltip, small)
check:SetPoint("TOPLEFT", list, "TOPLEFT", 0, -y)
y = y + 22
check.subs = {}
if entry.subTypes then
check.func = function(self, checked) ToggleMessageTypeGroup(checked, entry) Edited() end
for k, sub in ipairs(entry.subTypes) do
local subcheck = CreateCheck(list, sub.text, sub.tooltip, true)
subcheck:SetPoint("TOPLEFT", check, "BOTTOMLEFT", 15 + (k - 1) % 2 * 95, -2 - ceil(k / 2 - 1) * 20)
local func = sub.func
subcheck.func = function(self, checked) func(self, checked) Edited() end
check.subs[k] = subcheck
end
y = y + ceil(#entry.subTypes / 2) * 20 + 8
else
local func = entry.func
check.func = function(self, checked) func(self, checked) Edited() end
end
list.checks[i] = check
end
list:SetHeight(y)
return list
end
-- subtypes take clicks only under a checked group
local function UpdateTieredList(list)
for i, entry in ipairs(list.entries) do
local check = list.checks[i]
if entry.subTypes then
local groupChecked = HasMessageTypeGroup(entry)
SetCheck(check, groupChecked)
for k, sub in ipairs(entry.subTypes) do
SetCheck(check.subs[k], Resolve(sub.checked))
SetCheckEnabled(check.subs[k], groupChecked)
end
else
SetCheck(check, Resolve(entry.checked))
end
end
end
local function PickColor(color)
local r, g, b = color.r, color.g, color.b
-- SetColorRGB runs the installed func; drop the last picker user's first
ColorPickerFrame.func = nil
ColorPickerFrame.cancelFunc = nil
ColorPickerFrame.hasOpacity = nil
ColorPickerFrame.opacityFunc = nil
ColorPickerFrame:SetColorRGB(r, g, b)
ColorPickerFrame.func = function()
color.r, color.g, color.b = ColorPickerFrame:GetColorRGB()
Edited()
end
ColorPickerFrame.cancelFunc = function()
color.r, color.g, color.b = r, g, b
Edited()
end
ColorPickerFrame:SetFrameStrata("DIALOG")
ShowUIPanel(ColorPickerFrame)
end
-- CombatConfig_Colorize_Update's example lines: you hit a target, it hits you
local function UpdateExamples(page)
local player, guid = UnitName("player"), UnitGUID("player")
local text, r, g, b = log.FormatEvent(selected, 0, "SPELL_DAMAGE", false,
guid, player, 0x511, 0, "0xF13000012B000820", EXAMPLE_TARGET_MONSTER, 0x10a28, 0,
116, EXAMPLE_SPELL_FROSTBOLT, SCHOOL_MASK_FROST, 27, 0, SCHOOL_MASK_FROST, nil, nil, nil, 1)
page.example1:SetText(text)
page.example1:SetTextColor(r, g, b)
text, r, g, b = log.FormatEvent(selected, 0, "SPELL_DAMAGE", false,
"0xF13000024D002914", EXAMPLE_TARGET_MONSTER, 0x10a48, 0, guid, player, 0x511, 0,
20793, EXAMPLE_SPELL_FIREBALL, SCHOOL_MASK_FIRE, 68, 0, SCHOOL_MASK_FIRE)
page.example2:SetText(text)
page.example2:SetTextColor(r, g, b)
end
local function CreateExamples(page)
page.example1 = page:CreateFontString(nil, "OVERLAY", "GameFontNormal")
page.example1:SetPoint("TOPLEFT", page, "TOPLEFT", 25, -27)
page.example1:SetWidth(540)
page.example1:SetJustifyH("LEFT")
page.example2 = page:CreateFontString(nil, "OVERLAY", "GameFontNormal")
page.example2:SetPoint("TOPLEFT", page.example1, "BOTTOMLEFT", 0, 0)
page.example2:SetWidth(540)
page.example2:SetJustifyH("LEFT")
local title = page:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
title:SetPoint("BOTTOMLEFT", page.example1, "TOPLEFT", 0, 3)
title:SetText(EXAMPLE_TEXT)
end
-- a checkbox bound to one of the selected filter's settings
local function SettingCheck(parent, text, tooltip, setting, small)
local check = CreateCheck(parent, text, tooltip, small)
check.setting = setting
check.func = function(self, checked)
selected.settings[setting] = checked and true or false
Edited()
end
return check
end
--
-- The window (ChatConfigFrame with only its Combat Log category)
--
local frame = CreateFrame("Frame", "pfCombatLogConfig", UIParent)
frame:SetSize(620, 600)
frame:SetPoint("CENTER", 0, 0)
frame:SetFrameStrata("HIGH")
frame:SetMovable(true)
frame:EnableMouse(true)
frame:SetClampedToScreen(true)
frame:RegisterForDrag("LeftButton")
frame:SetScript("OnDragStart", function() this:StartMoving() end)
frame:SetScript("OnDragStop", function() this:StopMovingOrSizing() end)
CreateBackdrop(frame, nil, nil, .9)
CreateBackdropShadow(frame)
tinsert(UISpecialFrames, "pfCombatLogConfig")
frame:Hide()
frame.title = frame:CreateFontString(nil, "OVERLAY", "GameFontNormal")
frame.title:SetPoint("TOP", frame, "TOP", 0, -10)
frame.title:SetText(COMBAT_LOG)
local closeButton = CreateFrame("Button", nil, frame, "UIPanelCloseButton")
closeButton:SetPoint("TOPRIGHT", frame, "TOPRIGHT", -4, -4)
SkinCloseButton(closeButton, frame.backdrop or frame, -6, -6)
--
-- The saved filters: a list to pick one from, and buttons to reorder,
-- copy, add and delete them
--
local filterList = CreateFrame("Frame", nil, frame, "pfUIScrollBoxListTemplate")
filterList:SetPoint("TOPLEFT", frame, "TOPLEFT", 12, -32)
filterList:SetPoint("TOPRIGHT", frame, "TOPRIGHT", -12, -32)
-- the template keeps 5px around the rows, which sit 2px apart
filterList:SetHeight(FILTER_ROWS * (FILTER_ROW_HEIGHT + 2) - 2 + 10)
CreateBackdrop(filterList, nil, nil, .75)
local SelectFilter
local function InitFilterRow(row, entry)
if not row.text then
row.highlight = row:CreateTexture(nil, "BACKGROUND")
row.highlight:SetAllPoints(row)
row.highlight:SetTexture(.3, .3, .3, .4)
row.hover = row:CreateTexture(nil, "BACKGROUND")
row.hover:SetAllPoints(row)
row.hover:SetTexture(1, 1, 1, .1)
row.hover:Hide()
row.text = row:CreateFontString(nil, "OVERLAY", "GameFontNormal")
row.text:SetPoint("LEFT", row, "LEFT", 4, 0)
row:SetScript("OnClick", function() SelectFilter(row.index) end)
row:SetScript("OnEnter", function() row.hover:Show() end)
row:SetScript("OnLeave", function() row.hover:Hide() end)
end
row.index = entry.index
row.text:SetText(entry.name)
row.highlight:SetShown(entry.index == selectedIndex)
end
local filterView = CreateScrollBoxListLinearView(0, 0, 0, 2, 2)
filterView:SetElementExtent(FILTER_ROW_HEIGHT)
filterView:SetElementInitializer("Button", InitFilterRow)
ScrollUtil.InitScrollBoxListWithScrollBar(filterList.ScrollBox, filterList.ScrollBar, filterView)
-- ChatConfig_MoveFilterUp / _MoveFilterDown; the active filter keeps its
-- place in the log
local function MoveFilter(offset)
local filters = log.GetFilters()
local to = selectedIndex + offset
if to < 1 or to > #filters.filters then return end
filters.filters[selectedIndex], filters.filters[to] = filters.filters[to], filters.filters[selectedIndex]
if filters.currentFilter == selectedIndex then
filters.currentFilter = to
elseif filters.currentFilter == to then
filters.currentFilter = selectedIndex
end
log.UpdateQuickButtons()
SelectFilter(to)
end
local function CreateMoveButton(dir, tooltip, offset)
local button = CreateFrame("Button", nil, frame)
SkinArrowButton(button, dir, 16)
button:SetScript("OnClick", function()
MoveFilter(offset)
PlaySound(SOUNDKIT.U_CHAT_SCROLL_BUTTON)
end)
button:SetScript("OnEnter", function()
GameTooltip:SetOwner(this, "ANCHOR_RIGHT")
GameTooltip:SetText(tooltip, 1, 1, 1)
GameTooltip:Show()
end)
button:SetScript("OnLeave", function() GameTooltip:Hide() end)
return button
end
local moveUp = CreateMoveButton("up", MOVE_FILTER_UP, -1)
moveUp:SetPoint("TOPLEFT", filterList, "BOTTOMLEFT", 4, -8)
local moveDown = CreateMoveButton("down", MOVE_FILTER_DOWN, 1)
moveDown:SetPoint("LEFT", moveUp, "RIGHT", 8, 0)
-- CombatConfig_DeleteCurrentCombatFilter; the log switches to the first
-- filter only when the one it shows is deleted
local function DeleteFilter()
local filters = log.GetFilters()
if #filters.filters <= 1 then return end
tremove(filters.filters, selectedIndex)
if selectedIndex == filters.currentFilter then
log.SelectFilter(1)
changed = nil
else
if selectedIndex < filters.currentFilter then
filters.currentFilter = filters.currentFilter - 1
end
log.UpdateQuickButtons()
end
SelectFilter(1)
end
local deleteButton = CreateButton(frame, DELETE, 70)
deleteButton:SetPoint("TOPRIGHT", filterList, "BOTTOMRIGHT", 0, -6)
deleteButton:SetScript("OnClick", function()
CreateQuestionDialog(CONFIRM_COMBAT_FILTER_DELETE, DeleteFilter)
end)
local addButton = CreateButton(frame, ADD_FILTER, 90)
addButton:SetPoint("RIGHT", deleteButton, "LEFT", -6, 0)
addButton:SetScript("OnClick", function()
log.NewFilter(nil, SelectFilter)
end)
local copyButton = CreateButton(frame, COPY_FILTER, 90)
copyButton:SetPoint("RIGHT", addButton, "LEFT", -6, 0)
copyButton:SetScript("OnClick", function()
log.NewFilter(selected, SelectFilter)
end)
--
-- The selected filter's settings, one tab at a time
--
local content = CreateFrame("Frame", nil, frame)
content:SetPoint("TOPLEFT", frame, "TOPLEFT", 12, -172)
content:SetPoint("BOTTOMRIGHT", frame, "BOTTOMRIGHT", -12, 42)
CreateBackdrop(content, nil, nil, .75)
local tabs = {}
local function SelectTab(id)
for i, tab in ipairs(tabs) do
tab.page:SetShown(i == id)
if i == id then tab:LockHighlight() else tab:UnlockHighlight() end
end
end
local function CreateTab(text)
local tab = CreateButton(frame, text, 10)
tab:SetHeight(20)
tab:SetWidth(tab:GetTextWidth() + 24)
local id = #tabs + 1
if id == 1 then
tab:SetPoint("BOTTOMLEFT", content, "TOPLEFT", 0, 6)
else
tab:SetPoint("LEFT", tabs[id - 1], "RIGHT", 4, 0)
end
tab:SetScript("OnClick", function()
PlaySound(SOUNDKIT.U_CHAT_SCROLL_BUTTON)
SelectTab(id)
end)
tab.page = CreateFrame("Frame", nil, content)
tab.page:SetAllPoints(content)
tabs[id] = tab
return tab.page
end
-- Message Sources
local page = CreateTab(MESSAGE_SOURCES)
local doneBy = CreateCheckboxList(page, DONE_BY, MESSAGESOURCES_BY)
doneBy:SetPoint("TOPLEFT", page, "TOPLEFT", 13, -25)
local doneTo = CreateCheckboxList(page, DONE_TO, MESSAGESOURCES_TO)
doneTo:SetPoint("TOPLEFT", doneBy, "TOPRIGHT", 50, 0)
-- Message Types
page = CreateTab(MESSAGE_TYPES)
local typesLeft = CreateTieredList(page, MESSAGETYPES_LEFT)
typesLeft:SetPoint("TOPLEFT", page, "TOPLEFT", 13, -10)
local typesRight = CreateTieredList(page, MESSAGETYPES_RIGHT)
typesRight:SetPoint("TOPLEFT", typesLeft, "TOPRIGHT", 60, 0)
local typesMisc = CreateTieredList(page, MESSAGETYPES_MISC, true)
typesMisc:SetPoint("TOPLEFT", typesRight, "BOTTOMLEFT", 0, -22)
local miscTitle = page:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
miscTitle:SetPoint("BOTTOMLEFT", typesMisc, "TOPLEFT", 0, 4)
miscTitle:SetText(MISCELLANEOUS)
-- Colors
local colors = CreateTab(COLORS)
CreateExamples(colors)
local unitColors = CreateBox(colors, UNIT_COLORS)
unitColors:SetPoint("TOPLEFT", colors, "TOPLEFT", 13, -100)
unitColors:SetSize(200, #UNIT_COLORS * 22 + 8)
unitColors.swatches = {}
for i, entry in ipairs(UNIT_COLORS) do
local label = unitColors:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
label:SetPoint("TOPLEFT", unitColors, "TOPLEFT", 8, -10 - (i - 1) * 22)
label:SetText(entry.text)
local swatch = CreateSwatch(unitColors)
swatch:SetPoint("RIGHT", unitColors, "TOPRIGHT", -8, -15 - (i - 1) * 22)
swatch.flag = entry.flag
swatch:SetScript("OnClick", function() PickColor(selected.colors.unitColoring[this.flag]) end)
unitColors.swatches[i] = swatch
end
local highlighting = CreateBox(colors, HIGHLIGHTING)
highlighting:SetPoint("TOPLEFT", unitColors, "BOTTOMLEFT", 0, -25)
highlighting:SetSize(200, 50)
local highlightChecks = {
SettingCheck(highlighting, COMBATLOG_HIGHLIGHT_KILL, HIGHLIGHT_KILL_COMBATLOG_TOOLTIP, "lineHighlighting", true),
SettingCheck(highlighting, COMBATLOG_HIGHLIGHT_ABILITY, HIGHLIGHT_ABILITY_COMBATLOG_TOOLTIP, "abilityHighlighting", true),
SettingCheck(highlighting, COMBATLOG_HIGHLIGHT_DAMAGE, HIGHLIGHT_DAMAGE_COMBATLOG_TOOLTIP, "amountHighlighting", true),
SettingCheck(highlighting, COMBATLOG_HIGHLIGHT_SCHOOL, HIGHLIGHT_SCHOOL_COMBATLOG_TOOLTIP, "schoolNameHighlighting", true),
}
for i, check in ipairs(highlightChecks) do
check:SetPoint("TOPLEFT", highlighting, "TOPLEFT", 8 + (i - 1) % 2 * 95, -6 - ceil(i / 2 - 1) * 20)
end
local colorize = CreateBox(colors, COLORIZE)
colorize:SetPoint("TOPLEFT", unitColors, "TOPRIGHT", 50, 0)
colorize:SetSize(240, 192)
local unitNames = CreateCheck(colorize, UNIT_NAMES, UNIT_NAMES_COMBATLOG_TOOLTIP)
unitNames:SetPoint("TOPLEFT", colorize, "TOPLEFT", 8, -6)
unitNames.func = function(self, checked)
local on = checked and true or false
selected.settings.unitColoring, selected.settings.sourceColoring, selected.settings.destColoring = on, on, on
Edited()
end
local spellNames = SettingCheck(colorize, SPELL_NAMES, SPELL_NAMES_COMBATLOG_TOOLTIP, "abilityColoring")
spellNames:SetPoint("TOPLEFT", unitNames, "BOTTOMLEFT", 0, -8)
local spellSchool = SettingCheck(colorize, COLOR_BY_SCHOOL, SPELL_COLOR_BY_SCHOOL_COMBATLOG_TOOLTIP, "abilitySchoolColoring", true)
spellSchool:SetPoint("TOPLEFT", spellNames, "BOTTOMLEFT", 15, -2)
local spellSwatch = CreateSwatch(colorize)
spellSwatch:SetPoint("RIGHT", colorize, "RIGHT", -8, 0)
spellSwatch:SetPoint("TOP", spellNames, "TOP", 0, -4)
spellSwatch:SetScript("OnClick", function() PickColor(selected.colors.defaults.spell) end)
local damageNumber = SettingCheck(colorize, DAMAGE_NUMBER, SPELL_DAMAGE_NUMBER_COMBATLOG_TOOLTIP, "amountColoring")
damageNumber:SetPoint("TOPLEFT", spellSchool, "BOTTOMLEFT", -15, -8)
local damageSchool = SettingCheck(colorize, COLOR_BY_SCHOOL, SPELL_COLOR_BY_SCHOOL_COMBATLOG_TOOLTIP, "amountSchoolColoring", true)
damageSchool:SetPoint("TOPLEFT", damageNumber, "BOTTOMLEFT", 15, -2)
local damageSwatch = CreateSwatch(colorize)
damageSwatch:SetPoint("RIGHT", colorize, "RIGHT", -8, 0)
damageSwatch:SetPoint("TOP", damageNumber, "TOP", 0, -4)
damageSwatch:SetScript("OnClick", function() PickColor(selected.colors.defaults.damage) end)
local schoolNames = SettingCheck(colorize, DAMAGE_SCHOOL_TEXT, SPELL_DAMAGE_SCHOOL_COMBATLOG_TOOLTIP, "schoolNameColoring")
schoolNames:SetPoint("TOPLEFT", damageSchool, "BOTTOMLEFT", -15, -8)
local entireLine = SettingCheck(colorize, ENTIRE_LINE, ENTIRE_LINE_COMBATLOG_TOOLTIP, "lineColoring")
entireLine:SetPoint("TOPLEFT", schoolNames, "BOTTOMLEFT", 0, -8)
-- the two line color priorities, as radio buttons
local function PriorityCheck(text, tooltip, priority)
local check = CreateCheck(colorize, text, tooltip, true)
check.func = function()
selected.settings.lineColorPriority = priority
Edited()
end
return check
end
local bySource = PriorityCheck(BY_SOURCE, BY_SOURCE_COMBATLOG_TOOLTIP, 1)
bySource:SetPoint("TOPLEFT", entireLine, "BOTTOMLEFT", 15, -2)
local byTarget = PriorityCheck(BY_TARGET, BY_TARGET_COMBATLOG_TOOLTIP, 2)
byTarget:SetPoint("LEFT", bySource, "RIGHT", 80, 0)
-- Formatting
local formatting = CreateTab(FORMATTING)
CreateExamples(formatting)
local timestamp = SettingCheck(formatting, SHOW_TIMESTAMP, TIMESTAMP_COMBATLOG_TOOLTIP, "timestamp")
timestamp:SetPoint("TOPLEFT", formatting, "TOPLEFT", 25, -100)
local braces = SettingCheck(formatting, SHOW_BRACES, SHOW_BRACES_COMBATLOG_TOOLTIP, "braces")
braces:SetPoint("TOPLEFT", timestamp, "BOTTOMLEFT", 0, -15)
local unitBraces = CreateCheck(formatting, UNIT_NAMES, UNIT_NAMES_SHOW_BRACES_COMBATLOG_TOOLTIP, true)
unitBraces:SetPoint("TOPLEFT", braces, "BOTTOMLEFT", 15, -2)
unitBraces.func = function(self, checked)
local on = checked and true or false
selected.settings.unitBraces, selected.settings.sourceBraces, selected.settings.destBraces = on, on, on
Edited()
end
local spellBraces = SettingCheck(formatting, SPELL_NAMES, SPELL_NAMES_SHOW_BRACES_COMBATLOG_TOOLTIP, "spellBraces", true)
spellBraces:SetPoint("TOPLEFT", unitBraces, "BOTTOMLEFT", 0, -2)
local itemBraces = SettingCheck(formatting, ITEM_NAMES, ITEM_NAMES_SHOW_BRACES_COMBATLOG_TOOLTIP, "itemBraces", true)
itemBraces:SetPoint("TOPLEFT", spellBraces, "BOTTOMLEFT", 0, -2)
local fullText = SettingCheck(formatting, USE_FULL_TEXT_MODE, FULL_TEXT_COMBATLOG_TOOLTIP, "fullText")
fullText:SetPoint("TOPLEFT", itemBraces, "BOTTOMLEFT", -15, -13)
-- Settings: the filter's name and its quick button
local settings = CreateTab(SETTINGS)
local SetFilterName
local nameBox = CreateTextBox(nil, settings)
nameBox:SetSize(150, 20)
nameBox:SetMaxLetters(32)
nameBox:SetPoint("TOPLEFT", settings, "TOPLEFT", 37, -36)
nameBox:SetScript("OnEnterPressed", function()
SetFilterName(this:GetText())
this:ClearFocus()
end)
local nameLabel = settings:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall")
nameLabel:SetPoint("BOTTOMLEFT", nameBox, "TOPLEFT", 0, 4)
nameLabel:SetText(FILTER_NAME)
local saveButton = CreateButton(settings, SAVE, 78)
saveButton:SetPoint("LEFT", nameBox, "RIGHT", 8, 0)
saveButton:SetScript("OnClick", function()
SetFilterName(nameBox:GetText())
nameBox:ClearFocus()
end)
-- CombatConfig_SetFilterName
function SetFilterName(name)
if name == "" then
name = format(DEFAULT_COMBATLOG_FILTER_NAME, #log.GetFilters().filters)
end
selected.name = name
log.UpdateQuickButtons()
Update()
end
local quickButton = CreateCheck(settings, SHOW_QUICK_BUTTON, QUICK_BUTTON_COMBATLOG_TOOLTIP)
quickButton:SetPoint("TOPLEFT", nameBox, "BOTTOMLEFT", -6, -19)
quickButton.func = function(self, checked)
selected.hasQuickButton = checked and true or false
log.UpdateQuickButtons()
Update()
end
local quickDisplay = {}
for i, entry in ipairs({ { SOLO, "solo" }, { PARTY, "party" }, { RAID, "raid" } }) do
local key = entry[2]
local check = CreateCheck(settings, entry[1], nil, true)
check:SetPoint("TOPLEFT", quickButton, "BOTTOMLEFT", 20 + (i - 1) * 95, -2)
check.func = function(self, checked)
selected.quickButtonDisplay[key] = checked and true or false
log.UpdateQuickButtons()
end
check.key = key
quickDisplay[i] = check
end
--
-- Refresh (ChatConfig_UpdateCombatSettings)
--
local function UpdateColors()
for _, swatch in ipairs(unitColors.swatches) do
local color = selected.colors.unitColoring[swatch.flag]
swatch.prev:SetTexture(color.r, color.g, color.b)
end
for _, check in ipairs(highlightChecks) do
SetCheck(check, selected.settings[check.setting])
end
local s = selected.settings
SetCheck(unitNames, s.unitColoring)
SetCheck(spellNames, s.abilityColoring)
SetCheck(spellSchool, s.abilityColoring and s.abilitySchoolColoring)
SetCheckEnabled(spellSchool, s.abilityColoring)
spellSwatch.prev:SetTexture(selected.colors.defaults.spell.r, selected.colors.defaults.spell.g, selected.colors.defaults.spell.b)
SetCheck(damageNumber, s.amountColoring)
SetCheck(damageSchool, s.amountSchoolColoring)
SetCheckEnabled(damageSchool, s.amountColoring)
damageSwatch.prev:SetTexture(selected.colors.defaults.damage.r, selected.colors.defaults.damage.g, selected.colors.defaults.damage.b)
SetCheck(schoolNames, s.schoolNameColoring)
SetCheck(entireLine, s.lineColoring)
SetCheck(bySource, s.lineColorPriority == 1)
SetCheck(byTarget, s.lineColorPriority ~= 1)
SetCheckEnabled(bySource, s.lineColoring)
SetCheckEnabled(byTarget, s.lineColoring)
UpdateExamples(colors)
end
local function UpdateFormatting()
local s = selected.settings
SetCheck(timestamp, s.timestamp)
SetCheck(braces, s.braces)
SetCheck(unitBraces, s.unitBraces)
SetCheck(spellBraces, s.spellBraces)
SetCheck(itemBraces, s.itemBraces)
SetCheckEnabled(unitBraces, s.braces)
SetCheckEnabled(spellBraces, s.braces)
SetCheckEnabled(itemBraces, s.braces)
SetCheck(fullText, s.fullText)
UpdateExamples(formatting)
end
local function UpdateSettings()
nameBox:SetText(selected.name or "")
SetCheck(quickButton, selected.hasQuickButton)
for _, check in ipairs(quickDisplay) do
SetCheck(check, selected.quickButtonDisplay[check.key])
SetCheckEnabled(check, selected.hasQuickButton)
end
end
function Update()
local filters = log.GetFilters().filters
local entries = {}
for i, filter in ipairs(filters) do
entries[i] = { index = i, name = filter.name }
end
filterList.ScrollBox:SetDataProvider(CreateDataProvider(entries), ScrollBoxConstants.RetainScrollPosition)
-- the last filter can't be deleted
if #filters > 1 then deleteButton:Enable() else deleteButton:Disable() end
if #filters < MAX_COMBATLOG_FILTERS then
addButton:Enable()
copyButton:Enable()
else
addButton:Disable()
copyButton:Disable()
end
if selectedIndex > 1 then moveUp:Enable() else moveUp:Disable() end
if selectedIndex < #filters then moveDown:Enable() else moveDown:Disable() end
UpdateCheckboxList(doneBy)
UpdateCheckboxList(doneTo)
UpdateTieredList(typesLeft)
UpdateTieredList(typesRight)
UpdateTieredList(typesMisc)
UpdateColors()
UpdateFormatting()
UpdateSettings()
end
-- ChatConfigFilter_OnClick
function SelectFilter(index)
selectedIndex = index
selected = log.GetFilters().filters[index]
Update()
end
--
-- Defaults and Okay
--
local defaultsButton = CreateButton(frame, COMBATLOG_DEFAULTS, 170)
defaultsButton:SetPoint("BOTTOMLEFT", frame, "BOTTOMLEFT", 12, 12)
defaultsButton:SetScript("OnClick", function()
CreateQuestionDialog(CONFIRM_COMBAT_FILTER_DEFAULTS, function()
log.Reset()
changed = nil
SelectFilter(1)
end)
end)
local okayButton = CreateButton(frame, OKAY, 78)
okayButton:SetPoint("BOTTOMRIGHT", frame, "BOTTOMRIGHT", -12, 12)
okayButton:SetScript("OnClick", function()
PlaySound(SOUNDKIT.GS_TITLE_OPTION_OK)
frame:Hide()
end)
-- opens on the filter the log shows
frame:SetScript("OnShow", function()
changed = nil
SelectFilter(log.GetFilters().currentFilter)
end)
frame:SetScript("OnHide", function()
if changed then
changed = nil
log.SelectFilter(log.GetFilters().currentFilter)
end
end)
SelectTab(1)
return frame
end