mirror of
https://github.com/Cockatrice/Cockatrice.git
synced 2026-11-05 01:23:24 -08:00
[Client] Tabbed room channels with unread states and collapsible chat (#7478)
* [Client] Add room channel selector with per-channel chat views
* [Client] Tabbed room channels with unread states, collapse, read-only, banners
Reworks the room chat channel display: channels move out of the combobox
into a tab bar below the message input showing every channel available
to the user.
- Each tab shows its unread message count and is tinted when it holds
unreads or a mention; both colors are user-configurable chat settings
(channelUnreadColor, channelMentionColor) with a settings-page section.
- Clicking the active tab collapses the chat: message view and input
hide and the splitter section minimizes to the tab strip; expanding
restores the previous splitter sizes. Viewing a channel clears its
unread state.
- Channels the user may read but not write (per write_access_level)
show with the message input disabled and a read-only placeholder;
sends are guarded locally, the server still enforces.
- Channel banner_message strings render at the top of their channel
view on join, like the room join message on Main.
- Replayed channel history no longer inflates unread counts; moderation
redaction now applies across all channel views.
* Fixup: keep chat tab bar visible when chat is collapsed
QSplitter children are collapsible by default, so setSizes({1, 0})
hid the whole chat group box including the channel tab bar.
Mark the chat widget non-collapsible so collapsing clamps to its
minimum size (title + tab bar) instead.
* [Room] Explicitly initialize MAIN_CHANNEL per review
* [Room] Address channel tab review follow-ups
- gate @all mentions on moderator/admin sender to match ChatView,
whole-word matching for own name and highlight words
- hide single-channel tab bar so old servers do not show a lone Main
tab that collapses chat on misclick
- capture target ChatView in sendMessage so errors land in the
channel the message was sent from
- share ignore/unregistered filtering between RoomSay and
RoomChannelSay via isRoomMessageIgnored
* [Client] Fix tab_room.cpp formatting per clang-format-18
---------
Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
Co-authored-by: BruebachL <bruebachl@cockatrice.example>
This commit is contained in:
parent
05dcde69db
commit
c99113ab0e
8 changed files with 543 additions and 38 deletions
|
|
@ -118,6 +118,26 @@ MessagesSettingsPage::MessagesSettingsPage()
|
||||||
highlightGroupBox = new QGroupBox;
|
highlightGroupBox = new QGroupBox;
|
||||||
highlightGroupBox->setLayout(highlightNotice);
|
highlightGroupBox->setLayout(highlightNotice);
|
||||||
|
|
||||||
|
channelUnreadColor = new QLineEdit();
|
||||||
|
channelUnreadColor->setText(SettingsCache::instance().chat().getChannelUnreadColor());
|
||||||
|
updateChannelUnreadPreview();
|
||||||
|
connect(channelUnreadColor, &QLineEdit::textChanged, this, &MessagesSettingsPage::updateChannelUnreadColor);
|
||||||
|
|
||||||
|
channelMentionColor = new QLineEdit();
|
||||||
|
channelMentionColor->setText(SettingsCache::instance().chat().getChannelMentionColor());
|
||||||
|
updateChannelMentionPreview();
|
||||||
|
connect(channelMentionColor, &QLineEdit::textChanged, this, &MessagesSettingsPage::updateChannelMentionColor);
|
||||||
|
|
||||||
|
auto *channelGrid = new QGridLayout;
|
||||||
|
channelGrid->addWidget(&channelUnreadColorLabel, 0, 0);
|
||||||
|
channelGrid->addWidget(channelUnreadColor, 0, 1);
|
||||||
|
channelGrid->addWidget(&hexChannelUnreadLabel, 0, 2);
|
||||||
|
channelGrid->addWidget(&channelMentionColorLabel, 1, 0);
|
||||||
|
channelGrid->addWidget(channelMentionColor, 1, 1);
|
||||||
|
channelGrid->addWidget(&hexChannelMentionLabel, 1, 2);
|
||||||
|
channelGroupBox = new QGroupBox;
|
||||||
|
channelGroupBox->setLayout(channelGrid);
|
||||||
|
|
||||||
messageList = new QListWidget;
|
messageList = new QListWidget;
|
||||||
|
|
||||||
int count = SettingsCache::instance().messages().getCount();
|
int count = SettingsCache::instance().messages().getCount();
|
||||||
|
|
@ -159,6 +179,7 @@ MessagesSettingsPage::MessagesSettingsPage()
|
||||||
mainLayout->addWidget(messageGroupBox);
|
mainLayout->addWidget(messageGroupBox);
|
||||||
mainLayout->addWidget(chatGroupBox);
|
mainLayout->addWidget(chatGroupBox);
|
||||||
mainLayout->addWidget(highlightGroupBox);
|
mainLayout->addWidget(highlightGroupBox);
|
||||||
|
mainLayout->addWidget(channelGroupBox);
|
||||||
|
|
||||||
setLayout(mainLayout);
|
setLayout(mainLayout);
|
||||||
|
|
||||||
|
|
@ -195,6 +216,34 @@ void MessagesSettingsPage::updateHighlightColor(const QString &value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MessagesSettingsPage::updateChannelUnreadColor(const QString &value)
|
||||||
|
{
|
||||||
|
#if (QT_VERSION >= QT_VERSION_CHECK(6, 4, 0))
|
||||||
|
QColor colorToSet = QColor::fromString("#" + value);
|
||||||
|
#else
|
||||||
|
QColor colorToSet;
|
||||||
|
colorToSet.setNamedColor("#" + value);
|
||||||
|
#endif
|
||||||
|
if (colorToSet.isValid()) {
|
||||||
|
SettingsCache::instance().chat().setChannelUnreadColor(value);
|
||||||
|
updateChannelUnreadPreview();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MessagesSettingsPage::updateChannelMentionColor(const QString &value)
|
||||||
|
{
|
||||||
|
#if (QT_VERSION >= QT_VERSION_CHECK(6, 4, 0))
|
||||||
|
QColor colorToSet = QColor::fromString("#" + value);
|
||||||
|
#else
|
||||||
|
QColor colorToSet;
|
||||||
|
colorToSet.setNamedColor("#" + value);
|
||||||
|
#endif
|
||||||
|
if (colorToSet.isValid()) {
|
||||||
|
SettingsCache::instance().chat().setChannelMentionColor(value);
|
||||||
|
updateChannelMentionPreview();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void MessagesSettingsPage::updateTextColor(QT_STATE_CHANGED_T value)
|
void MessagesSettingsPage::updateTextColor(QT_STATE_CHANGED_T value)
|
||||||
{
|
{
|
||||||
SettingsCache::instance().chat().setChatMentionForeground(value);
|
SettingsCache::instance().chat().setChatMentionForeground(value);
|
||||||
|
|
@ -221,6 +270,18 @@ void MessagesSettingsPage::updateHighlightPreview()
|
||||||
";color: " + (SettingsCache::instance().chat().getChatHighlightForeground() ? "white" : "black") + ";}");
|
";color: " + (SettingsCache::instance().chat().getChatHighlightForeground() ? "white" : "black") + ";}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MessagesSettingsPage::updateChannelUnreadPreview()
|
||||||
|
{
|
||||||
|
channelUnreadColor->setStyleSheet("QLineEdit{background:#" +
|
||||||
|
SettingsCache::instance().chat().getChannelUnreadColor() + ";}");
|
||||||
|
}
|
||||||
|
|
||||||
|
void MessagesSettingsPage::updateChannelMentionPreview()
|
||||||
|
{
|
||||||
|
channelMentionColor->setStyleSheet("QLineEdit{background:#" +
|
||||||
|
SettingsCache::instance().chat().getChannelMentionColor() + ";}");
|
||||||
|
}
|
||||||
|
|
||||||
void MessagesSettingsPage::storeSettings()
|
void MessagesSettingsPage::storeSettings()
|
||||||
{
|
{
|
||||||
SettingsCache::instance().messages().setCount(messageList->count());
|
SettingsCache::instance().messages().setCount(messageList->count());
|
||||||
|
|
@ -267,6 +328,9 @@ void MessagesSettingsPage::retranslateUi()
|
||||||
{
|
{
|
||||||
chatGroupBox->setTitle(tr("Chat settings"));
|
chatGroupBox->setTitle(tr("Chat settings"));
|
||||||
highlightGroupBox->setTitle(tr("Custom alert words"));
|
highlightGroupBox->setTitle(tr("Custom alert words"));
|
||||||
|
channelGroupBox->setTitle(tr("Room channel tabs"));
|
||||||
|
channelUnreadColorLabel.setText(tr("Unread messages color"));
|
||||||
|
channelMentionColorLabel.setText(tr("Mentioned messages color"));
|
||||||
chatMentionCheckBox.setText(tr("Enable chat mentions"));
|
chatMentionCheckBox.setText(tr("Enable chat mentions"));
|
||||||
chatMentionCompleterCheckbox.setText(tr("Enable mention completer"));
|
chatMentionCompleterCheckbox.setText(tr("Enable mention completer"));
|
||||||
messageGroupBox->setTitle(tr("In-game message macros"));
|
messageGroupBox->setTitle(tr("In-game message macros"));
|
||||||
|
|
@ -284,6 +348,8 @@ void MessagesSettingsPage::retranslateUi()
|
||||||
useGameTimeCheckBox.setText(tr("Use game time instead of local time in game logs"));
|
useGameTimeCheckBox.setText(tr("Use game time instead of local time in game logs"));
|
||||||
hexLabel.setText(tr("(Color is hexadecimal)"));
|
hexLabel.setText(tr("(Color is hexadecimal)"));
|
||||||
hexHighlightLabel.setText(tr("(Color is hexadecimal)"));
|
hexHighlightLabel.setText(tr("(Color is hexadecimal)"));
|
||||||
|
hexChannelUnreadLabel.setText(tr("(Color is hexadecimal)"));
|
||||||
|
hexChannelMentionLabel.setText(tr("(Color is hexadecimal)"));
|
||||||
customAlertStringLabel.setText(tr("Separate words with a space, alphanumeric characters only"));
|
customAlertStringLabel.setText(tr("Separate words with a space, alphanumeric characters only"));
|
||||||
customAlertString->setPlaceholderText(tr("Word1 Word2 Word3"));
|
customAlertString->setPlaceholderText(tr("Word1 Word2 Word3"));
|
||||||
aAdd->setText(tr("Add New Message"));
|
aAdd->setText(tr("Add New Message"));
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,8 @@ private slots:
|
||||||
void actRemove();
|
void actRemove();
|
||||||
void updateColor(const QString &value);
|
void updateColor(const QString &value);
|
||||||
void updateHighlightColor(const QString &value);
|
void updateHighlightColor(const QString &value);
|
||||||
|
void updateChannelUnreadColor(const QString &value);
|
||||||
|
void updateChannelMentionColor(const QString &value);
|
||||||
void updateTextColor(QT_STATE_CHANGED_T value);
|
void updateTextColor(QT_STATE_CHANGED_T value);
|
||||||
void updateTextHighlightColor(QT_STATE_CHANGED_T value);
|
void updateTextHighlightColor(QT_STATE_CHANGED_T value);
|
||||||
|
|
||||||
|
|
@ -49,18 +51,27 @@ private:
|
||||||
QCheckBox useGameTimeCheckBox;
|
QCheckBox useGameTimeCheckBox;
|
||||||
QGroupBox *chatGroupBox;
|
QGroupBox *chatGroupBox;
|
||||||
QGroupBox *highlightGroupBox;
|
QGroupBox *highlightGroupBox;
|
||||||
|
QGroupBox *channelGroupBox;
|
||||||
QGroupBox *messageGroupBox;
|
QGroupBox *messageGroupBox;
|
||||||
QLineEdit *mentionColor;
|
QLineEdit *mentionColor;
|
||||||
QLineEdit *highlightColor;
|
QLineEdit *highlightColor;
|
||||||
|
QLineEdit *channelUnreadColor;
|
||||||
|
QLineEdit *channelMentionColor;
|
||||||
QLineEdit *customAlertString;
|
QLineEdit *customAlertString;
|
||||||
QLabel hexLabel;
|
QLabel hexLabel;
|
||||||
QLabel hexHighlightLabel;
|
QLabel hexHighlightLabel;
|
||||||
|
QLabel channelUnreadColorLabel;
|
||||||
|
QLabel channelMentionColorLabel;
|
||||||
|
QLabel hexChannelUnreadLabel;
|
||||||
|
QLabel hexChannelMentionLabel;
|
||||||
QLabel customAlertStringLabel;
|
QLabel customAlertStringLabel;
|
||||||
QLabel explainMessagesLabel;
|
QLabel explainMessagesLabel;
|
||||||
|
|
||||||
void storeSettings();
|
void storeSettings();
|
||||||
void updateMentionPreview();
|
void updateMentionPreview();
|
||||||
void updateHighlightPreview();
|
void updateHighlightPreview();
|
||||||
|
void updateChannelUnreadPreview();
|
||||||
|
void updateChannelMentionPreview();
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // COCKATRICE_MESSAGES_SETTINGS_PAGE_H
|
#endif // COCKATRICE_MESSAGES_SETTINGS_PAGE_H
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,7 @@
|
||||||
#include <QAction>
|
#include <QAction>
|
||||||
#include <QApplication>
|
#include <QApplication>
|
||||||
#include <QCloseEvent>
|
#include <QCloseEvent>
|
||||||
|
#include <QColor>
|
||||||
#include <QCompleter>
|
#include <QCompleter>
|
||||||
#include <QDateTime>
|
#include <QDateTime>
|
||||||
#include <QFlag>
|
#include <QFlag>
|
||||||
|
|
@ -38,9 +39,11 @@
|
||||||
#include <QMenu>
|
#include <QMenu>
|
||||||
#include <QRegularExpression>
|
#include <QRegularExpression>
|
||||||
#include <QSplitter>
|
#include <QSplitter>
|
||||||
|
#include <QStackedWidget>
|
||||||
#include <QStringListModel>
|
#include <QStringListModel>
|
||||||
#include <QStringLiteral>
|
#include <QStringLiteral>
|
||||||
#include <QSystemTrayIcon>
|
#include <QSystemTrayIcon>
|
||||||
|
#include <QTabBar>
|
||||||
#include <QToolButton>
|
#include <QToolButton>
|
||||||
#include <QVBoxLayout>
|
#include <QVBoxLayout>
|
||||||
#include <QWidget>
|
#include <QWidget>
|
||||||
|
|
@ -52,6 +55,7 @@
|
||||||
#include <libcockatrice/protocol/pb/event_leave_room.pb.h>
|
#include <libcockatrice/protocol/pb/event_leave_room.pb.h>
|
||||||
#include <libcockatrice/protocol/pb/event_list_games.pb.h>
|
#include <libcockatrice/protocol/pb/event_list_games.pb.h>
|
||||||
#include <libcockatrice/protocol/pb/event_remove_messages.pb.h>
|
#include <libcockatrice/protocol/pb/event_remove_messages.pb.h>
|
||||||
|
#include <libcockatrice/protocol/pb/event_room_channel_say.pb.h>
|
||||||
#include <libcockatrice/protocol/pb/event_room_say.pb.h>
|
#include <libcockatrice/protocol/pb/event_room_say.pb.h>
|
||||||
#include <libcockatrice/protocol/pb/room_commands.pb.h>
|
#include <libcockatrice/protocol/pb/room_commands.pb.h>
|
||||||
#include <libcockatrice/protocol/pb/serverinfo_room.pb.h>
|
#include <libcockatrice/protocol/pb/serverinfo_room.pb.h>
|
||||||
|
|
@ -61,12 +65,45 @@
|
||||||
#include <qnamespace.h>
|
#include <qnamespace.h>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
// Empty id refers to the implicit Main channel.
|
||||||
|
const QString MAIN_CHANNEL = QString();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Privilege rank mirroring Server_Room::userPrivilegeLevel.
|
||||||
|
*
|
||||||
|
* Higher = more privileged. Used to evaluate channel write access locally so
|
||||||
|
* the input can reflect read-only channels; the server re-checks on send.
|
||||||
|
*/
|
||||||
|
int userPrivilegeRank(uint32_t userLevel)
|
||||||
|
{
|
||||||
|
if (userLevel & ServerInfo_User::IsDeveloper) {
|
||||||
|
return static_cast<int>(ServerInfo_RoomChannel::Developer);
|
||||||
|
}
|
||||||
|
if (userLevel & ServerInfo_User::IsAdmin) {
|
||||||
|
return static_cast<int>(ServerInfo_RoomChannel::Admin);
|
||||||
|
}
|
||||||
|
if (userLevel & ServerInfo_User::IsModerator) {
|
||||||
|
return static_cast<int>(ServerInfo_RoomChannel::Moderator);
|
||||||
|
}
|
||||||
|
if (userLevel & ServerInfo_User::IsJudge) {
|
||||||
|
return static_cast<int>(ServerInfo_RoomChannel::Judge);
|
||||||
|
}
|
||||||
|
if (userLevel & ServerInfo_User::IsRegistered) {
|
||||||
|
return static_cast<int>(ServerInfo_RoomChannel::Registered);
|
||||||
|
}
|
||||||
|
return static_cast<int>(ServerInfo_RoomChannel::Public);
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
TabRoom::TabRoom(TabSupervisor *_tabSupervisor,
|
TabRoom::TabRoom(TabSupervisor *_tabSupervisor,
|
||||||
AbstractClient *_client,
|
AbstractClient *_client,
|
||||||
ServerInfo_User *_ownUser,
|
ServerInfo_User *_ownUser,
|
||||||
const ServerInfo_Room &info)
|
const ServerInfo_Room &info)
|
||||||
: Tab(_tabSupervisor), client(_client), roomId(info.room_id()), roomName(QString::fromStdString(info.name())),
|
: Tab(_tabSupervisor), client(_client), roomId(info.room_id()), roomName(QString::fromStdString(info.name())),
|
||||||
ownUser(_ownUser), userListProxy(_tabSupervisor->getUserListManager())
|
ownUser(_ownUser), userListProxy(_tabSupervisor->getUserListManager()), activeChannelId(MAIN_CHANNEL),
|
||||||
|
chatCollapsed(false)
|
||||||
{
|
{
|
||||||
const int gameTypeListSize = info.gametype_list_size();
|
const int gameTypeListSize = info.gametype_list_size();
|
||||||
for (int i = 0; i < gameTypeListSize; ++i) {
|
for (int i = 0; i < gameTypeListSize; ++i) {
|
||||||
|
|
@ -86,14 +123,61 @@ TabRoom::TabRoom(TabSupervisor *_tabSupervisor,
|
||||||
const auto gameInviteLinkProvider = [this]() { return tabSupervisor->getGameInviteLinksForRoom(roomId); };
|
const auto gameInviteLinkProvider = [this]() { return tabSupervisor->getGameInviteLinksForRoom(roomId); };
|
||||||
userList->setGameInviteLinkProvider(gameInviteLinkProvider);
|
userList->setGameInviteLinkProvider(gameInviteLinkProvider);
|
||||||
|
|
||||||
chatView = new ChatView(tabSupervisor, nullptr, true, this);
|
chatViewStack = new QStackedWidget;
|
||||||
connect(chatView, &ChatView::showMentionPopup, this, &TabRoom::actShowMentionPopup);
|
channelTabBar = new QTabBar;
|
||||||
connect(chatView, &ChatView::messageClickedSignal, this, &TabRoom::focusTab);
|
channelTabBar->setShape(QTabBar::RoundedSouth);
|
||||||
connect(chatView, &ChatView::openMessageDialog, this, &TabRoom::openMessageDialog);
|
channelTabBar->setExpanding(true);
|
||||||
connect(chatView, &ChatView::cockatriceLinkActivated, this, &TabRoom::cockatriceLinkActivated);
|
channelTabBar->setUsesScrollButtons(true);
|
||||||
connect(chatView, &ChatView::showCardInfoPopup, this, &TabRoom::showCardInfoPopup);
|
|
||||||
connect(chatView, &ChatView::deleteCardInfoPopup, this, &TabRoom::deleteCardInfoPopup);
|
channelViews.insert(MAIN_CHANNEL, makeChatView());
|
||||||
connect(chatView, &ChatView::addMentionTag, this, &TabRoom::addMentionTag);
|
channelIds << MAIN_CHANNEL;
|
||||||
|
channelDisplayNames.insert(MAIN_CHANNEL, tr("Main"));
|
||||||
|
channelUnreadCounts.insert(MAIN_CHANNEL, 0);
|
||||||
|
channelMentioned.insert(MAIN_CHANNEL, false);
|
||||||
|
channelWritable.insert(MAIN_CHANNEL, true);
|
||||||
|
channelTabBar->addTab(channelDisplayNames.value(MAIN_CHANNEL));
|
||||||
|
chatViewStack->addWidget(channelViews.value(MAIN_CHANNEL));
|
||||||
|
|
||||||
|
const uint32_t ownLevel = ownUser != nullptr ? ownUser->user_level() : 0;
|
||||||
|
const int ownRank = userPrivilegeRank(ownLevel);
|
||||||
|
|
||||||
|
const int channelCount = info.channel_list_size();
|
||||||
|
for (int i = 0; i < channelCount; ++i) {
|
||||||
|
const ServerInfo_RoomChannel &channel = info.channel_list(i);
|
||||||
|
const QString channelId = QString::fromStdString(channel.id());
|
||||||
|
if (channelId.isEmpty()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
QString displayName = QString::fromStdString(channel.display_name());
|
||||||
|
if (displayName.isEmpty()) {
|
||||||
|
displayName = channelId;
|
||||||
|
}
|
||||||
|
const int writeLevel = channel.has_write_access_level() ? static_cast<int>(channel.write_access_level())
|
||||||
|
: static_cast<int>(channel.access_level());
|
||||||
|
ChatView *channelView = makeChatView();
|
||||||
|
const QString banner = QString::fromStdString(channel.banner_message());
|
||||||
|
if (!banner.isEmpty()) {
|
||||||
|
channelView->appendMessage(banner);
|
||||||
|
}
|
||||||
|
channelViews.insert(channelId, channelView);
|
||||||
|
channelIds << channelId;
|
||||||
|
channelDisplayNames.insert(channelId, displayName);
|
||||||
|
channelUnreadCounts.insert(channelId, 0);
|
||||||
|
channelMentioned.insert(channelId, false);
|
||||||
|
channelWritable.insert(channelId, ownRank >= writeLevel);
|
||||||
|
channelTabBar->addTab(displayName);
|
||||||
|
chatViewStack->addWidget(channelViews.value(channelId));
|
||||||
|
}
|
||||||
|
|
||||||
|
chatView = channelViews.value(MAIN_CHANNEL);
|
||||||
|
chatViewStack->setCurrentWidget(chatView);
|
||||||
|
channelTabBar->setCurrentIndex(0);
|
||||||
|
// Old servers never send a channel catalog, so the tab bar would only show
|
||||||
|
// a single "Main" tab where a misclick hides the chat. Only show it when
|
||||||
|
// there is more than one channel to switch between.
|
||||||
|
channelTabBar->setVisible(channelIds.size() > 1);
|
||||||
|
connect(channelTabBar, &QTabBar::currentChanged, this, &TabRoom::onChannelChanged);
|
||||||
|
connect(channelTabBar, &QTabBar::tabBarClicked, this, &TabRoom::onChannelTabClicked);
|
||||||
connect(&SettingsCache::instance().chat(), &ChatSettings::chatMentionCompleterChanged, this,
|
connect(&SettingsCache::instance().chat(), &ChatSettings::chatMentionCompleterChanged, this,
|
||||||
&TabRoom::actCompleterChanged);
|
&TabRoom::actCompleterChanged);
|
||||||
sayLabel = new QLabel;
|
sayLabel = new QLabel;
|
||||||
|
|
@ -101,6 +185,7 @@ TabRoom::TabRoom(TabSupervisor *_tabSupervisor,
|
||||||
sayEdit->setMaxLength(MAX_TEXT_LENGTH);
|
sayEdit->setMaxLength(MAX_TEXT_LENGTH);
|
||||||
sayLabel->setBuddy(sayEdit);
|
sayLabel->setBuddy(sayEdit);
|
||||||
connect(sayEdit, &LineEditCompleter::returnPressed, this, &TabRoom::sendMessage);
|
connect(sayEdit, &LineEditCompleter::returnPressed, this, &TabRoom::sendMessage);
|
||||||
|
updateSayEnabled();
|
||||||
|
|
||||||
auto *chatSettingsMenu = new QMenu(this);
|
auto *chatSettingsMenu = new QMenu(this);
|
||||||
|
|
||||||
|
|
@ -122,19 +207,27 @@ TabRoom::TabRoom(TabSupervisor *_tabSupervisor,
|
||||||
sayHbox->addWidget(sayEdit);
|
sayHbox->addWidget(sayEdit);
|
||||||
sayHbox->addWidget(chatSettingsButton);
|
sayHbox->addWidget(chatSettingsButton);
|
||||||
|
|
||||||
|
sayWidget = new QWidget;
|
||||||
|
sayWidget->setLayout(sayHbox);
|
||||||
|
|
||||||
auto *chatVbox = new QVBoxLayout;
|
auto *chatVbox = new QVBoxLayout;
|
||||||
chatVbox->addWidget(chatView);
|
chatVbox->addWidget(chatViewStack);
|
||||||
chatVbox->addLayout(sayHbox);
|
chatVbox->addWidget(sayWidget);
|
||||||
|
chatVbox->addWidget(channelTabBar);
|
||||||
|
|
||||||
chatGroupBox = new QGroupBox;
|
chatGroupBox = new QGroupBox;
|
||||||
chatGroupBox->setLayout(chatVbox);
|
chatGroupBox->setLayout(chatVbox);
|
||||||
|
|
||||||
auto *splitter = new QSplitter(Qt::Vertical);
|
chatSplitter = new QSplitter(Qt::Vertical);
|
||||||
splitter->addWidget(gameSelector);
|
chatSplitter->addWidget(gameSelector);
|
||||||
splitter->addWidget(chatGroupBox);
|
chatSplitter->addWidget(chatGroupBox);
|
||||||
|
// Splitter children are collapsible by default, so a zero size would hide
|
||||||
|
// the whole chat group box including the tab bar. Keep it clamped to its
|
||||||
|
// minimum size (title + tab bar) when collapsed.
|
||||||
|
chatSplitter->setCollapsible(1, false);
|
||||||
|
|
||||||
auto *hbox = new QHBoxLayout;
|
auto *hbox = new QHBoxLayout;
|
||||||
hbox->addWidget(splitter, 3);
|
hbox->addWidget(chatSplitter, 3);
|
||||||
hbox->addWidget(userListPanel, 1);
|
hbox->addWidget(userListPanel, 1);
|
||||||
|
|
||||||
aLeaveRoom = new QAction(this);
|
aLeaveRoom = new QAction(this);
|
||||||
|
|
@ -189,7 +282,9 @@ TabRoom::TabRoom(TabSupervisor *_tabSupervisor,
|
||||||
void TabRoom::retranslateUi()
|
void TabRoom::retranslateUi()
|
||||||
{
|
{
|
||||||
gameSelector->retranslateUi();
|
gameSelector->retranslateUi();
|
||||||
chatView->retranslateUi();
|
for (auto it = channelViews.constBegin(); it != channelViews.constEnd(); ++it) {
|
||||||
|
it.value()->retranslateUi();
|
||||||
|
}
|
||||||
userListPanel->retranslateUi();
|
userListPanel->retranslateUi();
|
||||||
sayLabel->setText(tr("&Say:"));
|
sayLabel->setText(tr("&Say:"));
|
||||||
chatGroupBox->setTitle(tr("Chat"));
|
chatGroupBox->setTitle(tr("Chat"));
|
||||||
|
|
@ -197,6 +292,11 @@ void TabRoom::retranslateUi()
|
||||||
aLeaveRoom->setText(tr("&Leave room"));
|
aLeaveRoom->setText(tr("&Leave room"));
|
||||||
aClearChat->setText(tr("&Clear chat"));
|
aClearChat->setText(tr("&Clear chat"));
|
||||||
aOpenChatSettings->setText(tr("Chat Settings..."));
|
aOpenChatSettings->setText(tr("Chat Settings..."));
|
||||||
|
channelDisplayNames.insert(MAIN_CHANNEL, tr("Main"));
|
||||||
|
for (const QString &channelId : channelIds) {
|
||||||
|
updateChannelTab(channelId);
|
||||||
|
}
|
||||||
|
updateSayEnabled();
|
||||||
}
|
}
|
||||||
|
|
||||||
void TabRoom::focusTab()
|
void TabRoom::focusTab()
|
||||||
|
|
@ -233,6 +333,11 @@ void TabRoom::tabActivated()
|
||||||
if (!sayEdit->hasFocus()) {
|
if (!sayEdit->hasFocus()) {
|
||||||
sayEdit->setFocus();
|
sayEdit->setFocus();
|
||||||
}
|
}
|
||||||
|
if (!chatCollapsed) {
|
||||||
|
channelUnreadCounts[activeChannelId] = 0;
|
||||||
|
channelMentioned[activeChannelId] = false;
|
||||||
|
updateChannelTab(activeChannelId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
QString TabRoom::sanitizeHtml(QString dirty) const
|
QString TabRoom::sanitizeHtml(QString dirty) const
|
||||||
|
|
@ -240,29 +345,232 @@ QString TabRoom::sanitizeHtml(QString dirty) const
|
||||||
return dirty.replace("&", "&").replace("<", "<").replace(">", ">");
|
return dirty.replace("&", "&").replace("<", "<").replace(">", ">");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void TabRoom::updateSayEnabled()
|
||||||
|
{
|
||||||
|
const bool writable = channelWritable.value(activeChannelId, true);
|
||||||
|
sayLabel->setEnabled(writable);
|
||||||
|
sayEdit->setEnabled(writable);
|
||||||
|
sayEdit->setPlaceholderText(writable ? QString() : tr("This channel is read-only."));
|
||||||
|
}
|
||||||
|
|
||||||
void TabRoom::sendMessage()
|
void TabRoom::sendMessage()
|
||||||
{
|
{
|
||||||
|
if (!channelWritable.value(activeChannelId, true)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (sayEdit->text().isEmpty()) {
|
if (sayEdit->text().isEmpty()) {
|
||||||
return;
|
return;
|
||||||
} else if (sayEdit->hasVisibleCompleterPopup()) {
|
} else if (sayEdit->hasVisibleCompleterPopup()) {
|
||||||
sayEdit->hideCompleterPopups();
|
sayEdit->hideCompleterPopups();
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
|
ChatView *targetView = channelViews.value(activeChannelId, chatView);
|
||||||
|
auto onFinished = [this, targetView](const Response &response) {
|
||||||
|
if (response.response_code() == Response::RespChatFlood) {
|
||||||
|
targetView->appendMessage(tr("You are flooding the chat. Please wait a couple of seconds."));
|
||||||
|
} else if (response.response_code() == Response::RespFunctionNotAllowed ||
|
||||||
|
response.response_code() == Response::RespInvalidCommand) {
|
||||||
|
targetView->appendMessage(tr("Your message could not be sent: the channel is not available or you "
|
||||||
|
"are not allowed to talk there."));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (activeChannelId.isEmpty()) {
|
||||||
Command_RoomSay cmd;
|
Command_RoomSay cmd;
|
||||||
cmd.set_message(sayEdit->text().toStdString());
|
cmd.set_message(sayEdit->text().toStdString());
|
||||||
|
|
||||||
PendingCommand *pend = prepareRoomCommand(cmd);
|
PendingCommand *pend = prepareRoomCommand(cmd);
|
||||||
connect(pend, &PendingCommand::finished, this, &TabRoom::sayFinished);
|
connect(pend, &PendingCommand::finished, this, onFinished);
|
||||||
sendRoomCommand(pend);
|
sendRoomCommand(pend);
|
||||||
|
} else {
|
||||||
|
Command_RoomChannelSay cmd;
|
||||||
|
cmd.set_channel_id(activeChannelId.toStdString());
|
||||||
|
cmd.set_message(sayEdit->text().toStdString());
|
||||||
|
|
||||||
|
PendingCommand *pend = prepareRoomCommand(cmd);
|
||||||
|
connect(pend, &PendingCommand::finished, this, onFinished);
|
||||||
|
sendRoomCommand(pend);
|
||||||
|
}
|
||||||
sayEdit->clear();
|
sayEdit->clear();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void TabRoom::sayFinished(const Response &response)
|
void TabRoom::onChannelChanged(int index)
|
||||||
{
|
{
|
||||||
if (response.response_code() == Response::RespChatFlood) {
|
if (index < 0 || index >= channelIds.size()) {
|
||||||
chatView->appendMessage(tr("You are flooding the chat. Please wait a couple of seconds."));
|
return;
|
||||||
}
|
}
|
||||||
|
activeChannelId = channelIds.at(index);
|
||||||
|
ChatView *view = channelViews.value(activeChannelId);
|
||||||
|
if (view) {
|
||||||
|
chatView = view;
|
||||||
|
chatViewStack->setCurrentWidget(view);
|
||||||
|
}
|
||||||
|
// Viewing a channel clears its unread state
|
||||||
|
channelUnreadCounts[activeChannelId] = 0;
|
||||||
|
channelMentioned[activeChannelId] = false;
|
||||||
|
updateChannelTab(activeChannelId);
|
||||||
|
updateSayEnabled();
|
||||||
|
if (chatCollapsed) {
|
||||||
|
setChatCollapsed(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TabRoom::onChannelTabClicked(int index)
|
||||||
|
{
|
||||||
|
if (index < 0 || index >= channelIds.size()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Clicking the active tab toggles collapse; clicking another tab switches to it
|
||||||
|
if (channelTabBar->currentIndex() == index && channelIds.at(index) == activeChannelId) {
|
||||||
|
setChatCollapsed(!chatCollapsed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TabRoom::setChatCollapsed(bool collapsed)
|
||||||
|
{
|
||||||
|
chatCollapsed = collapsed;
|
||||||
|
chatViewStack->setVisible(!collapsed);
|
||||||
|
sayWidget->setVisible(!collapsed);
|
||||||
|
if (collapsed) {
|
||||||
|
// Minimize the chat section of the splitter down to just the tab bar
|
||||||
|
savedSplitterSizes = chatSplitter->sizes();
|
||||||
|
chatSplitter->setSizes({1, 0});
|
||||||
|
} else {
|
||||||
|
// Expanding onto the active channel marks it read
|
||||||
|
channelUnreadCounts[activeChannelId] = 0;
|
||||||
|
channelMentioned[activeChannelId] = false;
|
||||||
|
updateChannelTab(activeChannelId);
|
||||||
|
chatViewStack->setCurrentWidget(chatView);
|
||||||
|
if (!savedSplitterSizes.isEmpty()) {
|
||||||
|
chatSplitter->setSizes(savedSplitterSizes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QColor TabRoom::parseColorHex(const QString &hex, const QColor &fallback)
|
||||||
|
{
|
||||||
|
#if (QT_VERSION >= QT_VERSION_CHECK(6, 4, 0))
|
||||||
|
QColor color = QColor::fromString("#" + hex);
|
||||||
|
#else
|
||||||
|
QColor color;
|
||||||
|
color.setNamedColor("#" + hex);
|
||||||
|
#endif
|
||||||
|
return color.isValid() ? color : fallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TabRoom::updateChannelTab(const QString &channelId)
|
||||||
|
{
|
||||||
|
int index = channelIds.indexOf(channelId);
|
||||||
|
if (index < 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
QString baseName = channelDisplayNames.value(channelId, channelId.isEmpty() ? tr("Main") : channelId);
|
||||||
|
int unread = channelUnreadCounts.value(channelId, 0);
|
||||||
|
bool mentioned = channelMentioned.value(channelId, false);
|
||||||
|
|
||||||
|
QString label = baseName;
|
||||||
|
if (unread > 0) {
|
||||||
|
label += tr(" (%1)").arg(unread);
|
||||||
|
}
|
||||||
|
channelTabBar->setTabText(index, label);
|
||||||
|
|
||||||
|
if (mentioned) {
|
||||||
|
channelTabBar->setTabTextColor(
|
||||||
|
index, parseColorHex(SettingsCache::instance().chat().getChannelMentionColor(), QColor(194, 31, 47)));
|
||||||
|
} else if (unread > 0) {
|
||||||
|
channelTabBar->setTabTextColor(
|
||||||
|
index, parseColorHex(SettingsCache::instance().chat().getChannelUnreadColor(), QColor(26, 123, 0)));
|
||||||
|
} else {
|
||||||
|
channelTabBar->setTabTextColor(index, QColor());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TabRoom::messageContainsMention(const QString &message, const ServerInfo_User &senderInfo) const
|
||||||
|
{
|
||||||
|
if (ownUser == nullptr) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
QString ownName = QString::fromStdString(ownUser->name());
|
||||||
|
if (ownName.isEmpty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// Match ChatView::checkMention / checkWord: whole-word, case-insensitive.
|
||||||
|
// "@zach" must not match "@zachary", and highlight words match full words.
|
||||||
|
auto containsWholeWord = [](const QString &haystack, const QString &needle) {
|
||||||
|
if (needle.isEmpty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
QRegularExpression re(QStringLiteral("\\b%1\\b").arg(QRegularExpression::escape(needle)),
|
||||||
|
QRegularExpression::CaseInsensitiveOption);
|
||||||
|
return re.match(haystack).hasMatch();
|
||||||
|
};
|
||||||
|
|
||||||
|
if (containsWholeWord(message, "@" + ownName)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// ChatView only highlights @all for moderator/admin senders
|
||||||
|
// (ChatView::isModeratorSendingGlobal). Gating here keeps the tab
|
||||||
|
// mention color from drifting ahead of the message view.
|
||||||
|
const uint32_t senderLevel = senderInfo.user_level();
|
||||||
|
const bool senderIsPrivileged =
|
||||||
|
(senderLevel & ServerInfo_User::IsModerator) || (senderLevel & ServerInfo_User::IsAdmin);
|
||||||
|
if (senderIsPrivileged && containsWholeWord(message, "@all")) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
const QStringList words = SettingsCache::instance().chat().getHighlightWords().split(' ', Qt::SkipEmptyParts);
|
||||||
|
for (const QString &word : words) {
|
||||||
|
if (containsWholeWord(message, word)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TabRoom::markChannelMessage(const QString &channelId, const QString &message, const ServerInfo_User &senderInfo)
|
||||||
|
{
|
||||||
|
bool roomVisible = (tabSupervisor->currentWidget() == this);
|
||||||
|
bool viewing = (channelId == activeChannelId) && !chatCollapsed && roomVisible;
|
||||||
|
if (viewing) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
channelUnreadCounts[channelId] = channelUnreadCounts.value(channelId, 0) + 1;
|
||||||
|
if (messageContainsMention(message, senderInfo)) {
|
||||||
|
channelMentioned[channelId] = true;
|
||||||
|
}
|
||||||
|
updateChannelTab(channelId);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TabRoom::isRoomMessageIgnored(const QString &senderName, ServerInfo_User &userInfoOut) const
|
||||||
|
{
|
||||||
|
if (userListProxy->isUserIgnored(senderName)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
UserListTWI *twi = userList->getUsers().value(senderName);
|
||||||
|
if (twi) {
|
||||||
|
userInfoOut = twi->getUserInfo();
|
||||||
|
if (SettingsCache::instance().chat().getIgnoreUnregisteredUsers() &&
|
||||||
|
!UserLevelFlags(userInfoOut.user_level()).testFlag(ServerInfo_User::IsRegistered)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
userInfoOut = ServerInfo_User{};
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
ChatView *TabRoom::makeChatView()
|
||||||
|
{
|
||||||
|
ChatView *view = new ChatView(tabSupervisor, nullptr, true, this);
|
||||||
|
connect(view, &ChatView::showMentionPopup, this, &TabRoom::actShowMentionPopup);
|
||||||
|
connect(view, &ChatView::messageClickedSignal, this, &TabRoom::focusTab);
|
||||||
|
connect(view, &ChatView::openMessageDialog, this, &TabRoom::openMessageDialog);
|
||||||
|
connect(view, &ChatView::cockatriceLinkActivated, this, &TabRoom::cockatriceLinkActivated);
|
||||||
|
connect(view, &ChatView::showCardInfoPopup, this, &TabRoom::showCardInfoPopup);
|
||||||
|
connect(view, &ChatView::deleteCardInfoPopup, this, &TabRoom::deleteCardInfoPopup);
|
||||||
|
connect(view, &ChatView::addMentionTag, this, &TabRoom::addMentionTag);
|
||||||
|
return view;
|
||||||
}
|
}
|
||||||
|
|
||||||
void TabRoom::actClearChat()
|
void TabRoom::actClearChat()
|
||||||
|
|
@ -298,6 +606,9 @@ void TabRoom::processRoomEvent(const RoomEvent &event)
|
||||||
case RoomEvent::ROOM_SAY:
|
case RoomEvent::ROOM_SAY:
|
||||||
processRoomSayEvent(event.GetExtension(Event_RoomSay::ext));
|
processRoomSayEvent(event.GetExtension(Event_RoomSay::ext));
|
||||||
break;
|
break;
|
||||||
|
case RoomEvent::ROOM_CHANNEL_SAY:
|
||||||
|
processRoomChannelSayEvent(event.GetExtension(Event_RoomChannelSay::ext));
|
||||||
|
break;
|
||||||
case RoomEvent::REMOVE_MESSAGES:
|
case RoomEvent::REMOVE_MESSAGES:
|
||||||
processRemoveMessagesEvent(event.GetExtension(Event_RemoveMessages::ext));
|
processRemoveMessagesEvent(event.GetExtension(Event_RemoveMessages::ext));
|
||||||
break;
|
break;
|
||||||
|
|
@ -335,19 +646,10 @@ void TabRoom::processRoomSayEvent(const Event_RoomSay &event)
|
||||||
QString senderName = QString::fromStdString(event.name());
|
QString senderName = QString::fromStdString(event.name());
|
||||||
QString message = QString::fromStdString(event.message());
|
QString message = QString::fromStdString(event.message());
|
||||||
|
|
||||||
if (userListProxy->isUserIgnored(senderName)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
UserListTWI *twi = userList->getUsers().value(senderName);
|
|
||||||
ServerInfo_User userInfo = {};
|
ServerInfo_User userInfo = {};
|
||||||
if (twi) {
|
if (isRoomMessageIgnored(senderName, userInfo)) {
|
||||||
userInfo = twi->getUserInfo();
|
|
||||||
if (SettingsCache::instance().chat().getIgnoreUnregisteredUsers() &&
|
|
||||||
!UserLevelFlags(userInfo.user_level()).testFlag(ServerInfo_User::IsRegistered)) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (event.message_type() == Event_RoomSay::ChatHistory && !SettingsCache::instance().chat().getRoomHistory()) {
|
if (event.message_type() == Event_RoomSay::ChatHistory && !SettingsCache::instance().chat().getRoomHistory()) {
|
||||||
return;
|
return;
|
||||||
|
|
@ -360,7 +662,49 @@ void TabRoom::processRoomSayEvent(const Event_RoomSay &event)
|
||||||
"] " + message;
|
"] " + message;
|
||||||
}
|
}
|
||||||
|
|
||||||
chatView->appendMessage(message, event.message_type(), userInfo, true);
|
ChatView *mainView = channelViews.value(MAIN_CHANNEL);
|
||||||
|
if (mainView == nullptr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
mainView->appendMessage(message, event.message_type(), userInfo, true);
|
||||||
|
if (event.message_type() != Event_RoomSay::ChatHistory) {
|
||||||
|
markChannelMessage(MAIN_CHANNEL, message, userInfo);
|
||||||
|
}
|
||||||
|
emit userEvent(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TabRoom::processRoomChannelSayEvent(const Event_RoomChannelSay &event)
|
||||||
|
{
|
||||||
|
const QString channelId = QString::fromStdString(event.channel_id());
|
||||||
|
ChatView *view = channelViews.value(channelId);
|
||||||
|
if (view == nullptr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString senderName = QString::fromStdString(event.name());
|
||||||
|
QString message = QString::fromStdString(event.message());
|
||||||
|
|
||||||
|
ServerInfo_User userInfo = {};
|
||||||
|
if (isRoomMessageIgnored(senderName, userInfo)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (senderName.isEmpty()) {
|
||||||
|
if (!SettingsCache::instance().chat().getRoomHistory()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
message =
|
||||||
|
"[" +
|
||||||
|
QString(QDateTime::fromMSecsSinceEpoch(event.time_of()).toLocalTime().toString("d MMM yyyy HH:mm:ss")) +
|
||||||
|
"] " + message;
|
||||||
|
}
|
||||||
|
|
||||||
|
view->appendMessage(message, {}, userInfo, true);
|
||||||
|
if (!senderName.isEmpty()) {
|
||||||
|
// Empty senders mark replayed history, which must not count as unread
|
||||||
|
markChannelMessage(channelId, message, userInfo);
|
||||||
|
}
|
||||||
emit userEvent(false);
|
emit userEvent(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -368,7 +712,9 @@ void TabRoom::processRemoveMessagesEvent(const Event_RemoveMessages &event)
|
||||||
{
|
{
|
||||||
QString userName = QString::fromStdString(event.name());
|
QString userName = QString::fromStdString(event.name());
|
||||||
int amount = event.amount();
|
int amount = event.amount();
|
||||||
chatView->redactMessages(userName, amount);
|
for (ChatView *view : channelViews) {
|
||||||
|
view->redactMessages(userName, amount);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void TabRoom::refreshShortcuts()
|
void TabRoom::refreshShortcuts()
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@
|
||||||
|
|
||||||
#include "tab.h"
|
#include "tab.h"
|
||||||
|
|
||||||
|
#include <QColor>
|
||||||
#include <QList>
|
#include <QList>
|
||||||
#include <QMap>
|
#include <QMap>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
|
|
@ -18,8 +19,12 @@
|
||||||
|
|
||||||
class UserListProxy;
|
class UserListProxy;
|
||||||
class QAction;
|
class QAction;
|
||||||
|
class QTabBar;
|
||||||
|
class QWidget;
|
||||||
class QGroupBox;
|
class QGroupBox;
|
||||||
|
class QSplitter;
|
||||||
class QMenu;
|
class QMenu;
|
||||||
|
class QStackedWidget;
|
||||||
class QStringListModel;
|
class QStringListModel;
|
||||||
class TabSupervisor;
|
class TabSupervisor;
|
||||||
|
|
||||||
|
|
@ -42,6 +47,7 @@ class Event_ListGames;
|
||||||
class Event_JoinRoom;
|
class Event_JoinRoom;
|
||||||
class Event_LeaveRoom;
|
class Event_LeaveRoom;
|
||||||
class Event_RoomSay;
|
class Event_RoomSay;
|
||||||
|
class Event_RoomChannelSay;
|
||||||
class Event_RemoveMessages;
|
class Event_RemoveMessages;
|
||||||
class GameSelector;
|
class GameSelector;
|
||||||
class Response;
|
class Response;
|
||||||
|
|
@ -64,10 +70,23 @@ private:
|
||||||
UserListWidget *userList;
|
UserListWidget *userList;
|
||||||
const UserListProxy *userListProxy;
|
const UserListProxy *userListProxy;
|
||||||
ChatView *chatView;
|
ChatView *chatView;
|
||||||
|
QStackedWidget *chatViewStack;
|
||||||
|
QTabBar *channelTabBar;
|
||||||
|
QWidget *sayWidget;
|
||||||
|
QMap<QString, ChatView *> channelViews;
|
||||||
|
QMap<QString, QString> channelDisplayNames;
|
||||||
|
QMap<QString, int> channelUnreadCounts;
|
||||||
|
QMap<QString, bool> channelMentioned;
|
||||||
|
QMap<QString, bool> channelWritable;
|
||||||
|
QStringList channelIds;
|
||||||
|
QString activeChannelId;
|
||||||
|
bool chatCollapsed;
|
||||||
QLabel *sayLabel;
|
QLabel *sayLabel;
|
||||||
LineEditCompleter *sayEdit;
|
LineEditCompleter *sayEdit;
|
||||||
QStringListModel *mentionModel;
|
QStringListModel *mentionModel;
|
||||||
QGroupBox *chatGroupBox;
|
QGroupBox *chatGroupBox;
|
||||||
|
QSplitter *chatSplitter;
|
||||||
|
QList<int> savedSplitterSizes;
|
||||||
|
|
||||||
QMenu *roomMenu;
|
QMenu *roomMenu;
|
||||||
QAction *aLeaveRoom;
|
QAction *aLeaveRoom;
|
||||||
|
|
@ -85,7 +104,9 @@ signals:
|
||||||
void gameListUpdated();
|
void gameListUpdated();
|
||||||
private slots:
|
private slots:
|
||||||
void sendMessage();
|
void sendMessage();
|
||||||
void sayFinished(const Response &response);
|
void onChannelChanged(int index);
|
||||||
|
void onChannelTabClicked(int index);
|
||||||
|
void setChatCollapsed(bool collapsed);
|
||||||
void actClearChat();
|
void actClearChat();
|
||||||
void actOpenChatSettings();
|
void actOpenChatSettings();
|
||||||
void addMentionTag(QString mentionTag);
|
void addMentionTag(QString mentionTag);
|
||||||
|
|
@ -98,9 +119,18 @@ private slots:
|
||||||
void processJoinRoomEvent(const Event_JoinRoom &event);
|
void processJoinRoomEvent(const Event_JoinRoom &event);
|
||||||
void processLeaveRoomEvent(const Event_LeaveRoom &event);
|
void processLeaveRoomEvent(const Event_LeaveRoom &event);
|
||||||
void processRoomSayEvent(const Event_RoomSay &event);
|
void processRoomSayEvent(const Event_RoomSay &event);
|
||||||
|
void processRoomChannelSayEvent(const Event_RoomChannelSay &event);
|
||||||
void processRemoveMessagesEvent(const Event_RemoveMessages &event);
|
void processRemoveMessagesEvent(const Event_RemoveMessages &event);
|
||||||
void refreshShortcuts();
|
void refreshShortcuts();
|
||||||
|
|
||||||
|
ChatView *makeChatView();
|
||||||
|
void updateChannelTab(const QString &channelId);
|
||||||
|
void updateSayEnabled();
|
||||||
|
void markChannelMessage(const QString &channelId, const QString &message, const ServerInfo_User &senderInfo);
|
||||||
|
[[nodiscard]] bool messageContainsMention(const QString &message, const ServerInfo_User &senderInfo) const;
|
||||||
|
[[nodiscard]] bool isRoomMessageIgnored(const QString &senderName, ServerInfo_User &userInfoOut) const;
|
||||||
|
static QColor parseColorHex(const QString &hex, const QColor &fallback);
|
||||||
|
|
||||||
protected slots:
|
protected slots:
|
||||||
void closeEvent(QCloseEvent *event) override;
|
void closeEvent(QCloseEvent *event) override;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,8 @@ public:
|
||||||
[[nodiscard]] virtual bool getChatMentionCompleter() const = 0;
|
[[nodiscard]] virtual bool getChatMentionCompleter() const = 0;
|
||||||
[[nodiscard]] virtual QString getChatMentionColor() const = 0;
|
[[nodiscard]] virtual QString getChatMentionColor() const = 0;
|
||||||
[[nodiscard]] virtual QString getChatHighlightColor() const = 0;
|
[[nodiscard]] virtual QString getChatHighlightColor() const = 0;
|
||||||
|
[[nodiscard]] virtual QString getChannelUnreadColor() const = 0;
|
||||||
|
[[nodiscard]] virtual QString getChannelMentionColor() const = 0;
|
||||||
[[nodiscard]] virtual bool getChatMentionForeground() const = 0;
|
[[nodiscard]] virtual bool getChatMentionForeground() const = 0;
|
||||||
[[nodiscard]] virtual bool getChatHighlightForeground() const = 0;
|
[[nodiscard]] virtual bool getChatHighlightForeground() const = 0;
|
||||||
[[nodiscard]] virtual bool getIgnoreUnregisteredUsers() const = 0;
|
[[nodiscard]] virtual bool getIgnoreUnregisteredUsers() const = 0;
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,16 @@ QString ChatSettings::getChatHighlightColor() const
|
||||||
return getValue("highlightColor", QString(), QString(), "A6120D").toString();
|
return getValue("highlightColor", QString(), QString(), "A6120D").toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QString ChatSettings::getChannelUnreadColor() const
|
||||||
|
{
|
||||||
|
return getValue("channelUnreadColor", QString(), QString(), "1A7B00").toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
QString ChatSettings::getChannelMentionColor() const
|
||||||
|
{
|
||||||
|
return getValue("channelMentionColor", QString(), QString(), "A6120D").toString();
|
||||||
|
}
|
||||||
|
|
||||||
bool ChatSettings::getChatMentionForeground() const
|
bool ChatSettings::getChatMentionForeground() const
|
||||||
{
|
{
|
||||||
return getValue("mentionForeground", QString(), QString(), true).toBool();
|
return getValue("mentionForeground", QString(), QString(), true).toBool();
|
||||||
|
|
@ -107,6 +117,16 @@ void ChatSettings::setChatHighlightColor(const QString &_chatHighlightColor)
|
||||||
setValue(_chatHighlightColor, "highlightColor");
|
setValue(_chatHighlightColor, "highlightColor");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ChatSettings::setChannelUnreadColor(const QString &_channelUnreadColor)
|
||||||
|
{
|
||||||
|
setValue(_channelUnreadColor, "channelUnreadColor");
|
||||||
|
}
|
||||||
|
|
||||||
|
void ChatSettings::setChannelMentionColor(const QString &_channelMentionColor)
|
||||||
|
{
|
||||||
|
setValue(_channelMentionColor, "channelMentionColor");
|
||||||
|
}
|
||||||
|
|
||||||
void ChatSettings::setChatMentionForeground(bool _chatMentionForeground)
|
void ChatSettings::setChatMentionForeground(bool _chatMentionForeground)
|
||||||
{
|
{
|
||||||
setValue(_chatMentionForeground, "mentionForeground");
|
setValue(_chatMentionForeground, "mentionForeground");
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,8 @@ public:
|
||||||
[[nodiscard]] bool getChatMentionCompleter() const override;
|
[[nodiscard]] bool getChatMentionCompleter() const override;
|
||||||
[[nodiscard]] QString getChatMentionColor() const override;
|
[[nodiscard]] QString getChatMentionColor() const override;
|
||||||
[[nodiscard]] QString getChatHighlightColor() const override;
|
[[nodiscard]] QString getChatHighlightColor() const override;
|
||||||
|
[[nodiscard]] QString getChannelUnreadColor() const override;
|
||||||
|
[[nodiscard]] QString getChannelMentionColor() const override;
|
||||||
[[nodiscard]] bool getChatMentionForeground() const override;
|
[[nodiscard]] bool getChatMentionForeground() const override;
|
||||||
[[nodiscard]] bool getChatHighlightForeground() const override;
|
[[nodiscard]] bool getChatHighlightForeground() const override;
|
||||||
[[nodiscard]] bool getIgnoreUnregisteredUsers() const override;
|
[[nodiscard]] bool getIgnoreUnregisteredUsers() const override;
|
||||||
|
|
@ -35,6 +37,8 @@ public:
|
||||||
void setChatMentionCompleter(bool _chatMentionCompleter);
|
void setChatMentionCompleter(bool _chatMentionCompleter);
|
||||||
void setChatMentionColor(const QString &_chatMentionColor);
|
void setChatMentionColor(const QString &_chatMentionColor);
|
||||||
void setChatHighlightColor(const QString &_chatHighlightColor);
|
void setChatHighlightColor(const QString &_chatHighlightColor);
|
||||||
|
void setChannelUnreadColor(const QString &_channelUnreadColor);
|
||||||
|
void setChannelMentionColor(const QString &_channelMentionColor);
|
||||||
void setChatMentionForeground(bool _chatMentionForeground);
|
void setChatMentionForeground(bool _chatMentionForeground);
|
||||||
void setChatHighlightForeground(bool _chatHighlightForeground);
|
void setChatHighlightForeground(bool _chatHighlightForeground);
|
||||||
void setIgnoreUnregisteredUsers(bool _ignoreUnregisteredUsers);
|
void setIgnoreUnregisteredUsers(bool _ignoreUnregisteredUsers);
|
||||||
|
|
|
||||||
|
|
@ -285,6 +285,32 @@ TEST_F(SettingsDefaultsTest, Chat_HighlightColor_Default)
|
||||||
ASSERT_EQ(s.getChatHighlightColor(), QString("A6120D"));
|
ASSERT_EQ(s.getChatHighlightColor(), QString("A6120D"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_F(SettingsDefaultsTest, Chat_ChannelUnreadColor_Default)
|
||||||
|
{
|
||||||
|
ChatSettings s(settingsPath, nullptr);
|
||||||
|
ASSERT_EQ(s.getChannelUnreadColor(), QString("1A7B00"));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(SettingsDefaultsTest, Chat_ChannelUnreadColor_SetAndGet)
|
||||||
|
{
|
||||||
|
ChatSettings s(settingsPath, nullptr);
|
||||||
|
s.setChannelUnreadColor(QString("0000FF"));
|
||||||
|
ASSERT_EQ(s.getChannelUnreadColor(), QString("0000FF"));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(SettingsDefaultsTest, Chat_ChannelMentionColor_Default)
|
||||||
|
{
|
||||||
|
ChatSettings s(settingsPath, nullptr);
|
||||||
|
ASSERT_EQ(s.getChannelMentionColor(), QString("A6120D"));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(SettingsDefaultsTest, Chat_ChannelMentionColor_SetAndGet)
|
||||||
|
{
|
||||||
|
ChatSettings s(settingsPath, nullptr);
|
||||||
|
s.setChannelMentionColor(QString("FF0000"));
|
||||||
|
ASSERT_EQ(s.getChannelMentionColor(), QString("FF0000"));
|
||||||
|
}
|
||||||
|
|
||||||
TEST_F(SettingsDefaultsTest, Chat_MentionForeground_Default)
|
TEST_F(SettingsDefaultsTest, Chat_MentionForeground_Default)
|
||||||
{
|
{
|
||||||
ChatSettings s(settingsPath, nullptr);
|
ChatSettings s(settingsPath, nullptr);
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue