Compare commits
62 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 46b63ffdd1 | |||
| 4ba5004322 | |||
| 3584c94523 | |||
| 303729f3d3 | |||
| 12085ff1e2 | |||
| e4593a0fda | |||
| 3fc42963ae | |||
| 7c52e890e6 | |||
| 4d977d5118 | |||
| ddd72a878e | |||
| 66450dd518 | |||
| 7de28ef9b2 | |||
| da3c1f6832 | |||
| e66ae1f5b4 | |||
| 281a1e172f | |||
| 45a5035426 | |||
| e1931fc7d2 | |||
| 2201478a54 | |||
| 50963ccf1b | |||
| fde85e6d69 | |||
| c22b169b73 | |||
| 6839ccaf34 | |||
| fa108c2271 | |||
| 395a0d7c98 | |||
| b76bfb3cfc | |||
| 0512e4729c | |||
| 654f24c609 | |||
| 0e2a14197c | |||
| 52e163a472 | |||
| e086afe2a8 | |||
| c97ce7543b | |||
| cca4571470 | |||
| 444d7f8e2e | |||
| 71ae95d79c | |||
| 9a38f7f094 | |||
| c33e519bb9 | |||
| 14e585ef63 | |||
| d4aa3971c5 | |||
| e9ec587e3b | |||
| 39cd7ab801 | |||
| bb6259e14d | |||
| 757370dd53 | |||
| 3f35b76c54 | |||
| 74bdc4f927 | |||
| eb379d84ef | |||
| 7add74dbbe | |||
| e91c7a3888 | |||
| f8b0804321 | |||
| a9d4e9bd69 | |||
| 7e3e4c8b72 | |||
| 397c84be2c | |||
| 269708150d | |||
| a2977ef75b | |||
| baa4d011e8 | |||
| 4810e8b518 | |||
| 133f5c536f | |||
| 92bb368d2b | |||
| 07f47f32e3 | |||
| 141fcbf074 | |||
| 32c410e8e2 | |||
| 824037e55f | |||
| 173cb76bea |
@@ -372,6 +372,8 @@ MigrationBackup/
|
||||
# Fody - auto-generated XML schema
|
||||
FodyWeavers.xsd
|
||||
|
||||
#Specs und Plan datein
|
||||
/.superpowers/
|
||||
TestResults
|
||||
*.db-shm
|
||||
*.db-wal
|
||||
|
||||
+56
-21
@@ -1,18 +1,49 @@
|
||||
# AI assistance disclosure
|
||||
|
||||
Per the [Dalamud Plugin AI Usage Policy](https://github.com/goatcorp/DalamudPluginsD17/),
|
||||
this fork uses AI assistance at the **Pair** level. Pair means the maintainer
|
||||
plans the architecture, decides what gets built, reviews each change and
|
||||
tests against the running game; Claude (Anthropic) helps explain Dalamud
|
||||
APIs, suggests patterns, drafts code on request, and reviews approaches.
|
||||
Neither side acts autonomously: nothing ships without the maintainer's
|
||||
review, and Claude can't run the game.
|
||||
This fork uses AI assistance per the [Dalamud Plugin AI Usage Policy](https://github.com/goatcorp/DalamudPluginsD17/)
|
||||
at the **Pair** level.
|
||||
|
||||
The level varies by area and over time. Some commits are mostly hand-written
|
||||
with the AI used as a sounding board, others lean more on Claude for an API
|
||||
walkthrough or a code draft that the maintainer then reads, edits and
|
||||
integrates. The maintainer's commitment is to be able to explain why every
|
||||
piece of Hellion code is the way it is — not "I typed every character."
|
||||
A note up front: Hellion Chat is currently in a rebuild and adjustment
|
||||
phase, and there are no plans to submit it to the Dalamud team for review
|
||||
while it stays standalone. If the plugin stays out of the official repo I
|
||||
technically wouldn't need to disclose any of this, but I'd rather be
|
||||
upfront about how it's built.
|
||||
|
||||
Hellion Chat is my entry point into game modding and plugin development. I
|
||||
have never written a plugin for a game before. I work alone, so I get help
|
||||
where I need it. That's not something I want to hide.
|
||||
|
||||
## How I actually work
|
||||
|
||||
I plan the architecture, decide what gets built, and own every design
|
||||
decision. For each change I:
|
||||
|
||||
- Read the code Claude drafts before I integrate it
|
||||
- Test with my own tooling and in the running game
|
||||
- Read the Dalamud log output to verify behaviour
|
||||
- Run security and privacy audits on anything that touches user data
|
||||
|
||||
One of the main reasons I use AI is consistency. I want the Hellion code to
|
||||
match the style of the upstream Chat 2 codebase and stay readable for
|
||||
anyone who opens the repo, not just for me. Claude helps me catch when I'm
|
||||
drifting from upstream conventions or writing something that only makes
|
||||
sense in my own head.
|
||||
|
||||
The balance is shifting toward more hand-written work as I get more
|
||||
comfortable with Dalamud and plugin development in general.
|
||||
|
||||
## What AI is used for
|
||||
|
||||
- API explanations (Dalamud, ImGui, .NET specifics I haven't worked with before)
|
||||
- Code drafts that I read, edit, and integrate
|
||||
- Pattern suggestions and code review
|
||||
- Keeping the style aligned with the upstream Chat 2 codebase
|
||||
|
||||
## What AI isn't used for
|
||||
|
||||
- **Visual assets.** Logos, icons, banners, and screenshots are human-drawn
|
||||
or taken from the running game.
|
||||
- **German translations.** Written by me as a native speaker.
|
||||
|
||||
## What's where
|
||||
|
||||
@@ -22,20 +53,24 @@ produced with AI assistance. Hellion-specific code lives in
|
||||
`Ui/SettingsTabs/Privacy.cs`, `Ui/FirstRunWizard.cs`, `Ui/HellionStyle.cs`,
|
||||
plus the Migrate3 recovery and plugin layout migration in `MessageStore.cs`
|
||||
and `Plugin.cs`. These were developed with Pair-level assistance as
|
||||
described above; the share of human vs. AI authorship varies file by file
|
||||
and is expected to keep shifting toward more hand-written work as the
|
||||
maintainer's plugin-dev experience grows.
|
||||
described above.
|
||||
|
||||
## What AI is not used for
|
||||
## If AI-assisted development is a dealbreaker for you
|
||||
|
||||
- **Visual assets.** Logos, icons, banners, screenshots are human-drawn or
|
||||
taken from the running game.
|
||||
- **German translations.** Written by the maintainer (native speaker).
|
||||
Fair enough. There are solid alternatives that don't rely on AI in their
|
||||
development:
|
||||
|
||||
- [Chat 2](https://github.com/Infiziert90/ChatTwo), the original upstream
|
||||
this fork is based on
|
||||
- [XIV Instant Messenger](https://github.com/NightmareXIV/XIVInstantMessenger),
|
||||
a different approach to chat in FFXIV
|
||||
|
||||
Both are good projects. Use what fits you best.
|
||||
|
||||
## Tooling
|
||||
|
||||
- Claude (Anthropic) via Claude Code CLI as the main pair partner.
|
||||
- Context7 / Microsoft Learn for current Dalamud and .NET documentation.
|
||||
- Claude (Anthropic) via Claude Code CLI
|
||||
- Context7 / Microsoft Learn for current Dalamud and .NET documentation
|
||||
|
||||
## Contact
|
||||
|
||||
|
||||
@@ -0,0 +1,167 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using ChatTwo.Code;
|
||||
using ChatTwo.Util;
|
||||
using Dalamud.Game.Text;
|
||||
using Dalamud.Game.Text.SeStringHandling;
|
||||
using Dalamud.Game.Text.SeStringHandling.Payloads;
|
||||
using JetBrains.Annotations;
|
||||
using Microsoft.VisualStudio.TestTools.UnitTesting;
|
||||
|
||||
namespace ChatTwo.Tests;
|
||||
|
||||
// Hellion Chat — Auto-Tell-Tabs history-preload coverage.
|
||||
//
|
||||
// These tests exercise MessageStore.GetTellHistoryWithSender, the query the
|
||||
// AutoTellTabsService uses to populate a freshly spawned temp tab with the
|
||||
// last conversations with that player.
|
||||
//
|
||||
// NOTE: like the rest of ChatTwo.Tests today, these will fail at runtime
|
||||
// until the project's Dalamud.dll runtime dependency is sorted out (see
|
||||
// Phase-2 backlog item "Test-Projekt fixen"). Compile-time the suite builds
|
||||
// fine via DALAMUD_HOME, so the tests guard against API drift even before
|
||||
// they can be executed locally.
|
||||
[TestClass]
|
||||
[TestSubject(typeof(MessageStore))]
|
||||
public class AutoTellTabsHistoryTest
|
||||
{
|
||||
public TestContext TestContext { get; set; }
|
||||
|
||||
[TestMethod]
|
||||
[Timeout(5000)]
|
||||
public void GetTellHistoryWithSender_FiltersByNameAndWorld()
|
||||
{
|
||||
var tempDir = Directory.CreateTempSubdirectory("ChatTwo_test_");
|
||||
var dbPath = Path.Join(tempDir.FullName, "test.db");
|
||||
TestContext.WriteLine("Using database path: " + dbPath);
|
||||
using var store = new MessageStore(dbPath);
|
||||
|
||||
const ulong receiver = 99001;
|
||||
var now = DateTimeOffset.UtcNow;
|
||||
|
||||
// Two tells with the target sender, one with a different sender on
|
||||
// the same world, one with the same name on a different world. Only
|
||||
// the first two should make it into the result.
|
||||
var asukaLichIn = TellMessage("Asuka", 76, receiver, now.AddMinutes(-30), ChatType.TellIncoming);
|
||||
var asukaLichOut = TellMessage("Asuka", 76, receiver, now.AddMinutes(-20), ChatType.TellOutgoing);
|
||||
var broboLich = TellMessage("Brobo", 76, receiver, now.AddMinutes(-10), ChatType.TellIncoming);
|
||||
var asukaOmega = TellMessage("Asuka", 90, receiver, now.AddMinutes(-5), ChatType.TellIncoming);
|
||||
|
||||
store.UpsertMessage(asukaLichIn);
|
||||
store.UpsertMessage(asukaLichOut);
|
||||
store.UpsertMessage(broboLich);
|
||||
store.UpsertMessage(asukaOmega);
|
||||
|
||||
var result = store.GetTellHistoryWithSender(receiver, "Asuka", 76, limit: 50);
|
||||
|
||||
Assert.AreEqual(2, result.Count);
|
||||
// Result is oldest-first so a tab can append messages chronologically.
|
||||
Assert.AreEqual(asukaLichIn.Id, result[0].Id);
|
||||
Assert.AreEqual(asukaLichOut.Id, result[1].Id);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[Timeout(5000)]
|
||||
public void GetTellHistoryWithSender_RespectsLimit()
|
||||
{
|
||||
var tempDir = Directory.CreateTempSubdirectory("ChatTwo_test_");
|
||||
var dbPath = Path.Join(tempDir.FullName, "test.db");
|
||||
TestContext.WriteLine("Using database path: " + dbPath);
|
||||
using var store = new MessageStore(dbPath);
|
||||
|
||||
const ulong receiver = 99002;
|
||||
var now = DateTimeOffset.UtcNow;
|
||||
|
||||
for (var i = 0; i < 30; i++)
|
||||
{
|
||||
var msg = TellMessage("Asuka", 76, receiver, now.AddMinutes(-i - 1), ChatType.TellIncoming);
|
||||
store.UpsertMessage(msg);
|
||||
}
|
||||
|
||||
var result = store.GetTellHistoryWithSender(receiver, "Asuka", 76, limit: 5);
|
||||
|
||||
Assert.AreEqual(5, result.Count);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[Timeout(5000)]
|
||||
public void GetTellHistoryWithSender_ZeroLimitReturnsEmpty()
|
||||
{
|
||||
var tempDir = Directory.CreateTempSubdirectory("ChatTwo_test_");
|
||||
var dbPath = Path.Join(tempDir.FullName, "test.db");
|
||||
TestContext.WriteLine("Using database path: " + dbPath);
|
||||
using var store = new MessageStore(dbPath);
|
||||
|
||||
const ulong receiver = 99003;
|
||||
|
||||
var msg = TellMessage("Asuka", 76, receiver, DateTimeOffset.UtcNow, ChatType.TellIncoming);
|
||||
store.UpsertMessage(msg);
|
||||
|
||||
var result = store.GetTellHistoryWithSender(receiver, "Asuka", 76, limit: 0);
|
||||
|
||||
Assert.AreEqual(0, result.Count);
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[Timeout(5000)]
|
||||
public void GetTellHistoryWithSender_IgnoresOtherReceivers()
|
||||
{
|
||||
var tempDir = Directory.CreateTempSubdirectory("ChatTwo_test_");
|
||||
var dbPath = Path.Join(tempDir.FullName, "test.db");
|
||||
TestContext.WriteLine("Using database path: " + dbPath);
|
||||
using var store = new MessageStore(dbPath);
|
||||
|
||||
const ulong ourReceiver = 99004;
|
||||
const ulong otherReceiver = 99005;
|
||||
var now = DateTimeOffset.UtcNow;
|
||||
|
||||
// Tell on the local player's account.
|
||||
var ours = TellMessage("Asuka", 76, ourReceiver, now.AddMinutes(-1), ChatType.TellIncoming);
|
||||
// Same sender, but logged against a different local character —
|
||||
// common when the user has alts. Must not surface.
|
||||
var foreign = TellMessage("Asuka", 76, otherReceiver, now, ChatType.TellIncoming);
|
||||
|
||||
store.UpsertMessage(ours);
|
||||
store.UpsertMessage(foreign);
|
||||
|
||||
var result = store.GetTellHistoryWithSender(ourReceiver, "Asuka", 76, limit: 50);
|
||||
|
||||
Assert.AreEqual(1, result.Count);
|
||||
Assert.AreEqual(ours.Id, result[0].Id);
|
||||
}
|
||||
|
||||
private static Message TellMessage(
|
||||
string senderName,
|
||||
uint senderWorld,
|
||||
ulong receiver,
|
||||
DateTimeOffset dateTime,
|
||||
ChatType chatType)
|
||||
{
|
||||
var senderSeString = new SeStringBuilder()
|
||||
.Add(new PlayerPayload(senderName, senderWorld))
|
||||
.AddText(senderName)
|
||||
.Add(RawPayload.LinkTerminator)
|
||||
.Build();
|
||||
|
||||
var contentSeString = new SeStringBuilder()
|
||||
.AddText("test message")
|
||||
.Build();
|
||||
|
||||
var senderChunks = ChunkUtil.ToChunks(senderSeString, ChunkSource.Sender, chatType).ToList();
|
||||
var contentChunks = ChunkUtil.ToChunks(contentSeString, ChunkSource.Content, chatType).ToList();
|
||||
|
||||
var chatCode = new ChatCode((XivChatType)chatType, XivChatRelationKind.LocalPlayer, XivChatRelationKind.LocalPlayer);
|
||||
return new Message(
|
||||
Guid.NewGuid(),
|
||||
receiver,
|
||||
0,
|
||||
dateTime,
|
||||
chatCode,
|
||||
senderChunks,
|
||||
contentChunks,
|
||||
senderSeString,
|
||||
contentSeString,
|
||||
Guid.Empty);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,358 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using ChatTwo.Code;
|
||||
using ChatTwo.GameFunctions.Types;
|
||||
using ChatTwo.Resources;
|
||||
using ChatTwo.Util;
|
||||
using Dalamud.Game.Text;
|
||||
using Dalamud.Game.Text.SeStringHandling;
|
||||
|
||||
namespace ChatTwo;
|
||||
|
||||
// Hellion Chat — Auto-Tell-Tabs.
|
||||
//
|
||||
// Spawns a session-only tab per /tell partner so a club greeter can track
|
||||
// multiple parallel conversations without losing context. Subscribes to
|
||||
// MessageManager.MessageProcessed for live tells and to ClientState.Logout
|
||||
// for the cleanup pass; everything else hangs off these two entry points.
|
||||
//
|
||||
// See spec: Hellion Chat Auto-Tell-Tabs Spec (Obsidian vault).
|
||||
internal sealed class AutoTellTabsService : IDisposable
|
||||
{
|
||||
private readonly Plugin _plugin;
|
||||
private readonly MessageManager _messageManager;
|
||||
private readonly MessageStore _store;
|
||||
private readonly object _tempTabsLock = new();
|
||||
|
||||
private bool _initialized;
|
||||
|
||||
internal AutoTellTabsService(Plugin plugin, MessageManager messageManager, MessageStore store)
|
||||
{
|
||||
_plugin = plugin;
|
||||
_messageManager = messageManager;
|
||||
_store = store;
|
||||
}
|
||||
|
||||
internal int ActiveTempTabCount
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (_tempTabsLock)
|
||||
{
|
||||
return Plugin.Config.Tabs.Count(t => t.IsTempTab);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal void Initialize()
|
||||
{
|
||||
if (_initialized)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_messageManager.MessageProcessed += HandleTell;
|
||||
Plugin.ClientState.Logout += OnLogout;
|
||||
_initialized = true;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (!_initialized)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Plugin.ClientState.Logout -= OnLogout;
|
||||
_messageManager.MessageProcessed -= HandleTell;
|
||||
_initialized = false;
|
||||
}
|
||||
|
||||
internal void HandleTell(Message message)
|
||||
{
|
||||
if (!Plugin.Config.EnableAutoTellTabs)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (message.Code.Type != ChatType.TellIncoming && message.Code.Type != ChatType.TellOutgoing)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var partner = ExtractTellPartner(message);
|
||||
if (partner == null)
|
||||
{
|
||||
// Real message without a player payload — e.g. GM tells, which
|
||||
// we deliberately skip. The diagnostics make future regressions
|
||||
// (FFXIV changing tell payload shape, new edge cases) findable
|
||||
// without having to crank up debug logging at the source.
|
||||
Plugin.Log.Warning(
|
||||
$"[AutoTellTabs] Could not extract tell partner. type={message.Code.Type}, " +
|
||||
$"senderChunks={message.Sender.Count}, contentChunks={message.Content.Count}, " +
|
||||
$"senderSourcePayloads={message.SenderSource?.Payloads?.Count ?? 0}, " +
|
||||
$"contentSourcePayloads={message.ContentSource?.Payloads?.Count ?? 0}");
|
||||
return;
|
||||
}
|
||||
|
||||
lock (_tempTabsLock)
|
||||
{
|
||||
var existing = FindTempTab(partner.Value.Name, partner.Value.World);
|
||||
if (existing != null)
|
||||
{
|
||||
// Tab already exists; Tab.Matches has already routed this
|
||||
// message via the MessageManager pipeline (see Task 2 sender
|
||||
// filter).
|
||||
return;
|
||||
}
|
||||
|
||||
if (ActiveTempTabCount >= Plugin.Config.AutoTellTabsLimit)
|
||||
{
|
||||
DropOldestTempTab();
|
||||
}
|
||||
|
||||
SpawnTempTab(partner.Value, message);
|
||||
}
|
||||
}
|
||||
|
||||
private (string Name, uint World)? ExtractTellPartner(Message message)
|
||||
{
|
||||
if (message.Code.Type == ChatType.TellIncoming)
|
||||
{
|
||||
// Incoming tell: the sender is the conversation partner. The
|
||||
// PlayerPayload normally rides on a chunk's Link slot, but for
|
||||
// some tell types FFXIV only puts it in the raw SeString —
|
||||
// fall back to that before giving up.
|
||||
var fromSender = ChunkUtil.TryGetPlayerPayload(message.Sender)
|
||||
?? ChunkUtil.TryGetPlayerPayload(message.SenderSource);
|
||||
if (fromSender != null)
|
||||
{
|
||||
return (fromSender.PlayerName, fromSender.World.RowId);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// Outgoing tell: the local player is the sender, the partner shows
|
||||
// up either as a payload in the content (for tells typed via the
|
||||
// Chat 2 input bar) or as the channel's tracked tell target (set by
|
||||
// the SetContextTellTarget game hook). Same SeString fallback.
|
||||
var fromContent = ChunkUtil.TryGetPlayerPayload(message.Content)
|
||||
?? ChunkUtil.TryGetPlayerPayload(message.ContentSource)
|
||||
?? ChunkUtil.TryGetPlayerPayload(message.Sender)
|
||||
?? ChunkUtil.TryGetPlayerPayload(message.SenderSource);
|
||||
if (fromContent != null)
|
||||
{
|
||||
return (fromContent.PlayerName, fromContent.World.RowId);
|
||||
}
|
||||
|
||||
var current = _plugin.CurrentTab.CurrentChannel.TellTarget
|
||||
?? _plugin.CurrentTab.CurrentChannel.TempTellTarget;
|
||||
if (current != null && current.IsSet())
|
||||
{
|
||||
return (current.Name, current.World);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private Tab? FindTempTab(string name, uint world)
|
||||
{
|
||||
return Plugin.Config.Tabs.FirstOrDefault(t =>
|
||||
t.IsTempTab
|
||||
&& t.TellTarget != null
|
||||
&& string.Equals(t.TellTarget.Name, name, StringComparison.OrdinalIgnoreCase)
|
||||
&& t.TellTarget.World == world);
|
||||
}
|
||||
|
||||
private void DropOldestTempTab()
|
||||
{
|
||||
// Greeted tabs are dropped before un-greeted ones (the user said
|
||||
// "I'm done with that conversation"), and within each bucket we
|
||||
// pick the oldest LastActivity. This protects active conversations
|
||||
// and unfinished greetings while still freeing up a slot.
|
||||
var victim = Plugin.Config.Tabs
|
||||
.Select((tab, idx) => (Tab: tab, Index: idx))
|
||||
.Where(t => t.Tab.IsTempTab)
|
||||
.OrderByDescending(t => t.Tab.IsGreeted)
|
||||
.ThenBy(t => t.Tab.LastActivity)
|
||||
.FirstOrDefault();
|
||||
|
||||
if (victim.Tab == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Plugin.Config.Tabs.RemoveAt(victim.Index);
|
||||
|
||||
// Re-anchor the active tab so the user does not silently end up on
|
||||
// a different conversation when their tab gets dropped or shifted.
|
||||
if (victim.Index <= _plugin.LastTab)
|
||||
{
|
||||
_plugin.WantedTab = 0;
|
||||
}
|
||||
}
|
||||
|
||||
private void SpawnTempTab((string Name, uint World) partner, Message currentMessage)
|
||||
{
|
||||
var tab = BuildTempTab(partner.Name, partner.World);
|
||||
|
||||
// Preload first so the tab opens with chronological history above
|
||||
// the current message — and so a slow DB query never causes a
|
||||
// visible "empty tab, then history pops in" effect on screen.
|
||||
PreloadHistory(tab, partner.Name, partner.World);
|
||||
|
||||
tab.AddMessage(currentMessage, unread: true);
|
||||
Plugin.Config.Tabs.Add(tab);
|
||||
}
|
||||
|
||||
private static Tab BuildTempTab(string playerName, uint worldRowId)
|
||||
{
|
||||
return new Tab
|
||||
{
|
||||
Name = FormatTabName(playerName, worldRowId),
|
||||
IsTempTab = true,
|
||||
AllSenderMessages = true,
|
||||
TellTarget = new TellTarget(playerName, worldRowId, 0, TellReason.Direct),
|
||||
Channel = InputChannel.Tell,
|
||||
DisplayTimestamp = true,
|
||||
UnreadMode = UnreadMode.Unseen,
|
||||
HideWhenInactive = false,
|
||||
SelectedChannels = new Dictionary<ChatType, (ChatSource, ChatSource)>
|
||||
{
|
||||
[ChatType.TellIncoming] = (ChatSourceExt.All, ChatSourceExt.All),
|
||||
[ChatType.TellOutgoing] = (ChatSourceExt.All, ChatSourceExt.All),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
private static string FormatTabName(string playerName, uint worldRowId)
|
||||
{
|
||||
if (Sheets.WorldSheet.TryGetRow(worldRowId, out var worldRow))
|
||||
{
|
||||
return $"{playerName}@{worldRow.Name}";
|
||||
}
|
||||
// World sheet lookup miss is rare (only for FFXIV worlds Dalamud has
|
||||
// not yet seen). Fall back to the raw RowId so the user still has a
|
||||
// unique, readable label.
|
||||
return $"{playerName}@World{worldRowId}";
|
||||
}
|
||||
|
||||
private void PreloadHistory(Tab tab, string senderName, uint senderWorld)
|
||||
{
|
||||
var preloadCount = Plugin.Config.AutoTellTabsHistoryPreload;
|
||||
if (preloadCount <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var history = _store.GetTellHistoryWithSender(
|
||||
_messageManager.CurrentContentId,
|
||||
senderName,
|
||||
senderWorld,
|
||||
preloadCount);
|
||||
|
||||
if (history.Count == 0)
|
||||
{
|
||||
// No prior tells with this player — leave the tab to start
|
||||
// empty so the user does not see a "history loaded" marker
|
||||
// sitting alone above the very first message.
|
||||
return;
|
||||
}
|
||||
|
||||
// The history list is already oldest-first, so a plain AddPrune
|
||||
// loop produces the chronological order the user expects to see
|
||||
// when the tab opens.
|
||||
foreach (var message in history)
|
||||
{
|
||||
tab.Messages.AddPrune(message, MessageManager.MessageDisplayLimit);
|
||||
}
|
||||
|
||||
// Visible separator between the loaded history and the live
|
||||
// tell that triggered this spawn. Goes in last so it sorts
|
||||
// after the historical messages but before the current one.
|
||||
tab.Messages.AddPrune(
|
||||
MakeSystemMarker(HellionStrings.AutoTellTabs_HistorySeparator),
|
||||
MessageManager.MessageDisplayLimit);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// Non-fatal: the tab still spawns, but the user gets a visible
|
||||
// notice instead of silently missing history. The error logs
|
||||
// once with full stack trace for diagnosis.
|
||||
Plugin.Log.Error(ex, "[AutoTellTabs] History preload failed");
|
||||
tab.Messages.AddPrune(
|
||||
MakeSystemMarker(HellionStrings.AutoTellTabs_HistoryLoadError),
|
||||
MessageManager.MessageDisplayLimit);
|
||||
}
|
||||
}
|
||||
|
||||
private static Message MakeSystemMarker(string text)
|
||||
{
|
||||
var seString = new SeStringBuilder().AddText(text).Build();
|
||||
var chunks = ChunkUtil.ToChunks(seString, ChunkSource.Content, ChatType.System).ToList();
|
||||
var code = new ChatCode((XivChatType)ChatType.System, 0, 0);
|
||||
return Message.FakeMessage(chunks, code);
|
||||
}
|
||||
|
||||
internal void MarkGreeted(Tab tab)
|
||||
{
|
||||
SetGreeted(tab, true);
|
||||
}
|
||||
|
||||
internal void UnmarkGreeted(Tab tab)
|
||||
{
|
||||
SetGreeted(tab, false);
|
||||
}
|
||||
|
||||
internal bool IsGreeted(Tab tab)
|
||||
{
|
||||
return tab.IsGreeted;
|
||||
}
|
||||
|
||||
private void SetGreeted(Tab tab, bool greeted)
|
||||
{
|
||||
if (tab == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
lock (_tempTabsLock)
|
||||
{
|
||||
// Frame-race guard (E5): the sidebar might still render a tab
|
||||
// that has already been removed by LRU drop or logout cleanup.
|
||||
// Silently skip the toggle so we don't mutate stale state.
|
||||
if (!Plugin.Config.Tabs.Contains(tab))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
tab.IsGreeted = greeted;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnLogout(int type, int code)
|
||||
{
|
||||
lock (_tempTabsLock)
|
||||
{
|
||||
// Snapshot whether the active tab is about to be removed, BEFORE
|
||||
// we mutate the list — index lookups would lie to us afterwards.
|
||||
var lastIndex = _plugin.LastTab;
|
||||
var lastIndexValid = lastIndex >= 0 && lastIndex < Plugin.Config.Tabs.Count;
|
||||
var currentWasTempTab = lastIndexValid && Plugin.Config.Tabs[lastIndex].IsTempTab;
|
||||
|
||||
Plugin.Config.Tabs.RemoveAll(t => t.IsTempTab);
|
||||
|
||||
// Force a switch to tab 0 if the active tab was a temp tab OR
|
||||
// if drops before the active index pushed LastTab out of range.
|
||||
// Otherwise the user keeps their current persistent tab.
|
||||
var stillValid = lastIndex >= 0 && lastIndex < Plugin.Config.Tabs.Count;
|
||||
if (currentWasTempTab || !stillValid)
|
||||
{
|
||||
_plugin.WantedTab = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@
|
||||
0.1.0 is our bootstrap release; the underlying Chat 2 base is
|
||||
called out in the yaml changelog so users can see what it
|
||||
derives from. -->
|
||||
<Version>0.3.1</Version>
|
||||
<Version>0.5.1</Version>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<!-- HellionChat fork: assembly is renamed so Dalamud uses
|
||||
pluginConfigs/HellionChat instead of pluginConfigs/ChatTwo,
|
||||
|
||||
@@ -6,6 +6,7 @@ using ChatTwo.Util;
|
||||
using Dalamud;
|
||||
using Dalamud.Configuration;
|
||||
using Dalamud.Game.ClientState.Keys;
|
||||
using Dalamud.Game.Text.SeStringHandling.Payloads;
|
||||
using Dalamud.Interface.FontIdentifier;
|
||||
using Dalamud.Bindings.ImGui;
|
||||
|
||||
@@ -33,7 +34,7 @@ public class ConfigKeyBind
|
||||
[Serializable]
|
||||
public class Configuration : IPluginConfiguration
|
||||
{
|
||||
private const int LatestVersion = 8;
|
||||
private const int LatestVersion = 10;
|
||||
|
||||
public int Version { get; set; } = LatestVersion;
|
||||
|
||||
@@ -81,6 +82,25 @@ public class Configuration : IPluginConfiguration
|
||||
// to fall back to the user's chosen system or Dalamud font.
|
||||
public bool UseHellionFont = true;
|
||||
|
||||
// Hellion Chat — Auto-Tell-Tabs. When enabled, an incoming or outgoing
|
||||
// /tell spawns a session-only tab dedicated to that conversation
|
||||
// partner. See spec: Hellion Chat Auto-Tell-Tabs Spec (Obsidian).
|
||||
public bool EnableAutoTellTabs = true;
|
||||
// Hard cap on simultaneously open auto tell tabs. Range enforced by the
|
||||
// settings slider (1–50). LRU drop favors greeted tabs first.
|
||||
public int AutoTellTabsLimit = 15;
|
||||
// When true the sidebar shows only a thin separator before the temp
|
||||
// tabs; when false a section header "Active Tells (n)" is rendered.
|
||||
public bool AutoTellTabsCompactDisplay;
|
||||
// Number of prior tells to preload from the message store when an
|
||||
// auto tell tab is spawned. Range 0–100; 0 disables preload.
|
||||
public int AutoTellTabsHistoryPreload = 20;
|
||||
// Show the greeter "marked-as-greeted" toggle button next to each
|
||||
// temp tab and dim the tab name when set. Off by default because the
|
||||
// workflow is specific to club-greeter use cases — most users just
|
||||
// want the auto tabs themselves without the extra UI affordance.
|
||||
public bool AutoTellTabsShowGreetedToggle;
|
||||
|
||||
public int GetRetentionDays(ChatType type)
|
||||
{
|
||||
if (RetentionPerChannelDays.TryGetValue(type, out var userOverride))
|
||||
@@ -112,7 +132,12 @@ public class Configuration : IPluginConfiguration
|
||||
public bool MoreCompactPretty;
|
||||
public bool HideSameTimestamps;
|
||||
public bool ShowNoviceNetwork;
|
||||
public bool SidebarTabView;
|
||||
// Hellion Chat — vertical sidebar tab layout reads better than the
|
||||
// horizontal tab strip in the company of Auto-Tell-Tabs (a club
|
||||
// greeter typically tracks 5–15 simultaneous conversations). Bestand
|
||||
// users keep their saved value untouched — only fresh installs pick
|
||||
// up the new default.
|
||||
public bool SidebarTabView = true;
|
||||
public bool PrintChangelog = true;
|
||||
public bool OnlyPreviewIf;
|
||||
public int PreviewMinimum = 1;
|
||||
@@ -122,7 +147,7 @@ public class Configuration : IPluginConfiguration
|
||||
public LanguageOverride LanguageOverride = LanguageOverride.None;
|
||||
public bool CanMove = true;
|
||||
public bool CanResize = true;
|
||||
public bool ShowTitleBar;
|
||||
public bool ShowTitleBar = true;
|
||||
public bool ShowPopOutTitleBar = true;
|
||||
public bool DatabaseBattleMessages;
|
||||
public bool LoadPreviousSession;
|
||||
@@ -132,7 +157,7 @@ public class Configuration : IPluginConfiguration
|
||||
public bool CollapseKeepUniqueLinks;
|
||||
public bool PlaySounds = true;
|
||||
public bool KeepInputFocus = true;
|
||||
public int MaxLinesToRender = 10_000; // 1-10000
|
||||
public int MaxLinesToRender = 5_000; // 1-10000
|
||||
public bool Use24HourClock;
|
||||
|
||||
public bool ShowEmotes = true;
|
||||
@@ -230,7 +255,17 @@ public class Configuration : IPluginConfiguration
|
||||
TooltipOffset = other.TooltipOffset;
|
||||
WindowAlpha = other.WindowAlpha;
|
||||
ChatColours = other.ChatColours.ToDictionary(entry => entry.Key, entry => entry.Value);
|
||||
Tabs = other.Tabs.Select(t => t.Clone()).ToList();
|
||||
|
||||
// Hellion Chat — Auto-Tell-Tabs are session-only and therefore
|
||||
// never present in a disk-loaded copy. Keep the live temp tabs of
|
||||
// *this* configuration alive across an UpdateFrom so a settings
|
||||
// save (or sidebar-mode toggle) does not silently destroy the
|
||||
// user's open tell conversations. Persistent tabs from `other`
|
||||
// still get the regular clone-replace treatment.
|
||||
var liveTempTabs = Tabs.Where(t => t.IsTempTab).ToList();
|
||||
Tabs = other.Tabs.Where(t => !t.IsTempTab).Select(t => t.Clone()).ToList();
|
||||
Tabs.AddRange(liveTempTabs);
|
||||
|
||||
OverrideStyle = other.OverrideStyle;
|
||||
ChosenStyle = other.ChosenStyle;
|
||||
ChatTabForward = other.ChatTabForward;
|
||||
@@ -249,6 +284,12 @@ public class Configuration : IPluginConfiguration
|
||||
HellionThemeEnabled = other.HellionThemeEnabled;
|
||||
HellionThemeWindowOpacity = other.HellionThemeWindowOpacity;
|
||||
UseHellionFont = other.UseHellionFont;
|
||||
|
||||
EnableAutoTellTabs = other.EnableAutoTellTabs;
|
||||
AutoTellTabsLimit = other.AutoTellTabsLimit;
|
||||
AutoTellTabsCompactDisplay = other.AutoTellTabsCompactDisplay;
|
||||
AutoTellTabsHistoryPreload = other.AutoTellTabsHistoryPreload;
|
||||
AutoTellTabsShowGreetedToggle = other.AutoTellTabsShowGreetedToggle;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -324,9 +365,27 @@ public class Tab
|
||||
|
||||
[NonSerialized] public Guid Identifier = Guid.NewGuid();
|
||||
|
||||
// Hellion Chat — Auto-Tell-Tabs greeted flag. Toggled manually from the
|
||||
// sidebar to mark a tell partner as already greeted in the current
|
||||
// session. NonSerialized because the temp tab itself is session-only.
|
||||
[NonSerialized] public bool IsGreeted;
|
||||
|
||||
public bool Matches(Message message)
|
||||
{
|
||||
return message.Matches(SelectedChannels, ExtraChatAll, ExtraChatChannels);
|
||||
if (!message.Matches(SelectedChannels, ExtraChatAll, ExtraChatChannels))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Auto-tell temp tabs are bound to a single conversation partner;
|
||||
// every other tell that matches the channel filter must NOT land
|
||||
// here, otherwise all temp tabs would mirror "Tell Exclusive".
|
||||
if (IsTempTab && TellTarget?.IsSet() == true)
|
||||
{
|
||||
return ChunkUtil.MatchesSender(message, TellTarget.Name, TellTarget.World);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void AddMessage(Message message, bool unread = true)
|
||||
@@ -375,6 +434,7 @@ public class Tab
|
||||
IsTempTab = IsTempTab,
|
||||
AllSenderMessages = AllSenderMessages,
|
||||
TellTarget = TellTarget.From(TellTarget),
|
||||
IsGreeted = IsGreeted,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
+10
-5
@@ -35,20 +35,20 @@ public static class EmoteCache
|
||||
public Emote Emote { get; set; }
|
||||
|
||||
[JsonPropertyName("id")]
|
||||
public string Id { get; set; }
|
||||
public required string Id { get; set; }
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public struct Emote()
|
||||
{
|
||||
[JsonPropertyName("id")]
|
||||
public string Id { get; set; }
|
||||
public required string Id { get; set; }
|
||||
|
||||
[JsonPropertyName("code")]
|
||||
public string Code { get; set; }
|
||||
public required string Code { get; set; }
|
||||
|
||||
[JsonPropertyName("imageType")]
|
||||
public string ImageType { get; set; }
|
||||
public required string ImageType { get; set; }
|
||||
}
|
||||
|
||||
public enum LoadingState
|
||||
@@ -66,7 +66,7 @@ public static class EmoteCache
|
||||
|
||||
public static string[] SortedCodeArray = [];
|
||||
|
||||
public static async void LoadData()
|
||||
public static async Task LoadData()
|
||||
{
|
||||
if (State is not LoadingState.Unloaded)
|
||||
return;
|
||||
@@ -105,6 +105,11 @@ public static class EmoteCache
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// Reset to Unloaded so a later trigger (e.g. the user reopening
|
||||
// the Emotes tab after the network recovers) can retry. Without
|
||||
// this the State stays on Loading and the early-out at the top
|
||||
// of LoadData blocks every further attempt until plugin reload.
|
||||
State = LoadingState.Unloaded;
|
||||
Plugin.Log.Error(ex, "BetterTTV cache wasn't initialized");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,6 +40,114 @@ tags:
|
||||
- Replacement
|
||||
- Privacy
|
||||
changelog: |-
|
||||
**Hellion Chat 0.5.1 — Backlog Sweep**
|
||||
|
||||
Pure hardening and polish. No new features. Eight backlog items
|
||||
from the v0.5.0 codebase review collected into one patch:
|
||||
|
||||
- Cleanup preview now flags itself as out-of-date when the user
|
||||
edits the whitelist after the last refresh, and the refresh
|
||||
button is visually emphasised in that state
|
||||
- Greeted Auto-Tell-Tabs now also dim their selection and hover
|
||||
backgrounds in the sidebar, not just the text
|
||||
- Performance section in the General tab moves to the standard
|
||||
HelpMarker tooltip pattern instead of a wall-of-text description
|
||||
- Tabs and Database settings tabs pull their display name from
|
||||
HellionStrings instead of the upstream Language bundle, so all
|
||||
eight tabs share one i18n source
|
||||
- FontChooser results are now marshalled onto the framework thread
|
||||
via Plugin.Framework.Run instead of being written to settings
|
||||
state directly from the threadpool
|
||||
- EmoteCache.LoadData drops async void and the four CS8618 build
|
||||
warnings the build has been carrying since v0.4.0
|
||||
- All MessageStore SQL paths that fed dynamic value lists into
|
||||
interpolated SQL now use named parameter bindings via a new
|
||||
BindIntList helper. Same behaviour, defence against future
|
||||
user-input regressions
|
||||
|
||||
Configuration version is unchanged at 10. No migration. Existing
|
||||
installs upgrade silently.
|
||||
|
||||
Based on Chat 2 1.35.3 (upstream Infiziert90/ChatTwo, EUPL-1.2).
|
||||
|
||||
**Hellion Chat 0.5.0 — Settings UX polish**
|
||||
|
||||
The settings window has been pulled apart and rebuilt around eight
|
||||
themed tabs instead of the twelve organic ones it grew into.
|
||||
Settings now sit where they belong and the wall-of-text descriptions
|
||||
have been replaced with hover help markers across every section.
|
||||
|
||||
What changed in this release:
|
||||
|
||||
- Twelve tabs collapsed into eight: General, Appearance, Window,
|
||||
Chat, Tabs, Privacy, Database and Information
|
||||
- Theme and font controls moved out of the Privacy tab into
|
||||
Appearance where they belong
|
||||
- Auto-Tell-Tabs settings, message preview and emote controls now
|
||||
live under one Chat tab with collapsible sections
|
||||
- About and Changelog merged into a single Information tab
|
||||
- Disabled settings keep their tooltip help marker visible so you
|
||||
can still read why an option is greyed out
|
||||
- Section headings start collapsed by default, the same pattern
|
||||
used for the Auto-Tell-Tabs preload section in 0.4.0
|
||||
|
||||
Configuration version bumps from 9 to 10 as a wipe migration. The
|
||||
old config file is copied to HellionChat.json.pre-v10-backup before
|
||||
the new defaults are written, so you can restore your previous
|
||||
setup by hand if anything looks off. A one-shot notification on
|
||||
first start explains the reset.
|
||||
|
||||
No changes to message storage, retention sweep, the privacy filter
|
||||
or the export pipeline. Tabs and chat history are untouched by the
|
||||
migration.
|
||||
|
||||
Based on Chat 2 1.35.3 (upstream Infiziert90/ChatTwo, EUPL-1.2).
|
||||
|
||||
**Hellion Chat 0.4.0 — Auto-Tell-Tabs**
|
||||
|
||||
Auto-Tell-Tabs lets you turn each /tell into a session-only tab
|
||||
dedicated to that conversation partner. The original use case is
|
||||
the FFXIV club greeter who has to track 5–15 parallel "hi, welcome"
|
||||
exchanges; everyone else can disable the feature in one click and
|
||||
go back to a single Tell Exclusive tab.
|
||||
|
||||
What lands in this release:
|
||||
|
||||
- Auto-spawn temp tab "Name@World" on /tell (incoming and outgoing)
|
||||
- Tab limit (default 15, range 1–50) with LRU drop that prefers
|
||||
greeted tabs first, then sorts by last activity
|
||||
- History preload from the local message store (default 20 tells,
|
||||
range 0–100) with a "— Earlier conversations —" separator above
|
||||
the live tell that triggered the spawn
|
||||
- Optional "mark as greeted" toggle button (off by default,
|
||||
greeter-specific) that dims the tab name and lets you flip the
|
||||
status
|
||||
- Section header "Active Tells (n)" or compact-mode separator in
|
||||
the sidebar between persistent tabs and the temp tabs
|
||||
- Settings UI under Chat (toggle / limit / compact / greeted-toggle)
|
||||
and Privacy (history preload count), with hover-tooltip help
|
||||
markers replacing the previous wall-of-text descriptions for the
|
||||
new sections
|
||||
- Save and load filters strip temp tabs from the on-disk config so
|
||||
a crash or a sidebar-mode toggle never persists or wipes them
|
||||
|
||||
Compatibility note: if XIV Messanger or another plugin is
|
||||
suppressing direct messages, disable its "Suppress DMs" option so
|
||||
Hellion Chat can receive tells and open the auto tabs.
|
||||
|
||||
Configuration version bumps from 8 to 9. Existing users get a one-
|
||||
shot notification on the first start, defaults are seeded by
|
||||
property initializers, persistent tabs are untouched.
|
||||
|
||||
The vertical sidebar tab view becomes the default for fresh
|
||||
installs; existing users keep their saved preference.
|
||||
|
||||
Inspired by the per-sender tab pattern in XIV InstantMessenger
|
||||
(Limiana, AGPL-3.0). No code was ported across the licence
|
||||
boundary; only the architectural concept influenced this design.
|
||||
|
||||
Based on Chat 2 1.35.3 (upstream Infiziert90/ChatTwo, EUPL-1.2).
|
||||
|
||||
**Hellion Chat 0.3.1 — Upstream emote regression fix**
|
||||
|
||||
Cherry-picks Infi's upstream commit ff899ff "Fix a regression
|
||||
|
||||
@@ -50,6 +50,13 @@ internal class MessageManager : IAsyncDisposable
|
||||
}
|
||||
}
|
||||
|
||||
// Hellion Chat — Auto-Tell-Tabs hook. Fires after a fully processed
|
||||
// message has been routed to all matching persistent tabs and stored
|
||||
// in the database. The AutoTellTabsService subscribes to spawn or
|
||||
// refresh temp tabs without having to wedge itself into ProcessMessage
|
||||
// directly.
|
||||
public event Action<Message>? MessageProcessed;
|
||||
|
||||
internal unsafe MessageManager(Plugin plugin)
|
||||
{
|
||||
Plugin = plugin;
|
||||
@@ -266,6 +273,8 @@ internal class MessageManager : IAsyncDisposable
|
||||
if (tab.Matches(message))
|
||||
tab.AddMessage(message, unread);
|
||||
}
|
||||
|
||||
MessageProcessed?.Invoke(message);
|
||||
}
|
||||
|
||||
internal class NameFormatting
|
||||
|
||||
+149
-31
@@ -239,6 +239,9 @@ internal class MessageStore : IDisposable
|
||||
|
||||
private bool ColumnExists(string table, string column)
|
||||
{
|
||||
// PRAGMA does not accept SQLite parameter bindings. The table name is
|
||||
// a compile-time constant fed in from internal call sites, so the
|
||||
// interpolation cannot be reached from any user-controlled path.
|
||||
using var cmd = Connection.CreateCommand();
|
||||
cmd.CommandText = $"PRAGMA table_info({table});";
|
||||
using var reader = cmd.ExecuteReader();
|
||||
@@ -298,8 +301,10 @@ internal class MessageStore : IDisposable
|
||||
{
|
||||
Plugin.Log.Information($"Setting version {version}");
|
||||
using var cmd = Connection.CreateCommand();
|
||||
// Parameters aren't supported for PRAGMA queries, and you can't set the
|
||||
// version with a pragma_ function.
|
||||
// PRAGMA does not accept SQLite parameter bindings, and there is no
|
||||
// pragma_ function variant that can set the version either. The
|
||||
// version is a compile-time int from the migration sequence, never
|
||||
// user input.
|
||||
cmd.CommandText = $"PRAGMA user_version = {version};";
|
||||
cmd.ExecuteNonQuery();
|
||||
}
|
||||
@@ -346,31 +351,44 @@ internal class MessageStore : IDisposable
|
||||
throw new ArgumentOutOfRangeException(nameof(chatTypeDaysMap), "Negative retention is not allowed.");
|
||||
|
||||
var nowMs = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
|
||||
var clauses = new List<string>();
|
||||
foreach (var (type, days) in chatTypeDaysMap)
|
||||
{
|
||||
var cutoff = nowMs - days * 86400000L;
|
||||
clauses.Add($"(ChatType = {type} AND Date < {cutoff})");
|
||||
}
|
||||
|
||||
// Catch-all for channels without an explicit override. "0" is treated
|
||||
// as "do not delete by default" — without an explicit user override,
|
||||
// unmapped channels stay forever instead of getting wiped immediately.
|
||||
if (defaultDays > 0)
|
||||
{
|
||||
var cutoff = nowMs - defaultDays * 86400000L;
|
||||
var explicitTypes = chatTypeDaysMap.Count > 0
|
||||
? string.Join(",", chatTypeDaysMap.Keys)
|
||||
: "-1"; // empty list would produce invalid SQL
|
||||
clauses.Add($"(ChatType NOT IN ({explicitTypes}) AND Date < {cutoff})");
|
||||
}
|
||||
|
||||
if (clauses.Count == 0)
|
||||
if (chatTypeDaysMap.Count == 0 && defaultDays <= 0)
|
||||
return 0;
|
||||
|
||||
long deleted;
|
||||
using (var cmd = Connection.CreateCommand())
|
||||
{
|
||||
var clauses = new List<string>();
|
||||
var index = 0;
|
||||
foreach (var (type, days) in chatTypeDaysMap)
|
||||
{
|
||||
var cutoff = nowMs - days * 86400000L;
|
||||
var typeParam = $"$type{index}";
|
||||
var cutoffParam = $"$cutoff{index}";
|
||||
cmd.Parameters.AddWithValue(typeParam, type);
|
||||
cmd.Parameters.AddWithValue(cutoffParam, cutoff);
|
||||
clauses.Add($"(ChatType = {typeParam} AND Date < {cutoffParam})");
|
||||
index++;
|
||||
}
|
||||
|
||||
// Catch-all for channels without an explicit override. "0" is
|
||||
// treated as "do not delete by default" — without an explicit
|
||||
// user override, unmapped channels stay forever instead of
|
||||
// getting wiped immediately.
|
||||
if (defaultDays > 0)
|
||||
{
|
||||
var defaultCutoff = nowMs - defaultDays * 86400000L;
|
||||
cmd.Parameters.AddWithValue("$defaultCutoff", defaultCutoff);
|
||||
|
||||
var explicitPlaceholders = chatTypeDaysMap.Count > 0
|
||||
? BindIntList(cmd, "explicit", chatTypeDaysMap.Keys)
|
||||
: "-1"; // empty list would produce invalid SQL
|
||||
clauses.Add($"(ChatType NOT IN ({explicitPlaceholders}) AND Date < $defaultCutoff)");
|
||||
}
|
||||
|
||||
if (clauses.Count == 0)
|
||||
return 0;
|
||||
|
||||
cmd.CommandText = $"DELETE FROM messages WHERE {string.Join(" OR ", clauses)};";
|
||||
cmd.CommandTimeout = 600;
|
||||
deleted = cmd.ExecuteNonQuery();
|
||||
@@ -395,11 +413,11 @@ internal class MessageStore : IDisposable
|
||||
throw new InvalidOperationException("CleanupRetainOnly requires at least one allowed ChatType. Use ClearMessages for a full wipe.");
|
||||
}
|
||||
|
||||
var inList = string.Join(",", allowedTypes);
|
||||
long deleted;
|
||||
using (var cmd = Connection.CreateCommand())
|
||||
{
|
||||
cmd.CommandText = $"DELETE FROM messages WHERE ChatType NOT IN ({inList});";
|
||||
var placeholders = BindIntList(cmd, "ct", allowedTypes);
|
||||
cmd.CommandText = $"DELETE FROM messages WHERE ChatType NOT IN ({placeholders});";
|
||||
cmd.CommandTimeout = 600;
|
||||
deleted = cmd.ExecuteNonQuery();
|
||||
}
|
||||
@@ -512,15 +530,16 @@ internal class MessageStore : IDisposable
|
||||
DateTimeOffset? from,
|
||||
DateTimeOffset? to)
|
||||
{
|
||||
var cmd = Connection.CreateCommand();
|
||||
|
||||
var clauses = new List<string> { "deleted = false" };
|
||||
if (chatTypes is { Count: > 0 })
|
||||
clauses.Add($"ChatType IN ({string.Join(",", chatTypes)})");
|
||||
clauses.Add($"ChatType IN ({BindIntList(cmd, "exct", chatTypes)})");
|
||||
if (from is not null)
|
||||
clauses.Add("Date >= $From");
|
||||
if (to is not null)
|
||||
clauses.Add("Date <= $To");
|
||||
|
||||
var cmd = Connection.CreateCommand();
|
||||
cmd.CommandText = @"
|
||||
SELECT
|
||||
Id,
|
||||
@@ -602,6 +621,84 @@ internal class MessageStore : IDisposable
|
||||
return new MessageEnumerator(cmd.ExecuteReader());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Hellion Chat — Auto-Tell-Tabs history preload.
|
||||
///
|
||||
/// Returns up to <paramref name="limit"/> tells exchanged with the named
|
||||
/// player, oldest-first, ready to be added to a freshly spawned auto
|
||||
/// tell tab. The Sender column is a serialized chunk blob, so SQL on its
|
||||
/// own cannot filter by player identity; we narrow with SQL on Receiver
|
||||
/// + ChatType (cheap, indexed) and let the client do the final
|
||||
/// PlayerPayload comparison on the result set.
|
||||
///
|
||||
/// <paramref name="sqlScanLimit"/> caps how many recent tells we scan
|
||||
/// before giving up. 500 covers around 10 days for an active greeter
|
||||
/// and stays well under the 20 ms budget required to keep the spawn on
|
||||
/// the message-processing worker thread.
|
||||
/// </summary>
|
||||
internal IReadOnlyList<Message> GetTellHistoryWithSender(
|
||||
ulong receiver,
|
||||
string senderName,
|
||||
uint senderWorld,
|
||||
int limit,
|
||||
int sqlScanLimit = 500)
|
||||
{
|
||||
if (limit <= 0)
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
using var cmd = Connection.CreateCommand();
|
||||
cmd.CommandText = @"
|
||||
SELECT
|
||||
Id,
|
||||
Receiver,
|
||||
ContentId,
|
||||
Date,
|
||||
ChatType,
|
||||
SourceKind,
|
||||
TargetKind,
|
||||
Sender,
|
||||
Content,
|
||||
SenderSource,
|
||||
ContentSource,
|
||||
ExtraChatChannel
|
||||
FROM messages
|
||||
WHERE deleted = false
|
||||
AND Receiver = $Receiver
|
||||
AND ChatType IN ($TellIncoming, $TellOutgoing)
|
||||
ORDER BY Date DESC
|
||||
LIMIT $ScanLimit;
|
||||
";
|
||||
cmd.CommandTimeout = 60;
|
||||
cmd.Parameters.AddWithValue("$Receiver", receiver);
|
||||
cmd.Parameters.AddWithValue("$TellIncoming", (int)ChatType.TellIncoming);
|
||||
cmd.Parameters.AddWithValue("$TellOutgoing", (int)ChatType.TellOutgoing);
|
||||
cmd.Parameters.AddWithValue("$ScanLimit", sqlScanLimit);
|
||||
|
||||
var collected = new List<Message>();
|
||||
using var enumerator = new MessageEnumerator(cmd.ExecuteReader());
|
||||
foreach (var message in enumerator)
|
||||
{
|
||||
if (!ChunkUtil.MatchesSender(message, senderName, senderWorld))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
collected.Add(message);
|
||||
if (collected.Count >= limit)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// SQL was DESC (newest-first) so we hit the limit on the most
|
||||
// recent matching tells. Reverse to oldest-first for chronological
|
||||
// display in the tab.
|
||||
collected.Reverse();
|
||||
return collected;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Marks a message as deleted so it won't get returned in queries.
|
||||
/// </summary>
|
||||
@@ -615,16 +712,17 @@ internal class MessageStore : IDisposable
|
||||
|
||||
internal long CountDateRange(DateTime after, DateTime before, IEnumerable<byte> channels, ulong? receiver = null)
|
||||
{
|
||||
using var cmd = Connection.CreateCommand();
|
||||
|
||||
List<string> whereClauses = ["deleted = false"];
|
||||
if (receiver != null)
|
||||
whereClauses.Add("Receiver = $Receiver");
|
||||
|
||||
whereClauses.Add("Date BETWEEN $After AND $Before");
|
||||
whereClauses.Add($"ChatType IN ({string.Join(", ", channels)})");
|
||||
whereClauses.Add($"ChatType IN ({BindIntList(cmd, "cdr", channels.Select(c => (int)c))})");
|
||||
|
||||
var whereClause = "WHERE " + string.Join(" AND ", whereClauses);
|
||||
|
||||
using var cmd = Connection.CreateCommand();
|
||||
// Select last N messages by date DESC, but reverse the order to get
|
||||
// them in ascending order.
|
||||
cmd.CommandText = @"
|
||||
@@ -644,16 +742,17 @@ internal class MessageStore : IDisposable
|
||||
|
||||
internal MessageEnumerator GetDateRange(DateTime after, DateTime before, IEnumerable<byte> channels, ulong? receiver = null)
|
||||
{
|
||||
var cmd = Connection.CreateCommand();
|
||||
|
||||
List<string> whereClauses = ["deleted = false"];
|
||||
if (receiver != null)
|
||||
whereClauses.Add("Receiver = $Receiver");
|
||||
|
||||
whereClauses.Add("Date BETWEEN $After AND $Before");
|
||||
whereClauses.Add($"ChatType IN ({string.Join(", ", channels)})");
|
||||
whereClauses.Add($"ChatType IN ({BindIntList(cmd, "gdr", channels.Select(c => (int)c))})");
|
||||
|
||||
var whereClause = $"WHERE {string.Join(" AND ", whereClauses)}";
|
||||
|
||||
var cmd = Connection.CreateCommand();
|
||||
// Select last N messages by date DESC, but reverse the order to get
|
||||
// them in ascending order.
|
||||
cmd.CommandText = @"
|
||||
@@ -685,16 +784,17 @@ internal class MessageStore : IDisposable
|
||||
|
||||
internal MessageEnumerator GetPagedDateRange(DateTime after, DateTime before, IEnumerable<byte> channels, ulong? receiver = null, int page = 0)
|
||||
{
|
||||
var cmd = Connection.CreateCommand();
|
||||
|
||||
List<string> whereClauses = ["deleted = false"];
|
||||
if (receiver != null)
|
||||
whereClauses.Add("Receiver = $Receiver");
|
||||
|
||||
whereClauses.Add("Date BETWEEN $After AND $Before");
|
||||
whereClauses.Add($"ChatType IN ({string.Join(", ", channels)})");
|
||||
whereClauses.Add($"ChatType IN ({BindIntList(cmd, "pdr", channels.Select(c => (int)c))})");
|
||||
|
||||
var whereClause = $"WHERE {string.Join(" AND ", whereClauses)}";
|
||||
|
||||
var cmd = Connection.CreateCommand();
|
||||
// Select last N messages by date DESC, but reverse the order to get
|
||||
// them in ascending order.
|
||||
cmd.CommandText = @"
|
||||
@@ -728,6 +828,24 @@ internal class MessageStore : IDisposable
|
||||
|
||||
return new MessageEnumerator(cmd.ExecuteReader());
|
||||
}
|
||||
|
||||
// Build "$prefix0,$prefix1,..." placeholder list and bind values to
|
||||
// the command. SQLite has no native array parameter, so we generate
|
||||
// the list at runtime and bind each entry under its own name. Used
|
||||
// for IN-clauses and similar dynamic-arity SQL fragments.
|
||||
private static string BindIntList(SqliteCommand cmd, string prefix, IEnumerable<int> values)
|
||||
{
|
||||
var names = new List<string>();
|
||||
var index = 0;
|
||||
foreach (var value in values)
|
||||
{
|
||||
var name = $"${prefix}{index}";
|
||||
cmd.Parameters.AddWithValue(name, value);
|
||||
names.Add(name);
|
||||
index++;
|
||||
}
|
||||
return string.Join(",", names);
|
||||
}
|
||||
}
|
||||
|
||||
internal class MessageEnumerator(DbDataReader reader) : IEnumerable<Message>, IDisposable, IAsyncDisposable
|
||||
|
||||
+77
-55
@@ -1,6 +1,7 @@
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using ChatTwo.Ipc;
|
||||
using ChatTwo.Resources;
|
||||
using ChatTwo.Ui;
|
||||
@@ -57,6 +58,7 @@ public sealed class Plugin : IDalamudPlugin
|
||||
internal Commands Commands { get; }
|
||||
internal GameFunctions.GameFunctions Functions { get; }
|
||||
internal MessageManager MessageManager { get; }
|
||||
internal AutoTellTabsService AutoTellTabsService { get; }
|
||||
internal IpcManager Ipc { get; }
|
||||
internal ExtraChat ExtraChat { get; }
|
||||
internal TypingIpc TypingIpc { get; }
|
||||
@@ -70,8 +72,11 @@ public sealed class Plugin : IDalamudPlugin
|
||||
// plugin start would launch two sweeps in parallel and the second one
|
||||
// would just re-do work the first one already finished. The lock guards
|
||||
// the flag — the flag check itself bails before we touch the database.
|
||||
// Volatile because the ImGui thread reads the flag outside the lock to
|
||||
// gate the manual button; without it the JIT may cache the value in a
|
||||
// register and miss the background-thread update.
|
||||
internal readonly object RetentionSweepLock = new();
|
||||
internal bool RetentionSweepRunning;
|
||||
internal volatile bool RetentionSweepRunning;
|
||||
|
||||
internal DateTime GameStarted { get; }
|
||||
|
||||
@@ -100,75 +105,70 @@ public sealed class Plugin : IDalamudPlugin
|
||||
|
||||
Config = Interface.GetPluginConfig() as Configuration ?? new Configuration();
|
||||
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
// TODO Remove after 01.07.2026
|
||||
// Migrate old channel values
|
||||
if (Config.Version <= 5)
|
||||
// Hellion Chat — Auto-Tell-Tabs Defense-in-Depth. SaveConfig
|
||||
// already strips temp tabs before persistence, but a previous
|
||||
// crash or external write could have left them in the JSON.
|
||||
// Drop them on load to guarantee the session-only invariant.
|
||||
Config.Tabs.RemoveAll(t => t.IsTempTab);
|
||||
|
||||
// Hellion Chat v9 → v10 — wipes the configuration so the new 8-tab
|
||||
// layout starts from defaults instead of mapping every previous setting
|
||||
// to its new position. Backup-Failure ist non-fatal, der Wipe läuft
|
||||
// trotzdem; dem User fehlt dann nur das manuelle Restore-Sicherheitsnetz.
|
||||
if (Config.Version < 10)
|
||||
{
|
||||
foreach (var tab in Config.Tabs)
|
||||
var pluginConfigsDir = Interface.ConfigDirectory.Parent?.FullName;
|
||||
if (pluginConfigsDir is not null)
|
||||
{
|
||||
if (tab.ChatCodes.Count > 0)
|
||||
var liveConfigPath = Path.Combine(pluginConfigsDir, $"{Interface.InternalName}.json");
|
||||
var backupPath = Path.Combine(pluginConfigsDir, $"{Interface.InternalName}.json.pre-v10-backup");
|
||||
|
||||
try
|
||||
{
|
||||
tab.SelectedChannels = tab.ChatCodes.ToDictionary(pair => pair.Key, pair => (pair.Value, pair.Value));
|
||||
tab.ChatCodes.Clear();
|
||||
if (File.Exists(liveConfigPath))
|
||||
{
|
||||
File.Copy(liveConfigPath, backupPath, overwrite: true);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Warning(ex, "HellionChat: pre-v10 config backup failed");
|
||||
}
|
||||
}
|
||||
|
||||
if (Config.InactivityHideChannels.Count > 0)
|
||||
Config = new Configuration
|
||||
{
|
||||
Config.InactivityHideChannelsV2 = Config.InactivityHideChannels.ToDictionary(pair => pair.Key, pair => (pair.Value, pair.Value));
|
||||
Config.InactivityHideChannels.Clear();
|
||||
}
|
||||
|
||||
Config.Version = 6;
|
||||
SaveConfig();
|
||||
}
|
||||
}
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
|
||||
// Hellion Chat v6→v7: seed Privacy-First defaults.
|
||||
if (Config.Version <= 6)
|
||||
{
|
||||
Config.PrivacyFilterEnabled = true;
|
||||
Config.PrivacyPersistChannels = [..Privacy.PrivacyDefaults.PrivacyFirstWhitelist];
|
||||
Config.PrivacyPersistUnknownChannels = false;
|
||||
// Existing ChatTwo users skip the first-run wizard — the
|
||||
// migration toast already explains what changed and they
|
||||
// can reopen the wizard from Settings → Privacy if they
|
||||
// want to pick a different profile.
|
||||
Config.FirstRunCompleted = true;
|
||||
Config.Version = 7;
|
||||
Version = 10,
|
||||
FirstRunCompleted = true,
|
||||
};
|
||||
SaveConfig();
|
||||
|
||||
Notification.AddNotification(new Dalamud.Interface.ImGuiNotification.Notification
|
||||
{
|
||||
Title = HellionStrings.Migration_Notification_Title,
|
||||
Content = HellionStrings.Migration_Notification_Content,
|
||||
Title = HellionStrings.SettingsRefactor_Migration_Title,
|
||||
Content = HellionStrings.SettingsRefactor_Migration_Content,
|
||||
Type = Dalamud.Interface.ImGuiNotification.NotificationType.Info,
|
||||
InitialDuration = TimeSpan.FromSeconds(15),
|
||||
});
|
||||
}
|
||||
|
||||
// Hellion Chat v7→v8: webinterface removed in 0.2.0. Old config
|
||||
// entries (WebinterfacePassword, AuthStore, etc.) get dropped on
|
||||
// the next save because their properties no longer exist on the
|
||||
// Configuration class. The bump is recorded so the notification
|
||||
// only fires once.
|
||||
if (Config.Version <= 7)
|
||||
{
|
||||
Config.Version = 8;
|
||||
SaveConfig();
|
||||
|
||||
Notification.AddNotification(new Dalamud.Interface.ImGuiNotification.Notification
|
||||
{
|
||||
Title = HellionStrings.Migration_Webinterface_Removed_Title,
|
||||
Content = HellionStrings.Migration_Webinterface_Removed_Content,
|
||||
Type = Dalamud.Interface.ImGuiNotification.NotificationType.Info,
|
||||
InitialDuration = TimeSpan.FromSeconds(20),
|
||||
InitialDuration = TimeSpan.FromSeconds(25),
|
||||
});
|
||||
}
|
||||
|
||||
// Hellion default tab layout for first-run and v10-wipe.
|
||||
// General catches player chat plus active gameplay events; the
|
||||
// System tab takes the technical noise so it does not bury real
|
||||
// conversation. Beginner tab only appears when the Novice
|
||||
// Network is enabled in Audio and Notifications, otherwise it
|
||||
// would just sit empty.
|
||||
if (Config.Tabs.Count == 0)
|
||||
{
|
||||
Config.Tabs.Add(TabsUtil.VanillaGeneral);
|
||||
Config.Tabs.Add(TabsUtil.HellionSystem);
|
||||
Config.Tabs.Add(TabsUtil.HellionFreeCompany);
|
||||
Config.Tabs.Add(TabsUtil.HellionParty);
|
||||
if (Config.ShowNoviceNetwork)
|
||||
Config.Tabs.Add(TabsUtil.HellionBeginner);
|
||||
Config.Tabs.Add(TabsUtil.HellionLinkshell);
|
||||
Config.Tabs.Add(TabsUtil.VanillaTellExclusive);
|
||||
}
|
||||
|
||||
LanguageChanged(Interface.UiLanguage);
|
||||
ImGuiUtil.Initialize(this);
|
||||
@@ -184,6 +184,14 @@ public sealed class Plugin : IDalamudPlugin
|
||||
|
||||
MessageManager = new MessageManager(this); // Does it require UI?
|
||||
|
||||
// Hellion Chat — Auto-Tell-Tabs service. Subscribes to the
|
||||
// MessageManager's MessageProcessed event for live tells and
|
||||
// to ClientState.Logout for the cleanup pass. Created after
|
||||
// MessageManager so the constructor can hand off the live
|
||||
// store and event source.
|
||||
AutoTellTabsService = new AutoTellTabsService(this, MessageManager, MessageManager.Store);
|
||||
AutoTellTabsService.Initialize();
|
||||
|
||||
// Hellion Chat — daily retention sweep, off-thread so it never
|
||||
// blocks plugin load. Skips itself when disabled or already ran
|
||||
// within the past 24 hours.
|
||||
@@ -233,7 +241,7 @@ public sealed class Plugin : IDalamudPlugin
|
||||
Interface.UiBuilder.OpenMainUi += OpenMainUi;
|
||||
|
||||
if (Config.ShowEmotes)
|
||||
Task.Run(EmoteCache.LoadData);
|
||||
_ = EmoteCache.LoadData(); // Fire-and-forget intentional, exceptions are caught inside
|
||||
|
||||
#if !DEBUG
|
||||
// Avoid 300ms hitch when sending first message by preloading the
|
||||
@@ -274,6 +282,10 @@ public sealed class Plugin : IDalamudPlugin
|
||||
TypingIpc?.Dispose();
|
||||
ExtraChat?.Dispose();
|
||||
Ipc?.Dispose();
|
||||
// Dispose the Auto-Tell-Tabs service before MessageManager so it
|
||||
// can cleanly unsubscribe from the MessageProcessed event before
|
||||
// its source goes away.
|
||||
AutoTellTabsService?.Dispose();
|
||||
MessageManager?.DisposeAsync().AsTask().Wait();
|
||||
Functions?.Dispose();
|
||||
Commands?.Dispose();
|
||||
@@ -491,7 +503,17 @@ public sealed class Plugin : IDalamudPlugin
|
||||
|
||||
internal void SaveConfig()
|
||||
{
|
||||
// Hellion Chat — Auto-Tell-Tabs are session-only. Strip them out
|
||||
// before serialization so a crash mid-session can never persist
|
||||
// them. We snapshot the full tab list first and restore it after
|
||||
// the save, preserving the user's order and open conversations.
|
||||
var snapshot = Config.Tabs.ToList();
|
||||
Config.Tabs.RemoveAll(t => t.IsTempTab);
|
||||
|
||||
Interface.SavePluginConfig(Config);
|
||||
|
||||
Config.Tabs.Clear();
|
||||
Config.Tabs.AddRange(snapshot);
|
||||
}
|
||||
|
||||
internal void LanguageChanged(string langCode)
|
||||
|
||||
+84
-6
@@ -45,6 +45,7 @@ internal class HellionStrings
|
||||
internal static string Privacy_FilterEnabled_Name => Get(nameof(Privacy_FilterEnabled_Name));
|
||||
internal static string Privacy_FilterEnabled_Description => Get(nameof(Privacy_FilterEnabled_Description));
|
||||
internal static string Privacy_FilterEnabled_StorageOnly_Help => Get(nameof(Privacy_FilterEnabled_StorageOnly_Help));
|
||||
internal static string Privacy_Filter_Tree_Heading => Get(nameof(Privacy_Filter_Tree_Heading));
|
||||
internal static string Privacy_Whitelist_Help => Get(nameof(Privacy_Whitelist_Help));
|
||||
internal static string Privacy_Preset_PrivacyFirst => Get(nameof(Privacy_Preset_PrivacyFirst));
|
||||
internal static string Privacy_Preset_ClearAll => Get(nameof(Privacy_Preset_ClearAll));
|
||||
@@ -63,6 +64,8 @@ internal class HellionStrings
|
||||
internal static string Cleanup_Heading => Get(nameof(Cleanup_Heading));
|
||||
internal static string Cleanup_Help_Intro => Get(nameof(Cleanup_Help_Intro));
|
||||
internal static string Cleanup_Help_SavedNote => Get(nameof(Cleanup_Help_SavedNote));
|
||||
internal static string Cleanup_Preview_Stale => Get(nameof(Cleanup_Preview_Stale));
|
||||
internal static string Retention_Help_SavedNote => Get(nameof(Retention_Help_SavedNote));
|
||||
internal static string Cleanup_RefreshPreview => Get(nameof(Cleanup_RefreshPreview));
|
||||
internal static string Cleanup_NoPreview => Get(nameof(Cleanup_NoPreview));
|
||||
internal static string Cleanup_TotalStored => Get(nameof(Cleanup_TotalStored));
|
||||
@@ -98,11 +101,6 @@ internal class HellionStrings
|
||||
internal static string Retention_Success => Get(nameof(Retention_Success));
|
||||
internal static string Retention_Error => Get(nameof(Retention_Error));
|
||||
|
||||
internal static string Migration_Notification_Title => Get(nameof(Migration_Notification_Title));
|
||||
internal static string Migration_Notification_Content => Get(nameof(Migration_Notification_Content));
|
||||
internal static string Migration_Webinterface_Removed_Title => Get(nameof(Migration_Webinterface_Removed_Title));
|
||||
internal static string Migration_Webinterface_Removed_Content => Get(nameof(Migration_Webinterface_Removed_Content));
|
||||
|
||||
internal static string Wizard_Title => Get(nameof(Wizard_Title));
|
||||
internal static string Wizard_Intro => Get(nameof(Wizard_Intro));
|
||||
internal static string Wizard_Profile_PrivacyFirst_Heading => Get(nameof(Wizard_Profile_PrivacyFirst_Heading));
|
||||
@@ -134,7 +132,6 @@ internal class HellionStrings
|
||||
internal static string Export_Empty => Get(nameof(Export_Empty));
|
||||
internal static string Export_Error => Get(nameof(Export_Error));
|
||||
|
||||
internal static string Theme_Heading => Get(nameof(Theme_Heading));
|
||||
internal static string Theme_Enabled_Name => Get(nameof(Theme_Enabled_Name));
|
||||
internal static string Theme_Enabled_Description => Get(nameof(Theme_Enabled_Description));
|
||||
internal static string Theme_WindowOpacity_Label => Get(nameof(Theme_WindowOpacity_Label));
|
||||
@@ -164,4 +161,85 @@ internal class HellionStrings
|
||||
internal static string About_Localization_P1 => Get(nameof(About_Localization_P1));
|
||||
internal static string About_Localization_P2 => Get(nameof(About_Localization_P2));
|
||||
internal static string About_Translators_TreeNode => Get(nameof(About_Translators_TreeNode));
|
||||
|
||||
// Hellion Chat — Auto-Tell-Tabs runtime strings
|
||||
internal static string AutoTellTabs_SectionHeader => Get(nameof(AutoTellTabs_SectionHeader));
|
||||
internal static string AutoTellTabs_HistorySeparator => Get(nameof(AutoTellTabs_HistorySeparator));
|
||||
internal static string AutoTellTabs_HistoryLoadError => Get(nameof(AutoTellTabs_HistoryLoadError));
|
||||
internal static string AutoTellTabs_GreetedTooltip => Get(nameof(AutoTellTabs_GreetedTooltip));
|
||||
internal static string AutoTellTabs_UnGreetedTooltip => Get(nameof(AutoTellTabs_UnGreetedTooltip));
|
||||
|
||||
// Hellion Chat — Auto-Tell-Tabs Chat settings tab
|
||||
internal static string ChatLog_AutoTellTabs_Section_Title => Get(nameof(ChatLog_AutoTellTabs_Section_Title));
|
||||
internal static string ChatLog_AutoTellTabs_Enable_Name => Get(nameof(ChatLog_AutoTellTabs_Enable_Name));
|
||||
internal static string ChatLog_AutoTellTabs_Enable_Description => Get(nameof(ChatLog_AutoTellTabs_Enable_Description));
|
||||
internal static string ChatLog_AutoTellTabs_Limit_Name => Get(nameof(ChatLog_AutoTellTabs_Limit_Name));
|
||||
internal static string ChatLog_AutoTellTabs_Limit_Description => Get(nameof(ChatLog_AutoTellTabs_Limit_Description));
|
||||
internal static string ChatLog_AutoTellTabs_Compact_Name => Get(nameof(ChatLog_AutoTellTabs_Compact_Name));
|
||||
internal static string ChatLog_AutoTellTabs_Compact_Description => Get(nameof(ChatLog_AutoTellTabs_Compact_Description));
|
||||
internal static string ChatLog_AutoTellTabs_GreetedToggle_Name => Get(nameof(ChatLog_AutoTellTabs_GreetedToggle_Name));
|
||||
internal static string ChatLog_AutoTellTabs_GreetedToggle_Description => Get(nameof(ChatLog_AutoTellTabs_GreetedToggle_Description));
|
||||
internal static string ChatLog_AutoTellTabs_PreloadHint => Get(nameof(ChatLog_AutoTellTabs_PreloadHint));
|
||||
internal static string ChatLog_AutoTellTabs_ConflictHint => Get(nameof(ChatLog_AutoTellTabs_ConflictHint));
|
||||
|
||||
// Hellion Chat — Auto-Tell-Tabs Privacy settings tab
|
||||
internal static string Privacy_AutoTellTabs_Section_Title => Get(nameof(Privacy_AutoTellTabs_Section_Title));
|
||||
internal static string Privacy_AutoTellTabs_Preload_Name => Get(nameof(Privacy_AutoTellTabs_Preload_Name));
|
||||
internal static string Privacy_AutoTellTabs_Preload_Description => Get(nameof(Privacy_AutoTellTabs_Preload_Description));
|
||||
internal static string Privacy_AutoTellTabs_Preload_Hint => Get(nameof(Privacy_AutoTellTabs_Preload_Hint));
|
||||
|
||||
// Hellion Chat — Settings UX Polish v10 wipe migration
|
||||
internal static string SettingsRefactor_Migration_Title => Get(nameof(SettingsRefactor_Migration_Title));
|
||||
internal static string SettingsRefactor_Migration_Content => Get(nameof(SettingsRefactor_Migration_Content));
|
||||
|
||||
// Hellion Chat — Settings UX Polish 8-tab structure
|
||||
internal static string Settings_Tab_General => Get(nameof(Settings_Tab_General));
|
||||
internal static string Settings_Tab_Appearance => Get(nameof(Settings_Tab_Appearance));
|
||||
internal static string Settings_Tab_Window => Get(nameof(Settings_Tab_Window));
|
||||
internal static string Settings_Tab_Chat => Get(nameof(Settings_Tab_Chat));
|
||||
internal static string Settings_Tab_Tabs => Get(nameof(Settings_Tab_Tabs));
|
||||
internal static string Settings_Tab_Database => Get(nameof(Settings_Tab_Database));
|
||||
internal static string Settings_Tab_Information => Get(nameof(Settings_Tab_Information));
|
||||
|
||||
// Hellion Chat — General-Tab section headings
|
||||
internal static string Settings_General_Input_Heading => Get(nameof(Settings_General_Input_Heading));
|
||||
internal static string Settings_General_Audio_Heading => Get(nameof(Settings_General_Audio_Heading));
|
||||
internal static string Settings_General_Performance_Heading => Get(nameof(Settings_General_Performance_Heading));
|
||||
internal static string Settings_General_Language_Heading => Get(nameof(Settings_General_Language_Heading));
|
||||
|
||||
// Hellion Chat — Appearance-Tab section headings
|
||||
internal static string Settings_Appearance_Theme_Heading => Get(nameof(Settings_Appearance_Theme_Heading));
|
||||
internal static string Settings_Appearance_Fonts_Heading => Get(nameof(Settings_Appearance_Fonts_Heading));
|
||||
internal static string Settings_Appearance_Colours_Heading => Get(nameof(Settings_Appearance_Colours_Heading));
|
||||
internal static string Settings_Appearance_Timestamps_Heading => Get(nameof(Settings_Appearance_Timestamps_Heading));
|
||||
|
||||
// Hellion Chat — Window-Tab section headings
|
||||
internal static string Settings_Window_Hide_Heading => Get(nameof(Settings_Window_Hide_Heading));
|
||||
internal static string Settings_Window_InactivityHide_Heading => Get(nameof(Settings_Window_InactivityHide_Heading));
|
||||
internal static string Settings_Window_Frame_Heading => Get(nameof(Settings_Window_Frame_Heading));
|
||||
internal static string Settings_Window_Tooltips_Heading => Get(nameof(Settings_Window_Tooltips_Heading));
|
||||
|
||||
// Hellion Chat — Chat-Tab section headings
|
||||
internal static string Settings_Chat_AutoTellTabs_Heading => Get(nameof(Settings_Chat_AutoTellTabs_Heading));
|
||||
internal static string Settings_Chat_Behaviour_Heading => Get(nameof(Settings_Chat_Behaviour_Heading));
|
||||
internal static string Settings_Chat_Preview_Heading => Get(nameof(Settings_Chat_Preview_Heading));
|
||||
internal static string Settings_Chat_Emotes_Heading => Get(nameof(Settings_Chat_Emotes_Heading));
|
||||
|
||||
// Hellion Chat — Database-Tab section headings
|
||||
internal static string Settings_Database_Storage_Heading => Get(nameof(Settings_Database_Storage_Heading));
|
||||
internal static string Settings_Database_Viewer_Heading => Get(nameof(Settings_Database_Viewer_Heading));
|
||||
internal static string Settings_Database_Stats_Heading => Get(nameof(Settings_Database_Stats_Heading));
|
||||
|
||||
// Hellion Chat — Information-Tab section headings
|
||||
internal static string Settings_Information_VersionInfo_Heading => Get(nameof(Settings_Information_VersionInfo_Heading));
|
||||
internal static string Settings_Information_About_Heading => Get(nameof(Settings_Information_About_Heading));
|
||||
internal static string Settings_Information_Changelog_Heading => Get(nameof(Settings_Information_Changelog_Heading));
|
||||
|
||||
// Hellion Chat — Default tab presets (channel-themed)
|
||||
internal static string Tabs_Presets_System => Get(nameof(Tabs_Presets_System));
|
||||
internal static string Tabs_Presets_FreeCompany => Get(nameof(Tabs_Presets_FreeCompany));
|
||||
internal static string Tabs_Presets_Party => Get(nameof(Tabs_Presets_Party));
|
||||
internal static string Tabs_Presets_Beginner => Get(nameof(Tabs_Presets_Beginner));
|
||||
internal static string Tabs_Presets_Linkshell => Get(nameof(Tabs_Presets_Linkshell));
|
||||
internal static string Tabs_Presets_Linkshell_Hint => Get(nameof(Tabs_Presets_Linkshell_Hint));
|
||||
}
|
||||
|
||||
@@ -24,6 +24,9 @@
|
||||
<data name="Privacy_FilterEnabled_StorageOnly_Help" xml:space="preserve">
|
||||
<value>Der Filter steuert nur, was in die lokale Datenbank geschrieben wird. Im Chat-Log siehst du weiterhin jede Nachricht live, ausgeschlossene Kanäle werden nur nicht mehr gespeichert. Wenn du Kanäle auch aus der sichtbaren Anzeige entfernen willst, nutze die normalen Chat-Tab-Filter im Spiel.</value>
|
||||
</data>
|
||||
<data name="Privacy_Filter_Tree_Heading" xml:space="preserve">
|
||||
<value>Privacy-Filter und Whitelist</value>
|
||||
</data>
|
||||
<data name="Privacy_Whitelist_Help" xml:space="preserve">
|
||||
<value>Wähle aus, welche Kanäle in die lokale Datenbank gespeichert werden. Standard nach Datensparsamkeit: nur deine eigenen Konversationen. Über die Buttons unten kannst du eine Voreinstellung anwenden.</value>
|
||||
</data>
|
||||
@@ -75,6 +78,12 @@
|
||||
<data name="Cleanup_Help_SavedNote" xml:space="preserve">
|
||||
<value>Das Aufräumen nutzt deine GESPEICHERTE Whitelist (Plugin.Config), nicht ungespeicherte Änderungen oben. Klicke zuerst Speichern, wenn du deine aktuellen Änderungen anwenden willst.</value>
|
||||
</data>
|
||||
<data name="Retention_Help_SavedNote" xml:space="preserve">
|
||||
<value>Der manuelle Lauf nutzt deine GESPEICHERTE Retention-Policy, nicht die Slider-Werte oben. Klicke zuerst Speichern, wenn der Lauf deine aktuellen Änderungen anwenden soll.</value>
|
||||
</data>
|
||||
<data name="Cleanup_Preview_Stale" xml:space="preserve">
|
||||
<value>Vorschau veraltet, deine Whitelist hat sich seit dem letzten Aktualisieren geändert. Klicke Aktualisieren, um neu zu berechnen.</value>
|
||||
</data>
|
||||
<data name="Cleanup_RefreshPreview" xml:space="preserve">
|
||||
<value>Vorschau aktualisieren</value>
|
||||
</data>
|
||||
@@ -174,18 +183,6 @@
|
||||
<data name="Retention_Error" xml:space="preserve">
|
||||
<value>Aufbewahrungs-Bereinigung fehlgeschlagen, siehe /xllog</value>
|
||||
</data>
|
||||
<data name="Migration_Notification_Title" xml:space="preserve">
|
||||
<value>Hellion Chat</value>
|
||||
</data>
|
||||
<data name="Migration_Notification_Content" xml:space="preserve">
|
||||
<value>Datenschutz-Filter ist standardmäßig aktiviert. Einstellungen → Datenschutz zum Anpassen.</value>
|
||||
</data>
|
||||
<data name="Migration_Webinterface_Removed_Title" xml:space="preserve">
|
||||
<value>Hellion Chat 0.2.0</value>
|
||||
</data>
|
||||
<data name="Migration_Webinterface_Removed_Content" xml:space="preserve">
|
||||
<value>Das Webinterface wurde in dieser Version entfernt, weil es nicht auf das Datenschutz-Niveau gehärtet werden konnte das Hellion Chat standardmäßig zusichert. Falls du es genutzt hast, schau bitte in die README für Hintergründe.</value>
|
||||
</data>
|
||||
<data name="Wizard_Title" xml:space="preserve">
|
||||
<value>Hellion Chat — Willkommen</value>
|
||||
</data>
|
||||
@@ -273,9 +270,6 @@
|
||||
<data name="Export_Error" xml:space="preserve">
|
||||
<value>Export fehlgeschlagen, siehe /xllog</value>
|
||||
</data>
|
||||
<data name="Theme_Heading" xml:space="preserve">
|
||||
<value>Erscheinungsbild</value>
|
||||
</data>
|
||||
<data name="Theme_Enabled_Name" xml:space="preserve">
|
||||
<value>Hellion-Theme für alle Plugin-Fenster verwenden</value>
|
||||
</data>
|
||||
@@ -366,4 +360,199 @@
|
||||
<data name="About_Translators_TreeNode" xml:space="preserve">
|
||||
<value>Chat-2-Community-Übersetzer (Upstream)</value>
|
||||
</data>
|
||||
|
||||
<!-- Hellion Chat — Auto-Tell-Tabs (Runtime-Strings) -->
|
||||
<data name="AutoTellTabs_SectionHeader" xml:space="preserve">
|
||||
<value>Aktive Tells</value>
|
||||
</data>
|
||||
<data name="AutoTellTabs_HistorySeparator" xml:space="preserve">
|
||||
<value>— Frühere Unterhaltungen —</value>
|
||||
</data>
|
||||
<data name="AutoTellTabs_HistoryLoadError" xml:space="preserve">
|
||||
<value>Verlauf konnte nicht geladen werden.</value>
|
||||
</data>
|
||||
<data name="AutoTellTabs_GreetedTooltip" xml:space="preserve">
|
||||
<value>Als begrüßt markiert. Klicken um die Markierung zu entfernen.</value>
|
||||
</data>
|
||||
<data name="AutoTellTabs_UnGreetedTooltip" xml:space="preserve">
|
||||
<value>Als begrüßt markieren.</value>
|
||||
</data>
|
||||
|
||||
<!-- Hellion Chat — Auto-Tell-Tabs (Chat-Einstellungstab) -->
|
||||
<data name="ChatLog_AutoTellTabs_Section_Title" xml:space="preserve">
|
||||
<value>Auto-Tell-Tabs</value>
|
||||
</data>
|
||||
<data name="ChatLog_AutoTellTabs_Enable_Name" xml:space="preserve">
|
||||
<value>Bei jedem /tell automatisch einen Tab pro Gesprächspartner öffnen</value>
|
||||
</data>
|
||||
<data name="ChatLog_AutoTellTabs_Enable_Description" xml:space="preserve">
|
||||
<value>Sobald du einen /tell empfängst oder sendest, wird automatisch ein temporärer Tab für diesen Spieler geöffnet. Die Tabs verschwinden beim Logout.</value>
|
||||
</data>
|
||||
<data name="ChatLog_AutoTellTabs_Limit_Name" xml:space="preserve">
|
||||
<value>Maximale Anzahl der Auto-Tell-Tabs</value>
|
||||
</data>
|
||||
<data name="ChatLog_AutoTellTabs_Limit_Description" xml:space="preserve">
|
||||
<value>Beim Erreichen werden begrüßte Tabs mit der ältesten Aktivität zuerst geschlossen. Änderungen greifen beim nächsten /tell.</value>
|
||||
</data>
|
||||
<data name="ChatLog_AutoTellTabs_Compact_Name" xml:space="preserve">
|
||||
<value>Kompakte Anzeige</value>
|
||||
</data>
|
||||
<data name="ChatLog_AutoTellTabs_Compact_Description" xml:space="preserve">
|
||||
<value>Zeigt nur einen dünnen Separator zwischen normalen Tabs und Auto-Tell-Tabs, ohne Sektions-Header.</value>
|
||||
</data>
|
||||
<data name="ChatLog_AutoTellTabs_GreetedToggle_Name" xml:space="preserve">
|
||||
<value>„Als begrüßt markieren"-Button anzeigen</value>
|
||||
</data>
|
||||
<data name="ChatLog_AutoTellTabs_GreetedToggle_Description" xml:space="preserve">
|
||||
<value>Fügt neben jedem Auto-Tell-Tab einen Klick-Button hinzu, um einen Gesprächspartner als bereits begrüßt zu markieren — der Tab-Name wird dann gedimmt. Nützlich für Club-Greeter, die parallel viele Konversationen führen. Standardmäßig aus.</value>
|
||||
</data>
|
||||
<data name="ChatLog_AutoTellTabs_PreloadHint" xml:space="preserve">
|
||||
<value>Die Anzahl der vorgeladenen Tells lässt sich im Datenschutz-Tab einstellen.</value>
|
||||
</data>
|
||||
<data name="ChatLog_AutoTellTabs_ConflictHint" xml:space="preserve">
|
||||
<value>Hinweis: Falls XIV Messanger oder ein ähnliches Plugin Tells unterdrückt, dort die Option „Suppress DMs" deaktivieren, damit Hellion Chat Tells empfangen und die Auto-Tabs öffnen kann.</value>
|
||||
</data>
|
||||
|
||||
<!-- Hellion Chat — Auto-Tell-Tabs (Datenschutz-Einstellungstab) -->
|
||||
<data name="Privacy_AutoTellTabs_Section_Title" xml:space="preserve">
|
||||
<value>Tell-Verlauf in Auto-Tabs</value>
|
||||
</data>
|
||||
<data name="Privacy_AutoTellTabs_Preload_Name" xml:space="preserve">
|
||||
<value>Anzahl der vorgeladenen Tells</value>
|
||||
</data>
|
||||
<data name="Privacy_AutoTellTabs_Preload_Description" xml:space="preserve">
|
||||
<value>Wie viele frühere Tell-Nachrichten beim Öffnen eines Auto-Tell-Tabs aus der Datenbank geladen werden. 0 deaktiviert die Vorladung.</value>
|
||||
</data>
|
||||
<data name="Privacy_AutoTellTabs_Preload_Hint" xml:space="preserve">
|
||||
<value>Greift nur, wenn Auto-Tell-Tabs im Chat-Tab aktiviert sind.</value>
|
||||
</data>
|
||||
|
||||
<!-- Hellion Chat — Settings UX Polish v10 Wipe-Migration -->
|
||||
<data name="SettingsRefactor_Migration_Title" xml:space="preserve">
|
||||
<value>Settings umstrukturiert</value>
|
||||
</data>
|
||||
<data name="SettingsRefactor_Migration_Content" xml:space="preserve">
|
||||
<value>Hellion Chat 0.5.0 hat die Settings in thematische Tabs umstrukturiert. Deine Chat-Datenbank und dein Nachrichtenverlauf bleiben unverändert. Settings wurden auf Defaults zurückgesetzt. Falls du das Privacy-Profil neu wählen willst, findest du den Reopen-Button im Datenschutz-Tab. Ein Backup der vorherigen Config liegt unter HellionChat.json.pre-v10-backup neben der aktiven Config-Datei.</value>
|
||||
</data>
|
||||
|
||||
<!-- Hellion Chat — Settings UX Polish 8-Tab-Struktur -->
|
||||
<data name="Settings_Tab_General" xml:space="preserve">
|
||||
<value>Allgemein</value>
|
||||
</data>
|
||||
<data name="Settings_Tab_Appearance" xml:space="preserve">
|
||||
<value>Aussehen</value>
|
||||
</data>
|
||||
<data name="Settings_Tab_Window" xml:space="preserve">
|
||||
<value>Fenster</value>
|
||||
</data>
|
||||
<data name="Settings_Tab_Chat" xml:space="preserve">
|
||||
<value>Chat</value>
|
||||
</data>
|
||||
<data name="Settings_Tab_Tabs" xml:space="preserve">
|
||||
<value>Kanäle</value>
|
||||
</data>
|
||||
<data name="Settings_Tab_Database" xml:space="preserve">
|
||||
<value>Datenbank</value>
|
||||
</data>
|
||||
<data name="Settings_Tab_Information" xml:space="preserve">
|
||||
<value>Über</value>
|
||||
</data>
|
||||
|
||||
<!-- Hellion Chat — Sektions-Überschriften des Allgemein-Tabs -->
|
||||
<data name="Settings_General_Input_Heading" xml:space="preserve">
|
||||
<value>Eingabe</value>
|
||||
</data>
|
||||
<data name="Settings_General_Audio_Heading" xml:space="preserve">
|
||||
<value>Audio & Benachrichtigungen</value>
|
||||
</data>
|
||||
<data name="Settings_General_Performance_Heading" xml:space="preserve">
|
||||
<value>Performance</value>
|
||||
</data>
|
||||
<data name="Settings_General_Language_Heading" xml:space="preserve">
|
||||
<value>Sprache & Eingabe-Hilfen</value>
|
||||
</data>
|
||||
|
||||
<!-- Hellion Chat — Sektions-Überschriften des Aussehen-Tabs -->
|
||||
<data name="Settings_Appearance_Theme_Heading" xml:space="preserve">
|
||||
<value>Theme</value>
|
||||
</data>
|
||||
<data name="Settings_Appearance_Fonts_Heading" xml:space="preserve">
|
||||
<value>Schriftarten</value>
|
||||
</data>
|
||||
<data name="Settings_Appearance_Colours_Heading" xml:space="preserve">
|
||||
<value>Chat-Farben</value>
|
||||
</data>
|
||||
<data name="Settings_Appearance_Timestamps_Heading" xml:space="preserve">
|
||||
<value>Zeitstempel</value>
|
||||
</data>
|
||||
|
||||
<!-- Hellion Chat — Sektions-Überschriften des Fenster-Tabs -->
|
||||
<data name="Settings_Window_Hide_Heading" xml:space="preserve">
|
||||
<value>Verstecken</value>
|
||||
</data>
|
||||
<data name="Settings_Window_InactivityHide_Heading" xml:space="preserve">
|
||||
<value>Inaktivitäts-Verstecken</value>
|
||||
</data>
|
||||
<data name="Settings_Window_Frame_Heading" xml:space="preserve">
|
||||
<value>Fenster-Rahmen</value>
|
||||
</data>
|
||||
<data name="Settings_Window_Tooltips_Heading" xml:space="preserve">
|
||||
<value>Tooltips</value>
|
||||
</data>
|
||||
|
||||
<!-- Hellion Chat — Sektions-Überschriften des Chat-Tabs -->
|
||||
<data name="Settings_Chat_AutoTellTabs_Heading" xml:space="preserve">
|
||||
<value>Auto-Tell-Tabs</value>
|
||||
</data>
|
||||
<data name="Settings_Chat_Behaviour_Heading" xml:space="preserve">
|
||||
<value>Nachrichten-Verhalten</value>
|
||||
</data>
|
||||
<data name="Settings_Chat_Preview_Heading" xml:space="preserve">
|
||||
<value>Vorschau</value>
|
||||
</data>
|
||||
<data name="Settings_Chat_Emotes_Heading" xml:space="preserve">
|
||||
<value>Emotes</value>
|
||||
</data>
|
||||
|
||||
<!-- Hellion Chat — Sektions-Überschriften des Database-Tabs -->
|
||||
<data name="Settings_Database_Storage_Heading" xml:space="preserve">
|
||||
<value>Speicherung</value>
|
||||
</data>
|
||||
<data name="Settings_Database_Viewer_Heading" xml:space="preserve">
|
||||
<value>Übersicht</value>
|
||||
</data>
|
||||
<data name="Settings_Database_Stats_Heading" xml:space="preserve">
|
||||
<value>Wartung</value>
|
||||
</data>
|
||||
|
||||
<!-- Hellion Chat — Sektions-Überschriften des Information-Tabs -->
|
||||
<data name="Settings_Information_VersionInfo_Heading" xml:space="preserve">
|
||||
<value>Versionsinfo</value>
|
||||
</data>
|
||||
<data name="Settings_Information_About_Heading" xml:space="preserve">
|
||||
<value>Über HellionChat</value>
|
||||
</data>
|
||||
<data name="Settings_Information_Changelog_Heading" xml:space="preserve">
|
||||
<value>Changelog</value>
|
||||
</data>
|
||||
|
||||
<!-- Hellion Chat — Default-Tab-Presets (kanalspezifisch) -->
|
||||
<data name="Tabs_Presets_System" xml:space="preserve">
|
||||
<value>System</value>
|
||||
</data>
|
||||
<data name="Tabs_Presets_FreeCompany" xml:space="preserve">
|
||||
<value>Free Company</value>
|
||||
</data>
|
||||
<data name="Tabs_Presets_Party" xml:space="preserve">
|
||||
<value>Gruppe</value>
|
||||
</data>
|
||||
<data name="Tabs_Presets_Beginner" xml:space="preserve">
|
||||
<value>Neulinge</value>
|
||||
</data>
|
||||
<data name="Tabs_Presets_Linkshell" xml:space="preserve">
|
||||
<value>Linkshell</value>
|
||||
</data>
|
||||
<data name="Tabs_Presets_Linkshell_Hint" xml:space="preserve">
|
||||
<value>Wenn du mehrere Linkshells benutzt, empfiehlt der Maintainer einen Tab pro Shell für eine sauberere Übersicht. Tab duplizieren und je Kopie die Kanalauswahl einschränken.</value>
|
||||
</data>
|
||||
</root>
|
||||
|
||||
@@ -24,6 +24,9 @@
|
||||
<data name="Privacy_FilterEnabled_StorageOnly_Help" xml:space="preserve">
|
||||
<value>The filter only controls what is written to the local database. The chat log itself keeps showing every message live, disallowed channels just stop being saved. Use the channel hide options in your in-game chat tabs if you want to remove channels from the visible chat.</value>
|
||||
</data>
|
||||
<data name="Privacy_Filter_Tree_Heading" xml:space="preserve">
|
||||
<value>Privacy filter and whitelist</value>
|
||||
</data>
|
||||
<data name="Privacy_Whitelist_Help" xml:space="preserve">
|
||||
<value>Pick which channels are stored in the local database. Privacy-First default: only your own conversations. Use the buttons below to apply a preset.</value>
|
||||
</data>
|
||||
@@ -75,6 +78,12 @@
|
||||
<data name="Cleanup_Help_SavedNote" xml:space="preserve">
|
||||
<value>Cleanup uses your SAVED whitelist (Plugin.Config), not unsaved edits above. Click Save first if you want to apply your current edits.</value>
|
||||
</data>
|
||||
<data name="Retention_Help_SavedNote" xml:space="preserve">
|
||||
<value>The manual sweep uses your SAVED retention policy, not the slider values above. Click Save first if you want the run to apply your current edits.</value>
|
||||
</data>
|
||||
<data name="Cleanup_Preview_Stale" xml:space="preserve">
|
||||
<value>Preview is out of date — your whitelist has changed since the last refresh. Click Refresh to recalculate.</value>
|
||||
</data>
|
||||
<data name="Cleanup_RefreshPreview" xml:space="preserve">
|
||||
<value>Refresh preview</value>
|
||||
</data>
|
||||
@@ -174,18 +183,6 @@
|
||||
<data name="Retention_Error" xml:space="preserve">
|
||||
<value>Retention sweep failed, see /xllog</value>
|
||||
</data>
|
||||
<data name="Migration_Notification_Title" xml:space="preserve">
|
||||
<value>Hellion Chat</value>
|
||||
</data>
|
||||
<data name="Migration_Notification_Content" xml:space="preserve">
|
||||
<value>Privacy filter activated by default. Settings → Privacy to adjust.</value>
|
||||
</data>
|
||||
<data name="Migration_Webinterface_Removed_Title" xml:space="preserve">
|
||||
<value>Hellion Chat 0.2.0</value>
|
||||
</data>
|
||||
<data name="Migration_Webinterface_Removed_Content" xml:space="preserve">
|
||||
<value>The webinterface has been removed in this version because it could not be hardened to the privacy guarantees Hellion Chat makes by default. If you used it, please consult the README for context.</value>
|
||||
</data>
|
||||
<data name="Wizard_Title" xml:space="preserve">
|
||||
<value>Hellion Chat — Welcome</value>
|
||||
</data>
|
||||
@@ -273,9 +270,6 @@
|
||||
<data name="Export_Error" xml:space="preserve">
|
||||
<value>Export failed, see /xllog</value>
|
||||
</data>
|
||||
<data name="Theme_Heading" xml:space="preserve">
|
||||
<value>Appearance</value>
|
||||
</data>
|
||||
<data name="Theme_Enabled_Name" xml:space="preserve">
|
||||
<value>Use the Hellion theme across all plugin windows</value>
|
||||
</data>
|
||||
@@ -366,4 +360,199 @@
|
||||
<data name="About_Translators_TreeNode" xml:space="preserve">
|
||||
<value>Chat 2 community translators (upstream)</value>
|
||||
</data>
|
||||
|
||||
<!-- Hellion Chat — Auto-Tell-Tabs (runtime strings) -->
|
||||
<data name="AutoTellTabs_SectionHeader" xml:space="preserve">
|
||||
<value>Active Tells</value>
|
||||
</data>
|
||||
<data name="AutoTellTabs_HistorySeparator" xml:space="preserve">
|
||||
<value>— Earlier conversations —</value>
|
||||
</data>
|
||||
<data name="AutoTellTabs_HistoryLoadError" xml:space="preserve">
|
||||
<value>History could not be loaded.</value>
|
||||
</data>
|
||||
<data name="AutoTellTabs_GreetedTooltip" xml:space="preserve">
|
||||
<value>Marked as greeted. Click to remove the marker.</value>
|
||||
</data>
|
||||
<data name="AutoTellTabs_UnGreetedTooltip" xml:space="preserve">
|
||||
<value>Mark as greeted.</value>
|
||||
</data>
|
||||
|
||||
<!-- Hellion Chat — Auto-Tell-Tabs (Chat settings tab) -->
|
||||
<data name="ChatLog_AutoTellTabs_Section_Title" xml:space="preserve">
|
||||
<value>Auto-Tell-Tabs</value>
|
||||
</data>
|
||||
<data name="ChatLog_AutoTellTabs_Enable_Name" xml:space="preserve">
|
||||
<value>Open a tab automatically for each tell partner</value>
|
||||
</data>
|
||||
<data name="ChatLog_AutoTellTabs_Enable_Description" xml:space="preserve">
|
||||
<value>When you receive or send a /tell, a temporary tab dedicated to that player is opened automatically. Tabs vanish on logout.</value>
|
||||
</data>
|
||||
<data name="ChatLog_AutoTellTabs_Limit_Name" xml:space="preserve">
|
||||
<value>Maximum number of auto tell tabs</value>
|
||||
</data>
|
||||
<data name="ChatLog_AutoTellTabs_Limit_Description" xml:space="preserve">
|
||||
<value>When the limit is reached, greeted tabs with the oldest activity are dropped first. The change applies on the next /tell.</value>
|
||||
</data>
|
||||
<data name="ChatLog_AutoTellTabs_Compact_Name" xml:space="preserve">
|
||||
<value>Compact display</value>
|
||||
</data>
|
||||
<data name="ChatLog_AutoTellTabs_Compact_Description" xml:space="preserve">
|
||||
<value>Show only a thin separator between persistent tabs and auto tell tabs, without the section header.</value>
|
||||
</data>
|
||||
<data name="ChatLog_AutoTellTabs_GreetedToggle_Name" xml:space="preserve">
|
||||
<value>Show "mark as greeted" button</value>
|
||||
</data>
|
||||
<data name="ChatLog_AutoTellTabs_GreetedToggle_Description" xml:space="preserve">
|
||||
<value>Adds a click-to-toggle button next to each auto tell tab to mark a partner as already greeted, dimming the tab name when set. Useful for club greeters tracking many parallel conversations; off by default.</value>
|
||||
</data>
|
||||
<data name="ChatLog_AutoTellTabs_PreloadHint" xml:space="preserve">
|
||||
<value>The number of preloaded tells is configured in the Privacy tab.</value>
|
||||
</data>
|
||||
<data name="ChatLog_AutoTellTabs_ConflictHint" xml:space="preserve">
|
||||
<value>Heads-up: if XIV Messanger or a similar plugin is suppressing direct messages, turn its "Suppress DMs" option off so Hellion Chat can receive tells and open the auto tabs.</value>
|
||||
</data>
|
||||
|
||||
<!-- Hellion Chat — Auto-Tell-Tabs (Privacy settings tab) -->
|
||||
<data name="Privacy_AutoTellTabs_Section_Title" xml:space="preserve">
|
||||
<value>Tell history in auto tabs</value>
|
||||
</data>
|
||||
<data name="Privacy_AutoTellTabs_Preload_Name" xml:space="preserve">
|
||||
<value>Number of preloaded tells</value>
|
||||
</data>
|
||||
<data name="Privacy_AutoTellTabs_Preload_Description" xml:space="preserve">
|
||||
<value>How many earlier tell messages are loaded from the database when an auto tell tab is opened. 0 disables the preload.</value>
|
||||
</data>
|
||||
<data name="Privacy_AutoTellTabs_Preload_Hint" xml:space="preserve">
|
||||
<value>Only takes effect when auto tell tabs are enabled in the Chat tab.</value>
|
||||
</data>
|
||||
|
||||
<!-- Hellion Chat — Settings UX Polish v10 wipe migration -->
|
||||
<data name="SettingsRefactor_Migration_Title" xml:space="preserve">
|
||||
<value>Settings reorganised</value>
|
||||
</data>
|
||||
<data name="SettingsRefactor_Migration_Content" xml:space="preserve">
|
||||
<value>Hellion Chat 0.5.0 reorganised the settings into themed tabs. Your chat database and your message history stay untouched. Settings have been reset to defaults; if you want to pick a privacy profile again, the reopen button is in the Privacy tab. A backup of your previous config is at HellionChat.json.pre-v10-backup next to the live config file.</value>
|
||||
</data>
|
||||
|
||||
<!-- Hellion Chat — Settings UX Polish 8-tab structure -->
|
||||
<data name="Settings_Tab_General" xml:space="preserve">
|
||||
<value>General</value>
|
||||
</data>
|
||||
<data name="Settings_Tab_Appearance" xml:space="preserve">
|
||||
<value>Appearance</value>
|
||||
</data>
|
||||
<data name="Settings_Tab_Window" xml:space="preserve">
|
||||
<value>Window</value>
|
||||
</data>
|
||||
<data name="Settings_Tab_Chat" xml:space="preserve">
|
||||
<value>Chat</value>
|
||||
</data>
|
||||
<data name="Settings_Tab_Tabs" xml:space="preserve">
|
||||
<value>Tabs</value>
|
||||
</data>
|
||||
<data name="Settings_Tab_Database" xml:space="preserve">
|
||||
<value>Database</value>
|
||||
</data>
|
||||
<data name="Settings_Tab_Information" xml:space="preserve">
|
||||
<value>Information</value>
|
||||
</data>
|
||||
|
||||
<!-- Hellion Chat — General-Tab section headings -->
|
||||
<data name="Settings_General_Input_Heading" xml:space="preserve">
|
||||
<value>Input</value>
|
||||
</data>
|
||||
<data name="Settings_General_Audio_Heading" xml:space="preserve">
|
||||
<value>Audio & Notifications</value>
|
||||
</data>
|
||||
<data name="Settings_General_Performance_Heading" xml:space="preserve">
|
||||
<value>Performance</value>
|
||||
</data>
|
||||
<data name="Settings_General_Language_Heading" xml:space="preserve">
|
||||
<value>Language & Input Helpers</value>
|
||||
</data>
|
||||
|
||||
<!-- Hellion Chat — Appearance-Tab section headings -->
|
||||
<data name="Settings_Appearance_Theme_Heading" xml:space="preserve">
|
||||
<value>Theme</value>
|
||||
</data>
|
||||
<data name="Settings_Appearance_Fonts_Heading" xml:space="preserve">
|
||||
<value>Fonts</value>
|
||||
</data>
|
||||
<data name="Settings_Appearance_Colours_Heading" xml:space="preserve">
|
||||
<value>Chat Colours</value>
|
||||
</data>
|
||||
<data name="Settings_Appearance_Timestamps_Heading" xml:space="preserve">
|
||||
<value>Timestamps</value>
|
||||
</data>
|
||||
|
||||
<!-- Hellion Chat — Window-Tab section headings -->
|
||||
<data name="Settings_Window_Hide_Heading" xml:space="preserve">
|
||||
<value>Hide</value>
|
||||
</data>
|
||||
<data name="Settings_Window_InactivityHide_Heading" xml:space="preserve">
|
||||
<value>Inactivity Hide</value>
|
||||
</data>
|
||||
<data name="Settings_Window_Frame_Heading" xml:space="preserve">
|
||||
<value>Window Frame</value>
|
||||
</data>
|
||||
<data name="Settings_Window_Tooltips_Heading" xml:space="preserve">
|
||||
<value>Tooltips</value>
|
||||
</data>
|
||||
|
||||
<!-- Hellion Chat — Chat-Tab section headings -->
|
||||
<data name="Settings_Chat_AutoTellTabs_Heading" xml:space="preserve">
|
||||
<value>Auto-Tell-Tabs</value>
|
||||
</data>
|
||||
<data name="Settings_Chat_Behaviour_Heading" xml:space="preserve">
|
||||
<value>Message Behaviour</value>
|
||||
</data>
|
||||
<data name="Settings_Chat_Preview_Heading" xml:space="preserve">
|
||||
<value>Preview</value>
|
||||
</data>
|
||||
<data name="Settings_Chat_Emotes_Heading" xml:space="preserve">
|
||||
<value>Emotes</value>
|
||||
</data>
|
||||
|
||||
<!-- Hellion Chat — Database-Tab section headings -->
|
||||
<data name="Settings_Database_Storage_Heading" xml:space="preserve">
|
||||
<value>Storage</value>
|
||||
</data>
|
||||
<data name="Settings_Database_Viewer_Heading" xml:space="preserve">
|
||||
<value>Overview</value>
|
||||
</data>
|
||||
<data name="Settings_Database_Stats_Heading" xml:space="preserve">
|
||||
<value>Maintenance</value>
|
||||
</data>
|
||||
|
||||
<!-- Hellion Chat — Information-Tab section headings -->
|
||||
<data name="Settings_Information_VersionInfo_Heading" xml:space="preserve">
|
||||
<value>Version Info</value>
|
||||
</data>
|
||||
<data name="Settings_Information_About_Heading" xml:space="preserve">
|
||||
<value>About HellionChat</value>
|
||||
</data>
|
||||
<data name="Settings_Information_Changelog_Heading" xml:space="preserve">
|
||||
<value>Changelog</value>
|
||||
</data>
|
||||
|
||||
<!-- Hellion Chat — Default tab presets (channel-themed) -->
|
||||
<data name="Tabs_Presets_System" xml:space="preserve">
|
||||
<value>System</value>
|
||||
</data>
|
||||
<data name="Tabs_Presets_FreeCompany" xml:space="preserve">
|
||||
<value>Free Company</value>
|
||||
</data>
|
||||
<data name="Tabs_Presets_Party" xml:space="preserve">
|
||||
<value>Party</value>
|
||||
</data>
|
||||
<data name="Tabs_Presets_Beginner" xml:space="preserve">
|
||||
<value>Beginner</value>
|
||||
</data>
|
||||
<data name="Tabs_Presets_Linkshell" xml:space="preserve">
|
||||
<value>Linkshell</value>
|
||||
</data>
|
||||
<data name="Tabs_Presets_Linkshell_Hint" xml:space="preserve">
|
||||
<value>If you use multiple linkshells, the maintainer recommends one tab per shell for cleaner readability. Duplicate this tab and narrow the channel selection per copy.</value>
|
||||
</data>
|
||||
</root>
|
||||
|
||||
@@ -451,7 +451,9 @@ public sealed class ChatLogWindow : Window
|
||||
Flags |= ImGuiWindowFlags.NoTitleBar;
|
||||
|
||||
if (LastViewport == ImGuiHelpers.MainViewport.Handle && !WasDocked)
|
||||
BgAlpha = Plugin.Config.WindowAlpha / 100f;
|
||||
BgAlpha = Plugin.Config.HellionThemeEnabled
|
||||
? Plugin.Config.HellionThemeWindowOpacity
|
||||
: Plugin.Config.WindowAlpha / 100f;
|
||||
|
||||
LastViewport = ImGui.GetWindowViewport().Handle;
|
||||
WasDocked = ImGui.IsWindowDocked();
|
||||
@@ -1188,7 +1190,13 @@ public sealed class ChatLogWindow : Window
|
||||
if (tab.DisplayTimestamp)
|
||||
{
|
||||
var localTime = message.Date.ToLocalTime();
|
||||
var timestamp = localTime.ToString("t", !Plugin.Config.Use24HourClock ? null : CultureInfo.CreateSpecificCulture("de-DE"));
|
||||
// Force the format explicitly per setting. Relying on the
|
||||
// current culture meant a German system locale always
|
||||
// produced 24h regardless of the toggle, so the checkbox
|
||||
// looked dead.
|
||||
var timestamp = Plugin.Config.Use24HourClock
|
||||
? localTime.ToString("HH:mm", CultureInfo.InvariantCulture)
|
||||
: localTime.ToString("h:mm tt", CultureInfo.InvariantCulture);
|
||||
if (isTable)
|
||||
{
|
||||
if (!Plugin.Config.HideSameTimestamps || timestamp != lastTimestamp)
|
||||
@@ -1303,14 +1311,90 @@ public sealed class ChatLogWindow : Window
|
||||
if (child)
|
||||
{
|
||||
var previousTab = Plugin.CurrentTab;
|
||||
// Hellion Chat — auto-tell-tabs section divider rendered
|
||||
// exactly once before the first temp tab, with a live unit
|
||||
// counter pulled directly from the tab list.
|
||||
var tempTabHeaderRendered = false;
|
||||
var tempTabCount = Plugin.Config.Tabs.Count(t => t.IsTempTab);
|
||||
|
||||
for (var tabI = 0; tabI < Plugin.Config.Tabs.Count; tabI++)
|
||||
{
|
||||
var tab = Plugin.Config.Tabs[tabI];
|
||||
if (tab.PopOut)
|
||||
continue;
|
||||
|
||||
if (tab.IsTempTab && !tempTabHeaderRendered)
|
||||
{
|
||||
ImGui.Separator();
|
||||
if (!Plugin.Config.AutoTellTabsCompactDisplay)
|
||||
{
|
||||
ImGui.TextDisabled($"{HellionStrings.AutoTellTabs_SectionHeader} ({tempTabCount})");
|
||||
}
|
||||
tempTabHeaderRendered = true;
|
||||
}
|
||||
|
||||
var unread = tabI == Plugin.LastTab || tab.UnreadMode == UnreadMode.None || tab.Unread == 0 ? "" : $" ({tab.Unread})";
|
||||
var clicked = ImGui.Selectable($"{tab.Name}{unread}###log-tab-{tabI}", Plugin.LastTab == tabI || Plugin.WantedTab == tabI);
|
||||
var selectableLabel = $"{tab.Name}{unread}###log-tab-{tabI}";
|
||||
var isCurrentTab = Plugin.LastTab == tabI || Plugin.WantedTab == tabI;
|
||||
|
||||
var showGreetedAffordance = tab.IsTempTab && Plugin.Config.AutoTellTabsShowGreetedToggle;
|
||||
|
||||
if (showGreetedAffordance)
|
||||
{
|
||||
// Greeted toggle sits left of the selectable so the
|
||||
// click areas stay separate. The icon also doubles
|
||||
// as the visual "I'm done with this person" cue.
|
||||
// Compact frame padding keeps the icon dezent next
|
||||
// to the tab name instead of a chunky button block.
|
||||
var greetedIcon = tab.IsGreeted ? FontAwesomeIcon.CheckCircle : FontAwesomeIcon.Check;
|
||||
var greetedTooltip = tab.IsGreeted
|
||||
? HellionStrings.AutoTellTabs_GreetedTooltip
|
||||
: HellionStrings.AutoTellTabs_UnGreetedTooltip;
|
||||
|
||||
using (ImRaii.PushStyle(ImGuiStyleVar.FramePadding, new Vector2(2, 1)))
|
||||
using (ImRaii.PushColor(ImGuiCol.Button, 0))
|
||||
{
|
||||
if (ImGuiUtil.IconButton(greetedIcon, $"greeted-{tabI}", greetedTooltip))
|
||||
{
|
||||
if (tab.IsGreeted)
|
||||
{
|
||||
Plugin.AutoTellTabsService.UnmarkGreeted(tab);
|
||||
}
|
||||
else
|
||||
{
|
||||
Plugin.AutoTellTabsService.MarkGreeted(tab);
|
||||
}
|
||||
}
|
||||
}
|
||||
ImGui.SameLine();
|
||||
}
|
||||
|
||||
bool clicked;
|
||||
if (showGreetedAffordance && tab.IsGreeted)
|
||||
{
|
||||
// Dim the tab name once the user marked the partner
|
||||
// as greeted, so a glance at the sidebar tells them
|
||||
// who still needs attention. Selectable has no idle
|
||||
// background slot in ImGui, so the dim only applies
|
||||
// to the selected and hovered states — the text dim
|
||||
// alone signals greeted in the idle state.
|
||||
var headerBase = ImGui.GetColorU32(ImGuiCol.Header);
|
||||
var hoverBase = ImGui.GetColorU32(ImGuiCol.HeaderHovered);
|
||||
var dimHeader = (headerBase & 0xFF000000u) | ((headerBase & 0x00FEFEFEu) >> 1);
|
||||
var dimHover = (hoverBase & 0xFF000000u) | ((hoverBase & 0x00FEFEFEu) >> 1);
|
||||
|
||||
using (ImRaii.PushColor(ImGuiCol.Text, ImGui.GetColorU32(ImGuiCol.TextDisabled)))
|
||||
using (ImRaii.PushColor(ImGuiCol.Header, dimHeader))
|
||||
using (ImRaii.PushColor(ImGuiCol.HeaderHovered, dimHover))
|
||||
{
|
||||
clicked = ImGui.Selectable(selectableLabel, isCurrentTab);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
clicked = ImGui.Selectable(selectableLabel, isCurrentTab);
|
||||
}
|
||||
|
||||
DrawTabContextMenu(tab, tabI);
|
||||
|
||||
if (!clicked && Plugin.WantedTab != tabI)
|
||||
|
||||
+10
-2
@@ -70,8 +70,16 @@ internal class Popout : Window
|
||||
|
||||
if (!ChatLogWindow.PopOutDocked[Idx])
|
||||
{
|
||||
var alpha = Tab.IndependentOpacity ? Tab.Opacity : Plugin.Config.WindowAlpha;
|
||||
BgAlpha = alpha / 100f;
|
||||
if (Tab.IndependentOpacity)
|
||||
{
|
||||
BgAlpha = Tab.Opacity / 100f;
|
||||
}
|
||||
else
|
||||
{
|
||||
BgAlpha = Plugin.Config.HellionThemeEnabled
|
||||
? Plugin.Config.HellionThemeWindowOpacity
|
||||
: Plugin.Config.WindowAlpha / 100f;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+13
-15
@@ -9,7 +9,7 @@ using Dalamud.Bindings.ImGui;
|
||||
|
||||
namespace ChatTwo.Ui;
|
||||
|
||||
public sealed class SettingsWindow : Window
|
||||
public sealed class SettingsWindow : Dalamud.Interface.Windowing.Window
|
||||
{
|
||||
private readonly Plugin Plugin;
|
||||
|
||||
@@ -33,18 +33,14 @@ public sealed class SettingsWindow : Window
|
||||
|
||||
Tabs =
|
||||
[
|
||||
new Display(Mutable),
|
||||
new ChatLog(Plugin, Mutable),
|
||||
new Emote(Plugin, Mutable),
|
||||
new Preview(Mutable),
|
||||
new Fonts(Mutable),
|
||||
new ChatColours(Plugin, Mutable),
|
||||
new Tabs(Plugin, Mutable),
|
||||
new General(Plugin, Mutable),
|
||||
new Appearance(Plugin, Mutable),
|
||||
new SettingsTabs.Window(Plugin, Mutable),
|
||||
new Chat(Plugin, Mutable),
|
||||
new SettingsTabs.Tabs(Plugin, Mutable),
|
||||
new SettingsTabs.Privacy(Plugin, Mutable),
|
||||
new Database(Plugin, Mutable),
|
||||
new Miscellaneous(Mutable),
|
||||
new Changelog(Mutable),
|
||||
new About()
|
||||
new Information(Mutable),
|
||||
];
|
||||
|
||||
RespectCloseHotkey = false;
|
||||
@@ -114,14 +110,16 @@ public sealed class SettingsWindow : Window
|
||||
|
||||
ImGui.SameLine();
|
||||
|
||||
if (ImGui.Button(Language.Settings_SaveAndClose)) {
|
||||
if (ImGui.Button(Language.Settings_SaveAndClose))
|
||||
{
|
||||
save = true;
|
||||
IsOpen = false;
|
||||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
|
||||
if (ImGui.Button(Language.Settings_Discard)) {
|
||||
if (ImGui.Button(Language.Settings_Discard))
|
||||
{
|
||||
IsOpen = false;
|
||||
}
|
||||
|
||||
@@ -135,7 +133,7 @@ public sealed class SettingsWindow : Window
|
||||
{
|
||||
var buttonWidth = ImGui.CalcTextSize(buttonLabel).X + ImGui.GetStyle().FramePadding.X * 2;
|
||||
var buttonWidth2 = ImGui.CalcTextSize(buttonLabel2).X + ImGui.GetStyle().FramePadding.X * 2;
|
||||
ImGui.SameLine(ImGui.GetContentRegionAvail().X - buttonWidth - buttonWidth2);
|
||||
ImGui.SameLine(ImGui.GetContentRegionAvail().X - buttonWidth - buttonWidth2 - ImGui.GetStyle().ItemSpacing.X);
|
||||
|
||||
if (ImGui.Button(buttonLabel2))
|
||||
Dalamud.Utility.Util.OpenLink("https://ko-fi.com/infiii");
|
||||
@@ -179,7 +177,7 @@ public sealed class SettingsWindow : Window
|
||||
GameFunctions.GameFunctions.SetChatInteractable(true);
|
||||
|
||||
if (Plugin.Config.ShowEmotes)
|
||||
Task.Run(EmoteCache.LoadData);
|
||||
_ = EmoteCache.LoadData(); // Fire-and-forget intentional, exceptions are caught inside
|
||||
|
||||
Initialise();
|
||||
}
|
||||
|
||||
@@ -1,128 +0,0 @@
|
||||
using System.Numerics;
|
||||
using ChatTwo.Resources;
|
||||
using ChatTwo.Util;
|
||||
using Dalamud.Interface;
|
||||
using Dalamud.Interface.Colors;
|
||||
using Dalamud.Interface.Utility;
|
||||
using Dalamud.Interface.Utility.Raii;
|
||||
using Dalamud.Bindings.ImGui;
|
||||
|
||||
namespace ChatTwo.Ui.SettingsTabs;
|
||||
|
||||
internal sealed class About : ISettingsTab
|
||||
{
|
||||
public string Name => string.Format(Language.Options_About_Tab, Plugin.PluginName) + "###tabs-about";
|
||||
|
||||
private readonly List<string> Translators =
|
||||
[
|
||||
"q673135110", "Akizem", "d0tiKs",
|
||||
"Moonlight_Everlit", "Dark32", "andreycout",
|
||||
"Button_", "Cali666", "cassandra308",
|
||||
"lokinmodar", "jtabox", "AkiraYorumoto",
|
||||
"MKhayle", "elena.space", "imlisa",
|
||||
"andrei5125", "ShivaMaheshvara", "aislinn87",
|
||||
"nishinatsu051", "lichuyuan", "Risu64",
|
||||
"yummypillow", "witchymary", "Yuzumi",
|
||||
"zomsakura", "Sirayuki"
|
||||
];
|
||||
|
||||
internal About()
|
||||
{
|
||||
Translators.Sort((a, b) => string.Compare(a.ToLowerInvariant(), b.ToLowerInvariant(), StringComparison.Ordinal));
|
||||
}
|
||||
|
||||
public void Draw(bool changed)
|
||||
{
|
||||
using var wrap = ImRaii.TextWrapPos(0.0f);
|
||||
|
||||
ImGui.TextUnformatted(string.Format(Language.Options_About_Opening, Plugin.PluginName));
|
||||
|
||||
ImGuiHelpers.ScaledDummy(10.0f);
|
||||
|
||||
ImGui.TextUnformatted(Language.Options_About_Authors);
|
||||
ImGui.SameLine();
|
||||
ImGui.TextColored(ImGuiColors.ParsedGold, Plugin.Interface.Manifest.Author);
|
||||
|
||||
ImGui.TextUnformatted(Language.Options_About_Discord);
|
||||
ImGui.SameLine();
|
||||
ImGui.TextColored(ImGuiColors.ParsedGold, "@j.j_kazama");
|
||||
|
||||
ImGui.TextUnformatted(Language.Options_About_Version);
|
||||
ImGui.SameLine();
|
||||
ImGui.TextColored(ImGuiColors.ParsedOrange, Plugin.Interface.Manifest.AssemblyVersion.ToString(3));
|
||||
|
||||
ImGuiHelpers.ScaledDummy(10.0f);
|
||||
|
||||
ImGui.TextUnformatted(Language.Options_About_Github_Issues);
|
||||
ImGui.SameLine();
|
||||
if (ImGuiUtil.IconButton(FontAwesomeIcon.ExternalLinkAlt, "githubIssues"))
|
||||
Dalamud.Utility.Util.OpenLink("https://github.com/JonKazama-Hellion/HellionChat/issues");
|
||||
|
||||
ImGuiHelpers.ScaledDummy(10.0f);
|
||||
|
||||
ImGui.TextColored(ImGuiColors.ParsedGold, HellionStrings.About_Maintainer_Heading);
|
||||
ImGui.TextUnformatted(HellionStrings.About_Maintainer_Body);
|
||||
ImGui.TextUnformatted(HellionStrings.About_Maintainer_Website_Label);
|
||||
ImGui.SameLine();
|
||||
if (ImGuiUtil.IconButton(FontAwesomeIcon.ExternalLinkAlt, "hellionMedia"))
|
||||
Dalamud.Utility.Util.OpenLink("https://hellion-media.de");
|
||||
|
||||
ImGuiHelpers.ScaledDummy(10.0f);
|
||||
|
||||
ImGui.TextColored(ImGuiColors.ParsedGold, HellionStrings.About_Mission_Heading);
|
||||
ImGui.TextUnformatted(HellionStrings.About_Mission_P1);
|
||||
ImGui.Spacing();
|
||||
ImGui.TextUnformatted(HellionStrings.About_Mission_P2);
|
||||
ImGui.Spacing();
|
||||
ImGui.TextUnformatted(HellionStrings.About_Mission_P3);
|
||||
|
||||
ImGuiHelpers.ScaledDummy(10.0f);
|
||||
|
||||
ImGui.TextColored(ImGuiColors.ParsedGold, HellionStrings.About_BuiltOn_Heading);
|
||||
ImGui.TextUnformatted(HellionStrings.About_BuiltOn_P1);
|
||||
ImGui.Spacing();
|
||||
ImGui.TextUnformatted(HellionStrings.About_BuiltOn_P2);
|
||||
ImGui.Spacing();
|
||||
ImGui.TextUnformatted(HellionStrings.About_BuiltOn_Upstream_Label);
|
||||
ImGui.SameLine();
|
||||
if (ImGuiUtil.IconButton(FontAwesomeIcon.ExternalLinkAlt, "chatTwoUpstream"))
|
||||
Dalamud.Utility.Util.OpenLink("https://github.com/Infiziert90/ChatTwo");
|
||||
|
||||
ImGuiHelpers.ScaledDummy(10.0f);
|
||||
|
||||
ImGui.TextColored(ImGuiColors.ParsedGold, HellionStrings.About_License_Heading);
|
||||
ImGui.TextUnformatted(HellionStrings.About_License_P1);
|
||||
ImGui.TextUnformatted(HellionStrings.About_License_P2);
|
||||
ImGui.TextUnformatted(HellionStrings.About_License_P3);
|
||||
|
||||
ImGuiHelpers.ScaledDummy(10.0f);
|
||||
|
||||
ImGui.TextColored(ImGuiColors.DalamudOrange, HellionStrings.About_SE_Heading);
|
||||
ImGui.TextUnformatted(HellionStrings.About_SE_P1);
|
||||
ImGui.TextUnformatted(HellionStrings.About_SE_P2);
|
||||
|
||||
ImGui.Spacing();
|
||||
|
||||
ImGui.TextColored(ImGuiColors.ParsedGold, HellionStrings.About_Localization_Heading);
|
||||
ImGui.TextUnformatted(HellionStrings.About_Localization_P1);
|
||||
ImGui.TextUnformatted(HellionStrings.About_Localization_P2);
|
||||
|
||||
ImGui.Spacing();
|
||||
|
||||
// The translator list lives at the bottom of the About tab. Render
|
||||
// it directly inside the parent scroll container instead of a
|
||||
// fixed-height child — the previous "remaining space" calculation
|
||||
// shrank to zero (or below) once the About copy grew, which made
|
||||
// the section unreachable on smaller settings windows.
|
||||
using (var treeNode = ImRaii.TreeNode(HellionStrings.About_Translators_TreeNode))
|
||||
{
|
||||
if (treeNode)
|
||||
{
|
||||
using var indent = ImRaii.PushIndent(ImGui.GetStyle().IndentSpacing, false);
|
||||
foreach (var translator in Translators)
|
||||
ImGui.TextUnformatted(translator);
|
||||
}
|
||||
}
|
||||
ImGui.Spacing();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,292 @@
|
||||
using ChatTwo.Code;
|
||||
using ChatTwo.Resources;
|
||||
using ChatTwo.Util;
|
||||
using Dalamud;
|
||||
using Dalamud.Interface;
|
||||
using Dalamud.Interface.FontIdentifier;
|
||||
using Dalamud.Interface.Style;
|
||||
using Dalamud.Interface.Utility;
|
||||
using Dalamud.Interface.Utility.Raii;
|
||||
using Dalamud.Bindings.ImGui;
|
||||
|
||||
namespace ChatTwo.Ui.SettingsTabs;
|
||||
|
||||
internal sealed class Appearance : ISettingsTab
|
||||
{
|
||||
private Plugin Plugin { get; }
|
||||
private Configuration Mutable { get; }
|
||||
|
||||
public string Name => HellionStrings.Settings_Tab_Appearance + "###tabs-appearance";
|
||||
|
||||
internal Appearance(Plugin plugin, Configuration mutable)
|
||||
{
|
||||
Plugin = plugin;
|
||||
Mutable = mutable;
|
||||
}
|
||||
|
||||
public void Draw(bool changed)
|
||||
{
|
||||
DrawThemeSection();
|
||||
ImGui.Spacing();
|
||||
DrawFontsSection();
|
||||
ImGui.Spacing();
|
||||
DrawColoursSection();
|
||||
ImGui.Spacing();
|
||||
DrawTimestampsSection();
|
||||
}
|
||||
|
||||
private void DrawThemeSection()
|
||||
{
|
||||
using var tree = ImRaii.TreeNode(HellionStrings.Settings_Appearance_Theme_Heading);
|
||||
if (!tree.Success)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
using (ImRaii.PushIndent(ImGui.GetStyle().IndentSpacing, false))
|
||||
{
|
||||
ImGui.Checkbox(HellionStrings.Theme_Enabled_Name, ref Mutable.HellionThemeEnabled);
|
||||
ImGuiUtil.HelpMarker(HellionStrings.Theme_Enabled_Description);
|
||||
|
||||
// Clamp 0.5–1.0 stays consistent with Privacy.cs which already
|
||||
// shipped this slider; lower values would let chat windows
|
||||
// disappear behind game UI.
|
||||
using (ImRaii.Disabled(!Mutable.HellionThemeEnabled))
|
||||
{
|
||||
ImGui.SetNextItemWidth(200f * ImGuiHelpers.GlobalScale);
|
||||
var opacity = Mutable.HellionThemeWindowOpacity;
|
||||
if (ImGui.SliderFloat($"{HellionStrings.Theme_WindowOpacity_Label}##theme-opacity", ref opacity, 0.5f, 1.0f, "%.2f"))
|
||||
{
|
||||
Mutable.HellionThemeWindowOpacity = Math.Clamp(opacity, 0.5f, 1.0f);
|
||||
}
|
||||
ImGuiUtil.HelpMarker(HellionStrings.Theme_WindowOpacity_Help);
|
||||
}
|
||||
|
||||
ImGui.Spacing();
|
||||
|
||||
ImGui.Checkbox(Language.Options_OverrideStyle_Name, ref Mutable.OverrideStyle);
|
||||
ImGuiUtil.HelpMarker(Language.Options_OverrideStyle_Name_Desc);
|
||||
|
||||
if (Mutable.OverrideStyle)
|
||||
{
|
||||
DrawStyleCombo();
|
||||
}
|
||||
|
||||
// The Bestand-Slider WindowAlpha targets the chat log window's
|
||||
// background only. The Hellion theme opacity above already covers
|
||||
// every plugin window globally, so the two sliders fight each
|
||||
// other when the theme is active. Disable the legacy slider in
|
||||
// that case to make Hellion theme the single source of truth.
|
||||
using (ImRaii.Disabled(Mutable.HellionThemeEnabled))
|
||||
{
|
||||
ImGuiUtil.DragFloatVertical(Language.Options_WindowOpacity_Name, ref Mutable.WindowAlpha, .25f, 0f, 100f, $"{Mutable.WindowAlpha:N2}%%", ImGuiSliderFlags.AlwaysClamp);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawStyleCombo()
|
||||
{
|
||||
var styles = StyleModel.GetConfiguredStyles();
|
||||
if (styles == null)
|
||||
{
|
||||
ImGui.TextUnformatted(Language.Options_OverrideStyle_NotAvailable);
|
||||
return;
|
||||
}
|
||||
|
||||
var currentStyle = Mutable.ChosenStyle ?? Language.Options_OverrideStyle_NotSelected;
|
||||
using var combo = ImRaii.Combo(Language.Options_OverrideStyleDropdown_Name, currentStyle);
|
||||
if (!combo)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var style in styles)
|
||||
{
|
||||
if (ImGui.Selectable(style.Name, Mutable.ChosenStyle == style.Name))
|
||||
{
|
||||
Mutable.ChosenStyle = style.Name;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawFontsSection()
|
||||
{
|
||||
using var tree = ImRaii.TreeNode(HellionStrings.Settings_Appearance_Fonts_Heading);
|
||||
if (!tree.Success)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
using (ImRaii.PushIndent(ImGui.GetStyle().IndentSpacing, false))
|
||||
{
|
||||
if (ImGui.Checkbox(HellionStrings.Theme_UseHellionFont_Name, ref Mutable.UseHellionFont))
|
||||
{
|
||||
// Mutex with the Bestand custom-font stack. Leaving FontsEnabled
|
||||
// checked alongside UseHellionFont made both checkboxes look
|
||||
// active even though the lower stack was greyed out, which
|
||||
// confused the user during the v0.5.0 walkthrough.
|
||||
if (Mutable.UseHellionFont)
|
||||
Mutable.FontsEnabled = false;
|
||||
}
|
||||
ImGuiUtil.HelpMarker(HellionStrings.Theme_UseHellionFont_Description);
|
||||
ImGui.Spacing();
|
||||
|
||||
using var fontDisabled = ImRaii.Disabled(Mutable.UseHellionFont);
|
||||
|
||||
ImGui.Checkbox(Language.Options_FontsEnabled, ref Mutable.FontsEnabled);
|
||||
ImGui.Spacing();
|
||||
|
||||
var unused = false;
|
||||
if (!Mutable.FontsEnabled)
|
||||
{
|
||||
ImGuiUtil.FontSizeCombo(Language.Options_FontSize_Name, ref Mutable.FontSizeV2);
|
||||
}
|
||||
else
|
||||
{
|
||||
var globalChooser = ImGuiUtil.FontChooser(Language.Options_Font_Name, Mutable.GlobalFontV2, false, ref unused);
|
||||
globalChooser?.ResultTask.ContinueWith(r =>
|
||||
{
|
||||
if (r.IsCompletedSuccessfully)
|
||||
{
|
||||
Plugin.Framework.Run(() => Mutable.GlobalFontV2 = r.Result);
|
||||
}
|
||||
});
|
||||
ImGui.SameLine();
|
||||
if (ImGui.Button("Reset##global"))
|
||||
{
|
||||
Mutable.GlobalFontV2 = new SingleFontSpec { FontId = new DalamudAssetFontAndFamilyId(DalamudAsset.NotoSansCjkRegular), SizePt = 12.75f };
|
||||
}
|
||||
|
||||
ImGuiUtil.HelpMarker(string.Format(Language.Options_Font_Description, Plugin.PluginName));
|
||||
ImGuiUtil.WarningText(Language.Options_Font_Warning);
|
||||
ImGui.Spacing();
|
||||
|
||||
// LocaleNames being null means it is likely a game font which all support JP symbols.
|
||||
var japaneseChooser = ImGuiUtil.FontChooser(Language.Options_JapaneseFont_Name, Mutable.JapaneseFontV2, false, ref unused, id => !id.LocaleNames?.ContainsKey("ja-jp") ?? false, "いろはにほへと ちりぬるを");
|
||||
japaneseChooser?.ResultTask.ContinueWith(r =>
|
||||
{
|
||||
if (r.IsCompletedSuccessfully)
|
||||
{
|
||||
Plugin.Framework.Run(() => Mutable.JapaneseFontV2 = r.Result);
|
||||
}
|
||||
});
|
||||
ImGui.SameLine();
|
||||
if (ImGui.Button("Reset##japanese"))
|
||||
{
|
||||
Mutable.JapaneseFontV2 = new SingleFontSpec { FontId = new DalamudAssetFontAndFamilyId(DalamudAsset.NotoSansCjkMedium), SizePt = 12.75f };
|
||||
}
|
||||
|
||||
ImGuiUtil.HelpMarker(string.Format(Language.Options_JapaneseFont_Description, Plugin.PluginName));
|
||||
ImGui.Spacing();
|
||||
|
||||
var italicChooser = ImGuiUtil.FontChooser(Language.Options_ItalicFont_Name, Mutable.ItalicFontV2, true, ref Mutable.ItalicEnabled);
|
||||
italicChooser?.ResultTask.ContinueWith(r =>
|
||||
{
|
||||
if (r.IsCompletedSuccessfully)
|
||||
{
|
||||
Plugin.Framework.Run(() => Mutable.ItalicFontV2 = r.Result);
|
||||
}
|
||||
});
|
||||
ImGui.SameLine();
|
||||
if (ImGui.Button("Reset##italic"))
|
||||
{
|
||||
Mutable.ItalicEnabled = false;
|
||||
Mutable.ItalicFontV2 = new SingleFontSpec { FontId = new DalamudAssetFontAndFamilyId(DalamudAsset.NotoSansCjkRegular), SizePt = 12.75f };
|
||||
}
|
||||
|
||||
ImGuiUtil.HelpMarker(string.Format(Language.Options_Italic_Description, Plugin.PluginName));
|
||||
ImGui.Spacing();
|
||||
|
||||
if (ImGui.CollapsingHeader(Language.Options_ExtraGlyphs_Name))
|
||||
{
|
||||
ImGuiUtil.HelpMarker(string.Format(Language.Options_ExtraGlyphs_Description, Plugin.PluginName));
|
||||
|
||||
var range = (int)Mutable.ExtraGlyphRanges;
|
||||
foreach (var extra in Enum.GetValues<ExtraGlyphRanges>())
|
||||
{
|
||||
ImGui.CheckboxFlags(extra.Name(), ref range, (int)extra);
|
||||
}
|
||||
|
||||
Mutable.ExtraGlyphRanges = (ExtraGlyphRanges)range;
|
||||
}
|
||||
|
||||
ImGui.Spacing();
|
||||
}
|
||||
|
||||
ImGuiUtil.FontSizeCombo(Language.Options_SymbolsFontSize_Name, ref Mutable.SymbolsFontSizeV2);
|
||||
ImGuiUtil.HelpMarker(Language.Options_SymbolsFontSize_Description);
|
||||
|
||||
ImGui.Spacing();
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawColoursSection()
|
||||
{
|
||||
using var tree = ImRaii.TreeNode(HellionStrings.Settings_Appearance_Colours_Heading);
|
||||
if (!tree.Success)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
using (ImRaii.PushIndent(ImGui.GetStyle().IndentSpacing, false))
|
||||
{
|
||||
foreach (var (_, types) in ChatTypeExt.SortOrder)
|
||||
{
|
||||
foreach (var type in types)
|
||||
{
|
||||
if (ImGuiUtil.IconButton(FontAwesomeIcon.UndoAlt, $"{type}", Language.Options_ChatColours_Reset))
|
||||
{
|
||||
Mutable.ChatColours.Remove(type);
|
||||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
|
||||
if (ImGuiUtil.IconButton(FontAwesomeIcon.LongArrowAltDown, $"{type}", Language.Options_ChatColours_Import))
|
||||
{
|
||||
var gameColour = Plugin.Functions.Chat.GetChannelColor(type);
|
||||
Mutable.ChatColours[type] = gameColour ?? type.DefaultColor() ?? 0;
|
||||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
|
||||
var vec = Mutable.ChatColours.TryGetValue(type, out var colour)
|
||||
? ColourUtil.RgbaToVector3(colour)
|
||||
: ColourUtil.RgbaToVector3(type.DefaultColor() ?? 0);
|
||||
if (ImGui.ColorEdit3(type.Name(), ref vec, ImGuiColorEditFlags.NoInputs))
|
||||
{
|
||||
Mutable.ChatColours[type] = ColourUtil.Vector3ToRgba(vec);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ImGui.Spacing();
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawTimestampsSection()
|
||||
{
|
||||
using var tree = ImRaii.TreeNode(HellionStrings.Settings_Appearance_Timestamps_Heading);
|
||||
if (!tree.Success)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
using (ImRaii.PushIndent(ImGui.GetStyle().IndentSpacing, false))
|
||||
{
|
||||
ImGui.Checkbox(Language.Options_PrettierTimestamps_Name, ref Mutable.PrettierTimestamps);
|
||||
ImGuiUtil.HelpMarker(Language.Options_PrettierTimestamps_Description);
|
||||
|
||||
if (Mutable.PrettierTimestamps)
|
||||
{
|
||||
ImGui.Checkbox(Language.Options_MoreCompactPretty_Name, ref Mutable.MoreCompactPretty);
|
||||
ImGuiUtil.HelpMarker(Language.Options_MoreCompactPretty_Description);
|
||||
|
||||
ImGui.Checkbox(Language.Options_HideSameTimestamps_Name, ref Mutable.HideSameTimestamps);
|
||||
ImGuiUtil.HelpMarker(Language.Options_HideSameTimestamps_Description);
|
||||
}
|
||||
|
||||
ImGui.Checkbox(Language.Options_Use24HourClock_Name, ref Mutable.Use24HourClock);
|
||||
ImGuiUtil.HelpMarker(Language.Options_Use24HourClock_Description);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,51 +0,0 @@
|
||||
using ChatTwo.Resources;
|
||||
using ChatTwo.Util;
|
||||
using Dalamud.Interface.Utility.Raii;
|
||||
using Dalamud.Bindings.ImGui;
|
||||
|
||||
namespace ChatTwo.Ui.SettingsTabs;
|
||||
|
||||
internal sealed class Changelog : ISettingsTab
|
||||
{
|
||||
private Configuration Mutable { get; }
|
||||
|
||||
public string Name => Language.Options_Changelog_Tab + "###tabs-changelog";
|
||||
|
||||
internal Changelog(Configuration mutable)
|
||||
{
|
||||
Mutable = mutable;
|
||||
}
|
||||
|
||||
public void Draw(bool changed)
|
||||
{
|
||||
using var wrap = ImRaii.TextWrapPos(0.0f);
|
||||
|
||||
ImGui.TextUnformatted(Language.Options_Warning_NotImplemented);
|
||||
ImGuiUtil.OptionCheckbox(ref Mutable.PrintChangelog, Language.Options_PrintChangelog_Name, Language.Options_PrintChangelog_Description);
|
||||
|
||||
ImGui.Spacing();
|
||||
ImGui.Separator();
|
||||
ImGui.Spacing();
|
||||
|
||||
var changelog = Plugin.Interface.Manifest.Changelog;
|
||||
if (changelog != null)
|
||||
{
|
||||
ImGui.TextUnformatted(Language.Options_Changelog_Header);
|
||||
ImGui.TextUnformatted($"Version {Plugin.Interface.Manifest.AssemblyVersion.ToString(3)}");
|
||||
ImGui.Spacing();
|
||||
foreach (var sentence in changelog.Split("\n"))
|
||||
{
|
||||
if (sentence == string.Empty)
|
||||
{
|
||||
ImGui.NewLine();
|
||||
continue;
|
||||
}
|
||||
|
||||
var condition = sentence.StartsWith('-') || sentence.StartsWith(" -");
|
||||
using var indent = ImRaii.PushIndent(10.0f, true, condition);
|
||||
ImGui.TextUnformatted(sentence);
|
||||
}
|
||||
}
|
||||
ImGui.Spacing();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,234 @@
|
||||
using System.Numerics;
|
||||
using ChatTwo.Resources;
|
||||
using ChatTwo.Util;
|
||||
using Dalamud.Interface;
|
||||
using Dalamud.Interface.Colors;
|
||||
using Dalamud.Interface.Utility;
|
||||
using Dalamud.Interface.Utility.Raii;
|
||||
using Dalamud.Bindings.ImGui;
|
||||
|
||||
namespace ChatTwo.Ui.SettingsTabs;
|
||||
|
||||
// Chat-Tab — vier eigenständige Sektionen: Auto-Tell-Tabs, Behaviour,
|
||||
// Preview, Emotes. Der Emotes-Block ist 1:1 aus der Bestand-Datei
|
||||
// Emote.cs übernommen; die Datei wird in Plan-Task 11 (Settings UX
|
||||
// Polish v0.5.0) entfernt, sobald alle Tabs migriert sind.
|
||||
internal sealed class Chat : ISettingsTab
|
||||
{
|
||||
private Plugin Plugin { get; }
|
||||
private Configuration Mutable { get; }
|
||||
|
||||
public string Name => HellionStrings.Settings_Tab_Chat + "###tabs-chat";
|
||||
|
||||
private SearchSelector.SelectorPopupOptions WordPopupOptions;
|
||||
|
||||
internal Chat(Plugin plugin, Configuration mutable)
|
||||
{
|
||||
Plugin = plugin;
|
||||
Mutable = mutable;
|
||||
|
||||
WordPopupOptions = RefillSheet();
|
||||
}
|
||||
|
||||
private SearchSelector.SelectorPopupOptions RefillSheet()
|
||||
{
|
||||
return new SearchSelector.SelectorPopupOptions
|
||||
{
|
||||
FilteredSheet = EmoteCache.SortedCodeArray.Where(w => !Mutable.BlockedEmotes.Contains(w)).ToArray()
|
||||
};
|
||||
}
|
||||
|
||||
public void Draw(bool changed)
|
||||
{
|
||||
DrawAutoTellTabsSection();
|
||||
ImGui.Spacing();
|
||||
DrawBehaviourSection();
|
||||
ImGui.Spacing();
|
||||
DrawPreviewSection();
|
||||
ImGui.Spacing();
|
||||
DrawEmotesSection();
|
||||
}
|
||||
|
||||
private void DrawAutoTellTabsSection()
|
||||
{
|
||||
using var tree = ImRaii.TreeNode(HellionStrings.Settings_Chat_AutoTellTabs_Heading);
|
||||
if (!tree.Success)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
using (ImRaii.PushIndent(ImGui.GetStyle().IndentSpacing, false))
|
||||
{
|
||||
ImGui.Checkbox(HellionStrings.ChatLog_AutoTellTabs_Enable_Name, ref Mutable.EnableAutoTellTabs);
|
||||
ImGuiUtil.HelpMarker(HellionStrings.ChatLog_AutoTellTabs_Enable_Description);
|
||||
|
||||
ImGui.SetNextItemWidth(200f * ImGuiHelpers.GlobalScale);
|
||||
var limit = Mutable.AutoTellTabsLimit;
|
||||
if (ImGui.SliderInt(HellionStrings.ChatLog_AutoTellTabs_Limit_Name, ref limit, 1, 50))
|
||||
{
|
||||
Mutable.AutoTellTabsLimit = limit;
|
||||
}
|
||||
ImGuiUtil.HelpMarker(HellionStrings.ChatLog_AutoTellTabs_Limit_Description);
|
||||
|
||||
ImGui.Checkbox(HellionStrings.ChatLog_AutoTellTabs_Compact_Name, ref Mutable.AutoTellTabsCompactDisplay);
|
||||
ImGuiUtil.HelpMarker(HellionStrings.ChatLog_AutoTellTabs_Compact_Description);
|
||||
|
||||
ImGui.Checkbox(HellionStrings.ChatLog_AutoTellTabs_GreetedToggle_Name, ref Mutable.AutoTellTabsShowGreetedToggle);
|
||||
ImGuiUtil.HelpMarker(HellionStrings.ChatLog_AutoTellTabs_GreetedToggle_Description);
|
||||
|
||||
ImGui.Spacing();
|
||||
ImGuiUtil.HelpText(HellionStrings.ChatLog_AutoTellTabs_PreloadHint);
|
||||
|
||||
ImGui.Spacing();
|
||||
ImGuiUtil.WarningText(HellionStrings.ChatLog_AutoTellTabs_ConflictHint);
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawBehaviourSection()
|
||||
{
|
||||
using var tree = ImRaii.TreeNode(HellionStrings.Settings_Chat_Behaviour_Heading);
|
||||
if (!tree.Success)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
using (ImRaii.PushIndent(ImGui.GetStyle().IndentSpacing, false))
|
||||
{
|
||||
ImGui.Checkbox(Language.Options_CollapseDuplicateMessages_Name, ref Mutable.CollapseDuplicateMessages);
|
||||
ImGuiUtil.HelpMarker(Language.Options_CollapseDuplicateMessages_Description);
|
||||
|
||||
if (Mutable.CollapseDuplicateMessages)
|
||||
{
|
||||
ImGui.Checkbox(Language.Options_CollapseDuplicateMsgUniqueLink_Name, ref Mutable.CollapseKeepUniqueLinks);
|
||||
ImGuiUtil.HelpMarker(Language.Options_CollapseDuplicateMsgUniqueLink_Description);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawPreviewSection()
|
||||
{
|
||||
using var tree = ImRaii.TreeNode(HellionStrings.Settings_Chat_Preview_Heading);
|
||||
if (!tree.Success)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
using (ImRaii.PushIndent(ImGui.GetStyle().IndentSpacing, false))
|
||||
{
|
||||
using (var combo = ImGuiUtil.BeginComboVertical(Language.Options_Preview_Name, Mutable.PreviewPosition.Name()))
|
||||
{
|
||||
if (combo)
|
||||
{
|
||||
foreach (var position in Enum.GetValues<PreviewPosition>())
|
||||
{
|
||||
if (ImGui.Selectable(position.Name(), Mutable.PreviewPosition == position))
|
||||
{
|
||||
Mutable.PreviewPosition = position;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
ImGuiUtil.HelpMarker(Language.Options_Preview_Description);
|
||||
|
||||
if (ImGuiUtil.InputIntVertical(Language.Options_PreviewMinimum_Name, Language.Options_PreviewMinimum_Description, ref Mutable.PreviewMinimum))
|
||||
{
|
||||
Mutable.PreviewMinimum = Math.Clamp(Mutable.PreviewMinimum, 1, 250);
|
||||
}
|
||||
|
||||
ImGui.Checkbox(Language.Options_PreviewOnlyIf_Name, ref Mutable.OnlyPreviewIf);
|
||||
ImGuiUtil.HelpMarker(Language.Options_PreviewOnlyIf_Description);
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawEmotesSection()
|
||||
{
|
||||
using var tree = ImRaii.TreeNode(HellionStrings.Settings_Chat_Emotes_Heading);
|
||||
if (!tree.Success)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
using (ImRaii.PushIndent(ImGui.GetStyle().IndentSpacing, false))
|
||||
{
|
||||
ImGui.Checkbox(Language.Options_ShowEmotes_Name, ref Mutable.ShowEmotes);
|
||||
ImGuiUtil.HelpMarker(Language.Options_ShowEmotes_Desc);
|
||||
|
||||
ImGui.Spacing();
|
||||
ImGui.TextUnformatted(Language.Options_Emote_BlockedEmotes);
|
||||
ImGui.Spacing();
|
||||
|
||||
if (EmoteCache.State is EmoteCache.LoadingState.Done && WordPopupOptions.FilteredSheet.Length == 0)
|
||||
{
|
||||
WordPopupOptions = RefillSheet();
|
||||
}
|
||||
|
||||
var buttonWidth = ImGui.GetContentRegionAvail().X / 3;
|
||||
using (Plugin.FontManager.FontAwesome.Push())
|
||||
ImGui.Button(FontAwesomeIcon.Plus.ToIconString(), new Vector2(buttonWidth, 0));
|
||||
|
||||
if (SearchSelector.SelectorPopup("WordAddPopup", out var newWord, WordPopupOptions))
|
||||
{
|
||||
Mutable.BlockedEmotes.Add(newWord);
|
||||
}
|
||||
|
||||
using (var table = ImRaii.Table("##BlockedWords", 2, ImGuiTableFlags.RowBg | ImGuiTableFlags.BordersInner))
|
||||
{
|
||||
if (table)
|
||||
{
|
||||
ImGui.TableSetupColumn(Language.Options_Emote_EmoteTable);
|
||||
ImGui.TableSetupColumn("##Del", ImGuiTableColumnFlags.WidthStretch, 0.07f);
|
||||
|
||||
ImGui.TableHeadersRow();
|
||||
|
||||
var copiedList = Mutable.BlockedEmotes.ToArray();
|
||||
foreach (var word in copiedList)
|
||||
{
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.TextUnformatted(word);
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
if (ImGuiUtil.Button($"##{word}Del", FontAwesomeIcon.Trash, !ImGui.GetIO().KeyCtrl))
|
||||
{
|
||||
Mutable.BlockedEmotes.Remove(word);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ImGui.Spacing();
|
||||
ImGui.Separator();
|
||||
ImGui.Spacing();
|
||||
|
||||
ImGui.TextUnformatted(Language.Options_Emote_EmoteStats);
|
||||
ImGui.Spacing();
|
||||
|
||||
if (EmoteCache.State is EmoteCache.LoadingState.Done)
|
||||
{
|
||||
ImGui.TextColored(ImGuiColors.HealerGreen, Language.Options_Emote_Ready);
|
||||
}
|
||||
else
|
||||
{
|
||||
ImGui.TextColored(ImGuiColors.DPSRed, Language.Options_Emote_NotReady);
|
||||
}
|
||||
|
||||
ImGui.TextUnformatted($"{Language.Options_Emote_Loaded} {EmoteCache.SortedCodeArray.Length}");
|
||||
using (var emoteTable = ImRaii.Table("##LoadedEmotes", 5, ImGuiTableFlags.RowBg | ImGuiTableFlags.BordersInner))
|
||||
{
|
||||
if (emoteTable)
|
||||
{
|
||||
ImGui.TableSetupColumn("##word1");
|
||||
ImGui.TableSetupColumn("##word2");
|
||||
ImGui.TableSetupColumn("##word3");
|
||||
ImGui.TableSetupColumn("##word4");
|
||||
ImGui.TableSetupColumn("##word5");
|
||||
|
||||
foreach (var word in EmoteCache.SortedCodeArray)
|
||||
{
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.TextUnformatted(word);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,69 +0,0 @@
|
||||
using ChatTwo.Code;
|
||||
using ChatTwo.Resources;
|
||||
using ChatTwo.Util;
|
||||
using Dalamud.Interface;
|
||||
using Dalamud.Bindings.ImGui;
|
||||
|
||||
namespace ChatTwo.Ui.SettingsTabs;
|
||||
|
||||
internal sealed class ChatColours : ISettingsTab
|
||||
{
|
||||
private Plugin Plugin { get; }
|
||||
private Configuration Mutable { get; }
|
||||
|
||||
public string Name => Language.Options_ChatColours_Tab + "###tabs-chat-colours";
|
||||
|
||||
internal ChatColours(Plugin plugin, Configuration mutable)
|
||||
{
|
||||
Plugin = plugin;
|
||||
Mutable = mutable;
|
||||
|
||||
#if DEBUG
|
||||
// Users can set colours for ExtraChat linkshells in the ExtraChat plugin directly.
|
||||
var sortable = ChatTypeExt.SortOrder
|
||||
.SelectMany(entry => entry.Item2)
|
||||
.Where(type => !type.IsGm() && !type.IsExtraChatLinkshell())
|
||||
.ToHashSet();
|
||||
var total = Enum.GetValues<ChatType>()
|
||||
.Where(type => !type.IsGm() && !type.IsExtraChatLinkshell())
|
||||
.ToHashSet();
|
||||
if (sortable.Count != total.Count)
|
||||
{
|
||||
Plugin.Log.Warning($"There are {sortable.Count} sortable channels, but there are {total.Count} total channels.");
|
||||
total.ExceptWith(sortable);
|
||||
foreach (var missing in total)
|
||||
Plugin.Log.Information($"Missing {missing}");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
public void Draw(bool changed)
|
||||
{
|
||||
foreach (var (_, types) in ChatTypeExt.SortOrder)
|
||||
{
|
||||
foreach (var type in types)
|
||||
{
|
||||
if (ImGuiUtil.IconButton(FontAwesomeIcon.UndoAlt, $"{type}", Language.Options_ChatColours_Reset))
|
||||
Mutable.ChatColours.Remove(type);
|
||||
|
||||
ImGui.SameLine();
|
||||
|
||||
if (ImGuiUtil.IconButton(FontAwesomeIcon.LongArrowAltDown, $"{type}", Language.Options_ChatColours_Import))
|
||||
{
|
||||
var gameColour = Plugin.Functions.Chat.GetChannelColor(type);
|
||||
Mutable.ChatColours[type] = gameColour ?? type.DefaultColor() ?? 0;
|
||||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
|
||||
var vec = Mutable.ChatColours.TryGetValue(type, out var colour)
|
||||
? ColourUtil.RgbaToVector3(colour)
|
||||
: ColourUtil.RgbaToVector3(type.DefaultColor() ?? 0);
|
||||
if (ImGui.ColorEdit3(type.Name(), ref vec, ImGuiColorEditFlags.NoInputs))
|
||||
Mutable.ChatColours[type] = ColourUtil.Vector3ToRgba(vec);
|
||||
}
|
||||
}
|
||||
|
||||
ImGui.Spacing();
|
||||
}
|
||||
}
|
||||
@@ -1,119 +0,0 @@
|
||||
using ChatTwo.Resources;
|
||||
using ChatTwo.Util;
|
||||
using Dalamud.Interface.Style;
|
||||
using Dalamud.Interface.Utility.Raii;
|
||||
using Dalamud.Bindings.ImGui;
|
||||
|
||||
namespace ChatTwo.Ui.SettingsTabs;
|
||||
|
||||
internal sealed class ChatLog : ISettingsTab
|
||||
{
|
||||
private readonly Plugin Plugin;
|
||||
private Configuration Mutable { get; }
|
||||
|
||||
public string Name => Language.Options_ChatLog_Tab + "###tabs-chatlog";
|
||||
|
||||
internal ChatLog(Plugin plugin, Configuration mutable)
|
||||
{
|
||||
Plugin = plugin;
|
||||
Mutable = mutable;
|
||||
}
|
||||
|
||||
public void Draw(bool changed)
|
||||
{
|
||||
using (ImRaii.TextWrapPos(0.0f))
|
||||
{
|
||||
ImGuiUtil.OptionCheckbox(ref Mutable.KeepInputFocus, Language.Options_KeepInputFocus_Name, Language.Options_KeepInputFocus_Description);
|
||||
ImGui.Spacing();
|
||||
|
||||
ImGuiUtil.OptionCheckbox(ref Mutable.PlaySounds, Language.Options_PlaySounds_Name, Language.Options_PlaySounds_Description);
|
||||
ImGui.Spacing();
|
||||
|
||||
ImGuiUtil.OptionCheckbox(ref Mutable.SidebarTabView, Language.Options_SidebarTabView_Name, string.Format(Language.Options_SidebarTabView_Description, Plugin.PluginName));
|
||||
ImGui.Spacing();
|
||||
|
||||
ImGuiUtil.OptionCheckbox(ref Mutable.ShowNoviceNetwork, Language.Options_ShowNoviceNetwork_Name, Language.Options_ShowNoviceNetwork_Description);
|
||||
ImGui.Spacing();
|
||||
|
||||
ImGuiUtil.OptionCheckbox(ref Mutable.ShowHideButton, Language.Options_ShowHideButton_Name, Language.Options_ShowHideButton_Description);
|
||||
ImGui.Spacing();
|
||||
|
||||
ImGuiUtil.OptionCheckbox(ref Mutable.NativeItemTooltips, Language.Options_NativeItemTooltips_Name, string.Format(Language.Options_NativeItemTooltips_Description, Plugin.PluginName));
|
||||
ImGui.Spacing();
|
||||
|
||||
if (Mutable.NativeItemTooltips)
|
||||
{
|
||||
ImGuiUtil.DragFloatVertical(Language.Options_TooltipOffset_Name, Language.Options_TooltipOffset_Desc, ref Mutable.TooltipOffset, 1, 0f, 400f, $"{Mutable.TooltipOffset:N0}px", ImGuiSliderFlags.AlwaysClamp);
|
||||
ImGui.Spacing();
|
||||
}
|
||||
|
||||
ImGuiUtil.DragFloatVertical(Language.Options_WindowOpacity_Name, ref Mutable.WindowAlpha, .25f, 0f, 100f, $"{Mutable.WindowAlpha:N2}%%", ImGuiSliderFlags.AlwaysClamp);
|
||||
ImGui.Spacing();
|
||||
|
||||
if (ImGuiUtil.InputIntVertical(Language.Options_MaxLinesToShow_Name, Language.Options_MaxLinesToShow_Description, ref Mutable.MaxLinesToRender))
|
||||
Mutable.MaxLinesToRender = Math.Clamp(Mutable.MaxLinesToRender, 1, 10_000);
|
||||
ImGui.Spacing();
|
||||
|
||||
ImGuiUtil.OptionCheckbox(ref Mutable.CanMove, Language.Options_CanMove_Name);
|
||||
ImGui.Spacing();
|
||||
|
||||
ImGuiUtil.OptionCheckbox(ref Mutable.CanResize, Language.Options_CanResize_Name);
|
||||
ImGui.Spacing();
|
||||
|
||||
ImGuiUtil.OptionCheckbox(ref Mutable.ShowTitleBar, Language.Options_ShowTitleBar_Name);
|
||||
ImGui.Spacing();
|
||||
|
||||
ImGuiUtil.OptionCheckbox(ref Mutable.ShowPopOutTitleBar, Language.Options_ShowPopOutTitleBar_Name);
|
||||
ImGui.Spacing();
|
||||
|
||||
ImGuiUtil.OptionCheckbox(ref Mutable.OverrideStyle, Language.Options_OverrideStyle_Name, Language.Options_OverrideStyle_Name_Desc);
|
||||
ImGui.Spacing();
|
||||
|
||||
ImGui.Spacing();
|
||||
ImGui.Separator();
|
||||
ImGui.Spacing();
|
||||
|
||||
ImGui.TextUnformatted(Language.Options_ChatTabForwardKeybind_Name);
|
||||
ImGui.SetNextItemWidth(-1);
|
||||
ImGuiUtil.KeybindInput("ChatTabForwardKeybind", ref Mutable.ChatTabForward);
|
||||
|
||||
ImGui.TextUnformatted(Language.Options_ChatTabBackwardKeybind_Name);
|
||||
ImGui.SetNextItemWidth(-1);
|
||||
ImGuiUtil.KeybindInput("ChatTabBackwardKeybind", ref Mutable.ChatTabBackward);
|
||||
|
||||
ImGui.Spacing();
|
||||
ImGui.Separator();
|
||||
ImGui.Spacing();
|
||||
|
||||
ImGui.TextUnformatted(Language.Options_AdjustPosition_Name);
|
||||
ImGui.SetNextItemWidth(-1);
|
||||
var pos = Plugin.ChatLogWindow.LastWindowPos;
|
||||
if (ImGui.DragFloat2($"##{Language.Options_AdjustPosition_Name}", ref pos, 1, 0, float.MaxValue, "%.0fpx"))
|
||||
Plugin.ChatLogWindow.Position = pos;
|
||||
ImGuiUtil.WarningText(Language.Options_AdjustPosition_Warning);
|
||||
ImGui.Spacing();
|
||||
}
|
||||
|
||||
if (!Mutable.OverrideStyle)
|
||||
return;
|
||||
|
||||
var styles = StyleModel.GetConfiguredStyles();
|
||||
if (styles == null)
|
||||
{
|
||||
ImGui.TextUnformatted(Language.Options_OverrideStyle_NotAvailable);
|
||||
ImGui.Spacing();
|
||||
return;
|
||||
}
|
||||
|
||||
var currentStyle = Mutable.ChosenStyle ?? Language.Options_OverrideStyle_NotSelected;
|
||||
using var combo = ImRaii.Combo(Language.Options_OverrideStyleDropdown_Name, currentStyle);
|
||||
if (combo)
|
||||
{
|
||||
foreach (var style in styles)
|
||||
if (ImGui.Selectable(style.Name, Mutable.ChosenStyle == style.Name))
|
||||
Mutable.ChosenStyle = style.Name;
|
||||
}
|
||||
|
||||
ImGui.Spacing();
|
||||
}
|
||||
}
|
||||
@@ -16,7 +16,7 @@ internal sealed class Database : ISettingsTab
|
||||
private Plugin Plugin { get; }
|
||||
private Configuration Mutable { get; }
|
||||
|
||||
public string Name => Language.Options_Database_Tab + "###tabs-database";
|
||||
public string Name => HellionStrings.Settings_Tab_Database + "###tabs-database";
|
||||
|
||||
internal Database(Plugin plugin, Configuration mutable)
|
||||
{
|
||||
@@ -33,30 +33,47 @@ internal sealed class Database : ISettingsTab
|
||||
|
||||
public void Draw(bool changed)
|
||||
{
|
||||
// Shift-on-open keeps the Advanced tools available without a permanent
|
||||
// toggle in the UI, mirroring upstream Chat 2 behaviour.
|
||||
if (changed)
|
||||
ShowAdvanced = ImGui.GetIO().KeyShift;
|
||||
|
||||
ImGuiUtil.OptionCheckbox(ref Mutable.DatabaseBattleMessages, Language.Options_DatabaseBattleMessages_Name, Language.Options_DatabaseBattleMessages_Description);
|
||||
DrawStorageSection();
|
||||
ImGui.Spacing();
|
||||
DrawViewerSection();
|
||||
ImGui.Spacing();
|
||||
DrawStatsSection();
|
||||
}
|
||||
|
||||
if (ImGuiUtil.OptionCheckbox(ref Mutable.LoadPreviousSession, Language.Options_LoadPreviousSession_Name, Language.Options_LoadPreviousSession_Description))
|
||||
private void DrawStorageSection()
|
||||
{
|
||||
using var tree = ImRaii.TreeNode(HellionStrings.Settings_Database_Storage_Heading);
|
||||
if (!tree.Success)
|
||||
return;
|
||||
|
||||
using (ImRaii.PushIndent(ImGui.GetStyle().IndentSpacing, false))
|
||||
{
|
||||
ImGui.Checkbox(Language.Options_DatabaseBattleMessages_Name, ref Mutable.DatabaseBattleMessages);
|
||||
ImGuiUtil.HelpMarker(Language.Options_DatabaseBattleMessages_Description);
|
||||
|
||||
if (ImGui.Checkbox(Language.Options_LoadPreviousSession_Name, ref Mutable.LoadPreviousSession))
|
||||
if (Mutable.LoadPreviousSession)
|
||||
Mutable.FilterIncludePreviousSessions = true;
|
||||
ImGuiUtil.HelpMarker(Language.Options_LoadPreviousSession_Description);
|
||||
|
||||
ImGui.Spacing();
|
||||
|
||||
if (ImGuiUtil.OptionCheckbox(ref Mutable.FilterIncludePreviousSessions, Language.Options_FilterIncludePreviousSessions_Name, Language.Options_FilterIncludePreviousSessions_Description))
|
||||
if (ImGui.Checkbox(Language.Options_FilterIncludePreviousSessions_Name, ref Mutable.FilterIncludePreviousSessions))
|
||||
if (!Mutable.FilterIncludePreviousSessions)
|
||||
Mutable.LoadPreviousSession = false;
|
||||
|
||||
ImGui.Spacing();
|
||||
ImGui.Separator();
|
||||
ImGui.Spacing();
|
||||
ImGuiUtil.HelpMarker(Language.Options_FilterIncludePreviousSessions_Description);
|
||||
|
||||
var old = new FileInfo(Path.Join(Plugin.Interface.ConfigDirectory.FullName, "chat.db"));
|
||||
var migratedOld = new FileInfo(Path.Join(Plugin.Interface.ConfigDirectory.FullName, "chat-litedb.db"));
|
||||
if (old.Exists || migratedOld.Exists)
|
||||
{
|
||||
ImGui.Spacing();
|
||||
ImGui.Separator();
|
||||
ImGui.Spacing();
|
||||
|
||||
ImGui.TextUnformatted(Language.Options_Database_Old_Heading);
|
||||
ImGui.Spacing();
|
||||
|
||||
@@ -76,13 +93,16 @@ internal sealed class Database : ISettingsTab
|
||||
WrapperUtil.AddNotification(Language.Options_Database_Old_Delete_Error, NotificationType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
ImGui.Spacing();
|
||||
ImGui.Separator();
|
||||
ImGui.Spacing();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ImGui.TextUnformatted(Language.Options_Database_Metadata_Heading);
|
||||
private void DrawViewerSection()
|
||||
{
|
||||
using var tree = ImRaii.TreeNode(HellionStrings.Settings_Database_Viewer_Heading);
|
||||
if (!tree.Success)
|
||||
return;
|
||||
|
||||
using (ImRaii.PushIndent(ImGui.GetStyle().IndentSpacing, false))
|
||||
{
|
||||
// Refresh the database size and message count every 5 seconds to avoid
|
||||
@@ -132,13 +152,19 @@ internal sealed class Database : ISettingsTab
|
||||
WrapperUtil.AddNotification(Language.Options_ClearDatabase_Success, NotificationType.Info);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ImGui.Spacing();
|
||||
|
||||
private void DrawStatsSection()
|
||||
{
|
||||
if (!ShowAdvanced)
|
||||
return;
|
||||
|
||||
using var treeNode = ImRaii.TreeNode(Language.Options_Database_Advanced);
|
||||
using var tree = ImRaii.TreeNode(HellionStrings.Settings_Database_Stats_Heading);
|
||||
if (!tree.Success)
|
||||
return;
|
||||
|
||||
using (ImRaii.PushIndent(ImGui.GetStyle().IndentSpacing, false))
|
||||
{
|
||||
using var wrap = ImRaii.TextWrapPos(0.0f);
|
||||
|
||||
ImGuiUtil.WarningText(Language.Options_Database_Advanced_Warning);
|
||||
@@ -153,7 +179,7 @@ internal sealed class Database : ISettingsTab
|
||||
|
||||
if (ImGuiUtil.CtrlShiftButton("Inject 10,000 messages", "Ctrl+Shift: creates 10,000 unique messages (async)"))
|
||||
new Thread(() => InsertMessages(10_000)).Start();
|
||||
ImGui.Spacing();
|
||||
}
|
||||
}
|
||||
|
||||
private void InsertMessages(int count)
|
||||
|
||||
@@ -1,116 +0,0 @@
|
||||
using ChatTwo.Resources;
|
||||
using ChatTwo.Util;
|
||||
using Dalamud.Interface.Utility.Raii;
|
||||
using Dalamud.Bindings.ImGui;
|
||||
|
||||
namespace ChatTwo.Ui.SettingsTabs;
|
||||
|
||||
internal sealed class Display : ISettingsTab
|
||||
{
|
||||
private Configuration Mutable { get; }
|
||||
|
||||
public string Name => Language.Options_Display_Tab + "###tabs-display";
|
||||
|
||||
internal Display(Configuration mutable)
|
||||
{
|
||||
Mutable = mutable;
|
||||
}
|
||||
|
||||
public void Draw(bool changed)
|
||||
{
|
||||
using var wrap = ImRaii.TextWrapPos(0.0f);
|
||||
|
||||
ImGuiUtil.OptionCheckbox(ref Mutable.HideChat, Language.Options_HideChat_Name, Language.Options_HideChat_Description);
|
||||
ImGui.Spacing();
|
||||
|
||||
ImGuiUtil.OptionCheckbox(ref Mutable.HideDuringCutscenes, Language.Options_HideDuringCutscenes_Name, string.Format(Language.Options_HideDuringCutscenes_Description, Plugin.PluginName));
|
||||
ImGui.Spacing();
|
||||
|
||||
ImGuiUtil.OptionCheckbox(ref Mutable.HideWhenNotLoggedIn, Language.Options_HideWhenNotLoggedIn_Name, string.Format(Language.Options_HideWhenNotLoggedIn_Description, Plugin.PluginName));
|
||||
ImGui.Spacing();
|
||||
|
||||
ImGuiUtil.OptionCheckbox(ref Mutable.HideWhenUiHidden, Language.Options_HideWhenUiHidden_Name, string.Format(Language.Options_HideWhenUiHidden_Description, Plugin.PluginName));
|
||||
ImGui.Spacing();
|
||||
|
||||
ImGuiUtil.OptionCheckbox(ref Mutable.HideInLoadingScreens, Language.Options_HideInLoadingScreens_Name, string.Format(Language.Options_HideInLoadingScreens_Description, Plugin.PluginName));
|
||||
ImGui.Spacing();
|
||||
|
||||
ImGuiUtil.OptionCheckbox(ref Mutable.HideInBattle, Language.Options_HideInBattle_Name, Language.Options_HideInBattle_Description);
|
||||
ImGui.Spacing();
|
||||
|
||||
ImGui.Separator();
|
||||
ImGui.Spacing();
|
||||
|
||||
ImGuiUtil.OptionCheckbox(ref Mutable.HideWhenInactive, Language.Options_HideWhenInactive_Name, Language.Options_HideWhenInactive_Description);
|
||||
ImGui.Spacing();
|
||||
|
||||
if (Mutable.HideWhenInactive)
|
||||
{
|
||||
using var _ = ImRaii.PushIndent();
|
||||
ImGuiUtil.InputIntVertical(Language.Options_InactivityHideTimeout_Name,
|
||||
Language.Options_InactivityHideTimeout_Description, ref Mutable.InactivityHideTimeout, 1, 10);
|
||||
// Enforce a minimum of 2 seconds to avoid people soft locking
|
||||
// themselves.
|
||||
Mutable.InactivityHideTimeout = Math.Max(2, Mutable.InactivityHideTimeout);
|
||||
ImGui.Spacing();
|
||||
|
||||
// This setting conflicts with HideInBattle, so it's disabled.
|
||||
using (ImRaii.Disabled(Mutable.HideInBattle))
|
||||
{
|
||||
ImGuiUtil.OptionCheckbox(ref Mutable.InactivityHideActiveDuringBattle,
|
||||
Language.Options_InactivityHideActiveDuringBattle_Name,
|
||||
Language.Options_InactivityHideActiveDuringBattle_Description);
|
||||
ImGui.Spacing();
|
||||
}
|
||||
|
||||
using var channelTree = ImRaii.TreeNode(Language.Options_InactivityHideChannels_Name);
|
||||
if (channelTree.Success)
|
||||
{
|
||||
if (ImGuiUtil.CtrlShiftButton(Language.Options_InactivityHideChannels_All_Label, Language.Options_InactivityHideChannels_Button_Tooltip))
|
||||
{
|
||||
Mutable.InactivityHideChannelsV2 = TabsUtil.AllChannels();
|
||||
Mutable.InactivityHideExtraChatAll = true;
|
||||
Mutable.InactivityHideExtraChatChannels = [];
|
||||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
if (ImGuiUtil.CtrlShiftButton(Language.Options_InactivityHideChannels_None_Label, Language.Options_InactivityHideChannels_Button_Tooltip))
|
||||
{
|
||||
Mutable.InactivityHideChannelsV2 = [];
|
||||
Mutable.InactivityHideExtraChatAll = false;
|
||||
Mutable.InactivityHideExtraChatChannels = [];
|
||||
}
|
||||
|
||||
ImGui.Spacing();
|
||||
|
||||
ImGuiUtil.ChannelSelector(Language.Options_Tabs_Channels, Mutable.InactivityHideChannelsV2);
|
||||
ImGuiUtil.ExtraChatSelector(Language.Options_Tabs_ExtraChatChannels,
|
||||
ref Mutable.InactivityHideExtraChatAll, Mutable.InactivityHideExtraChatChannels);
|
||||
}
|
||||
ImGui.Spacing();
|
||||
}
|
||||
|
||||
ImGui.Separator();
|
||||
ImGui.Spacing();
|
||||
|
||||
ImGuiUtil.OptionCheckbox(ref Mutable.Use24HourClock, Language.Options_Use24HourClock_Name, Language.Options_Use24HourClock_Description);
|
||||
|
||||
ImGuiUtil.OptionCheckbox(ref Mutable.PrettierTimestamps, Language.Options_PrettierTimestamps_Name, Language.Options_PrettierTimestamps_Description);
|
||||
|
||||
if (Mutable.PrettierTimestamps)
|
||||
{
|
||||
using var _ = ImRaii.PushIndent();
|
||||
ImGuiUtil.OptionCheckbox(ref Mutable.MoreCompactPretty, Language.Options_MoreCompactPretty_Name, Language.Options_MoreCompactPretty_Description);
|
||||
ImGuiUtil.OptionCheckbox(ref Mutable.HideSameTimestamps, Language.Options_HideSameTimestamps_Name, Language.Options_HideSameTimestamps_Description);
|
||||
}
|
||||
ImGui.Spacing();
|
||||
|
||||
ImGuiUtil.OptionCheckbox(ref Mutable.CollapseDuplicateMessages, Language.Options_CollapseDuplicateMessages_Name, Language.Options_CollapseDuplicateMessages_Description);
|
||||
if (Mutable.CollapseDuplicateMessages)
|
||||
{
|
||||
using var _ = ImRaii.PushIndent();
|
||||
ImGuiUtil.OptionCheckbox(ref Mutable.CollapseKeepUniqueLinks, Language.Options_CollapseDuplicateMsgUniqueLink_Name, Language.Options_CollapseDuplicateMsgUniqueLink_Description);
|
||||
}
|
||||
ImGui.Spacing();
|
||||
}
|
||||
}
|
||||
@@ -1,113 +0,0 @@
|
||||
using System.Numerics;
|
||||
using ChatTwo.Resources;
|
||||
using ChatTwo.Util;
|
||||
using Dalamud.Interface;
|
||||
using Dalamud.Interface.Colors;
|
||||
using Dalamud.Interface.Utility.Raii;
|
||||
using Dalamud.Bindings.ImGui;
|
||||
|
||||
namespace ChatTwo.Ui.SettingsTabs;
|
||||
|
||||
internal sealed class Emote : ISettingsTab
|
||||
{
|
||||
private readonly Plugin Plugin;
|
||||
private Configuration Mutable { get; }
|
||||
|
||||
public string Name => Language.Options_Emote_Tab + "###tabs-emote";
|
||||
|
||||
private static SearchSelector.SelectorPopupOptions? WordPopupOptions;
|
||||
|
||||
internal Emote(Plugin plugin, Configuration mutable)
|
||||
{
|
||||
Plugin = plugin;
|
||||
Mutable = mutable;
|
||||
|
||||
WordPopupOptions = new SearchSelector.SelectorPopupOptions
|
||||
{
|
||||
FilteredSheet = EmoteCache.SortedCodeArray.Where(w => !Mutable.BlockedEmotes.Contains(w)).ToArray()
|
||||
};
|
||||
}
|
||||
|
||||
private SearchSelector.SelectorPopupOptions RefillSheet()
|
||||
{
|
||||
return new SearchSelector.SelectorPopupOptions
|
||||
{
|
||||
FilteredSheet = EmoteCache.SortedCodeArray.Where(w => !Mutable.BlockedEmotes.Contains(w)).ToArray()
|
||||
};
|
||||
}
|
||||
|
||||
public void Draw(bool changed)
|
||||
{
|
||||
using var wrap = ImRaii.TextWrapPos(0.0f);
|
||||
|
||||
ImGuiUtil.OptionCheckbox(ref Mutable.ShowEmotes, Language.Options_ShowEmotes_Name, Language.Options_ShowEmotes_Desc);
|
||||
ImGui.Spacing();
|
||||
|
||||
ImGui.TextUnformatted(Language.Options_Emote_BlockedEmotes);
|
||||
ImGui.Spacing();
|
||||
|
||||
WordPopupOptions ??= RefillSheet();
|
||||
if (EmoteCache.State is EmoteCache.LoadingState.Done && WordPopupOptions.FilteredSheet.Length == 0)
|
||||
WordPopupOptions = RefillSheet();
|
||||
|
||||
var buttonWidth = ImGui.GetContentRegionAvail().X / 3;
|
||||
using (Plugin.FontManager.FontAwesome.Push())
|
||||
ImGui.Button(FontAwesomeIcon.Plus.ToIconString(), new Vector2(buttonWidth, 0));
|
||||
|
||||
if (SearchSelector.SelectorPopup("WordAddPopup", out var newWord, WordPopupOptions))
|
||||
Mutable.BlockedEmotes.Add(newWord);
|
||||
|
||||
using(var table = ImRaii.Table("##BlockedWords", 2, ImGuiTableFlags.RowBg | ImGuiTableFlags.BordersInner))
|
||||
{
|
||||
if (table)
|
||||
{
|
||||
ImGui.TableSetupColumn(Language.Options_Emote_EmoteTable);
|
||||
ImGui.TableSetupColumn("##Del", ImGuiTableColumnFlags.WidthStretch, 0.07f);
|
||||
|
||||
ImGui.TableHeadersRow();
|
||||
|
||||
var copiedList = Mutable.BlockedEmotes.ToArray();
|
||||
foreach (var word in copiedList)
|
||||
{
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.TextUnformatted(word);
|
||||
|
||||
ImGui.TableNextColumn();
|
||||
if (ImGuiUtil.Button($"##{word}Del", FontAwesomeIcon.Trash, !ImGui.GetIO().KeyCtrl))
|
||||
Mutable.BlockedEmotes.Remove(word);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ImGui.Spacing();
|
||||
ImGui.Separator();
|
||||
ImGui.Spacing();
|
||||
|
||||
ImGui.TextUnformatted(Language.Options_Emote_EmoteStats);
|
||||
ImGui.Spacing();
|
||||
|
||||
if (EmoteCache.State is EmoteCache.LoadingState.Done)
|
||||
ImGui.TextColored(ImGuiColors.HealerGreen, Language.Options_Emote_Ready);
|
||||
else
|
||||
ImGui.TextColored(ImGuiColors.DPSRed, Language.Options_Emote_NotReady);
|
||||
|
||||
ImGui.TextUnformatted($"{Language.Options_Emote_Loaded} {EmoteCache.SortedCodeArray.Length}");
|
||||
using (var emoteTable = ImRaii.Table("##LoadedEmotes", 5, ImGuiTableFlags.RowBg | ImGuiTableFlags.BordersInner))
|
||||
{
|
||||
if (emoteTable)
|
||||
{
|
||||
ImGui.TableSetupColumn("##word1");
|
||||
ImGui.TableSetupColumn("##word2");
|
||||
ImGui.TableSetupColumn("##word3");
|
||||
ImGui.TableSetupColumn("##word4");
|
||||
ImGui.TableSetupColumn("##word5");
|
||||
|
||||
foreach (var word in EmoteCache.SortedCodeArray)
|
||||
{
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.TextUnformatted(word);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,97 +0,0 @@
|
||||
using ChatTwo.Resources;
|
||||
using ChatTwo.Util;
|
||||
using Dalamud;
|
||||
using Dalamud.Interface.FontIdentifier;
|
||||
using Dalamud.Bindings.ImGui;
|
||||
using Dalamud.Interface.Utility.Raii;
|
||||
|
||||
namespace ChatTwo.Ui.SettingsTabs;
|
||||
|
||||
public class Fonts : ISettingsTab
|
||||
{
|
||||
private Configuration Mutable { get; }
|
||||
|
||||
public string Name => Language.Options_Fonts_Tab + "###tabs-fonts";
|
||||
|
||||
internal Fonts(Configuration mutable)
|
||||
{
|
||||
Mutable = mutable;
|
||||
}
|
||||
|
||||
public void Draw(bool _)
|
||||
{
|
||||
using var wrap = ImRaii.TextWrapPos(0.0f);
|
||||
|
||||
ImGui.Checkbox(Language.Options_FontsEnabled, ref Mutable.FontsEnabled);
|
||||
ImGui.Spacing();
|
||||
|
||||
if (!Mutable.FontsEnabled)
|
||||
{
|
||||
ImGuiUtil.FontSizeCombo(Language.Options_FontSize_Name, ref Mutable.FontSizeV2);
|
||||
}
|
||||
else
|
||||
{
|
||||
var globalChooser = ImGuiUtil.FontChooser(Language.Options_Font_Name, Mutable.GlobalFontV2, false, ref _);
|
||||
globalChooser?.ResultTask.ContinueWith(r =>
|
||||
{
|
||||
if (r.IsCompletedSuccessfully)
|
||||
Mutable.GlobalFontV2 = r.Result;
|
||||
});
|
||||
ImGui.SameLine();
|
||||
if (ImGui.Button("Reset##global"))
|
||||
Mutable.GlobalFontV2 = new SingleFontSpec{ FontId = new DalamudAssetFontAndFamilyId(DalamudAsset.NotoSansCjkRegular), SizePt = 12.75f };
|
||||
|
||||
ImGuiUtil.HelpText(string.Format(Language.Options_Font_Description, Plugin.PluginName));
|
||||
ImGuiUtil.WarningText(Language.Options_Font_Warning);
|
||||
ImGui.Spacing();
|
||||
|
||||
// LocaleNames being null means it is likely a game font which all support JP symbols
|
||||
var japaneseChooser = ImGuiUtil.FontChooser(Language.Options_JapaneseFont_Name, Mutable.JapaneseFontV2, false, ref _, id => !id.LocaleNames?.ContainsKey("ja-jp") ?? false, "いろはにほへと ちりぬるを");
|
||||
japaneseChooser?.ResultTask.ContinueWith(r =>
|
||||
{
|
||||
if (r.IsCompletedSuccessfully)
|
||||
Mutable.JapaneseFontV2 = r.Result;
|
||||
});
|
||||
ImGui.SameLine();
|
||||
if (ImGui.Button("Reset##japanese"))
|
||||
Mutable.JapaneseFontV2 = new SingleFontSpec{ FontId = new DalamudAssetFontAndFamilyId(DalamudAsset.NotoSansCjkMedium), SizePt = 12.75f };
|
||||
|
||||
ImGuiUtil.HelpText(string.Format(Language.Options_JapaneseFont_Description, Plugin.PluginName));
|
||||
ImGui.Spacing();
|
||||
|
||||
var italicChooser = ImGuiUtil.FontChooser(Language.Options_ItalicFont_Name, Mutable.ItalicFontV2, true, ref Mutable.ItalicEnabled);
|
||||
italicChooser?.ResultTask.ContinueWith(r =>
|
||||
{
|
||||
if (r.IsCompletedSuccessfully)
|
||||
Mutable.ItalicFontV2 = r.Result;
|
||||
});
|
||||
ImGui.SameLine();
|
||||
if (ImGui.Button("Reset##italic"))
|
||||
{
|
||||
Mutable.ItalicEnabled = false;
|
||||
Mutable.ItalicFontV2 = new SingleFontSpec{ FontId = new DalamudAssetFontAndFamilyId(DalamudAsset.NotoSansCjkRegular), SizePt = 12.75f };
|
||||
}
|
||||
|
||||
ImGuiUtil.HelpText(string.Format(Language.Options_Italic_Description, Plugin.PluginName));
|
||||
ImGui.Spacing();
|
||||
|
||||
if (ImGui.CollapsingHeader(Language.Options_ExtraGlyphs_Name))
|
||||
{
|
||||
ImGuiUtil.HelpText(string.Format(Language.Options_ExtraGlyphs_Description, Plugin.PluginName));
|
||||
|
||||
var range = (int) Mutable.ExtraGlyphRanges;
|
||||
foreach (var extra in Enum.GetValues<ExtraGlyphRanges>())
|
||||
ImGui.CheckboxFlags(extra.Name(), ref range, (int) extra);
|
||||
|
||||
Mutable.ExtraGlyphRanges = (ExtraGlyphRanges) range;
|
||||
}
|
||||
|
||||
ImGui.Spacing();
|
||||
}
|
||||
|
||||
ImGuiUtil.FontSizeCombo(Language.Options_SymbolsFontSize_Name, ref Mutable.SymbolsFontSizeV2);
|
||||
ImGuiUtil.HelpText(Language.Options_SymbolsFontSize_Description);
|
||||
|
||||
ImGui.Spacing();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,161 @@
|
||||
using ChatTwo.Resources;
|
||||
using ChatTwo.Util;
|
||||
using Dalamud.Interface.Utility;
|
||||
using Dalamud.Interface.Utility.Raii;
|
||||
using Dalamud.Bindings.ImGui;
|
||||
|
||||
namespace ChatTwo.Ui.SettingsTabs;
|
||||
|
||||
internal sealed class General : ISettingsTab
|
||||
{
|
||||
private Plugin Plugin { get; }
|
||||
private Configuration Mutable { get; }
|
||||
|
||||
public string Name => HellionStrings.Settings_Tab_General + "###tabs-general";
|
||||
|
||||
internal General(Plugin plugin, Configuration mutable)
|
||||
{
|
||||
Plugin = plugin;
|
||||
Mutable = mutable;
|
||||
}
|
||||
|
||||
public void Draw(bool changed)
|
||||
{
|
||||
DrawInputSection();
|
||||
ImGui.Spacing();
|
||||
DrawAudioSection();
|
||||
ImGui.Spacing();
|
||||
DrawPerformanceSection();
|
||||
ImGui.Spacing();
|
||||
DrawLanguageSection();
|
||||
}
|
||||
|
||||
private void DrawInputSection()
|
||||
{
|
||||
using var tree = ImRaii.TreeNode(HellionStrings.Settings_General_Input_Heading);
|
||||
if (!tree.Success)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
using (ImRaii.PushIndent(ImGui.GetStyle().IndentSpacing, false))
|
||||
{
|
||||
ImGui.Checkbox(Language.Options_KeepInputFocus_Name, ref Mutable.KeepInputFocus);
|
||||
ImGuiUtil.HelpMarker(Language.Options_KeepInputFocus_Description);
|
||||
|
||||
ImGui.Spacing();
|
||||
ImGui.TextUnformatted(Language.Options_ChatTabForwardKeybind_Name);
|
||||
ImGui.SetNextItemWidth(-1);
|
||||
ImGuiUtil.KeybindInput("ChatTabForwardKeybind", ref Mutable.ChatTabForward);
|
||||
|
||||
ImGui.TextUnformatted(Language.Options_ChatTabBackwardKeybind_Name);
|
||||
ImGui.SetNextItemWidth(-1);
|
||||
ImGuiUtil.KeybindInput("ChatTabBackwardKeybind", ref Mutable.ChatTabBackward);
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawAudioSection()
|
||||
{
|
||||
using var tree = ImRaii.TreeNode(HellionStrings.Settings_General_Audio_Heading);
|
||||
if (!tree.Success)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
using (ImRaii.PushIndent(ImGui.GetStyle().IndentSpacing, false))
|
||||
{
|
||||
ImGui.Checkbox(Language.Options_PlaySounds_Name, ref Mutable.PlaySounds);
|
||||
ImGuiUtil.HelpMarker(Language.Options_PlaySounds_Description);
|
||||
|
||||
ImGui.Checkbox(Language.Options_ShowNoviceNetwork_Name, ref Mutable.ShowNoviceNetwork);
|
||||
ImGuiUtil.HelpMarker(Language.Options_ShowNoviceNetwork_Description);
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawPerformanceSection()
|
||||
{
|
||||
using var tree = ImRaii.TreeNode(HellionStrings.Settings_General_Performance_Heading);
|
||||
if (!tree.Success)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
using (ImRaii.PushIndent(ImGui.GetStyle().IndentSpacing, false))
|
||||
{
|
||||
ImGui.SetNextItemWidth(200f * ImGuiHelpers.GlobalScale);
|
||||
if (ImGui.InputInt(Language.Options_MaxLinesToShow_Name, ref Mutable.MaxLinesToRender))
|
||||
{
|
||||
Mutable.MaxLinesToRender = Math.Clamp(Mutable.MaxLinesToRender, 1, 10_000);
|
||||
}
|
||||
ImGuiUtil.HelpMarker(Language.Options_MaxLinesToShow_Description);
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawLanguageSection()
|
||||
{
|
||||
using var tree = ImRaii.TreeNode(HellionStrings.Settings_General_Language_Heading);
|
||||
if (!tree.Success)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
using (ImRaii.PushIndent(ImGui.GetStyle().IndentSpacing, false))
|
||||
{
|
||||
using (var combo = ImGuiUtil.BeginComboVertical(Language.Options_Language_Name, Mutable.LanguageOverride.Name()))
|
||||
{
|
||||
if (combo.Success)
|
||||
{
|
||||
foreach (var language in Enum.GetValues<LanguageOverride>())
|
||||
{
|
||||
if (ImGui.Selectable(language.Name()))
|
||||
{
|
||||
Mutable.LanguageOverride = language;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
ImGuiUtil.HelpMarker(string.Format(Language.Options_Language_Description, Plugin.PluginName));
|
||||
ImGui.Spacing();
|
||||
|
||||
using (var combo = ImGuiUtil.BeginComboVertical(Language.Options_CommandHelpSide_Name, Mutable.CommandHelpSide.Name()))
|
||||
{
|
||||
if (combo.Success)
|
||||
{
|
||||
foreach (var side in Enum.GetValues<CommandHelpSide>())
|
||||
{
|
||||
if (ImGui.Selectable(side.Name(), Mutable.CommandHelpSide == side))
|
||||
{
|
||||
Mutable.CommandHelpSide = side;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
ImGuiUtil.HelpMarker(string.Format(Language.Options_CommandHelpSide_Description, Plugin.PluginName));
|
||||
ImGui.Spacing();
|
||||
|
||||
using (var combo = ImGuiUtil.BeginComboVertical(Language.Options_KeybindMode_Name, Mutable.KeybindMode.Name()))
|
||||
{
|
||||
if (combo.Success)
|
||||
{
|
||||
foreach (var mode in Enum.GetValues<KeybindMode>())
|
||||
{
|
||||
if (ImGui.Selectable(mode.Name(), Mutable.KeybindMode == mode))
|
||||
{
|
||||
Mutable.KeybindMode = mode;
|
||||
}
|
||||
|
||||
if (ImGui.IsItemHovered())
|
||||
{
|
||||
ImGuiUtil.Tooltip(mode.Tooltip() ?? "");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
ImGuiUtil.HelpMarker(string.Format(Language.Options_KeybindMode_Description, Plugin.PluginName));
|
||||
ImGui.Spacing();
|
||||
|
||||
ImGui.Checkbox(Language.Options_SortAutoTranslate_Name, ref Mutable.SortAutoTranslate);
|
||||
ImGuiUtil.HelpMarker(Language.Options_SortAutoTranslate_Description);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,188 @@
|
||||
using ChatTwo.Resources;
|
||||
using ChatTwo.Util;
|
||||
using Dalamud.Interface;
|
||||
using Dalamud.Interface.Colors;
|
||||
using Dalamud.Interface.Utility;
|
||||
using Dalamud.Interface.Utility.Raii;
|
||||
using Dalamud.Bindings.ImGui;
|
||||
|
||||
namespace ChatTwo.Ui.SettingsTabs;
|
||||
|
||||
// Information-Tab vereint die früheren About- und Changelog-Tabs in
|
||||
// drei kollabierbaren Sektionen. Der About-Inhalt ist 1:1 aus About.cs
|
||||
// übernommen, die Changelog-Render-Logik aus Changelog.cs.
|
||||
internal sealed class Information : ISettingsTab
|
||||
{
|
||||
private Configuration Mutable { get; }
|
||||
|
||||
public string Name => HellionStrings.Settings_Tab_Information + "###tabs-information";
|
||||
|
||||
private readonly List<string> Translators =
|
||||
[
|
||||
"q673135110", "Akizem", "d0tiKs",
|
||||
"Moonlight_Everlit", "Dark32", "andreycout",
|
||||
"Button_", "Cali666", "cassandra308",
|
||||
"lokinmodar", "jtabox", "AkiraYorumoto",
|
||||
"MKhayle", "elena.space", "imlisa",
|
||||
"andrei5125", "ShivaMaheshvara", "aislinn87",
|
||||
"nishinatsu051", "lichuyuan", "Risu64",
|
||||
"yummypillow", "witchymary", "Yuzumi",
|
||||
"zomsakura", "Sirayuki"
|
||||
];
|
||||
|
||||
internal Information(Configuration mutable)
|
||||
{
|
||||
Mutable = mutable;
|
||||
Translators.Sort((a, b) => string.Compare(a.ToLowerInvariant(), b.ToLowerInvariant(), StringComparison.Ordinal));
|
||||
}
|
||||
|
||||
public void Draw(bool changed)
|
||||
{
|
||||
using var wrap = ImRaii.TextWrapPos(0.0f);
|
||||
|
||||
DrawVersionInfoSection();
|
||||
ImGui.Spacing();
|
||||
DrawAboutSection();
|
||||
ImGui.Spacing();
|
||||
DrawChangelogSection();
|
||||
}
|
||||
|
||||
private void DrawVersionInfoSection()
|
||||
{
|
||||
using var tree = ImRaii.TreeNode(HellionStrings.Settings_Information_VersionInfo_Heading);
|
||||
if (!tree.Success)
|
||||
return;
|
||||
|
||||
using (ImRaii.PushIndent(ImGui.GetStyle().IndentSpacing, false))
|
||||
{
|
||||
ImGui.TextUnformatted(string.Format(Language.Options_About_Opening, Plugin.PluginName));
|
||||
|
||||
ImGuiHelpers.ScaledDummy(10.0f);
|
||||
|
||||
ImGui.TextUnformatted(Language.Options_About_Authors);
|
||||
ImGui.SameLine();
|
||||
ImGui.TextColored(ImGuiColors.ParsedGold, Plugin.Interface.Manifest.Author);
|
||||
|
||||
ImGui.TextUnformatted(Language.Options_About_Discord);
|
||||
ImGui.SameLine();
|
||||
ImGui.TextColored(ImGuiColors.ParsedGold, "@j.j_kazama");
|
||||
|
||||
ImGui.TextUnformatted(Language.Options_About_Version);
|
||||
ImGui.SameLine();
|
||||
ImGui.TextColored(ImGuiColors.ParsedOrange, Plugin.Interface.Manifest.AssemblyVersion.ToString(3));
|
||||
|
||||
ImGuiHelpers.ScaledDummy(10.0f);
|
||||
|
||||
ImGui.TextUnformatted(Language.Options_About_Github_Issues);
|
||||
ImGui.SameLine();
|
||||
if (ImGuiUtil.IconButton(FontAwesomeIcon.ExternalLinkAlt, "githubIssues"))
|
||||
Dalamud.Utility.Util.OpenLink("https://github.com/JonKazama-Hellion/HellionChat/issues");
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawAboutSection()
|
||||
{
|
||||
using var tree = ImRaii.TreeNode(HellionStrings.Settings_Information_About_Heading);
|
||||
if (!tree.Success)
|
||||
return;
|
||||
|
||||
using (ImRaii.PushIndent(ImGui.GetStyle().IndentSpacing, false))
|
||||
{
|
||||
ImGui.TextColored(ImGuiColors.ParsedGold, HellionStrings.About_Maintainer_Heading);
|
||||
ImGui.TextUnformatted(HellionStrings.About_Maintainer_Body);
|
||||
ImGui.TextUnformatted(HellionStrings.About_Maintainer_Website_Label);
|
||||
ImGui.SameLine();
|
||||
if (ImGuiUtil.IconButton(FontAwesomeIcon.ExternalLinkAlt, "hellionMedia"))
|
||||
Dalamud.Utility.Util.OpenLink("https://hellion-media.de");
|
||||
|
||||
ImGuiHelpers.ScaledDummy(10.0f);
|
||||
|
||||
ImGui.TextColored(ImGuiColors.ParsedGold, HellionStrings.About_Mission_Heading);
|
||||
ImGui.TextUnformatted(HellionStrings.About_Mission_P1);
|
||||
ImGui.Spacing();
|
||||
ImGui.TextUnformatted(HellionStrings.About_Mission_P2);
|
||||
ImGui.Spacing();
|
||||
ImGui.TextUnformatted(HellionStrings.About_Mission_P3);
|
||||
|
||||
ImGuiHelpers.ScaledDummy(10.0f);
|
||||
|
||||
ImGui.TextColored(ImGuiColors.ParsedGold, HellionStrings.About_BuiltOn_Heading);
|
||||
ImGui.TextUnformatted(HellionStrings.About_BuiltOn_P1);
|
||||
ImGui.Spacing();
|
||||
ImGui.TextUnformatted(HellionStrings.About_BuiltOn_P2);
|
||||
ImGui.Spacing();
|
||||
ImGui.TextUnformatted(HellionStrings.About_BuiltOn_Upstream_Label);
|
||||
ImGui.SameLine();
|
||||
if (ImGuiUtil.IconButton(FontAwesomeIcon.ExternalLinkAlt, "chatTwoUpstream"))
|
||||
Dalamud.Utility.Util.OpenLink("https://github.com/Infiziert90/ChatTwo");
|
||||
|
||||
ImGuiHelpers.ScaledDummy(10.0f);
|
||||
|
||||
ImGui.TextColored(ImGuiColors.ParsedGold, HellionStrings.About_License_Heading);
|
||||
ImGui.TextUnformatted(HellionStrings.About_License_P1);
|
||||
ImGui.TextUnformatted(HellionStrings.About_License_P2);
|
||||
ImGui.TextUnformatted(HellionStrings.About_License_P3);
|
||||
|
||||
ImGuiHelpers.ScaledDummy(10.0f);
|
||||
|
||||
ImGui.TextColored(ImGuiColors.DalamudOrange, HellionStrings.About_SE_Heading);
|
||||
ImGui.TextUnformatted(HellionStrings.About_SE_P1);
|
||||
ImGui.TextUnformatted(HellionStrings.About_SE_P2);
|
||||
|
||||
ImGui.Spacing();
|
||||
|
||||
ImGui.TextColored(ImGuiColors.ParsedGold, HellionStrings.About_Localization_Heading);
|
||||
ImGui.TextUnformatted(HellionStrings.About_Localization_P1);
|
||||
ImGui.TextUnformatted(HellionStrings.About_Localization_P2);
|
||||
|
||||
ImGui.Spacing();
|
||||
|
||||
using (var translatorTree = ImRaii.TreeNode(HellionStrings.About_Translators_TreeNode))
|
||||
{
|
||||
if (translatorTree)
|
||||
{
|
||||
using var indent = ImRaii.PushIndent(ImGui.GetStyle().IndentSpacing, false);
|
||||
foreach (var translator in Translators)
|
||||
ImGui.TextUnformatted(translator);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawChangelogSection()
|
||||
{
|
||||
using var tree = ImRaii.TreeNode(HellionStrings.Settings_Information_Changelog_Heading);
|
||||
if (!tree.Success)
|
||||
return;
|
||||
|
||||
using (ImRaii.PushIndent(ImGui.GetStyle().IndentSpacing, false))
|
||||
{
|
||||
ImGui.Checkbox(Language.Options_PrintChangelog_Name, ref Mutable.PrintChangelog);
|
||||
ImGuiUtil.HelpMarker(Language.Options_PrintChangelog_Description);
|
||||
|
||||
ImGui.Spacing();
|
||||
ImGui.Separator();
|
||||
ImGui.Spacing();
|
||||
|
||||
var changelog = Plugin.Interface.Manifest.Changelog;
|
||||
if (changelog == null)
|
||||
return;
|
||||
|
||||
ImGui.TextUnformatted(Language.Options_Changelog_Header);
|
||||
ImGui.TextUnformatted($"Version {Plugin.Interface.Manifest.AssemblyVersion.ToString(3)}");
|
||||
ImGui.Spacing();
|
||||
foreach (var sentence in changelog.Split("\n"))
|
||||
{
|
||||
if (sentence == string.Empty)
|
||||
{
|
||||
ImGui.NewLine();
|
||||
continue;
|
||||
}
|
||||
|
||||
var indented = sentence.StartsWith('-') || sentence.StartsWith(" -");
|
||||
using var indent = ImRaii.PushIndent(10.0f, true, indented);
|
||||
ImGui.TextUnformatted(sentence);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
using ChatTwo.Resources;
|
||||
using ChatTwo.Util;
|
||||
using Dalamud.Bindings.ImGui;
|
||||
|
||||
namespace ChatTwo.Ui.SettingsTabs;
|
||||
|
||||
internal sealed class Miscellaneous(Configuration mutable) : ISettingsTab
|
||||
{
|
||||
private Configuration Mutable { get; } = mutable;
|
||||
public string Name => Language.Options_Miscellaneous_Tab + "###tabs-miscellaneous";
|
||||
|
||||
public void Draw(bool changed)
|
||||
{
|
||||
using (var combo = ImGuiUtil.BeginComboVertical(Language.Options_Language_Name, Mutable.LanguageOverride.Name()))
|
||||
{
|
||||
if (combo.Success)
|
||||
{
|
||||
foreach (var language in Enum.GetValues<LanguageOverride>())
|
||||
if (ImGui.Selectable(language.Name()))
|
||||
Mutable.LanguageOverride = language;
|
||||
}
|
||||
}
|
||||
|
||||
ImGuiUtil.HelpText(string.Format(Language.Options_Language_Description, Plugin.PluginName));
|
||||
ImGui.Spacing();
|
||||
|
||||
using (var combo = ImGuiUtil.BeginComboVertical(Language.Options_CommandHelpSide_Name, Mutable.CommandHelpSide.Name()))
|
||||
{
|
||||
if (combo.Success)
|
||||
{
|
||||
foreach (var side in Enum.GetValues<CommandHelpSide>())
|
||||
if (ImGui.Selectable(side.Name(), Mutable.CommandHelpSide == side))
|
||||
Mutable.CommandHelpSide = side;
|
||||
}
|
||||
}
|
||||
|
||||
ImGuiUtil.HelpText(string.Format(Language.Options_CommandHelpSide_Description, Plugin.PluginName));
|
||||
ImGui.Spacing();
|
||||
|
||||
using (var combo = ImGuiUtil.BeginComboVertical(Language.Options_KeybindMode_Name, Mutable.KeybindMode.Name()))
|
||||
{
|
||||
if (combo.Success)
|
||||
{
|
||||
foreach (var mode in Enum.GetValues<KeybindMode>())
|
||||
{
|
||||
if (ImGui.Selectable(mode.Name(), Mutable.KeybindMode == mode))
|
||||
Mutable.KeybindMode = mode;
|
||||
|
||||
if (ImGui.IsItemHovered())
|
||||
ImGuiUtil.Tooltip(mode.Tooltip() ?? "");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ImGuiUtil.HelpText(string.Format(Language.Options_KeybindMode_Description, Plugin.PluginName));
|
||||
ImGui.Spacing();
|
||||
|
||||
ImGui.Checkbox(Language.Options_SortAutoTranslate_Name, ref Mutable.SortAutoTranslate);
|
||||
ImGuiUtil.HelpText(Language.Options_SortAutoTranslate_Description);
|
||||
ImGui.Spacing();
|
||||
}
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
using ChatTwo.Resources;
|
||||
using ChatTwo.Util;
|
||||
using Dalamud.Bindings.ImGui;
|
||||
using Dalamud.Interface.Utility.Raii;
|
||||
|
||||
namespace ChatTwo.Ui.SettingsTabs;
|
||||
|
||||
internal sealed class Preview : ISettingsTab
|
||||
{
|
||||
private Configuration Mutable { get; }
|
||||
|
||||
public string Name => $"{Language.Options_Preview_Tab}###tabs-preview";
|
||||
|
||||
internal Preview(Configuration mutable)
|
||||
{
|
||||
Mutable = mutable;
|
||||
}
|
||||
|
||||
public void Draw(bool changed)
|
||||
{
|
||||
using var wrap = ImRaii.TextWrapPos(0.0f);
|
||||
|
||||
using (var combo = ImGuiUtil.BeginComboVertical(Language.Options_Preview_Name, Mutable.PreviewPosition.Name()))
|
||||
{
|
||||
if (combo)
|
||||
{
|
||||
foreach (var position in Enum.GetValues<PreviewPosition>())
|
||||
if (ImGui.Selectable(position.Name(), Mutable.PreviewPosition == position))
|
||||
Mutable.PreviewPosition = position;
|
||||
}
|
||||
}
|
||||
ImGuiUtil.HelpText(Language.Options_Preview_Description);
|
||||
ImGui.Spacing();
|
||||
|
||||
if (ImGuiUtil.InputIntVertical(Language.Options_PreviewMinimum_Name, Language.Options_PreviewMinimum_Description, ref Mutable.PreviewMinimum))
|
||||
Mutable.PreviewMinimum = Math.Clamp(Mutable.PreviewMinimum, 1, 250);
|
||||
ImGui.Spacing();
|
||||
ImGuiUtil.OptionCheckbox(ref Mutable.OnlyPreviewIf, Language.Options_PreviewOnlyIf_Name, Language.Options_PreviewOnlyIf_Description);
|
||||
|
||||
ImGui.Spacing();
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,9 @@ using ChatTwo.Export;
|
||||
using ChatTwo.Privacy;
|
||||
using ChatTwo.Resources;
|
||||
using ChatTwo.Util;
|
||||
using Dalamud.Interface.Colors;
|
||||
using Dalamud.Interface.ImGuiNotification;
|
||||
using Dalamud.Interface.Utility;
|
||||
using Dalamud.Interface.Utility.Raii;
|
||||
using Dalamud.Bindings.ImGui;
|
||||
|
||||
@@ -54,6 +56,8 @@ internal sealed class Privacy : ISettingsTab
|
||||
private long CleanupKeepCount;
|
||||
private long CleanupDeleteCount;
|
||||
private bool CleanupRunning;
|
||||
private bool CleanupPreviewStale;
|
||||
private HashSet<ChatType>? CleanupPreviewSnapshot;
|
||||
|
||||
// The retention-running state lives on Plugin so the auto-sweep and
|
||||
// this manual button see the same flag. UI reads stay lock-free
|
||||
@@ -73,41 +77,67 @@ internal sealed class Privacy : ISettingsTab
|
||||
Plugin.FirstRunWizard.IsOpen = true;
|
||||
ImGui.Spacing();
|
||||
|
||||
ImGui.TextUnformatted(HellionStrings.Theme_Heading);
|
||||
using (ImRaii.PushIndent(ImGui.GetStyle().IndentSpacing, false))
|
||||
{
|
||||
ImGuiUtil.OptionCheckbox(
|
||||
ref Mutable.HellionThemeEnabled,
|
||||
HellionStrings.Theme_Enabled_Name,
|
||||
HellionStrings.Theme_Enabled_Description);
|
||||
|
||||
using (ImRaii.Disabled(!Mutable.HellionThemeEnabled))
|
||||
{
|
||||
ImGui.Spacing();
|
||||
var opacity = Mutable.HellionThemeWindowOpacity;
|
||||
if (ImGui.SliderFloat($"{HellionStrings.Theme_WindowOpacity_Label}##theme-opacity", ref opacity, 0.5f, 1.0f, "%.2f"))
|
||||
Mutable.HellionThemeWindowOpacity = Math.Clamp(opacity, 0.5f, 1.0f);
|
||||
ImGuiUtil.HelpText(HellionStrings.Theme_WindowOpacity_Help);
|
||||
}
|
||||
|
||||
ImGui.Spacing();
|
||||
|
||||
ImGuiUtil.OptionCheckbox(
|
||||
ref Mutable.UseHellionFont,
|
||||
HellionStrings.Theme_UseHellionFont_Name,
|
||||
HellionStrings.Theme_UseHellionFont_Description);
|
||||
}
|
||||
DrawPrivacyFilterSection();
|
||||
|
||||
ImGui.Spacing();
|
||||
ImGui.Separator();
|
||||
ImGui.Spacing();
|
||||
|
||||
DrawRetentionSection();
|
||||
|
||||
ImGui.Spacing();
|
||||
ImGui.Separator();
|
||||
ImGui.Spacing();
|
||||
|
||||
DrawCleanupSection();
|
||||
|
||||
ImGui.Spacing();
|
||||
ImGui.Separator();
|
||||
ImGui.Spacing();
|
||||
|
||||
DrawExportSection();
|
||||
|
||||
ImGui.Spacing();
|
||||
ImGui.Separator();
|
||||
ImGui.Spacing();
|
||||
|
||||
DrawAutoTellTabsPreloadSection();
|
||||
}
|
||||
|
||||
private void DrawAutoTellTabsPreloadSection()
|
||||
{
|
||||
using var tree = ImRaii.TreeNode(HellionStrings.Privacy_AutoTellTabs_Section_Title);
|
||||
if (!tree.Success)
|
||||
return;
|
||||
|
||||
using (ImRaii.PushIndent(ImGui.GetStyle().IndentSpacing, false))
|
||||
{
|
||||
var preload = Mutable.AutoTellTabsHistoryPreload;
|
||||
ImGui.SetNextItemWidth(200f * ImGuiHelpers.GlobalScale);
|
||||
if (ImGui.SliderInt(HellionStrings.Privacy_AutoTellTabs_Preload_Name, ref preload, 0, 100))
|
||||
{
|
||||
Mutable.AutoTellTabsHistoryPreload = preload;
|
||||
}
|
||||
ImGuiUtil.HelpMarker(HellionStrings.Privacy_AutoTellTabs_Preload_Description);
|
||||
|
||||
ImGui.Spacing();
|
||||
ImGuiUtil.HelpText(HellionStrings.Privacy_AutoTellTabs_Preload_Hint);
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawPrivacyFilterSection()
|
||||
{
|
||||
using var tree = ImRaii.TreeNode(HellionStrings.Privacy_Filter_Tree_Heading);
|
||||
if (!tree.Success)
|
||||
return;
|
||||
|
||||
using (ImRaii.PushIndent(ImGui.GetStyle().IndentSpacing, false))
|
||||
{
|
||||
ImGuiUtil.OptionCheckbox(
|
||||
ref Mutable.PrivacyFilterEnabled,
|
||||
HellionStrings.Privacy_FilterEnabled_Name,
|
||||
HellionStrings.Privacy_FilterEnabled_Description);
|
||||
|
||||
ImGuiUtil.HelpText(HellionStrings.Privacy_FilterEnabled_StorageOnly_Help);
|
||||
ImGuiUtil.HelpMarker(HellionStrings.Privacy_FilterEnabled_StorageOnly_Help);
|
||||
|
||||
ImGui.Spacing();
|
||||
ImGui.Separator();
|
||||
@@ -138,8 +168,8 @@ internal sealed class Privacy : ISettingsTab
|
||||
|
||||
foreach (var (heading, types) in Groups)
|
||||
{
|
||||
using var tree = ImRaii.TreeNode(heading());
|
||||
if (!tree.Success)
|
||||
using var groupTree = ImRaii.TreeNode(heading());
|
||||
if (!groupTree.Success)
|
||||
continue;
|
||||
|
||||
using (ImRaii.PushIndent(ImGui.GetStyle().IndentSpacing, false))
|
||||
@@ -168,24 +198,7 @@ internal sealed class Privacy : ISettingsTab
|
||||
HellionStrings.Privacy_PersistUnknown_Name,
|
||||
HellionStrings.Privacy_PersistUnknown_Description);
|
||||
}
|
||||
|
||||
ImGui.Spacing();
|
||||
ImGui.Separator();
|
||||
ImGui.Spacing();
|
||||
|
||||
DrawRetentionSection();
|
||||
|
||||
ImGui.Spacing();
|
||||
ImGui.Separator();
|
||||
ImGui.Spacing();
|
||||
|
||||
DrawCleanupSection();
|
||||
|
||||
ImGui.Spacing();
|
||||
ImGui.Separator();
|
||||
ImGui.Spacing();
|
||||
|
||||
DrawExportSection();
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawExportSection()
|
||||
@@ -335,7 +348,7 @@ internal sealed class Privacy : ISettingsTab
|
||||
var defaultDays = Mutable.RetentionDefaultDays;
|
||||
if (ImGui.InputInt(HellionStrings.Retention_Default_Label, ref defaultDays))
|
||||
Mutable.RetentionDefaultDays = Math.Max(0, defaultDays);
|
||||
ImGuiUtil.HelpText(HellionStrings.Retention_Default_Help);
|
||||
ImGuiUtil.HelpMarker(HellionStrings.Retention_Default_Help);
|
||||
|
||||
ImGui.Spacing();
|
||||
|
||||
@@ -393,6 +406,9 @@ internal sealed class Privacy : ISettingsTab
|
||||
|
||||
ImGui.Spacing();
|
||||
|
||||
ImGuiUtil.HelpText(HellionStrings.Retention_Help_SavedNote);
|
||||
ImGui.Spacing();
|
||||
|
||||
using (ImRaii.Disabled(RetentionRunning))
|
||||
{
|
||||
if (ImGuiUtil.CtrlShiftButton(HellionStrings.Retention_Apply_Label, HellionStrings.Retention_Apply_Tooltip))
|
||||
@@ -471,6 +487,21 @@ internal sealed class Privacy : ISettingsTab
|
||||
|
||||
ImGui.Spacing();
|
||||
|
||||
// Drift-detection between the snapshot taken at last refresh
|
||||
// and the current Mutable whitelist. Cleanup itself runs on
|
||||
// the SAVED policy (Cleanup_Help_SavedNote covers that), but
|
||||
// the user usually expects "the preview reflects what I just
|
||||
// ticked" — so we surface the divergence instead of silently
|
||||
// showing stale numbers.
|
||||
if (CleanupPreviewSnapshot is not null
|
||||
&& !CleanupPreviewSnapshot.SetEquals(Mutable.PrivacyPersistChannels))
|
||||
{
|
||||
CleanupPreviewStale = true;
|
||||
}
|
||||
|
||||
using (var emphasis = CleanupPreviewStale
|
||||
? ImRaii.PushColor(ImGuiCol.Button, ImGuiColors.HealerGreen with { W = 0.6f })
|
||||
: null)
|
||||
using (ImRaii.Disabled(CleanupRunning))
|
||||
{
|
||||
if (ImGui.Button(HellionStrings.Cleanup_RefreshPreview))
|
||||
@@ -483,10 +514,22 @@ internal sealed class Privacy : ISettingsTab
|
||||
return;
|
||||
}
|
||||
|
||||
if (CleanupPreviewStale)
|
||||
{
|
||||
ImGui.Spacing();
|
||||
ImGuiUtil.HelpText(HellionStrings.Cleanup_Preview_Stale);
|
||||
}
|
||||
|
||||
ImGui.Spacing();
|
||||
|
||||
using (var staleColor = CleanupPreviewStale
|
||||
? ImRaii.PushColor(ImGuiCol.Text, ImGuiColors.DalamudGrey)
|
||||
: null)
|
||||
{
|
||||
ImGuiUtil.HelpText(string.Format(HellionStrings.Cleanup_TotalStored, CleanupKeepCount + CleanupDeleteCount));
|
||||
ImGuiUtil.HelpText(string.Format(HellionStrings.Cleanup_WillKeep, CleanupKeepCount));
|
||||
ImGuiUtil.HelpText(string.Format(HellionStrings.Cleanup_WillDelete, CleanupDeleteCount));
|
||||
}
|
||||
|
||||
using (var tree = ImRaii.TreeNode(HellionStrings.Cleanup_Breakdown))
|
||||
{
|
||||
@@ -542,6 +585,13 @@ internal sealed class Privacy : ISettingsTab
|
||||
else
|
||||
CleanupDeleteCount += count;
|
||||
}
|
||||
|
||||
// Snapshot the whitelist as it stood at preview-time so the
|
||||
// render pass can flag the user about subsequent edits. Only
|
||||
// updated on success — if the preview throws, the previous
|
||||
// snapshot stays in place so stale-detection keeps working.
|
||||
CleanupPreviewSnapshot = new HashSet<ChatType>(Mutable.PrivacyPersistChannels);
|
||||
CleanupPreviewStale = false;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
|
||||
@@ -10,10 +10,10 @@ namespace ChatTwo.Ui.SettingsTabs;
|
||||
|
||||
internal sealed class Tabs : ISettingsTab
|
||||
{
|
||||
private readonly Plugin Plugin;
|
||||
private Plugin Plugin { get; }
|
||||
private Configuration Mutable { get; }
|
||||
|
||||
public string Name => Language.Options_Tabs_Tab + "###tabs-tabs";
|
||||
public string Name => HellionStrings.Settings_Tab_Tabs + "###tabs-tabs";
|
||||
|
||||
private int ToOpen = -2;
|
||||
|
||||
@@ -27,6 +27,9 @@ internal sealed class Tabs : ISettingsTab
|
||||
{
|
||||
const string addTabPopup = "add-tab-popup";
|
||||
|
||||
ImGuiUtil.HelpText(HellionStrings.Tabs_Presets_Linkshell_Hint);
|
||||
ImGui.Spacing();
|
||||
|
||||
if (ImGuiUtil.IconButton(FontAwesomeIcon.Plus, tooltip: Language.Options_Tabs_Add))
|
||||
ImGui.OpenPopup(addTabPopup);
|
||||
|
||||
|
||||
@@ -0,0 +1,163 @@
|
||||
using ChatTwo.Resources;
|
||||
using ChatTwo.Util;
|
||||
using Dalamud.Interface.Utility.Raii;
|
||||
using Dalamud.Bindings.ImGui;
|
||||
|
||||
namespace ChatTwo.Ui.SettingsTabs;
|
||||
|
||||
internal sealed class Window : ISettingsTab
|
||||
{
|
||||
private Plugin Plugin { get; }
|
||||
private Configuration Mutable { get; }
|
||||
|
||||
public string Name => HellionStrings.Settings_Tab_Window + "###tabs-window";
|
||||
|
||||
internal Window(Plugin plugin, Configuration mutable)
|
||||
{
|
||||
Plugin = plugin;
|
||||
Mutable = mutable;
|
||||
}
|
||||
|
||||
public void Draw(bool changed)
|
||||
{
|
||||
DrawHideSection();
|
||||
ImGui.Spacing();
|
||||
DrawInactivityHideSection();
|
||||
ImGui.Spacing();
|
||||
DrawFrameSection();
|
||||
ImGui.Spacing();
|
||||
DrawTooltipsSection();
|
||||
}
|
||||
|
||||
private void DrawHideSection()
|
||||
{
|
||||
using var tree = ImRaii.TreeNode(HellionStrings.Settings_Window_Hide_Heading);
|
||||
if (!tree.Success)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
using (ImRaii.PushIndent(ImGui.GetStyle().IndentSpacing, false))
|
||||
{
|
||||
ImGui.Checkbox(Language.Options_HideChat_Name, ref Mutable.HideChat);
|
||||
ImGuiUtil.HelpMarker(Language.Options_HideChat_Description);
|
||||
|
||||
ImGui.Checkbox(Language.Options_HideDuringCutscenes_Name, ref Mutable.HideDuringCutscenes);
|
||||
ImGuiUtil.HelpMarker(string.Format(Language.Options_HideDuringCutscenes_Description, Plugin.PluginName));
|
||||
|
||||
ImGui.Checkbox(Language.Options_HideWhenNotLoggedIn_Name, ref Mutable.HideWhenNotLoggedIn);
|
||||
ImGuiUtil.HelpMarker(string.Format(Language.Options_HideWhenNotLoggedIn_Description, Plugin.PluginName));
|
||||
|
||||
ImGui.Checkbox(Language.Options_HideWhenUiHidden_Name, ref Mutable.HideWhenUiHidden);
|
||||
ImGuiUtil.HelpMarker(string.Format(Language.Options_HideWhenUiHidden_Description, Plugin.PluginName));
|
||||
|
||||
ImGui.Checkbox(Language.Options_HideInLoadingScreens_Name, ref Mutable.HideInLoadingScreens);
|
||||
ImGuiUtil.HelpMarker(string.Format(Language.Options_HideInLoadingScreens_Description, Plugin.PluginName));
|
||||
|
||||
ImGui.Checkbox(Language.Options_HideInBattle_Name, ref Mutable.HideInBattle);
|
||||
ImGuiUtil.HelpMarker(Language.Options_HideInBattle_Description);
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawInactivityHideSection()
|
||||
{
|
||||
using var tree = ImRaii.TreeNode(HellionStrings.Settings_Window_InactivityHide_Heading);
|
||||
if (!tree.Success)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
using (ImRaii.PushIndent(ImGui.GetStyle().IndentSpacing, false))
|
||||
{
|
||||
ImGui.Checkbox(Language.Options_HideWhenInactive_Name, ref Mutable.HideWhenInactive);
|
||||
ImGuiUtil.HelpMarker(Language.Options_HideWhenInactive_Description);
|
||||
|
||||
if (!Mutable.HideWhenInactive)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ImGuiUtil.InputIntVertical(Language.Options_InactivityHideTimeout_Name, Language.Options_InactivityHideTimeout_Description, ref Mutable.InactivityHideTimeout, 1, 10);
|
||||
// Untergrenze von 2 Sekunden gegen Selbst-Soft-Lock.
|
||||
Mutable.InactivityHideTimeout = Math.Max(2, Mutable.InactivityHideTimeout);
|
||||
|
||||
using (ImRaii.Disabled(Mutable.HideInBattle))
|
||||
{
|
||||
ImGui.Checkbox(Language.Options_InactivityHideActiveDuringBattle_Name, ref Mutable.InactivityHideActiveDuringBattle);
|
||||
ImGuiUtil.HelpMarker(Language.Options_InactivityHideActiveDuringBattle_Description);
|
||||
}
|
||||
|
||||
using var channelTree = ImRaii.TreeNode(Language.Options_InactivityHideChannels_Name);
|
||||
if (!channelTree.Success)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (ImGuiUtil.CtrlShiftButton(Language.Options_InactivityHideChannels_All_Label, Language.Options_InactivityHideChannels_Button_Tooltip))
|
||||
{
|
||||
Mutable.InactivityHideChannelsV2 = TabsUtil.AllChannels();
|
||||
Mutable.InactivityHideExtraChatAll = true;
|
||||
Mutable.InactivityHideExtraChatChannels = [];
|
||||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
if (ImGuiUtil.CtrlShiftButton(Language.Options_InactivityHideChannels_None_Label, Language.Options_InactivityHideChannels_Button_Tooltip))
|
||||
{
|
||||
Mutable.InactivityHideChannelsV2 = [];
|
||||
Mutable.InactivityHideExtraChatAll = false;
|
||||
Mutable.InactivityHideExtraChatChannels = [];
|
||||
}
|
||||
|
||||
ImGui.Spacing();
|
||||
|
||||
ImGuiUtil.ChannelSelector(Language.Options_Tabs_Channels, Mutable.InactivityHideChannelsV2);
|
||||
ImGuiUtil.ExtraChatSelector(Language.Options_Tabs_ExtraChatChannels, ref Mutable.InactivityHideExtraChatAll, Mutable.InactivityHideExtraChatChannels);
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawFrameSection()
|
||||
{
|
||||
using var tree = ImRaii.TreeNode(HellionStrings.Settings_Window_Frame_Heading);
|
||||
if (!tree.Success)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
using (ImRaii.PushIndent(ImGui.GetStyle().IndentSpacing, false))
|
||||
{
|
||||
ImGui.Checkbox(Language.Options_CanMove_Name, ref Mutable.CanMove);
|
||||
|
||||
ImGui.Checkbox(Language.Options_CanResize_Name, ref Mutable.CanResize);
|
||||
|
||||
ImGui.Checkbox(Language.Options_ShowTitleBar_Name, ref Mutable.ShowTitleBar);
|
||||
|
||||
ImGui.Checkbox(Language.Options_ShowPopOutTitleBar_Name, ref Mutable.ShowPopOutTitleBar);
|
||||
|
||||
ImGui.Checkbox(Language.Options_ShowHideButton_Name, ref Mutable.ShowHideButton);
|
||||
ImGuiUtil.HelpMarker(Language.Options_ShowHideButton_Description);
|
||||
|
||||
ImGui.Checkbox(Language.Options_SidebarTabView_Name, ref Mutable.SidebarTabView);
|
||||
ImGuiUtil.HelpMarker(string.Format(Language.Options_SidebarTabView_Description, Plugin.PluginName));
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawTooltipsSection()
|
||||
{
|
||||
using var tree = ImRaii.TreeNode(HellionStrings.Settings_Window_Tooltips_Heading);
|
||||
if (!tree.Success)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
using (ImRaii.PushIndent(ImGui.GetStyle().IndentSpacing, false))
|
||||
{
|
||||
ImGui.Checkbox(Language.Options_NativeItemTooltips_Name, ref Mutable.NativeItemTooltips);
|
||||
ImGuiUtil.HelpMarker(string.Format(Language.Options_NativeItemTooltips_Description, Plugin.PluginName));
|
||||
|
||||
if (Mutable.NativeItemTooltips)
|
||||
{
|
||||
ImGuiUtil.DragFloatVertical(Language.Options_TooltipOffset_Name, Language.Options_TooltipOffset_Desc, ref Mutable.TooltipOffset, 1, 0f, 400f, $"{Mutable.TooltipOffset:N0}px", ImGuiSliderFlags.AlwaysClamp);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -398,6 +398,60 @@ internal static class ChunkUtil
|
||||
return builder.ToString();
|
||||
}
|
||||
|
||||
// Hellion Chat — shared helper for Auto-Tell-Tabs and the MessageStore
|
||||
// history-preload query. Walks the chunk list once and returns the
|
||||
// first PlayerPayload it finds, or null when the message has no
|
||||
// resolved player link (e.g. system messages, GM tells we already
|
||||
// skipped earlier in the pipeline).
|
||||
internal static PlayerPayload? TryGetPlayerPayload(IReadOnlyList<Chunk> chunks)
|
||||
{
|
||||
foreach (var chunk in chunks)
|
||||
{
|
||||
if (chunk.Link is PlayerPayload pp)
|
||||
{
|
||||
return pp;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// Fallback for tells where the PlayerPayload lives in the raw SeString
|
||||
// payload list rather than on a chunk's Link slot. Same semantics as
|
||||
// the chunk-walking variant above: returns the first PlayerPayload or
|
||||
// null if the SeString has none.
|
||||
internal static PlayerPayload? TryGetPlayerPayload(SeString? seString)
|
||||
{
|
||||
if (seString == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
foreach (var payload in seString.Payloads)
|
||||
{
|
||||
if (payload is PlayerPayload pp)
|
||||
{
|
||||
return pp;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// True when the message's sender (or, as a fallback, content) carries a
|
||||
// PlayerPayload that matches the given identity. Used by both the
|
||||
// Tab.Matches sender filter and the MessageStore tell-history scan.
|
||||
internal static bool MatchesSender(Message message, string senderName, uint senderWorld)
|
||||
{
|
||||
var payload = TryGetPlayerPayload(message.Sender) ?? TryGetPlayerPayload(message.Content);
|
||||
if (payload == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (!string.Equals(payload.PlayerName, senderName, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return payload.World.RowId == senderWorld;
|
||||
}
|
||||
|
||||
internal static readonly RawPayload PeriodicRecruitmentLink = new([0x02, 0x27, 0x07, 0x08, 0x01, 0x01, 0x01, 0xFF, 0x01, 0x03]);
|
||||
|
||||
private static uint GetInteger(BinaryReader input)
|
||||
|
||||
@@ -215,6 +215,28 @@ internal static class ImGuiUtil
|
||||
ImGui.TextUnformatted(text);
|
||||
}
|
||||
|
||||
// Hellion Chat — compact help affordance: a dimmed "(?)" glyph rendered
|
||||
// on the same line as the previous item, with the long-form description
|
||||
// tucked into a hover tooltip. Lets us keep the settings panes scannable
|
||||
// instead of stacking a wall of HelpText paragraphs under every option.
|
||||
internal static void HelpMarker(string description)
|
||||
{
|
||||
ImGui.SameLine();
|
||||
using (ImRaii.PushColor(ImGuiCol.Text, ImGui.GetStyle().Colors[(int) ImGuiCol.TextDisabled]))
|
||||
ImGui.TextUnformatted("(?)");
|
||||
|
||||
// AllowWhenDisabled — ohne das Flag liefert IsItemHovered bei
|
||||
// ausgegrauten Settings false, der User könnte nicht mehr lesen
|
||||
// warum eine Option nicht aktiv ist. Genau dann braucht er den
|
||||
// Hover-Tooltip aber am dringendsten.
|
||||
if (!ImGui.IsItemHovered(ImGuiHoveredFlags.AllowWhenDisabled))
|
||||
return;
|
||||
|
||||
using var tooltip = ImRaii.Tooltip();
|
||||
using (ImRaii.TextWrapPos(35.0f * ImGui.GetFontSize()))
|
||||
ImGui.TextUnformatted(description);
|
||||
}
|
||||
|
||||
internal static void WarningText(string text, bool wrap = true)
|
||||
{
|
||||
var style = StyleModel.GetConfiguredStyle() ?? StyleModel.GetFromCurrent();
|
||||
|
||||
+104
-29
@@ -14,21 +14,20 @@ public static class TabsUtil
|
||||
return channels;
|
||||
}
|
||||
|
||||
// Hellion-tuned General preset. The pure player-talk catch-all plus
|
||||
// the active-gameplay event streams (loot, crafting, gathering, NPC
|
||||
// dialogue, party-finder pings). Pure technical noise (System, Error,
|
||||
// Login/Logout spam, retainer sales, alarms, sign messages) lives in
|
||||
// the dedicated System tab so it doesn't bury actual conversation.
|
||||
public static Tab VanillaGeneral => new()
|
||||
{
|
||||
Name = Language.Tabs_Presets_General,
|
||||
SelectedChannels = new Dictionary<ChatType, (ChatSource, ChatSource)>
|
||||
{
|
||||
// Special
|
||||
[ChatType.Debug] = (ChatSourceExt.All, ChatSourceExt.All),
|
||||
[ChatType.Urgent] = (ChatSourceExt.All, ChatSourceExt.All),
|
||||
[ChatType.Notice] = (ChatSourceExt.All, ChatSourceExt.All),
|
||||
// Chat
|
||||
// Player chat
|
||||
[ChatType.Say] = (ChatSourceExt.All, ChatSourceExt.All),
|
||||
[ChatType.Yell] = (ChatSourceExt.All, ChatSourceExt.All),
|
||||
[ChatType.Shout] = (ChatSourceExt.All, ChatSourceExt.All),
|
||||
[ChatType.TellIncoming] = (ChatSourceExt.All, ChatSourceExt.All),
|
||||
[ChatType.TellOutgoing] = (ChatSourceExt.All, ChatSourceExt.All),
|
||||
[ChatType.Party] = (ChatSourceExt.All, ChatSourceExt.All),
|
||||
[ChatType.CrossParty] = (ChatSourceExt.All, ChatSourceExt.All),
|
||||
[ChatType.Alliance] = (ChatSourceExt.All, ChatSourceExt.All),
|
||||
@@ -50,33 +49,13 @@ public static class TabsUtil
|
||||
[ChatType.Linkshell6] = (ChatSourceExt.All, ChatSourceExt.All),
|
||||
[ChatType.Linkshell7] = (ChatSourceExt.All, ChatSourceExt.All),
|
||||
[ChatType.Linkshell8] = (ChatSourceExt.All, ChatSourceExt.All),
|
||||
[ChatType.NoviceNetwork] = (ChatSourceExt.All, ChatSourceExt.All),
|
||||
[ChatType.StandardEmote] = (ChatSourceExt.All, ChatSourceExt.All),
|
||||
[ChatType.CustomEmote] = (ChatSourceExt.All, ChatSourceExt.All),
|
||||
// Announcements
|
||||
[ChatType.System] = (ChatSourceExt.All, ChatSourceExt.All),
|
||||
[ChatType.GatheringSystem] = (ChatSourceExt.All, ChatSourceExt.All),
|
||||
[ChatType.Error] = (ChatSourceExt.All, ChatSourceExt.All),
|
||||
[ChatType.Echo] = (ChatSourceExt.All, ChatSourceExt.All),
|
||||
[ChatType.NoviceNetworkSystem] = (ChatSourceExt.All, ChatSourceExt.All),
|
||||
[ChatType.FreeCompanyAnnouncement] = (ChatSourceExt.All, ChatSourceExt.All),
|
||||
[ChatType.PvpTeamAnnouncement] = (ChatSourceExt.All, ChatSourceExt.All),
|
||||
[ChatType.FreeCompanyLoginLogout] = (ChatSourceExt.All, ChatSourceExt.All),
|
||||
[ChatType.PvpTeamLoginLogout] = (ChatSourceExt.All, ChatSourceExt.All),
|
||||
[ChatType.RetainerSale] = (ChatSourceExt.All, ChatSourceExt.All),
|
||||
[ChatType.NpcAnnouncement] = (ChatSourceExt.All, ChatSourceExt.All),
|
||||
// Active-gameplay events
|
||||
[ChatType.NpcDialogue] = (ChatSourceExt.All, ChatSourceExt.All),
|
||||
[ChatType.LootNotice] = (ChatSourceExt.All, ChatSourceExt.All),
|
||||
[ChatType.Progress] = (ChatSourceExt.All, ChatSourceExt.All),
|
||||
[ChatType.LootRoll] = (ChatSourceExt.All, ChatSourceExt.All),
|
||||
[ChatType.Crafting] = (ChatSourceExt.All, ChatSourceExt.All),
|
||||
[ChatType.Gathering] = (ChatSource.LocalPlayer, ChatSource.LocalPlayer),
|
||||
[ChatType.PeriodicRecruitmentNotification] = (ChatSourceExt.All, ChatSourceExt.All),
|
||||
[ChatType.Sign] = (ChatSourceExt.All, ChatSourceExt.All),
|
||||
[ChatType.RandomNumber] = (ChatSourceExt.All, ChatSourceExt.All),
|
||||
[ChatType.Orchestrion] = (ChatSourceExt.All, ChatSourceExt.All),
|
||||
[ChatType.MessageBook] = (ChatSourceExt.All, ChatSourceExt.All),
|
||||
[ChatType.Alarm] = (ChatSourceExt.All, ChatSourceExt.All),
|
||||
[ChatType.GlamourNotifications] = (ChatSourceExt.All, ChatSourceExt.All),
|
||||
}
|
||||
};
|
||||
|
||||
@@ -98,6 +77,102 @@ public static class TabsUtil
|
||||
AllSenderMessages = true,
|
||||
};
|
||||
|
||||
// Hellion default-tab presets used by the v10 wipe migration. Names are
|
||||
// kept in HellionStrings (EN+DE) instead of Language.* so the upstream
|
||||
// resource files stay untouched. Channel selections cover the channels
|
||||
// a typical Eorzea raider uses without forcing the user to hand-tick
|
||||
// each box on first start.
|
||||
public static Tab HellionFreeCompany => new()
|
||||
{
|
||||
Name = HellionStrings.Tabs_Presets_FreeCompany,
|
||||
SelectedChannels = new Dictionary<ChatType, (ChatSource, ChatSource)>
|
||||
{
|
||||
[ChatType.FreeCompany] = (ChatSourceExt.All, ChatSourceExt.All),
|
||||
[ChatType.FreeCompanyAnnouncement] = (ChatSourceExt.All, ChatSourceExt.All),
|
||||
[ChatType.FreeCompanyLoginLogout] = (ChatSourceExt.All, ChatSourceExt.All),
|
||||
},
|
||||
Channel = InputChannel.FreeCompany,
|
||||
};
|
||||
|
||||
public static Tab HellionParty => new()
|
||||
{
|
||||
Name = HellionStrings.Tabs_Presets_Party,
|
||||
SelectedChannels = new Dictionary<ChatType, (ChatSource, ChatSource)>
|
||||
{
|
||||
[ChatType.Party] = (ChatSourceExt.All, ChatSourceExt.All),
|
||||
[ChatType.CrossParty] = (ChatSourceExt.All, ChatSourceExt.All),
|
||||
[ChatType.Alliance] = (ChatSourceExt.All, ChatSourceExt.All),
|
||||
[ChatType.PvpTeam] = (ChatSourceExt.All, ChatSourceExt.All),
|
||||
[ChatType.PvpTeamAnnouncement] = (ChatSourceExt.All, ChatSourceExt.All),
|
||||
[ChatType.PvpTeamLoginLogout] = (ChatSourceExt.All, ChatSourceExt.All),
|
||||
[ChatType.LootNotice] = (ChatSourceExt.All, ChatSourceExt.All),
|
||||
[ChatType.LootRoll] = (ChatSourceExt.All, ChatSourceExt.All),
|
||||
},
|
||||
Channel = InputChannel.Party,
|
||||
};
|
||||
|
||||
public static Tab HellionBeginner => new()
|
||||
{
|
||||
Name = HellionStrings.Tabs_Presets_Beginner,
|
||||
SelectedChannels = new Dictionary<ChatType, (ChatSource, ChatSource)>
|
||||
{
|
||||
[ChatType.NoviceNetwork] = (ChatSourceExt.All, ChatSourceExt.All),
|
||||
[ChatType.NoviceNetworkSystem] = (ChatSourceExt.All, ChatSourceExt.All),
|
||||
},
|
||||
Channel = InputChannel.NoviceNetwork,
|
||||
};
|
||||
|
||||
public static Tab HellionSystem => new()
|
||||
{
|
||||
Name = HellionStrings.Tabs_Presets_System,
|
||||
SelectedChannels = new Dictionary<ChatType, (ChatSource, ChatSource)>
|
||||
{
|
||||
[ChatType.Debug] = (ChatSourceExt.All, ChatSourceExt.All),
|
||||
[ChatType.Urgent] = (ChatSourceExt.All, ChatSourceExt.All),
|
||||
[ChatType.Notice] = (ChatSourceExt.All, ChatSourceExt.All),
|
||||
[ChatType.System] = (ChatSourceExt.All, ChatSourceExt.All),
|
||||
[ChatType.Error] = (ChatSourceExt.All, ChatSourceExt.All),
|
||||
[ChatType.Echo] = (ChatSourceExt.All, ChatSourceExt.All),
|
||||
[ChatType.GatheringSystem] = (ChatSourceExt.All, ChatSourceExt.All),
|
||||
[ChatType.NoviceNetworkSystem] = (ChatSourceExt.All, ChatSourceExt.All),
|
||||
[ChatType.NpcAnnouncement] = (ChatSourceExt.All, ChatSourceExt.All),
|
||||
[ChatType.FreeCompanyLoginLogout] = (ChatSourceExt.All, ChatSourceExt.All),
|
||||
[ChatType.PvpTeamLoginLogout] = (ChatSourceExt.All, ChatSourceExt.All),
|
||||
[ChatType.RetainerSale] = (ChatSourceExt.All, ChatSourceExt.All),
|
||||
[ChatType.Progress] = (ChatSourceExt.All, ChatSourceExt.All),
|
||||
[ChatType.RandomNumber] = (ChatSourceExt.All, ChatSourceExt.All),
|
||||
[ChatType.Orchestrion] = (ChatSourceExt.All, ChatSourceExt.All),
|
||||
[ChatType.MessageBook] = (ChatSourceExt.All, ChatSourceExt.All),
|
||||
[ChatType.Alarm] = (ChatSourceExt.All, ChatSourceExt.All),
|
||||
[ChatType.Sign] = (ChatSourceExt.All, ChatSourceExt.All),
|
||||
[ChatType.GlamourNotifications] = (ChatSourceExt.All, ChatSourceExt.All),
|
||||
},
|
||||
};
|
||||
|
||||
public static Tab HellionLinkshell => new()
|
||||
{
|
||||
Name = HellionStrings.Tabs_Presets_Linkshell,
|
||||
SelectedChannels = new Dictionary<ChatType, (ChatSource, ChatSource)>
|
||||
{
|
||||
[ChatType.Linkshell1] = (ChatSourceExt.All, ChatSourceExt.All),
|
||||
[ChatType.Linkshell2] = (ChatSourceExt.All, ChatSourceExt.All),
|
||||
[ChatType.Linkshell3] = (ChatSourceExt.All, ChatSourceExt.All),
|
||||
[ChatType.Linkshell4] = (ChatSourceExt.All, ChatSourceExt.All),
|
||||
[ChatType.Linkshell5] = (ChatSourceExt.All, ChatSourceExt.All),
|
||||
[ChatType.Linkshell6] = (ChatSourceExt.All, ChatSourceExt.All),
|
||||
[ChatType.Linkshell7] = (ChatSourceExt.All, ChatSourceExt.All),
|
||||
[ChatType.Linkshell8] = (ChatSourceExt.All, ChatSourceExt.All),
|
||||
[ChatType.CrossLinkshell1] = (ChatSourceExt.All, ChatSourceExt.All),
|
||||
[ChatType.CrossLinkshell2] = (ChatSourceExt.All, ChatSourceExt.All),
|
||||
[ChatType.CrossLinkshell3] = (ChatSourceExt.All, ChatSourceExt.All),
|
||||
[ChatType.CrossLinkshell4] = (ChatSourceExt.All, ChatSourceExt.All),
|
||||
[ChatType.CrossLinkshell5] = (ChatSourceExt.All, ChatSourceExt.All),
|
||||
[ChatType.CrossLinkshell6] = (ChatSourceExt.All, ChatSourceExt.All),
|
||||
[ChatType.CrossLinkshell7] = (ChatSourceExt.All, ChatSourceExt.All),
|
||||
[ChatType.CrossLinkshell8] = (ChatSourceExt.All, ChatSourceExt.All),
|
||||
},
|
||||
};
|
||||
|
||||
public static Dictionary<ChatType, (ChatSource, ChatSource)> MostlyPlayer => new()
|
||||
{
|
||||
// Special
|
||||
|
||||
Reference in New Issue
Block a user