Files
Cockatrice/cockatrice/src/interface/widgets/tabs/tab_room.cpp
T
2914874720 [Room][UserList] Introduce style delegate (#6981)
* [Room] Additionally show a tab for friends and ignored users instead of just all online users.

Took 21 minutes


Took 12 minutes

* [Room][UserList] Introduce style delegate for user list
- Allow users to set a card name and parameters as their background banner
- Allow mods to white/blacklist cards
- Allow toggling back to the old display style

Took 7 minutes

Took 28 seconds

Took 2 minutes

Took 2 minutes

* Right checkstate.

Took 14 minutes


Took 2 minutes

* Utility for test.

Took 9 minutes

Took 8 seconds

Took 2 seconds

* Lint.

Took 10 minutes

* Algorithm for sql schema migration

Took 13 minutes

* Use {prefix}, bound card name, return errors.

Took 27 seconds

* Convert queue to while loop.

Took 19 seconds

* Hover popup.

Took 36 minutes

Took 1 minute

* More granular signals, popup for user info.

Took 25 minutes

Took 8 seconds

Took 16 minutes

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-06-26 20:52:24 -04:00

358 lines
12 KiB
C++

#include "tab_room.h"
#include "../../../client/settings/cache_settings.h"
#include "../interface/widgets/dialogs/dlg_settings.h"
#include "../interface/widgets/server/chat_view/chat_view.h"
#include "../interface/widgets/server/game_selector.h"
#include "../interface/widgets/server/user/user_list_manager.h"
#include "../interface/widgets/server/user/user_list_widget.h"
#include "../main.h"
#include "tab_account.h"
#include "tab_supervisor.h"
#include <QApplication>
#include <QCompleter>
#include <QLabel>
#include <QMenu>
#include <QMessageBox>
#include <QPushButton>
#include <QSplitter>
#include <QSystemTrayIcon>
#include <QToolButton>
#include <QVBoxLayout>
#include <QtCore/qdatetime.h>
#include <libcockatrice/network/client/abstract/abstract_client.h>
#include <libcockatrice/protocol/get_pb_extension.h>
#include <libcockatrice/protocol/pb/event_join_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_remove_messages.pb.h>
#include <libcockatrice/protocol/pb/event_room_say.pb.h>
#include <libcockatrice/protocol/pb/room_commands.pb.h>
#include <libcockatrice/protocol/pb/serverinfo_room.pb.h>
#include <libcockatrice/protocol/pending_command.h>
#include <libcockatrice/utility/trice_limits.h>
TabRoom::TabRoom(TabSupervisor *_tabSupervisor,
AbstractClient *_client,
ServerInfo_User *_ownUser,
const ServerInfo_Room &info)
: Tab(_tabSupervisor), client(_client), roomId(info.room_id()), roomName(QString::fromStdString(info.name())),
ownUser(_ownUser), userListProxy(_tabSupervisor->getUserListManager())
{
const int gameTypeListSize = info.gametype_list_size();
for (int i = 0; i < gameTypeListSize; ++i) {
gameTypes.insert(info.gametype_list(i).game_type_id(),
QString::fromStdString(info.gametype_list(i).description()));
}
QMap<int, GameTypeMap> tempMap;
tempMap.insert(info.room_id(), gameTypes);
gameSelector = new GameSelector(client, tabSupervisor, this, QMap<int, QString>(), tempMap, true, true);
auto *tabs = new QTabWidget(this);
friendsList = new UserListWidget(tabSupervisor, client, UserListWidget::BuddyList);
friendsList->bind(tabSupervisor->getUserListManager());
userList = new UserListWidget(tabSupervisor, client, UserListWidget::RoomList);
userList->bind(tabSupervisor->getUserListManager());
ignoreList = new UserListWidget(tabSupervisor, client, UserListWidget::IgnoreList);
ignoreList->bind(tabSupervisor->getUserListManager());
connect(friendsList, SIGNAL(openMessageDialog(const QString &, bool)), this,
SIGNAL(openMessageDialog(const QString &, bool)));
connect(userList, SIGNAL(openMessageDialog(const QString &, bool)), this,
SIGNAL(openMessageDialog(const QString &, bool)));
tabs->addTab(friendsList, tr("Friends"));
tabs->addTab(userList, tr("Online"));
tabs->addTab(ignoreList, tr("Ignored"));
chatView = new ChatView(tabSupervisor, nullptr, true, this);
connect(chatView, &ChatView::showMentionPopup, this, &TabRoom::actShowMentionPopup);
connect(chatView, &ChatView::messageClickedSignal, this, &TabRoom::focusTab);
connect(chatView, &ChatView::openMessageDialog, this, &TabRoom::openMessageDialog);
connect(chatView, &ChatView::showCardInfoPopup, this, &TabRoom::showCardInfoPopup);
connect(chatView, &ChatView::deleteCardInfoPopup, this, &TabRoom::deleteCardInfoPopup);
connect(chatView, &ChatView::addMentionTag, this, &TabRoom::addMentionTag);
connect(&SettingsCache::instance(), &SettingsCache::chatMentionCompleterChanged, this,
&TabRoom::actCompleterChanged);
sayLabel = new QLabel;
sayEdit = new LineEditCompleter;
sayEdit->setMaxLength(MAX_TEXT_LENGTH);
sayLabel->setBuddy(sayEdit);
connect(sayEdit, &LineEditCompleter::returnPressed, this, &TabRoom::sendMessage);
auto *chatSettingsMenu = new QMenu(this);
aClearChat = chatSettingsMenu->addAction(QString());
connect(aClearChat, &QAction::triggered, this, &TabRoom::actClearChat);
chatSettingsMenu->addSeparator();
aOpenChatSettings = chatSettingsMenu->addAction(QString());
connect(aOpenChatSettings, &QAction::triggered, this, &TabRoom::actOpenChatSettings);
auto *chatSettingsButton = new QToolButton;
chatSettingsButton->setIcon(QPixmap("theme:icons/settings"));
chatSettingsButton->setMenu(chatSettingsMenu);
chatSettingsButton->setPopupMode(QToolButton::InstantPopup);
auto *sayHbox = new QHBoxLayout;
sayHbox->addWidget(sayLabel);
sayHbox->addWidget(sayEdit);
sayHbox->addWidget(chatSettingsButton);
auto *chatVbox = new QVBoxLayout;
chatVbox->addWidget(chatView);
chatVbox->addLayout(sayHbox);
chatGroupBox = new QGroupBox;
chatGroupBox->setLayout(chatVbox);
auto *splitter = new QSplitter(Qt::Vertical);
splitter->addWidget(gameSelector);
splitter->addWidget(chatGroupBox);
auto *hbox = new QHBoxLayout;
hbox->addWidget(splitter, 3);
hbox->addWidget(tabs, 1);
aLeaveRoom = new QAction(this);
connect(aLeaveRoom, &QAction::triggered, this, &TabRoom::closeRequest);
roomMenu = new QMenu(this);
roomMenu->addAction(aLeaveRoom);
addTabMenu(roomMenu);
const int userListSize = info.user_list_size();
for (int i = 0; i < userListSize; ++i) {
autocompleteUserList.append("@" + QString::fromStdString(info.user_list(i).name()));
}
const int gameListSize = info.game_list_size();
for (int i = 0; i < gameListSize; ++i) {
gameSelector->processGameInfo(info.game_list(i));
}
completer = new QCompleter(autocompleteUserList, sayEdit);
completer->setCaseSensitivity(Qt::CaseInsensitive);
completer->setMaxVisibleItems(5);
completer->setFilterMode(Qt::MatchStartsWith);
sayEdit->setCompleter(completer);
actCompleterChanged();
connect(&SettingsCache::instance().shortcuts(), &ShortcutsSettings::shortCutChanged, this,
&TabRoom::refreshShortcuts);
refreshShortcuts();
retranslateUi();
auto *mainWidget = new QWidget(this);
mainWidget->setLayout(hbox);
setCentralWidget(mainWidget);
}
void TabRoom::retranslateUi()
{
gameSelector->retranslateUi();
chatView->retranslateUi();
userList->retranslateUi();
sayLabel->setText(tr("&Say:"));
chatGroupBox->setTitle(tr("Chat"));
roomMenu->setTitle(tr("&Room"));
aLeaveRoom->setText(tr("&Leave room"));
aClearChat->setText(tr("&Clear chat"));
aOpenChatSettings->setText(tr("Chat Settings..."));
}
void TabRoom::focusTab()
{
activateWindow();
tabSupervisor->setCurrentIndex(tabSupervisor->indexOf(this));
emit maximizeClient();
}
void TabRoom::actShowMentionPopup(const QString &sender)
{
this->actShowPopup(sender + tr(" mentioned you."));
}
void TabRoom::actShowPopup(const QString &message)
{
if (trayIcon && (tabSupervisor->currentIndex() != tabSupervisor->indexOf(this) ||
QApplication::activeWindow() == nullptr || QApplication::focusWidget() == nullptr)) {
disconnect(trayIcon, &QSystemTrayIcon::messageClicked, nullptr, nullptr);
trayIcon->showMessage(message, tr("Click to view"));
connect(trayIcon, &QSystemTrayIcon::messageClicked, chatView, &ChatView::messageClickedSignal);
}
}
void TabRoom::closeEvent(QCloseEvent *event)
{
sendRoomCommand(prepareRoomCommand(Command_LeaveRoom()));
emit roomClosing(this);
event->accept();
}
void TabRoom::tabActivated()
{
if (!sayEdit->hasFocus()) {
sayEdit->setFocus();
}
}
QString TabRoom::sanitizeHtml(QString dirty) const
{
return dirty.replace("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;");
}
void TabRoom::sendMessage()
{
if (sayEdit->text().isEmpty()) {
return;
} else if (completer->popup()->isVisible()) {
completer->popup()->hide();
return;
} else {
Command_RoomSay cmd;
cmd.set_message(sayEdit->text().toStdString());
PendingCommand *pend = prepareRoomCommand(cmd);
connect(pend, &PendingCommand::finished, this, &TabRoom::sayFinished);
sendRoomCommand(pend);
sayEdit->clear();
}
}
void TabRoom::sayFinished(const Response &response)
{
if (response.response_code() == Response::RespChatFlood) {
chatView->appendMessage(tr("You are flooding the chat. Please wait a couple of seconds."));
}
}
void TabRoom::actClearChat()
{
chatView->clearChat();
}
void TabRoom::actOpenChatSettings()
{
DlgSettings settings(this);
settings.setTab(4);
settings.exec();
}
void TabRoom::actCompleterChanged()
{
SettingsCache::instance().getChatMentionCompleter() ? completer->setCompletionRole(2)
: completer->setCompletionRole(1);
}
void TabRoom::processRoomEvent(const RoomEvent &event)
{
switch (static_cast<RoomEvent::RoomEventType>(getPbExtension(event))) {
case RoomEvent::LIST_GAMES:
processListGamesEvent(event.GetExtension(Event_ListGames::ext));
break;
case RoomEvent::JOIN_ROOM:
processJoinRoomEvent(event.GetExtension(Event_JoinRoom::ext));
break;
case RoomEvent::LEAVE_ROOM:
processLeaveRoomEvent(event.GetExtension(Event_LeaveRoom::ext));
break;
case RoomEvent::ROOM_SAY:
processRoomSayEvent(event.GetExtension(Event_RoomSay::ext));
break;
case RoomEvent::REMOVE_MESSAGES:
processRemoveMessagesEvent(event.GetExtension(Event_RemoveMessages::ext));
break;
default:;
}
}
void TabRoom::processListGamesEvent(const Event_ListGames &event)
{
const int gameListSize = event.game_list_size();
for (int i = 0; i < gameListSize; ++i) {
gameSelector->processGameInfo(event.game_list(i));
}
}
void TabRoom::processJoinRoomEvent(const Event_JoinRoom &event)
{
if (!autocompleteUserList.contains("@" + QString::fromStdString(event.user_info().name()))) {
autocompleteUserList << "@" + QString::fromStdString(event.user_info().name());
sayEdit->setCompletionList(autocompleteUserList);
}
}
void TabRoom::processLeaveRoomEvent(const Event_LeaveRoom &event)
{
autocompleteUserList.removeOne("@" + QString::fromStdString(event.name()));
sayEdit->setCompletionList(autocompleteUserList);
}
void TabRoom::processRoomSayEvent(const Event_RoomSay &event)
{
QString senderName = QString::fromStdString(event.name());
QString message = QString::fromStdString(event.message());
if (userListProxy->isUserIgnored(senderName)) {
return;
}
UserListTWI *twi = userList->getUsers().value(senderName);
ServerInfo_User userInfo = {};
if (twi) {
userInfo = twi->getUserInfo();
if (SettingsCache::instance().getIgnoreUnregisteredUsers() &&
!UserLevelFlags(userInfo.user_level()).testFlag(ServerInfo_User::IsRegistered)) {
return;
}
}
if (event.message_type() == Event_RoomSay::ChatHistory && !SettingsCache::instance().getRoomHistory()) {
return;
}
if (event.message_type() == Event_RoomSay::ChatHistory) {
message =
"[" +
QString(QDateTime::fromMSecsSinceEpoch(event.time_of()).toLocalTime().toString("d MMM yyyy HH:mm:ss")) +
"] " + message;
}
chatView->appendMessage(message, event.message_type(), userInfo, true);
emit userEvent(false);
}
void TabRoom::processRemoveMessagesEvent(const Event_RemoveMessages &event)
{
QString userName = QString::fromStdString(event.name());
int amount = event.amount();
chatView->redactMessages(userName, amount);
}
void TabRoom::refreshShortcuts()
{
aClearChat->setShortcuts(SettingsCache::instance().shortcuts().getShortcut("tab_room/aClearChat"));
}
void TabRoom::addMentionTag(QString mentionTag)
{
sayEdit->insert(mentionTag + " ");
sayEdit->setFocus();
}
PendingCommand *TabRoom::prepareRoomCommand(const ::google::protobuf::Message &cmd)
{
return client->prepareRoomCommand(cmd, roomId);
}
void TabRoom::sendRoomCommand(PendingCommand *pend)
{
client->sendCommand(pend);
}