diff --git a/cockatrice/CMakeLists.txt b/cockatrice/CMakeLists.txt index 6fd683461..9fd05ae01 100644 --- a/cockatrice/CMakeLists.txt +++ b/cockatrice/CMakeLists.txt @@ -260,7 +260,6 @@ set(cockatrice_SOURCES src/interface/widgets/server/user/user_info_connection.cpp src/interface/widgets/server/user/user_list_manager.cpp src/interface/widgets/server/user/user_list_painter.cpp - src/interface/widgets/server/user/user_list_panel_widget.cpp src/interface/widgets/server/user/user_list_widget.cpp src/interface/widgets/settings_page/abstract_settings_page.cpp src/interface/widgets/settings_page/appearance_settings_page.cpp @@ -397,8 +396,6 @@ set(cockatrice_SOURCES src/interface/intents/intent_join_server_room.h src/interface/intents/intent_login.cpp src/interface/intents/intent_login.h - src/interface/intents/intent_open_server_room_by_name.cpp - src/interface/intents/intent_open_server_room_by_name.h src/interface/intents/url_parser.cpp src/interface/intents/url_parser.h src/interface/widgets/server/user/user_info_popup.cpp diff --git a/cockatrice/src/interface/intents/intent_open_server_room_by_name.cpp b/cockatrice/src/interface/intents/intent_open_server_room_by_name.cpp deleted file mode 100644 index d50f509a0..000000000 --- a/cockatrice/src/interface/intents/intent_open_server_room_by_name.cpp +++ /dev/null @@ -1,183 +0,0 @@ -#include "intent_open_server_room_by_name.h" - -#include "../widgets/tabs/tab_room.h" -#include "../widgets/tabs/tab_supervisor.h" -#include "intent_connect_to_server.h" - -#include -#include -#include -#include - -IntentOpenServerRoomByName::IntentOpenServerRoomByName(TabSupervisor *_tabSupervisor, - RemoteClient *_remoteClient, - std::unique_ptr _context, - const QString &_roomName) - : Intent(), tabSupervisor(_tabSupervisor), remoteClient(_remoteClient), context(_context.release()), - roomName(_roomName) -{ - checkTimer.setInterval(250); - connect(&checkTimer, &QTimer::timeout, this, [this]() { - if (selectOpenRoom()) { - checkTimer.stop(); - } - }); -} - -bool IntentOpenServerRoomByName::checkPrecondition() const -{ - if (remoteClient->getStatus() != ClientStatus::StatusLoggedIn) { - return false; - } - // peerPort() reflects the actual TCP peer, which may differ from the - // configured server port (e.g. when connecting through a proxy), so only - // the hostname is compared here. - if (remoteClient->peerName() != context->serverContext.hostname) { - return false; - } - if (QString::number(remoteClient->peerPort()) != context->serverContext.port) { - return false; - } - - return true; -} - -void IntentOpenServerRoomByName::onPreconditionSatisfied() -{ - if (listening) { - return; - } - listening = true; - - if (selectOpenRoom()) { - return; - } - - // The room selector is the component that requests the room list, so the - // server tab must exist for the room to be resolved by name. - if (!tabSupervisor->getTabServer()) { - tabSupervisor->openTabServer(); - } - if (!tabSupervisor->getTabServer()) { - emitFailed(tr("No server tab available")); - return; - } - - connect(remoteClient, &RemoteClient::listRoomsEventReceived, this, &IntentOpenServerRoomByName::processListRooms); - connect(remoteClient, &RemoteClient::statusChanged, this, &IntentOpenServerRoomByName::onClientStatusChanged); - - // The room tab may be opened by our own join, by the room selector's auto-join, or by a - // join that was already in flight. Poll until it shows up. - checkTimer.start(); - - // While no join has been sent yet, keep the room list fresh: the list may have been - // requested before we subscribed to it, or a response may have been dropped during a - // busy login burst. A stale list would otherwise leave the room unresolved forever. - connect(&refreshTimer, &QTimer::timeout, this, [this]() { - if (!joinPending) { - remoteClient->sendCommand(remoteClient->prepareSessionCommand(Command_ListRooms())); - } - }); - refreshTimer.setInterval(5000); - refreshTimer.start(); - - // Last-resort failure for "the room genuinely is not in a fresh list". This must NOT - // fire while a join is in flight: a loaded server may take longer than that to answer - // during a login burst, and killing the intent early would leave the connection - // registered in the room with no tab to display it and every later join attempt - // would then be rejected with RespContextError. - QTimer::singleShot(20000, this, [this]() { - if (!joinPending) { - emitFailed(tr("Timed out while looking for the server room %1").arg(roomName)); - } - }); -} - -void IntentOpenServerRoomByName::onPreconditionNotSatisfied() -{ - runDependency(new IntentConnectToServer(remoteClient, &context->serverContext)); -} - -void IntentOpenServerRoomByName::onClientStatusChanged(ClientStatus status) -{ - if (status != ClientStatus::StatusLoggedIn) { - emitFailed(tr("Disconnected while looking for the server room %1").arg(roomName)); - } -} - -bool IntentOpenServerRoomByName::selectOpenRoom() -{ - const auto &roomTabs = tabSupervisor->getRoomTabs(); - for (auto i = roomTabs.cbegin(), end = roomTabs.cend(); i != end; ++i) { - TabRoom *room = i.value(); - if (room->getRoomName() == roomName) { - tabSupervisor->setCurrentWidget(room); - emitFinished(); - return true; - } - } - return false; -} - -void IntentOpenServerRoomByName::processListRooms(const Event_ListRooms &event) -{ - if (selectOpenRoom()) { - return; - } - - for (int i = 0; i < event.room_list_size(); ++i) { - const ServerInfo_Room &room = event.room_list(i); - if (room.has_name() && QString::fromStdString(room.name()) == roomName) { - openRoom(room); - return; - } - } -} - -void IntentOpenServerRoomByName::openRoom(const ServerInfo_Room &roomInfo) -{ - if (joinPending) { - return; - } - joinPending = true; - - // Rooms flagged auto_join are joined by the room selector automatically. Sending our own - // Command_JoinRoom on top of that would be answered with RespContextError. - if (roomInfo.has_auto_join() && roomInfo.auto_join()) { - return; - } - - Command_JoinRoom cmd; - cmd.set_room_id(roomInfo.room_id()); - PendingCommand *pend = remoteClient->prepareSessionCommand(cmd); - connect(pend, &PendingCommand::finished, this, - [this](const Response &r, const CommandContainer &, const QVariant &) { handleJoinResponse(r); }); - remoteClient->sendCommand(pend); -} - -void IntentOpenServerRoomByName::handleJoinResponse(const Response &response) -{ - switch (response.response_code()) { - case Response::RespOk: { - const Response_JoinRoom &resp = response.GetExtension(Response_JoinRoom::ext); - if (!tabSupervisor->getRoomTabs().contains(resp.room_info().room_id())) { - tabSupervisor->addRoomTab(resp.room_info(), true); - } - emitFinished(); - return; - } - case Response::RespNameNotFound: - emitFailed(tr("Failed to join the server room %1: it doesn't exist on the server.").arg(roomName)); - return; - case Response::RespUserLevelTooLow: - emitFailed(tr("You do not have the required permission to join the server room %1.").arg(roomName)); - return; - case Response::RespContextError: - // The room was already joined by someone else (e.g. the room selector's - // auto-join). It will show up in the room tabs shortly, so keep waiting. - return; - default: - emitFailed(tr("Failed to join the server room %1 due to an unknown error.").arg(roomName)); - return; - } -} diff --git a/cockatrice/src/interface/intents/intent_open_server_room_by_name.h b/cockatrice/src/interface/intents/intent_open_server_room_by_name.h deleted file mode 100644 index 2f9e716af..000000000 --- a/cockatrice/src/interface/intents/intent_open_server_room_by_name.h +++ /dev/null @@ -1,61 +0,0 @@ -#ifndef COCKATRICE_INTENT_OPEN_SERVER_ROOM_BY_NAME_H -#define COCKATRICE_INTENT_OPEN_SERVER_ROOM_BY_NAME_H - -#include "contexts/context_join_room.h" -#include "intent.h" -#include "remote_client.h" - -#include -#include -#include -#include - -class TabRoom; -class TabSupervisor; -class Event_ListRooms; -class ServerInfo_Room; - -/** - * @brief Connects to the configured server and opens a room identified by its name. - * - * Room ids are assigned by the server per session, so the room is resolved by name from the - * room list once the client is logged in. If the room is already open it is simply selected. - * - * The join itself is sent directly through the client instead of `TabServer::joinRoom`, so a - * failed join only fails the intent silently instead of popping a modal error box during - * startup. Success is routed to `TabSupervisor::addRoomTab`, the same tab-creation machinery - * the normal join flow uses. - */ -class IntentOpenServerRoomByName : public Intent -{ - Q_OBJECT - -public: - IntentOpenServerRoomByName(TabSupervisor *_tabSupervisor, - RemoteClient *_remoteClient, - std::unique_ptr _context, - const QString &_roomName); - -protected: - bool checkPrecondition() const override; - void onPreconditionSatisfied() override; - void onPreconditionNotSatisfied() override; - -private: - void processListRooms(const Event_ListRooms &event); - void openRoom(const ServerInfo_Room &roomInfo); - void handleJoinResponse(const Response &response); - void onClientStatusChanged(ClientStatus status); - bool selectOpenRoom(); - - TabSupervisor *tabSupervisor; - RemoteClient *remoteClient; - QScopedPointer context; - QString roomName; - bool listening = false; - bool joinPending = false; - QTimer checkTimer; - QTimer refreshTimer; -}; - -#endif // COCKATRICE_INTENT_OPEN_SERVER_ROOM_BY_NAME_H diff --git a/cockatrice/src/interface/theme_manager.cpp b/cockatrice/src/interface/theme_manager.cpp index 8986a9f00..ebe35c771 100644 --- a/cockatrice/src/interface/theme_manager.cpp +++ b/cockatrice/src/interface/theme_manager.cpp @@ -123,7 +123,7 @@ void ThemeManager::ensureThemeDirectoryExists() } } -bool ThemeManager::isDarkMode(const QString &themeDirPath) const +bool ThemeManager::isDarkMode(const QString &themeDirPath) { ThemeConfig themeConfig = ThemeConfig::fromThemeDir(themeDirPath); if (themeConfig.colorScheme.compare("Dark", Qt::CaseInsensitive) == 0) { diff --git a/cockatrice/src/interface/theme_manager.h b/cockatrice/src/interface/theme_manager.h index e3a40660b..861ab838b 100644 --- a/cockatrice/src/interface/theme_manager.h +++ b/cockatrice/src/interface/theme_manager.h @@ -66,14 +66,7 @@ protected: public: bool isBuiltInTheme(); - // Explicit color scheme of the theme: theme.cfg's ColorScheme setting - // (Dark/Light), falling back to the OS color scheme when it is "System". - bool isDarkMode(const QString &themeDirPath) const; - // The resolved scheme of the currently active theme. - bool isDarkModeActive() const - { - return isDarkMode(currentThemePath); - } + bool isDarkMode(const QString &themeDirPath); QStringMap &getAvailableThemes(); // Returns the path to the currently active theme directory (empty = default) QString getCurrentThemePath() const diff --git a/cockatrice/src/interface/widgets/server/user/user_info_popup.cpp b/cockatrice/src/interface/widgets/server/user/user_info_popup.cpp index 5d36fbcdb..112f107d4 100644 --- a/cockatrice/src/interface/widgets/server/user/user_info_popup.cpp +++ b/cockatrice/src/interface/widgets/server/user/user_info_popup.cpp @@ -1,7 +1,6 @@ #include "user_info_popup.h" #include "../../interface/pixel_map_generator.h" -#include "../../interface/theme_manager.h" #include "../../interface/widgets/tabs/tab_supervisor.h" #include "user_list_painter.h" @@ -23,42 +22,6 @@ #include #include -/// Qt stylesheets accept #aarrggbb, which is QColor::name(QColor::HexArgb). -static QString colorStr(const QColor &color) -{ - return color.name(QColor::HexArgb); -} - -PopupTheme PopupTheme::fromPalette(const QPalette &palette, bool dark) -{ - PopupTheme t; - t.dark = dark; - const QColor window = palette.color(QPalette::Window); - const QColor base = palette.color(QPalette::Base); - const QColor mid = palette.color(QPalette::Mid); - const QColor text = palette.color(QPalette::Text); - const QColor disabledText = palette.color(QPalette::Disabled, QPalette::Text); - const QColor highlight = palette.color(QPalette::Highlight); - - t.bg = window; - t.border = mid; - t.text = text; - t.subText = disabledText; - t.statusText = disabledText; - t.buttonBg = base; - t.buttonBorder = mid; - t.buttonHover = UserListPainter::blend(base, highlight, dark ? 0.30 : 0.12); - t.buttonPressed = UserListPainter::blend(base, highlight, dark ? 0.50 : 0.25); - t.buttonDisabled = disabledText; - t.closeBg = UserListPainter::blend(base, window, 0.5); - t.closeHover = dark ? QColor(200, 50, 50) : UserListPainter::blend(QColor(200, 50, 50), base, 0.45); - t.gamesRow = base; - t.gamesSelected = UserListPainter::blend(base, highlight, dark ? 0.45 : 0.30); - t.gamesSeparator = mid; - t.gamesSeparator.setAlpha(90); - return t; -} - // ── Compact game row delegate ───────────────────────────────────────────────── class PopupGameDelegate : public QStyledItemDelegate @@ -85,14 +48,8 @@ public: const QRect rect = option.rect; const ServerInfo_Game game = var.value(); const bool selected = option.state & QStyle::State_Selected; - const bool dark = themeManager && themeManager->isDarkModeActive(); - // The widget palette can be stale after a runtime theme change, so the - // rows are styled from the application palette (always current). - const QPalette pal = qApp->palette(); - const QColor base = pal.color(QPalette::Base); - const QColor highlight = pal.color(QPalette::Highlight); - p->fillRect(rect, selected ? UserListPainter::blend(base, highlight, dark ? 0.45 : 0.30) : base); + p->fillRect(rect, selected ? QColor(35, 45, 62) : QColor(14, 18, 26)); // State colour dot const QColor dot = game.started() ? QColor(239, 68, 68) @@ -107,7 +64,7 @@ public: QFont tf = option.font; tf.setBold(true); p->setFont(tf); - p->setPen(pal.color(QPalette::Text)); + p->setPen(QColor(205, 215, 230)); const int textX = rect.left() + 26; const int countW = 52; const int titleW = rect.width() - textX - countW - 6; @@ -117,15 +74,13 @@ public: // Player count const bool full = game.player_count() >= game.max_players(); p->setFont(option.font); - p->setPen(full ? QColor(249, 115, 22) : pal.color(QPalette::Disabled, QPalette::Text)); + p->setPen(full ? QColor(249, 115, 22) : QColor(110, 128, 150)); p->drawText(QRect(rect.right() - countW - 4, rect.top(), countW, rect.height()), Qt::AlignVCenter | Qt::AlignRight, QStringLiteral("%1/%2").arg(game.player_count()).arg(game.max_players())); // Row separator - QColor separator = pal.color(QPalette::Mid); - separator.setAlpha(90); - p->setPen(separator); + p->setPen(QColor(24, 32, 44)); p->drawLine(rect.bottomLeft(), rect.bottomRight()); p->restore(); @@ -140,17 +95,17 @@ UserInfoHeaderWidget::UserInfoHeaderWidget(QWidget *parent) : QWidget(parent) setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); } -void UserInfoHeaderWidget::setUserData(const ServerInfo_User &_user, - bool _online, - const QPixmap &_avatar, - const QPixmap &_cardArt, - const CardArtParams &_params) +void UserInfoHeaderWidget::setUserData(const ServerInfo_User &user, + bool online, + const QPixmap &avatar, + const QPixmap &cardArt, + const CardArtParams ¶ms) { - user = _user; - online = _online; - avatar = _avatar; - cardArt = _cardArt; - params = _params; + m_user = user; + m_online = online; + m_avatar = avatar; + m_cardArt = cardArt; + m_params = params; update(); } @@ -160,37 +115,29 @@ void UserInfoHeaderWidget::paintEvent(QPaintEvent *) p.setRenderHints(QPainter::Antialiasing | QPainter::SmoothPixmapTransform | QPainter::TextAntialiasing); const QRect rect = this->rect(); - const UserLevelFlags level(user.user_level()); - const QString userName = QString::fromStdString(user.name()); - const QString privLevel = QString::fromStdString(user.privlevel()); + const UserLevelFlags level(m_user.user_level()); + const QString userName = QString::fromStdString(m_user.name()); + const QString privLevel = QString::fromStdString(m_user.privlevel()); - const bool dark = themeManager && themeManager->isDarkModeActive(); - const UserListPainter::Style style = UserListPainter::resolveStyle(qApp->palette(), dark); - - // Palette surface - { - QLinearGradient bg(0, 0, rect.width(), 0); - bg.setColorAt(0, style.cardStart); - bg.setColorAt(1, style.cardEnd); - p.fillRect(rect, bg); - } + // Dark base + p.fillRect(rect, QColor(14, 18, 26)); // ── Card art background ─────────────────────────────────────────────────── - if (!cardArt.isNull()) { + if (!m_cardArt.isNull()) { const int w = rect.width(); const int h = rect.height(); - const int mL = qRound(w * params.marginPctL); - const int mR = qRound(w * params.marginPctR); + const int mL = qRound(w * m_params.marginPctL); + const int mR = qRound(w * m_params.marginPctR); const int dW = w - mL - mR; - const double base = qMax(double(dW) / cardArt.width(), double(h) / cardArt.height()); - const double scale = base * params.zoom; - const int sW = qRound(cardArt.width() * scale); - const int sH = qRound(cardArt.height() * scale); + const double base = qMax(double(dW) / m_cardArt.width(), double(h) / m_cardArt.height()); + const double scale = base * m_params.zoom; + const int sW = qRound(m_cardArt.width() * scale); + const int sH = qRound(m_cardArt.height() * scale); - const QPixmap scaled = cardArt.scaled(sW, sH, Qt::IgnoreAspectRatio, Qt::SmoothTransformation); + const QPixmap scaled = m_cardArt.scaled(sW, sH, Qt::IgnoreAspectRatio, Qt::SmoothTransformation); const int srcX = (sW - dW) / 2; - const int srcY = qBound(0, qRound((sH - h) * params.verticalOffset), qMax(0, sH - h)); + const int srcY = qBound(0, qRound((sH - h) * m_params.verticalOffset), qMax(0, sH - h)); QImage img = scaled.copy(srcX, srcY, dW, h).toImage().convertToFormat(QImage::Format_ARGB32_Premultiplied); { @@ -208,14 +155,12 @@ void UserInfoHeaderWidget::paintEvent(QPaintEvent *) p.setOpacity(1.0); } - // Bottom gradient overlay so avatar and text are always legible. The scrim - // is the palette's Window color so it reads naturally in either scheme. + // Bottom gradient overlay so avatar and text are always legible { - const QColor scrim = qApp->palette().color(QPalette::Window); QLinearGradient ov(0, 0, 0, rect.height()); - ov.setColorAt(0.0, QColor(scrim.red(), scrim.green(), scrim.blue(), 0)); - ov.setColorAt(0.55, QColor(scrim.red(), scrim.green(), scrim.blue(), 110)); - ov.setColorAt(1.0, QColor(scrim.red(), scrim.green(), scrim.blue(), 230)); + ov.setColorAt(0.0, QColor(14, 18, 26, 0)); + ov.setColorAt(0.55, QColor(14, 18, 26, 110)); + ov.setColorAt(1.0, QColor(14, 18, 26, 230)); p.fillRect(rect, ov); } @@ -242,20 +187,20 @@ void UserInfoHeaderWidget::paintEvent(QPaintEvent *) p.save(); p.setClipPath(clip); - if (!avatar.isNull()) { - p.drawPixmap(ar, avatar.scaled(ar.size(), Qt::KeepAspectRatioByExpanding, Qt::SmoothTransformation)); + if (!m_avatar.isNull()) { + p.drawPixmap(ar, m_avatar.scaled(ar.size(), Qt::KeepAspectRatioByExpanding, Qt::SmoothTransformation)); } else { p.setPen(Qt::NoPen); - p.setBrush(UserListPainter::blend(accent, style.base, dark ? 0.45 : 0.72)); + p.setBrush(accent.darker(200)); p.drawEllipse(ar); const QPixmap pawn = - UserLevelPixmapGenerator::generatePixmap(AvatarPawnSize, level, user.pawn_colors(), false, privLevel); + UserLevelPixmapGenerator::generatePixmap(AvatarPawnSize, level, m_user.pawn_colors(), false, privLevel); p.drawPixmap(ar.center().x() - AvatarPawnSize / 2, ar.center().y() - AvatarPawnSize / 2, pawn); } p.restore(); // Status ring - p.setPen(QPen(online ? QColor(34, 197, 94) : style.ringOffline, 2.5)); + p.setPen(QPen(m_online ? QColor(34, 197, 94) : QColor(70, 80, 95), 2.5)); p.setBrush(Qt::NoBrush); p.drawEllipse(QRectF(ar).adjusted(-1.25, -1.25, 1.25, 1.25)); @@ -267,7 +212,7 @@ void UserInfoHeaderWidget::paintEvent(QPaintEvent *) nf.setBold(true); nf.setPointSizeF(nf.pointSizeF() * 1.12); p.setFont(nf); - p.setPen(online ? style.textOnline : style.textOffline); + p.setPen(m_online ? QColor(220, 228, 240) : QColor(90, 100, 115)); p.drawText(QRect(tx, ay, tw, AvatarSize / 2 + 4), Qt::AlignBottom | Qt::AlignLeft, QFontMetrics(nf).elidedText(userName, Qt::ElideRight, tw)); @@ -298,173 +243,143 @@ void UserInfoHeaderWidget::paintEvent(QPaintEvent *) const int bw = bfm.horizontalAdvance(badge.text) + 10; const QRect br(tx, ay + AvatarSize / 2 + 6, bw, 15); p.setPen(Qt::NoPen); - p.setBrush(UserListPainter::blend(badge.color, style.base, dark ? 0.55 : 0.78)); + p.setBrush(badge.color.darker(160)); p.drawRoundedRect(br, 3, 3); - p.setPen(dark ? UserListPainter::blend(badge.color, Qt::white, 0.5) - : UserListPainter::blend(badge.color, Qt::black, 0.35)); + p.setPen(badge.color.lighter(150)); p.drawText(br, Qt::AlignCenter, badge.text); } } // ── UserInfoPopup ───────────────────────────────────────────────────────────── -UserInfoPopup::UserInfoPopup(TabSupervisor *_ts, - AbstractClient *_client, - const QMap *_avatarCache, - const QMap *_cardArtCache, - const QMap *_cardArtParamsMap, +UserInfoPopup::UserInfoPopup(TabSupervisor *ts, + AbstractClient *client, + const QMap *avatarCache, + const QMap *cardArtCache, + const QMap *cardArtParamsMap, QWidget *parent) - : QFrame(parent, Qt::Tool | Qt::FramelessWindowHint), ts(_ts), client(_client), avatarCache(_avatarCache), - cardArtCache(_cardArtCache), cardArtParamsMap(_cardArtParamsMap) + : QFrame(parent, Qt::Tool | Qt::FramelessWindowHint), m_ts(ts), m_client(client), m_avatarCache(avatarCache), + m_cardArtCache(cardArtCache), m_cardArtParamsMap(cardArtParamsMap) { setAttribute(Qt::WA_ShowWithoutActivating); setFixedWidth(PopupWidth); setFrameShape(QFrame::NoFrame); buildUi(); - - // Restyle the popup chrome when the theme or its color scheme changes. - if (themeManager) { - connect(themeManager, &ThemeManager::themeChanged, this, &UserInfoPopup::applyTheme); - } } void UserInfoPopup::buildUi() { + setStyleSheet(QStringLiteral("UserInfoPopup {" + " background:#0e1218;" + " border:1px solid #1e2838;" + " border-radius:8px;" + "}")); + auto *root = new QVBoxLayout(this); root->setContentsMargins(0, 0, 0, 0); root->setSpacing(0); // Header - header = new UserInfoHeaderWidget(this); - root->addWidget(header); + m_header = new UserInfoHeaderWidget(this); + root->addWidget(m_header); // Action area — rebuilt per user - actionArea = new QWidget(this); - root->addWidget(actionArea); + m_actionArea = new QWidget(this); + m_actionArea->setStyleSheet(QStringLiteral("background:#0e1218;")); + root->addWidget(m_actionArea); // Thin separator - separator = new QFrame(this); - separator->setFrameShape(QFrame::HLine); - root->addWidget(separator); + auto *sep = new QFrame(this); + sep->setFrameShape(QFrame::HLine); + sep->setStyleSheet(QStringLiteral("color:#1a2434; margin: 0 8px;")); + root->addWidget(sep); // Games header row auto *gh = new QHBoxLayout; gh->setContentsMargins(10, 4, 8, 2); - gamesLabel = new QLabel(tr("Games"), this); - gh->addWidget(gamesLabel); + auto *gl = new QLabel(tr("Games"), this); + gl->setStyleSheet(QStringLiteral("color:#6882a0; font-size:11px; font-weight:bold; background:transparent;")); + gh->addWidget(gl); gh->addStretch(); - refreshBtn = new QPushButton(QStringLiteral("↻"), this); - refreshBtn->setFixedSize(20, 20); - refreshBtn->setFlat(true); - connect(refreshBtn, &QPushButton::clicked, this, &UserInfoPopup::refreshGames); - gh->addWidget(refreshBtn); + m_refreshBtn = new QPushButton(QStringLiteral("↻"), this); + m_refreshBtn->setFixedSize(20, 20); + m_refreshBtn->setFlat(true); + m_refreshBtn->setStyleSheet( + QStringLiteral("QPushButton{color:#6882a0;border:none;font-size:14px;background:transparent;}" + "QPushButton:hover{color:white;}")); + connect(m_refreshBtn, &QPushButton::clicked, this, &UserInfoPopup::refreshGames); + gh->addWidget(m_refreshBtn); root->addLayout(gh); // Status label - gamesStatus = new QLabel(this); - gamesStatus->setAlignment(Qt::AlignCenter); - root->addWidget(gamesStatus); + m_gamesStatus = new QLabel(this); + m_gamesStatus->setAlignment(Qt::AlignCenter); + m_gamesStatus->setStyleSheet( + QStringLiteral("color:#3a4a5e; font-size:11px; padding:10px; background:transparent;")); + root->addWidget(m_gamesStatus); // Games list - gamesModel = new QStandardItemModel(this); - gamesView = new QListView(this); - gamesView->setModel(gamesModel); - gamesView->setItemDelegate(new PopupGameDelegate(gamesView)); - gamesView->setFrameShape(QFrame::NoFrame); - gamesView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); - gamesView->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); - gamesView->setMaximumHeight(220); - gamesView->setContextMenuPolicy(Qt::CustomContextMenu); - connect(gamesView, &QListView::customContextMenuRequested, this, &UserInfoPopup::onGamesContextMenu); + m_gamesModel = new QStandardItemModel(this); + m_gamesView = new QListView(this); + m_gamesView->setModel(m_gamesModel); + m_gamesView->setItemDelegate(new PopupGameDelegate(m_gamesView)); + m_gamesView->setFrameShape(QFrame::NoFrame); + m_gamesView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + m_gamesView->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); + m_gamesView->setMaximumHeight(220); + m_gamesView->setStyleSheet(QStringLiteral("QListView{background:#0e1218;border:none;}" + "QListView::item:selected{background:#232e42;}")); + m_gamesView->setContextMenuPolicy(Qt::CustomContextMenu); + connect(m_gamesView, &QListView::customContextMenuRequested, this, &UserInfoPopup::onGamesContextMenu); - root->addWidget(gamesView); + root->addWidget(m_gamesView); // Close button — positioned absolutely in the top-right corner - closeBtn = new QPushButton(QStringLiteral("✕"), this); - closeBtn->setFixedSize(22, 22); - closeBtn->setFlat(true); - connect(closeBtn, &QPushButton::clicked, this, &UserInfoPopup::closeRequested); - - applyTheme(); -} - -void UserInfoPopup::applyTheme() -{ - const bool dark = themeManager && themeManager->isDarkModeActive(); - theme = PopupTheme::fromPalette(qApp->palette(), dark); - - setStyleSheet(QStringLiteral("UserInfoPopup {" - " background:%1;" - " border:1px solid %2;" - " border-radius:8px;" - "}") - .arg(colorStr(theme.bg), colorStr(theme.border))); - - actionArea->setStyleSheet(QStringLiteral("background:%1;").arg(colorStr(theme.bg))); - - separator->setStyleSheet(QStringLiteral("color:%1; margin: 0 8px;").arg(colorStr(theme.border))); - - gamesLabel->setStyleSheet(QStringLiteral("color:%1; font-size:11px; font-weight:bold; background:transparent;") - .arg(colorStr(theme.subText))); - - refreshBtn->setStyleSheet(QStringLiteral("QPushButton{color:%1;border:none;font-size:14px;background:transparent;}" - "QPushButton:hover{color:%2;}") - .arg(colorStr(theme.subText), colorStr(theme.text))); - - gamesStatus->setStyleSheet(QStringLiteral("color:%1; font-size:11px; padding:10px; background:transparent;") - .arg(colorStr(theme.statusText))); - - gamesView->setStyleSheet(QStringLiteral("QListView{background:%1;border:none;}" - "QListView::item:selected{background:%2;}") - .arg(colorStr(theme.gamesRow), colorStr(theme.gamesSelected))); - - closeBtn->setStyleSheet( - QStringLiteral("QPushButton{background:%1;color:%2;" - "border:none;border-radius:11px;font-size:10px;}" - "QPushButton:hover{color:%3;background:%4;}") - .arg(colorStr(theme.closeBg), colorStr(theme.subText), colorStr(theme.text), colorStr(theme.closeHover))); - - header->update(); + m_closeBtn = new QPushButton(QStringLiteral("✕"), this); + m_closeBtn->setFixedSize(22, 22); + m_closeBtn->setFlat(true); + m_closeBtn->setStyleSheet(QStringLiteral("QPushButton{background:rgba(14,18,26,180);color:#607080;" + "border:none;border-radius:11px;font-size:10px;}" + "QPushButton:hover{color:white;background:rgba(200,50,50,200);}")); + connect(m_closeBtn, &QPushButton::clicked, this, &UserInfoPopup::closeRequested); } // ── Action button factory ───────────────────────────────────────────────────── -static QPushButton *makeBtn(const QString &label, const QString &tip, QWidget *p, const PopupTheme &t) +static QPushButton *makeBtn(const QString &label, const QString &tip, QWidget *p) { auto *b = new QPushButton(label, p); b->setToolTip(tip); b->setFixedHeight(26); b->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); b->setStyleSheet(QStringLiteral("QPushButton{" - " background:%1;color:%2;border:1px solid %3;" + " background:#192030;color:#b8c8de;border:1px solid #263040;" " border-radius:4px;font-size:11px;padding:0 4px;" "}" - "QPushButton:hover{background:%4;color:%5;}" - "QPushButton:pressed{background:%6;}" - "QPushButton:disabled{color:%7;border-color:%3;}") - .arg(colorStr(t.buttonBg), colorStr(t.text), colorStr(t.buttonBorder), colorStr(t.buttonHover), - colorStr(t.text), colorStr(t.buttonPressed), colorStr(t.buttonDisabled))); + "QPushButton:hover{background:#223050;color:white;}" + "QPushButton:pressed{background:#162030;}" + "QPushButton:disabled{color:#384858;border-color:#192030;}")); return b; } void UserInfoPopup::rebuildActionButtons(const ServerInfo_User &userInfo, bool online, bool isBuddy, bool isIgnored) { // Clear previous contents - delete actionArea->layout(); - const auto old = actionArea->findChildren(QString{}, Qt::FindDirectChildrenOnly); + delete m_actionArea->layout(); + const auto old = m_actionArea->findChildren(QString{}, Qt::FindDirectChildrenOnly); for (auto *w : old) { w->deleteLater(); } const QString name = QString::fromStdString(userInfo.name()); - const auto ownLevel = UserLevelFlags(ts->getUserInfo()->user_level()); - const bool isSelf = (name == QString::fromStdString(ts->getUserInfo()->name())); + const auto ownLevel = UserLevelFlags(m_ts->getUserInfo()->user_level()); + const bool isSelf = (name == QString::fromStdString(m_ts->getUserInfo()->name())); const bool isMod = ownLevel.testFlag(ServerInfo_User::IsModerator); const bool isAdmin = ownLevel.testFlag(ServerInfo_User::IsAdmin); const auto their = UserLevelFlags(userInfo.user_level()); const bool isReg = their.testFlag(ServerInfo_User::IsRegistered); - auto *grid = new QGridLayout(actionArea); + auto *grid = new QGridLayout(m_actionArea); grid->setContentsMargins(8, 6, 8, 6); grid->setSpacing(4); @@ -479,16 +394,16 @@ void UserInfoPopup::rebuildActionButtons(const ServerInfo_User &userInfo, bool o }; // ── Always visible ──────────────────────────────────────────────────────── - auto *chat = makeBtn(tr("Chat"), tr("Open private chat"), actionArea, theme); + auto *chat = makeBtn(tr("Chat"), tr("Open private chat"), m_actionArea); chat->setEnabled(!isSelf && online); connect(chat, &QPushButton::clicked, this, [this, name] { emit chatRequested(name); }); add(chat); - auto *prof = makeBtn(tr("Profile"), tr("View user profile"), actionArea, theme); + auto *prof = makeBtn(tr("Profile"), tr("View user profile"), m_actionArea); connect(prof, &QPushButton::clicked, this, [this, name] { emit detailsRequested(name); }); add(prof); - auto *games = makeBtn(tr("Games"), tr("Show this user's games"), actionArea, theme); + auto *games = makeBtn(tr("Games"), tr("Show this user's games"), m_actionArea); games->setEnabled(!isSelf && online); connect(games, &QPushButton::clicked, this, [this, name] { emit showGamesRequested(name); }); add(games); @@ -496,20 +411,20 @@ void UserInfoPopup::rebuildActionButtons(const ServerInfo_User &userInfo, bool o // ── Buddy / ignore (registered users only) ──────────────────────────────── if (!isSelf && isReg) { if (isBuddy) { - auto *b = makeBtn(tr("− Buddy"), tr("Remove from buddy list"), actionArea, theme); + auto *b = makeBtn(tr("− Buddy"), tr("Remove from buddy list"), m_actionArea); connect(b, &QPushButton::clicked, this, [this, name] { emit removeBuddyRequested(name); }); add(b); } else { - auto *b = makeBtn(tr("+ Buddy"), tr("Add to buddy list"), actionArea, theme); + auto *b = makeBtn(tr("+ Buddy"), tr("Add to buddy list"), m_actionArea); connect(b, &QPushButton::clicked, this, [this, name] { emit addBuddyRequested(name); }); add(b); } if (isIgnored) { - auto *b = makeBtn(tr("− Ignore"), tr("Remove from ignore list"), actionArea, theme); + auto *b = makeBtn(tr("− Ignore"), tr("Remove from ignore list"), m_actionArea); connect(b, &QPushButton::clicked, this, [this, name] { emit removeIgnoreRequested(name); }); add(b); } else { - auto *b = makeBtn(tr("+ Ignore"), tr("Add to ignore list"), actionArea, theme); + auto *b = makeBtn(tr("+ Ignore"), tr("Add to ignore list"), m_actionArea); connect(b, &QPushButton::clicked, this, [this, name] { emit addIgnoreRequested(name); }); add(b); } @@ -522,10 +437,10 @@ void UserInfoPopup::rebuildActionButtons(const ServerInfo_User &userInfo, bool o col = 0; } // start mod section on a fresh row - auto *ban = makeBtn(tr("Ban"), tr("Ban from server"), actionArea, theme); - auto *warn = makeBtn(tr("Warn"), tr("Warn user"), actionArea, theme); - auto *bLog = makeBtn(tr("Ban log"), tr("View ban history"), actionArea, theme); - auto *wLog = makeBtn(tr("Warn log"), tr("View warning history"), actionArea, theme); + auto *ban = makeBtn(tr("Ban"), tr("Ban from server"), m_actionArea); + auto *warn = makeBtn(tr("Warn"), tr("Warn user"), m_actionArea); + auto *bLog = makeBtn(tr("Ban log"), tr("View ban history"), m_actionArea); + auto *wLog = makeBtn(tr("Warn log"), tr("View warning history"), m_actionArea); connect(ban, &QPushButton::clicked, this, [this, name] { emit banRequested(name); }); connect(warn, &QPushButton::clicked, this, [this, name] { emit warnRequested(name); }); connect(bLog, &QPushButton::clicked, this, [this, name] { emit banHistoryRequested(name); }); @@ -538,31 +453,31 @@ void UserInfoPopup::rebuildActionButtons(const ServerInfo_User &userInfo, bool o // ── Admin actions ───────────────────────────────────────────────────────── if (!isSelf && isAdmin) { - auto *notes = makeBtn(tr("Notes"), tr("View admin notes"), actionArea, theme); + auto *notes = makeBtn(tr("Notes"), tr("View admin notes"), m_actionArea); connect(notes, &QPushButton::clicked, this, [this, name] { emit adminNotesRequested(name); }); add(notes); if (their.testFlag(ServerInfo_User::IsModerator)) { - auto *b = makeBtn(tr("− Mod"), tr("Demote from moderator"), actionArea, theme); + auto *b = makeBtn(tr("− Mod"), tr("Demote from moderator"), m_actionArea); connect(b, &QPushButton::clicked, this, [this, name] { emit demoteFromModRequested(name); }); add(b); } else if (isReg) { - auto *b = makeBtn(tr("+ Mod"), tr("Promote to moderator"), actionArea, theme); + auto *b = makeBtn(tr("+ Mod"), tr("Promote to moderator"), m_actionArea); connect(b, &QPushButton::clicked, this, [this, name] { emit promoteToModRequested(name); }); add(b); } if (their.testFlag(ServerInfo_User::IsJudge)) { - auto *b = makeBtn(tr("− Judge"), tr("Demote from judge"), actionArea, theme); + auto *b = makeBtn(tr("− Judge"), tr("Demote from judge"), m_actionArea); connect(b, &QPushButton::clicked, this, [this, name] { emit demoteFromJudgeRequested(name); }); add(b); } else if (isReg) { - auto *b = makeBtn(tr("+ Judge"), tr("Promote to judge"), actionArea, theme); + auto *b = makeBtn(tr("+ Judge"), tr("Promote to judge"), m_actionArea); connect(b, &QPushButton::clicked, this, [this, name] { emit promoteToJudgeRequested(name); }); add(b); } } - actionArea->adjustSize(); + m_actionArea->adjustSize(); } void UserInfoPopup::updateActionButtons(const ServerInfo_User &userInfo, bool online, bool isBuddy, bool isIgnored) @@ -573,7 +488,7 @@ void UserInfoPopup::updateActionButtons(const ServerInfo_User &userInfo, bool on void UserInfoPopup::onGamesContextMenu(const QPoint &pos) { - const QModelIndex idx = gamesView->indexAt(pos); + const QModelIndex idx = m_gamesView->indexAt(pos); if (!idx.isValid()) { return; } @@ -586,9 +501,8 @@ void UserInfoPopup::onGamesContextMenu(const QPoint &pos) QMenu menu(this); menu.setStyleSheet( - QStringLiteral("QMenu{background:%1;color:%2;border:1px solid %3;border-radius:4px;}" - "QMenu::item:selected{background:%4;}") - .arg(colorStr(theme.bg), colorStr(theme.text), colorStr(theme.border), colorStr(theme.buttonHover))); + QStringLiteral("QMenu{background:#12182a;color:#c8d8ec;border:1px solid #1e2838;border-radius:4px;}" + "QMenu::item:selected{background:#223050;}")); const bool canJoin = !game.started() && game.player_count() < game.max_players(); QAction *join = menu.addAction(tr("Join game")); @@ -599,7 +513,7 @@ void UserInfoPopup::onGamesContextMenu(const QPoint &pos) spec = menu.addAction(tr("Spectate")); } - const QAction *chosen = menu.exec(gamesView->viewport()->mapToGlobal(pos)); + const QAction *chosen = menu.exec(m_gamesView->viewport()->mapToGlobal(pos)); if (!chosen) { return; } @@ -615,17 +529,17 @@ void UserInfoPopup::onGamesContextMenu(const QPoint &pos) void UserInfoPopup::refreshHeader() { - if (currentUser.isEmpty()) { + if (m_currentUser.isEmpty()) { return; } - const QPixmap avatar = avatarCache ? avatarCache->value(currentUser) : QPixmap{}; - const CardArtParams params = (cardArtParamsMap && cardArtParamsMap->contains(currentUser)) - ? cardArtParamsMap->value(currentUser) + const QPixmap avatar = m_avatarCache ? m_avatarCache->value(m_currentUser) : QPixmap{}; + const CardArtParams params = (m_cardArtParamsMap && m_cardArtParamsMap->contains(m_currentUser)) + ? m_cardArtParamsMap->value(m_currentUser) : CardArtParams{}; - const QString artKey = currentUser + u'|' + params.cardName + u'|' + params.cardProviderId; - const QPixmap cardArt = (cardArtCache && !params.cardName.isEmpty()) ? cardArtCache->value(artKey) : QPixmap{}; - header->setUserData(currentUserInfo, currentOnline, avatar, cardArt, params); + const QString artKey = m_currentUser + u'|' + params.cardName + u'|' + params.cardProviderId; + const QPixmap cardArt = (m_cardArtCache && !params.cardName.isEmpty()) ? m_cardArtCache->value(artKey) : QPixmap{}; + m_header->setUserData(m_currentUserInfo, m_currentOnline, avatar, cardArt, params); } void UserInfoPopup::showForUser(const QString &userName, @@ -634,9 +548,9 @@ void UserInfoPopup::showForUser(const QString &userName, bool isBuddy, bool isIgnored) { - currentUser = userName; - currentUserInfo = userInfo; - currentOnline = online; + m_currentUser = userName; + m_currentUserInfo = userInfo; + m_currentOnline = online; // Header refreshHeader(); @@ -645,14 +559,14 @@ void UserInfoPopup::showForUser(const QString &userName, rebuildActionButtons(userInfo, online, isBuddy, isIgnored); // Games list reset - gamesModel->clear(); - gamesView->hide(); - gamesStatus->setText(tr("Loading games…")); - gamesStatus->show(); + m_gamesModel->clear(); + m_gamesView->hide(); + m_gamesStatus->setText(tr("Loading games…")); + m_gamesStatus->show(); // Close button — top-right corner, above everything - closeBtn->move(PopupWidth - closeBtn->width() - 6, 6); - closeBtn->raise(); + m_closeBtn->move(PopupWidth - m_closeBtn->width() - 6, 6); + m_closeBtn->raise(); adjustSize(); fetchGames(); @@ -662,40 +576,40 @@ void UserInfoPopup::showForUser(const QString &userName, void UserInfoPopup::fetchGames() { - if (!client || currentUser.isEmpty()) { + if (!m_client || m_currentUser.isEmpty()) { return; } Command_GetGamesOfUser cmd; - cmd.set_user_name(currentUser.toStdString()); + cmd.set_user_name(m_currentUser.toStdString()); - const QString snapshot = currentUser; - PendingCommand *pend = client->prepareSessionCommand(cmd); + const QString snapshot = m_currentUser; + PendingCommand *pend = m_client->prepareSessionCommand(cmd); connect(pend, &PendingCommand::finished, this, [this, snapshot](const Response &r) { onGamesReceived(r, snapshot); }); - client->sendCommand(pend); + m_client->sendCommand(pend); } void UserInfoPopup::onGamesReceived(const Response &r, const QString &forUser) { - if (forUser != currentUser) { + if (forUser != m_currentUser) { return; // stale response — different user showing now } - gamesModel->clear(); + m_gamesModel->clear(); if (r.response_code() != Response::RespOk) { - gamesStatus->setText(tr("Could not load games.")); - gamesStatus->show(); - gamesView->hide(); + m_gamesStatus->setText(tr("Could not load games.")); + m_gamesStatus->show(); + m_gamesView->hide(); return; } const auto &resp = r.GetExtension(Response_GetGamesOfUser::ext); if (resp.game_list_size() == 0) { - gamesStatus->setText(tr("No active games.")); - gamesStatus->show(); - gamesView->hide(); + m_gamesStatus->setText(tr("No active games.")); + m_gamesStatus->show(); + m_gamesView->hide(); return; } @@ -703,29 +617,29 @@ void UserInfoPopup::onGamesReceived(const Response &r, const QString &forUser) auto *item = new QStandardItem; item->setData(QVariant::fromValue(resp.game_list(i)), PopupRoles::GameData); item->setEditable(false); - gamesModel->appendRow(item); + m_gamesModel->appendRow(item); } - gamesStatus->hide(); - gamesView->show(); + m_gamesStatus->hide(); + m_gamesView->show(); // Fit exactly to the number of visible rows, scroll when more than 5 constexpr int rowH = 38; // must match PopupGameDelegate::sizeHint constexpr int maxRows = 5; - const int count = gamesModel->rowCount(); + const int count = m_gamesModel->rowCount(); const int visible = qMin(count, maxRows); - gamesView->setFixedHeight(visible * rowH + 2); - gamesView->setVerticalScrollBarPolicy(count > maxRows ? Qt::ScrollBarAlwaysOn : Qt::ScrollBarAlwaysOff); + m_gamesView->setFixedHeight(visible * rowH + 2); + m_gamesView->setVerticalScrollBarPolicy(count > maxRows ? Qt::ScrollBarAlwaysOn : Qt::ScrollBarAlwaysOff); adjustSize(); } void UserInfoPopup::refreshGames() { - gamesModel->clear(); - gamesView->hide(); - gamesStatus->setText(tr("Loading games…")); - gamesStatus->show(); + m_gamesModel->clear(); + m_gamesView->hide(); + m_gamesStatus->setText(tr("Loading games…")); + m_gamesStatus->show(); fetchGames(); } diff --git a/cockatrice/src/interface/widgets/server/user/user_info_popup.h b/cockatrice/src/interface/widgets/server/user/user_info_popup.h index 851223c87..c634511e1 100644 --- a/cockatrice/src/interface/widgets/server/user/user_info_popup.h +++ b/cockatrice/src/interface/widgets/server/user/user_info_popup.h @@ -26,35 +26,6 @@ namespace PopupRoles constexpr int GameData = Qt::UserRole + 10; } -// Popup theme - -/** - * Palette-derived colors for the popup chrome. Both color schemes read from - * the active QPalette so custom palettes are respected. @c dark only tunes the - * blend strengths. - */ -struct PopupTheme -{ - bool dark = false; - QColor bg; - QColor border; - QColor text; - QColor subText; - QColor buttonBg; - QColor buttonBorder; - QColor buttonHover; - QColor buttonPressed; - QColor buttonDisabled; - QColor closeBg; - QColor closeHover; - QColor gamesRow; - QColor gamesSelected; - QColor gamesSeparator; - QColor statusText; - - static PopupTheme fromPalette(const QPalette &palette, bool dark); -}; - // ── Header widget ───────────────────────────────────────────────────────────── /** @@ -80,21 +51,21 @@ class UserInfoHeaderWidget : public QWidget public: explicit UserInfoHeaderWidget(QWidget *parent = nullptr); - void setUserData(const ServerInfo_User &_user, - bool _online, - const QPixmap &_avatar, - const QPixmap &_cardArt, - const CardArtParams &_params); + void setUserData(const ServerInfo_User &user, + bool online, + const QPixmap &avatar, + const QPixmap &cardArt, + const CardArtParams ¶ms); protected: void paintEvent(QPaintEvent *e) override; private: - ServerInfo_User user; - bool online = false; - QPixmap avatar; - QPixmap cardArt; - CardArtParams params; + ServerInfo_User m_user; + bool m_online = false; + QPixmap m_avatar; + QPixmap m_cardArt; + CardArtParams m_params; }; // ── Main popup ──────────────────────────────────────────────────────────────── @@ -122,11 +93,11 @@ class UserInfoPopup : public QFrame static constexpr int PopupWidth = 316; public: - explicit UserInfoPopup(TabSupervisor *_ts, - AbstractClient *_client, - const QMap *_avatarCache, - const QMap *_cardArtCache, - const QMap *_cardArtParamsMap, + explicit UserInfoPopup(TabSupervisor *tabSupervisor, + AbstractClient *client, + const QMap *avatarCache, + const QMap *cardArtCache, + const QMap *cardArtParamsMap, QWidget *parent); /** @@ -137,9 +108,9 @@ public: showForUser(const QString &userName, const ServerInfo_User &userInfo, bool online, bool isBuddy, bool isIgnored); void fetchGames(); - [[nodiscard]] QString getCurrentUser() const + [[nodiscard]] QString currentUser() const { - return currentUser; + return m_currentUser; } /** Called when buddy/ignore status changes externally while popup is open. */ @@ -185,30 +156,25 @@ private slots: private: void buildUi(); - void applyTheme(); void rebuildActionButtons(const ServerInfo_User &userInfo, bool online, bool isBuddy, bool isIgnored); - TabSupervisor *ts; - AbstractClient *client; - const QMap *avatarCache; - const QMap *cardArtCache; - const QMap *cardArtParamsMap; + TabSupervisor *m_ts; + AbstractClient *m_client; + const QMap *m_avatarCache; + const QMap *m_cardArtCache; + const QMap *m_cardArtParamsMap; - PopupTheme theme; + QString m_currentUser; + ServerInfo_User m_currentUserInfo; + bool m_currentOnline = false; - QString currentUser; - ServerInfo_User currentUserInfo; - bool currentOnline = false; - - UserInfoHeaderWidget *header; - QWidget *actionArea; ///< rebuilt per user - QLabel *gamesLabel; - QFrame *separator; - QListView *gamesView; - QStandardItemModel *gamesModel; - QLabel *gamesStatus; - QPushButton *closeBtn; - QPushButton *refreshBtn; + UserInfoHeaderWidget *m_header; + QWidget *m_actionArea; ///< rebuilt per user + QListView *m_gamesView; + QStandardItemModel *m_gamesModel; + QLabel *m_gamesStatus; + QPushButton *m_closeBtn; + QPushButton *m_refreshBtn; }; #endif // COCKATRICE_USER_INFO_POPUP_H \ No newline at end of file diff --git a/cockatrice/src/interface/widgets/server/user/user_list_painter.cpp b/cockatrice/src/interface/widgets/server/user/user_list_painter.cpp index 5a4723065..8891ff268 100644 --- a/cockatrice/src/interface/widgets/server/user/user_list_painter.cpp +++ b/cockatrice/src/interface/widgets/server/user/user_list_painter.cpp @@ -3,7 +3,6 @@ #include "../../interface/pixel_map_generator.h" #include -#include #include #include #include @@ -20,29 +19,6 @@ QSize UserListPainter::sizeHint() return QSize(0, RowHeight); } -UserListPainter::Style UserListPainter::resolveStyle(const QPalette &palette, bool dark) -{ - Style style; - style.dark = dark; - const QColor base = palette.color(QPalette::Base); - const QColor alt = palette.color(QPalette::AlternateBase); - style.cardStart = base; - style.cardEnd = (alt != base) ? alt : palette.color(QPalette::Midlight); - style.base = base; - style.textOnline = palette.color(QPalette::Text); - style.textOffline = palette.color(QPalette::Disabled, QPalette::Text); - style.ringOffline = palette.color(QPalette::Disabled, QPalette::Text); - style.dropShadow = dark; - return style; -} - -QColor UserListPainter::blend(const QColor &a, const QColor &b, qreal t) -{ - const qreal u = 1.0 - t; - return QColor(qRound(a.red() * u + b.red() * t), qRound(a.green() * u + b.green() * t), - qRound(a.blue() * u + b.blue() * t), qRound(a.alpha() * u + b.alpha() * t)); -} - QColor UserListPainter::getAccentColor(const UserLevelFlags &userLevel, bool online) { QColor accentColor; @@ -83,41 +59,18 @@ int UserListPainter::getCardRight(const QStyleOptionViewItem &option, const QRec void UserListPainter::drawBackground(QPainter *painter, const QRectF &cardRect, const QColor &accentColor, - bool selected, - const Style &style, - bool hasRole) + bool selected) { QLinearGradient bg(cardRect.topLeft(), cardRect.topRight()); - if (style.dark) { - // Dark mode darkens the role color to fit the dark surface and fades - // it into the deep navy surface on the right. The text drop shadow - // keeps the username legible over the colored edge. - bg.setColorAt(0, selected ? accentColor.darker(130) : accentColor.darker(320)); - bg.setColorAt(1, selected ? QColor(40, 48, 60) : QColor(18, 22, 30)); - } else if (hasRole) { - // Light mode pegs the role color on the left at near full strength - // and fades it into the white surface on the right. The tint stays - // bright enough that the dark text remains legible without a shadow. - bg.setColorAt(0, blend(style.cardStart, accentColor, selected ? 0.75 : 0.65)); - bg.setColorAt(1, blend(style.cardEnd, accentColor, selected ? 0.18 : 0.10)); - } else { - // Regular users are the light theme's neutral paper cards. A flat - // warm card fill (the normal row surface, slightly deepened) keeps - // every row clearly visible without borrowing a role color. Selection - // shifts the fill toward a soft slate so the highlight still reads. - const QColor paper = style.cardEnd.darker(108); - bg.setColorAt(0, blend(paper, accentColor, selected ? 0.35 : 0.0)); - bg.setColorAt(1, blend(paper, accentColor, selected ? 0.25 : 0.0)); - } + bg.setColorAt(0, selected ? accentColor.darker(130) : accentColor.darker(320)); + bg.setColorAt(1, selected ? QColor(40, 48, 60) : QColor(18, 22, 30)); painter->setPen(Qt::NoPen); painter->setBrush(bg); painter->drawRoundedRect(cardRect, 6, 6); - if (style.dark || hasRole || selected) { - painter->setBrush(accentColor); - painter->drawRoundedRect(QRectF(cardRect.left(), cardRect.top(), 3, cardRect.height()), 2, 2); - } + painter->setBrush(accentColor); + painter->drawRoundedRect(QRectF(cardRect.left(), cardRect.top(), 3, cardRect.height()), 2, 2); } static QString makeKey(const QString &user, const QString &card, const QString &providerId) @@ -210,8 +163,7 @@ void UserListPainter::drawAvatar(QPainter *painter, const UserLevelFlags &userLevel, const ServerInfo_User &userInfo, const QString &privLevel, - const QMap *avatarCache, - const Style &style) + const QMap *avatarCache) { QPainterPath clipPath; clipPath.addEllipse(avatarRect); @@ -231,7 +183,7 @@ void UserListPainter::drawAvatar(QPainter *painter, } if (!drewAvatar) { - painter->setBrush(blend(accentColor, style.base, style.dark ? 0.45 : 0.72)); + painter->setBrush(accentColor.darker(200)); painter->setPen(Qt::NoPen); painter->drawEllipse(avatarRect); @@ -244,9 +196,9 @@ void UserListPainter::drawAvatar(QPainter *painter, painter->restore(); } -void UserListPainter::drawStatusRing(QPainter *painter, const QRect &avatarRect, bool online, const Style &style) +void UserListPainter::drawStatusRing(QPainter *painter, const QRect &avatarRect, bool online) { - const QColor statusColor = online ? QColor(34, 197, 94) : style.ringOffline; + const QColor statusColor = online ? QColor(34, 197, 94) : QColor(70, 80, 95); painter->setPen(QPen(statusColor, 2)); painter->setBrush(Qt::NoBrush); @@ -260,7 +212,7 @@ void UserListPainter::drawUserName(QPainter *painter, int textX, const QString &userName, bool online, - const Style &style) + bool selected) { QFont nameFont = option.font; nameFont.setBold(true); @@ -269,12 +221,10 @@ void UserListPainter::drawUserName(QPainter *painter, const QRect nameRect(textX, rect.top() + 8, cardRight - textX - 10, 20); const QString elidedName = QFontMetrics(nameFont).elidedText(userName, Qt::ElideRight, cardRight - textX - 10); - if (style.dropShadow) { - painter->setPen(QColor(0, 0, 0, 200)); - painter->drawText(nameRect.translated(1, 1), Qt::AlignVCenter | Qt::AlignLeft, elidedName); - } + painter->setPen(QColor(0, 0, 0, 200)); + painter->drawText(nameRect.translated(1, 1), Qt::AlignVCenter | Qt::AlignLeft, elidedName); - painter->setPen(online ? style.textOnline : style.textOffline); + painter->setPen(online ? (selected ? Qt::white : QColor(226, 232, 240)) : QColor(90, 100, 115)); painter->drawText(nameRect, Qt::AlignVCenter | Qt::AlignLeft, elidedName); } @@ -312,8 +262,7 @@ void UserListPainter::drawBadges(QPainter *painter, const QRect &rect, int cardRight, const QList &badges, - bool online, - const Style &style) + bool online) { if (badges.isEmpty()) { return; @@ -335,17 +284,15 @@ void UserListPainter::drawBadges(QPainter *painter, int bx = cardRight - 6 - totalBadgeW; for (const Badge &b : badges) { - const QColor col = online ? b.color : blend(b.color, style.base, 0.55); - const QColor surface = blend(col, style.base, style.dark ? 0.55 : 0.78); - const QColor text = style.dark ? blend(col, Qt::white, 0.5) : blend(col, Qt::black, 0.35); + const QColor col = online ? b.color : b.color.darker(180); const int bw = fm.horizontalAdvance(b.text) + 8; const QRect br(bx, rect.top() + 44, bw, 13); painter->setPen(Qt::NoPen); - painter->setBrush(surface); + painter->setBrush(col.darker(online ? 160 : 220)); painter->drawRoundedRect(br, 3, 3); - painter->setPen(text); + painter->setPen(col.lighter(online ? 160 : 100)); painter->drawText(br, Qt::AlignCenter, b.text); bx += bw + 4; @@ -358,19 +305,11 @@ void UserListPainter::paint(QPainter *painter, const ServerInfo_User &userInfo, const QMap *avatarCache, const QMap *cardArtCache, - const QMap *cardArtParamsMap, - bool dark) + const QMap *cardArtParamsMap) { painter->save(); painter->setRenderHints(QPainter::Antialiasing | QPainter::SmoothPixmapTransform | QPainter::TextAntialiasing); - // The delegate supplies the application palette in option.palette, which - // always reflects the active theme. The widget palette can be stale after - // a runtime theme change, so it is only used as a defensive fallback. - const QPalette pal = - option.palette == QPalette() ? (option.widget ? option.widget->palette() : qApp->palette()) : option.palette; - const Style style = resolveStyle(pal, dark); - const QRect rect = option.rect; const bool online = index.data(Qt::UserRole + 1).toBool(); const bool selected = option.state & QStyle::State_Selected; @@ -378,9 +317,6 @@ void UserListPainter::paint(QPainter *painter, const QString userName = QString::fromStdString(userInfo.name()); const QString privLevel = QString::fromStdString(userInfo.privlevel()); const QColor accentColor = getAccentColor(userLevel, online); - const bool hasRole = userLevel.testFlag(ServerInfo_User::IsAdmin) || - userLevel.testFlag(ServerInfo_User::IsModerator) || - userLevel.testFlag(ServerInfo_User::IsJudge); const QRectF cardRect = QRectF(rect).adjusted(3, 2, -3, -2); const int cardRight = getCardRight(option, rect); @@ -388,19 +324,19 @@ void UserListPainter::paint(QPainter *painter, ? cardArtParamsMap->value(userName) : CardArtParams{}; - drawBackground(painter, cardRect, accentColor, selected, style, hasRole); + drawBackground(painter, cardRect, accentColor, selected); drawCardArt(painter, rect, cardRight, userName, cardArtCache, params); const QRect avatarRect = getAvatarRect(rect); - drawAvatar(painter, avatarRect, userName, accentColor, userLevel, userInfo, privLevel, avatarCache, style); - drawStatusRing(painter, avatarRect, online, style); + drawAvatar(painter, avatarRect, userName, accentColor, userLevel, userInfo, privLevel, avatarCache); + drawStatusRing(painter, avatarRect, online); const int textX = avatarRect.right() + TextSpacing; - drawUserName(painter, option, rect, cardRight, textX, userName, online, style); + drawUserName(painter, option, rect, cardRight, textX, userName, online, selected); drawCountryFlag(painter, rect, textX, userInfo); const QList badges = buildBadges(userLevel, privLevel); - drawBadges(painter, option, rect, cardRight, badges, online, style); + drawBadges(painter, option, rect, cardRight, badges, online); painter->restore(); } \ No newline at end of file diff --git a/cockatrice/src/interface/widgets/server/user/user_list_painter.h b/cockatrice/src/interface/widgets/server/user/user_list_painter.h index 352a01f6b..28cab9675 100644 --- a/cockatrice/src/interface/widgets/server/user/user_list_painter.h +++ b/cockatrice/src/interface/widgets/server/user/user_list_painter.h @@ -6,7 +6,6 @@ #include #include #include -#include #include #include #include @@ -29,36 +28,13 @@ struct CardArtParams class UserListPainter { public: - /** - * Palette-derived surface colors for the current color scheme. Both the - * light and the dark scheme read from the active QPalette so custom - * palettes are respected. @c dark only tunes the blend strengths (and - * whether the name text keeps its drop shadow). - */ - struct Style - { - bool dark = true; - QColor cardStart; ///< row fill, left edge (normal) - QColor cardEnd; ///< row fill, right edge (normal) - QColor base; ///< lightest surface, used for blending accent hues - QColor textOnline; - QColor textOffline; - QColor ringOffline; - bool dropShadow = false; - }; - - static Style resolveStyle(const QPalette &palette, bool dark); - /// Linear interpolation: @p t = 0 returns @p a, @p t = 1 returns @p b. - static QColor blend(const QColor &a, const QColor &b, qreal t); - static void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index, const ServerInfo_User &userInfo, const QMap *avatarCache, const QMap *cardArtCache, - const QMap *cardArtParamsMap, - bool dark); + const QMap *cardArtParamsMap); static QSize sizeHint(); @@ -79,12 +55,7 @@ private: static QColor getAccentColor(const UserLevelFlags &userLevel, bool online); static int getCardRight(const QStyleOptionViewItem &option, const QRect &rect); - static void drawBackground(QPainter *painter, - const QRectF &cardRect, - const QColor &accentColor, - bool selected, - const Style &style, - bool hasRole); + static void drawBackground(QPainter *painter, const QRectF &cardRect, const QColor &accentColor, bool selected); static QRect getAvatarRect(const QRect &rect); static void drawAvatar(QPainter *painter, const QRect &avatarRect, @@ -93,9 +64,8 @@ private: const UserLevelFlags &userLevel, const ServerInfo_User &userInfo, const QString &privLevel, - const QMap *avatarCache, - const Style &style); - static void drawStatusRing(QPainter *painter, const QRect &avatarRect, bool online, const Style &style); + const QMap *avatarCache); + static void drawStatusRing(QPainter *painter, const QRect &avatarRect, bool online); static void drawUserName(QPainter *painter, const QStyleOptionViewItem &option, const QRect &rect, @@ -103,7 +73,7 @@ private: int textX, const QString &userName, bool online, - const Style &style); + bool selected); static void drawCountryFlag(QPainter *painter, const QRect &rect, int textX, const ServerInfo_User &userInfo); static QList buildBadges(const UserLevelFlags &userLevel, const QString &privLevel); static void drawBadges(QPainter *painter, @@ -111,8 +81,7 @@ private: const QRect &rect, int cardRight, const QList &badges, - bool online, - const Style &style); + bool online); }; -#endif // COCKATRICE_USER_LIST_PAINTER_H +#endif // COCKATRICE_USER_LIST_PAINTER_H \ No newline at end of file diff --git a/cockatrice/src/interface/widgets/server/user/user_list_panel_widget.cpp b/cockatrice/src/interface/widgets/server/user/user_list_panel_widget.cpp deleted file mode 100644 index 937058024..000000000 --- a/cockatrice/src/interface/widgets/server/user/user_list_panel_widget.cpp +++ /dev/null @@ -1,88 +0,0 @@ -#include "user_list_panel_widget.h" - -#include "../../../../client/settings/cache_settings.h" -#include "user_list_manager.h" -#include "user_list_widget.h" - -#include -#include -#include -#include - -namespace -{ -// The persisted section keys are the serialization contract with the user's -// settings file, so the values must stay stable across versions. -QString sectionKey(UserListWidget::Section section) -{ - switch (section) { - case UserListWidget::Section::Buddy: - return QStringLiteral("buddy"); - case UserListWidget::Section::Online: - return QStringLiteral("online"); - case UserListWidget::Section::Ignore: - return QStringLiteral("ignore"); - } - return {}; -} -} // namespace - -UserListPanelWidget::UserListPanelWidget(TabSupervisor *_tabSupervisor, AbstractClient *_client, QWidget *parent) - : QWidget(parent) -{ - auto *mainLayout = new QVBoxLayout(this); - mainLayout->setContentsMargins(0, 0, 0, 0); - mainLayout->setSpacing(2); - - searchBar = new QLineEdit(this); - searchBar->setClearButtonEnabled(true); - mainLayout->addWidget(searchBar); - - userList = new UserListWidget(_tabSupervisor, _client, UserListWidget::RoomList, this); - userList->setSectioned( - {UserListWidget::Section::Buddy, UserListWidget::Section::Online, UserListWidget::Section::Ignore}); - mainLayout->addWidget(userList, 1); - - connect(searchBar, &QLineEdit::textChanged, userList, &UserListWidget::setFilterText); - - connect(userList, &UserListWidget::sectionExpanded, this, &UserListPanelWidget::persistExpandedSections); - connect(userList, &UserListWidget::openMessageDialog, this, &UserListPanelWidget::openMessageDialog); - - // Restore the persisted expansion state, then apply it to the tree. - const QStringList expandedSections = SettingsCache::instance().userInterface().getUserListExpandedSections(); - for (const UserListWidget::Section section : userList->getSectionIds()) { - userList->setSectionExpanded(section, expandedSections.contains(sectionKey(section))); - } - - retranslateUi(); -} - -void UserListPanelWidget::bind(UserListManager *manager) -{ - userList->bind(manager); -} - -void UserListPanelWidget::persistExpandedSections(UserListWidget::Section section, bool expanded) -{ - const QString key = sectionKey(section); - QStringList expandedSections = SettingsCache::instance().userInterface().getUserListExpandedSections(); - if (expanded) { - if (!expandedSections.contains(key)) { - expandedSections.append(key); - } - } else { - expandedSections.removeAll(key); - } - SettingsCache::instance().userInterface().setUserListExpandedSections(expandedSections); -} - -void UserListPanelWidget::retranslateUi() -{ - searchBar->setPlaceholderText(tr("Search users...")); - userList->retranslateUi(); -} - -UserListWidget *UserListPanelWidget::getUserList() const -{ - return userList; -} diff --git a/cockatrice/src/interface/widgets/server/user/user_list_panel_widget.h b/cockatrice/src/interface/widgets/server/user/user_list_panel_widget.h deleted file mode 100644 index 7ae14dfcf..000000000 --- a/cockatrice/src/interface/widgets/server/user/user_list_panel_widget.h +++ /dev/null @@ -1,43 +0,0 @@ -/** - * @file user_list_panel_widget.h - * @ingroup Lobby - */ - -#ifndef COCKATRICE_USER_LIST_PANEL_WIDGET_H -#define COCKATRICE_USER_LIST_PANEL_WIDGET_H - -#include "user_list_widget.h" - -#include - -class AbstractClient; -class QLineEdit; -class TabSupervisor; -class UserListManager; - -/** - * A unified user list: a search bar above a single tree whose section headers - * (buddy, online, ignored) are inline dividers. The tree owns the scrolling. - */ -class UserListPanelWidget : public QWidget -{ - Q_OBJECT - -public: - explicit UserListPanelWidget(TabSupervisor *tabSupervisor, AbstractClient *client, QWidget *parent = nullptr); - void bind(UserListManager *manager); - void retranslateUi(); - - [[nodiscard]] UserListWidget *getUserList() const; - -signals: - void openMessageDialog(const QString &userName, bool focus); - -private: - void persistExpandedSections(UserListWidget::Section section, bool expanded); - - QLineEdit *searchBar = nullptr; - UserListWidget *userList = nullptr; -}; - -#endif // COCKATRICE_USER_LIST_PANEL_WIDGET_H diff --git a/cockatrice/src/interface/widgets/server/user/user_list_widget.cpp b/cockatrice/src/interface/widgets/server/user/user_list_widget.cpp index 7a82b0c76..3ad357dd7 100644 --- a/cockatrice/src/interface/widgets/server/user/user_list_widget.cpp +++ b/cockatrice/src/interface/widgets/server/user/user_list_widget.cpp @@ -3,7 +3,6 @@ #include "../../../../client/settings/cache_settings.h" #include "../../../card_picture_loader/card_picture_loader.h" #include "../../interface/pixel_map_generator.h" -#include "../../interface/theme_manager.h" #include "../../interface/widgets/tabs/tab_account.h" #include "../../interface/widgets/tabs/tab_supervisor.h" #include "../game_selector.h" @@ -12,17 +11,11 @@ #include #include -#include -#include -#include -#include #include #include #include -#include #include #include -#include #include #include #include @@ -31,7 +24,6 @@ #include #include #include -#include #include #include #include @@ -332,15 +324,11 @@ constexpr int Online = Qt::UserRole + 1; constexpr int UserInfo = Qt::UserRole + 2; } // namespace UserListRoles -// Divider items (section headers) in sectioned mode are distinguished from user -// rows (UserListTWI, which uses QTreeWidgetItem::Type) by this item type. -constexpr int SectionItemType = QTreeWidgetItem::UserType + 1; - -UserListItemDelegate::UserListItemDelegate(QTreeWidget *tree, +UserListItemDelegate::UserListItemDelegate(QObject *const parent, const QMap *avatarCache, const QMap *cardArtCache, const QMap *cardArtParamsMap) - : QStyledItemDelegate(tree), tree(tree), avatarCache(avatarCache), cardArtCache(cardArtCache), + : QStyledItemDelegate(parent), avatarCache(avatarCache), cardArtCache(cardArtCache), cardArtParamsMap(cardArtParamsMap) { } @@ -365,129 +353,25 @@ QSize UserListItemDelegate::sizeHint(const QStyleOptionViewItem &option, const Q if (!SettingsCache::instance().appearance().getStyleUserList()) { return QStyledItemDelegate::sizeHint(option, index); } - if (!index.data(UserListRoles::UserInfo).isValid()) { - return QStyledItemDelegate::sizeHint(option, index); // section dividers stay compact - } return UserListPainter::sizeHint(); } void UserListItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const { - const bool styled = SettingsCache::instance().appearance().getStyleUserList(); - // UserInfo/Online are stored on column 0 only. The name lives on column 2, - // so resolve the user data against column 0 no matter which cell is painted. - const QModelIndex userIndex = index.siblingAtColumn(0).isValid() ? index.siblingAtColumn(0) : index; - const QVariant var = userIndex.data(UserListRoles::UserInfo); - - if (styled && var.isValid()) { - // Styled card rows: the tree's cached palette can be stale after a - // runtime theme change, so paint from the application palette (always - // current) instead of option.palette (frozen at the last style switch). - QStyleOptionViewItem opt = option; - opt.palette = qApp->palette(); - - UserListPainter::paint(painter, opt, index, var.value(), avatarCache, cardArtCache, - cardArtParamsMap, themeManager && themeManager->isDarkModeActive()); + if (!SettingsCache::instance().appearance().getStyleUserList()) { + QStyledItemDelegate::paint(painter, option, index); return; } - // Unstyled rows and section dividers are painted manually so every color - // is derived from the current application palette at paint time. The widget - // palette, the view's alternation, and the stored brushes (one per item) - // all go stale after a runtime theme change. - const QPalette appPal = qApp->palette(); - const bool selected = option.state & QStyle::State_Selected; - const bool hovered = option.state & QStyle::State_MouseOver; + const QVariant var = index.data(UserListRoles::UserInfo); - // Row background: selection, hover, zebra striping, plain base. - QColor bg = appPal.color(QPalette::Base); - if (selected) { - bg = appPal.color(QPalette::Highlight); - } else if (hovered) { - bg = UserListPainter::blend(appPal.color(QPalette::Base), appPal.color(QPalette::Highlight), 0.12); - } else if (var.isValid()) { - // Zebra alternation per section. The dividers are top level rows too, - // so the view's own alternation would drift across sections. Count the - // visible user rows back to the previous divider within the same parent - // (sectioned mode stores users as children of the dividers, flat mode - // as top level rows), so the stripe restarts at every divider and at - // the tree top. Hidden filter matches are skipped the same way the view - // skips them, so adjacent visible rows always alternate. - int usersSinceDivider = 0; - const QModelIndex parent = userIndex.parent(); - for (int r = userIndex.row() - 1; r >= 0; --r) { - if (tree->isRowHidden(r, parent)) { - continue; - } - const QModelIndex above = userIndex.model()->index(r, 0, parent); - if (above.isValid() && above.data(UserListRoles::UserInfo).isValid()) { - ++usersSinceDivider; - } else { - break; - } - } - if (usersSinceDivider % 2 == 1) { - bg = appPal.color(QPalette::AlternateBase); - } - } - // Paint the row background. In the column 0 pass the fill spans the full - // viewport width so stripes, hover and selection cover the whole row (the - // name column is content sized in unstyled mode). Later column passes fill - // only their own cell, which is the same color and cannot cover the icons. - QRect bgRect = option.rect; - if (index.column() == 0) { - bgRect = QRect(0, option.rect.top(), tree->viewport()->width(), option.rect.height()); - } - painter->fillRect(bgRect, bg); - - // Text color. - QColor fg = appPal.color(QPalette::Text); - if (selected) { - fg = appPal.color(QPalette::HighlightedText); - } else if (!var.isValid()) { - // Section divider: muted application text color. - fg = appPal.color(QPalette::WindowText); - fg.setAlpha(170); - } else if (index.column() == 2) { - // Name column: online/offline color recomputed at paint time instead - // of trusting the brush stored at login time. - QTreeWidgetItem *item = tree->itemFromIndex(index); - const bool online = item && item->data(0, UserListRoles::Online).toBool(); - if (online) { - fg = appPal.color(QPalette::WindowText); - } else { - fg = (themeManager && themeManager->isDarkModeActive()) - ? QColor(Qt::gray) - : UserListPainter::blend(appPal.color(QPalette::Text), appPal.color(QPalette::Mid), 0.5); - } + if (!var.isValid()) { + QStyledItemDelegate::paint(painter, option, index); + return; } - // Icon (level badge in column 0, country flag in column 1). - QRect textRect = option.rect; - const QIcon icon = index.data(Qt::DecorationRole).value(); - if (!icon.isNull()) { - const QSize iconSize = icon.actualSize(QSize(18, 18)); - const QRect iconRect(option.rect.left() + 2, option.rect.center().y() - iconSize.height() / 2, iconSize.width(), - iconSize.height()); - icon.paint(painter, iconRect); - textRect.setLeft(iconRect.right() + 4); - } - - // Text (name column / divider title), elided to the row width. - painter->save(); - painter->setPen(fg); - const QFont itemFont = index.data(Qt::FontRole).value(); - painter->setFont(itemFont.isCopyOf(QFont()) ? option.font : itemFont); - const QString text = index.data(Qt::DisplayRole).toString(); - const QString elided = painter->fontMetrics().elidedText(text, Qt::ElideRight, textRect.width() - 4); - painter->drawText(textRect.adjusted(2, 0, -2, 0), Qt::AlignLeft | Qt::AlignVCenter, elided); - painter->restore(); - - // Focus indicator for the current item. - if (option.state & QStyle::State_HasFocus) { - painter->setPen(appPal.color(QPalette::Highlight)); - painter->drawRect(option.rect.adjusted(0, 0, -1, -1)); - } + UserListPainter::paint(painter, option, index, var.value(), avatarCache, cardArtCache, + cardArtParamsMap); } UserListTWI::UserListTWI(const ServerInfo_User &_userInfo) : QTreeWidgetItem(Type) @@ -511,10 +395,8 @@ void UserListTWI::setUserInfo(const ServerInfo_User &_userInfo) void UserListTWI::setOnline(bool online) { - // Only the online state is stored here: the delegate derives the - // online/offline text color at paint time from the current application - // palette, so no brush is cached (it would go stale on theme change). setData(0, UserListRoles::Online, online); + setData(2, Qt::ForegroundRole, online ? qApp->palette().brush(QPalette::WindowText) : QBrush(Qt::gray)); } /** @@ -583,6 +465,9 @@ UserListWidget::UserListWidget(TabSupervisor *_tabSupervisor, avatarProvider = new UserAvatarProvider(client, this); cardArtProvider = new UserCardArtProvider(this); + itemDelegate = + new UserListItemDelegate(this, &avatarProvider->cache(), &cardArtProvider->cache(), &cardArtParamsMap); + userContextMenu = new UserContextMenu(tabSupervisor, this); connect(userContextMenu, &UserContextMenu::openMessageDialog, this, &UserListWidget::openMessageDialog); @@ -593,8 +478,6 @@ UserListWidget::UserListWidget(TabSupervisor *_tabSupervisor, userTree->setHeaderHidden(true); userTree->setRootIsDecorated(false); userTree->setIconSize(QSize(20, 18)); - itemDelegate = - new UserListItemDelegate(userTree, &avatarProvider->cache(), &cardArtProvider->cache(), &cardArtParamsMap); userTree->setItemDelegate(itemDelegate); userTree->setAlternatingRowColors(true); userTree->hideColumn(1); @@ -605,40 +488,28 @@ UserListWidget::UserListWidget(TabSupervisor *_tabSupervisor, userTree->header()->setStretchLastSection(true); // ── Hover popup ─────────────────────────────────────────────────────────── - userInfoPopup = new UserInfoPopup(tabSupervisor, tabSupervisor->getClient(), &avatarProvider->cache(), - &cardArtProvider->cache(), &cardArtParamsMap, - window()); // parented to main window so it floats above siblings + m_userInfoPopup = new UserInfoPopup(tabSupervisor, tabSupervisor->getClient(), &avatarProvider->cache(), + &cardArtProvider->cache(), &cardArtParamsMap, + window()); // parented to main window so it floats above siblings - userInfoPopup->hide(); - userInfoPopup->setWindowOpacity(0.0); - userInfoPopup->installEventFilter(this); + m_userInfoPopup->hide(); + m_userInfoPopup->setWindowOpacity(0.0); + m_userInfoPopup->installEventFilter(this); - showPopupTimer = new QTimer(this); - showPopupTimer->setSingleShot(true); - showPopupTimer->setInterval(280); - connect(showPopupTimer, &QTimer::timeout, this, [this] { - if (hoveredUser.isEmpty()) { - return; - } - // Resolve the row under the cursor again. In sectioned mode a user can - // own several rows (online + buddy), so the popup must anchor to the - // exact hovered row instead of a lookup by name. - const QPoint viewportPos = userTree->viewport()->mapFromGlobal(QCursor::pos()); - QTreeWidgetItem *item = userTree->itemAt(viewportPos); - if (item && item->type() == QTreeWidgetItem::Type && - QString::fromStdString(static_cast(item)->getUserInfo().name()) == hoveredUser) { - showPopupForUser(static_cast(item)); + m_showPopupTimer = new QTimer(this); + m_showPopupTimer->setSingleShot(true); + m_showPopupTimer->setInterval(280); + connect(m_showPopupTimer, &QTimer::timeout, this, [this] { + if (!m_hoveredUser.isEmpty()) { + showPopupForUser(m_hoveredUser); } }); - hidePopupTimer = new QTimer(this); - hidePopupTimer->setSingleShot(true); - hidePopupTimer->setInterval(160); - connect(hidePopupTimer, &QTimer::timeout, this, [this] { - // The hover ends when the cursor leaves the user row. Empty list - // space, a section divider and anything outside the tree all close - // the popup, while the popup itself keeps it alive. - if (!popupPinned && !userInfoPopup->underMouse() && (hoveredUser.isEmpty() || !userTree->underMouse())) { + m_hidePopupTimer = new QTimer(this); + m_hidePopupTimer->setSingleShot(true); + m_hidePopupTimer->setInterval(160); + connect(m_hidePopupTimer, &QTimer::timeout, this, [this] { + if (!m_popupPinned && !m_userInfoPopup->underMouse() && !userTree->underMouse()) { hidePopup(); } }); @@ -648,73 +519,36 @@ UserListWidget::UserListWidget(TabSupervisor *_tabSupervisor, userTree->setMouseTracking(true); userTree->viewport()->setMouseTracking(true); userTree->viewport()->installEventFilter(this); - userTree->installEventFilter(this); // keyboard handling for section dividers - - // Clicking anywhere outside the list clears its selection and closes the - // popup. The filter watches all widgets because the press can land on any - // part of the window, on another list or on the popup itself. - qApp->installEventFilter(this); // Pin on item click connect(userTree, &QTreeWidget::itemClicked, this, [this](QTreeWidgetItem *item, int) { - // Clicking a section divider toggles it - if (sectioned && item->type() == SectionItemType) { - setExpandedProgrammatically(item, !item->isExpanded()); - handleSectionExpansion(item, item->isExpanded()); - return; - } if (!SettingsCache::instance().appearance().getStyleUserList()) { return; } - if (item->type() != QTreeWidgetItem::Type) { - return; // divider rows have no user popup - } - popupPinned = false; // reset so showPopupForUser can update - showPopupForUser(static_cast(item)); - popupPinned = true; // pin after showing + const QString name = static_cast(item)->getUserInfo().name().c_str(); + m_popupPinned = false; // reset so showPopupForUser can update + showPopupForUser(name); + m_popupPinned = true; // pin after showing }); connect(userTree->selectionModel(), &QItemSelectionModel::selectionChanged, this, [this](const QItemSelection &sel, const QItemSelection &) { - if (sel.isEmpty() && popupPinned) { - popupPinned = false; + // if (m_rebuildingTree) return; + if (sel.isEmpty() && m_popupPinned) { + m_popupPinned = false; hidePopup(); } }); - // Keyboard selection: show the popup for the current row and hide it when - // the focus moves to a section divider or leaves the list entirely. The - // popup therefore follows arrow key navigation exactly like mouse hover. - // When it was pinned by a click it stays open and follows the selection. - connect(userTree, &QTreeWidget::currentItemChanged, this, [this](QTreeWidgetItem *current, QTreeWidgetItem *) { - if (!isVisible() || !SettingsCache::instance().appearance().getStyleUserList()) { - return; - } - if (current && current->type() == QTreeWidgetItem::Type) { - showPopupForUser(static_cast(current)); - } else { - popupPinned = false; - hidePopup(); - } - }); - - // Section dividers can be collapsed/expanded by the user. Surface those - // changes only from real user interaction. Programmatic expansion is - // applied through setSectionExpanded() / setExpandedProgrammatically(). - connect(userTree, &QTreeWidget::itemExpanded, this, - [this](QTreeWidgetItem *item) { handleSectionExpansion(item, true); }); - connect(userTree, &QTreeWidget::itemCollapsed, this, - [this](QTreeWidgetItem *item) { handleSectionExpansion(item, false); }); - // Hide popup when list scrolls (reference row has moved) connect(userTree->verticalScrollBar(), &QScrollBar::valueChanged, this, [this] { - showPopupTimer->stop(); + m_showPopupTimer->stop(); hidePopup(true); requestAvatarsForVisibleItems(); }); // Forward join requests from popup upward - connect(userInfoPopup, &UserInfoPopup::joinGameRequested, this, &UserListWidget::joinGameRequested); + connect(m_userInfoPopup, &UserInfoPopup::joinGameRequested, this, &UserListWidget::joinGameRequested); connect(avatarProvider, &UserAvatarProvider::avatarUpdated, this, &UserListWidget::refreshVisibleUserHeader); connect(cardArtProvider, &UserCardArtProvider::cardArtUpdated, this, &UserListWidget::refreshVisibleUserHeader); @@ -723,17 +557,6 @@ UserListWidget::UserListWidget(TabSupervisor *_tabSupervisor, &UserListWidget::applyDisplayMode); applyDisplayMode(); - // The tree's cached palette can go stale after a runtime theme change (Qt - // freezes widget palettes when the style is switched), so rows and dividers - // derive all colors from the application palette at paint time. The theme - // change only needs a repaint to pick them up. - if (themeManager) { - connect(themeManager, &ThemeManager::themeChanged, this, [this] { - userTree->viewport()->update(); - userTree->update(); - }); - } - QVBoxLayout *vbox = new QVBoxLayout; vbox->addWidget(userTree); @@ -742,11 +565,6 @@ UserListWidget::UserListWidget(TabSupervisor *_tabSupervisor, retranslateUi(); } -UserListWidget::~UserListWidget() -{ - qApp->removeEventFilter(this); -} - void UserListWidget::bind(UserListManager *mgr) { manager = mgr; @@ -754,70 +572,50 @@ void UserListWidget::bind(UserListManager *mgr) // ── Full rebuild: disconnect / reconnect / bulk initial load ────────────── connect(manager, &UserListManager::listReset, this, &UserListWidget::rebuild); - if (!sectioned) { - // Online users list (AllUsersList / RoomList) - if (type == AllUsersList || type == RoomList) { - connect(manager, &UserListManager::userJoinedOnline, this, - [this](const ServerInfo_User &user) { processUserInfo(user, true); }); - connect(manager, &UserListManager::userLeftOnline, this, [this](const QString &name) { deleteUser(name); }); - } - - // Buddy list - if (type == BuddyList) { - connect(manager, &UserListManager::addedToBuddyList, this, [this](const ServerInfo_User &user) { - const QString name = QString::fromStdString(user.name()); - processUserInfo(user, manager->getOnlineUser(name) != nullptr); - }); - connect(manager, &UserListManager::removedFromBuddyList, this, - [this](const QString &name) { deleteUser(name); }); - // Track online presence changes for buddies already in the tree - connect(manager, &UserListManager::userJoinedOnline, this, [this](const ServerInfo_User &user) { - const QString name = QString::fromStdString(user.name()); - if (users.contains(name)) { - users[name]->setUserInfo(user); - setUserOnline(name, true); - } - }); - connect(manager, &UserListManager::userLeftOnline, this, [this](const QString &name) { - if (users.contains(name)) { - setUserOnline(name, false); - } - }); - } - - // Ignore list - if (type == IgnoreList) { - connect(manager, &UserListManager::addedToIgnoreList, this, [this](const ServerInfo_User &user) { - const QString name = QString::fromStdString(user.name()); - processUserInfo(user, manager->getOnlineUser(name) != nullptr); - }); - connect(manager, &UserListManager::removedFromIgnoreList, this, - [this](const QString &name) { deleteUser(name); }); - } - } else { - // Sectioned mode: one tree, every source feeds its own section. - // Sections are pure membership views: the "Online" section holds every - // currently online user, the "Buddy"/"Ignore" sections hold those - // lists. A user can therefore appear in several sections at once (an - // online buddy gets one row in each). + // ── Online users list (AllUsersList / RoomList) ─────────────────────────── + if (type == AllUsersList || type == RoomList) { connect(manager, &UserListManager::userJoinedOnline, this, - [this](const ServerInfo_User &user) { handleOnlineChange(user); }); - connect(manager, &UserListManager::userLeftOnline, this, - [this](const QString &name) { handleOnlineChangeLeft(name); }); - connect(manager, &UserListManager::addedToBuddyList, this, - [this](const ServerInfo_User &user) { handleListAdd(Section::Buddy, user); }); + [this](const ServerInfo_User &user) { processUserInfo(user, true); }); + connect(manager, &UserListManager::userLeftOnline, this, [this](const QString &name) { deleteUser(name); }); + } + + // ── Buddy list ──────────────────────────────────────────────────────────── + if (type == BuddyList) { + connect(manager, &UserListManager::addedToBuddyList, this, [this](const ServerInfo_User &user) { + const QString name = QString::fromStdString(user.name()); + processUserInfo(user, manager->getOnlineUser(name) != nullptr); + }); connect(manager, &UserListManager::removedFromBuddyList, this, - [this](const QString &name) { handleListRemove(Section::Buddy, name); }); - connect(manager, &UserListManager::addedToIgnoreList, this, - [this](const ServerInfo_User &user) { handleListAdd(Section::Ignore, user); }); + [this](const QString &name) { deleteUser(name); }); + // Track online presence changes for buddies already in the tree + connect(manager, &UserListManager::userJoinedOnline, this, [this](const ServerInfo_User &user) { + const QString name = QString::fromStdString(user.name()); + if (users.contains(name)) { + users[name]->setUserInfo(user); + setUserOnline(name, true); + } + }); + connect(manager, &UserListManager::userLeftOnline, this, [this](const QString &name) { + if (users.contains(name)) { + setUserOnline(name, false); + } + }); + } + + // ── Ignore list ─────────────────────────────────────────────────────────── + if (type == IgnoreList) { + connect(manager, &UserListManager::addedToIgnoreList, this, [this](const ServerInfo_User &user) { + const QString name = QString::fromStdString(user.name()); + processUserInfo(user, manager->getOnlineUser(name) != nullptr); + }); connect(manager, &UserListManager::removedFromIgnoreList, this, - [this](const QString &name) { handleListRemove(Section::Ignore, name); }); + [this](const QString &name) { deleteUser(name); }); } // ── Popup button refresh ────────────────────────────────────────────────── // Any buddy/ignore mutation while the popup is open refreshes its buttons auto refreshIfPopupOpen = [this](const QString &name) { - if (userInfoPopup && userInfoPopup->isVisible() && userInfoPopup->getCurrentUser() == name) { + if (m_userInfoPopup && m_userInfoPopup->isVisible() && m_userInfoPopup->currentUser() == name) { refreshPopupButtons(name); } }; @@ -838,8 +636,8 @@ void UserListWidget::bind(UserListManager *mgr) void UserListWidget::refreshVisibleUserHeader(const QString &name) { userTree->viewport()->update(); - if (userInfoPopup->isVisible() && userInfoPopup->getCurrentUser() == name) { - userInfoPopup->refreshHeader(); + if (m_userInfoPopup->isVisible() && m_userInfoPopup->currentUser() == name) { + m_userInfoPopup->refreshHeader(); } } @@ -855,15 +653,15 @@ void UserListWidget::refreshPopupButtons(const QString &userName) const bool isBuddy = proxy->isUserBuddy(userName); const bool isIgn = proxy->isUserIgnored(userName); - userInfoPopup->updateActionButtons(item->getUserInfo(), online, isBuddy, isIgn); - positionPopup(item); // height may have changed, reposition + m_userInfoPopup->updateActionButtons(item->getUserInfo(), online, isBuddy, isIgn); + positionPopup(userName); // height may have changed — reposition } void UserListWidget::hideEvent(QHideEvent *e) { QGroupBox::hideEvent(e); - showPopupTimer->stop(); - hidePopupTimer->stop(); + m_showPopupTimer->stop(); + m_hidePopupTimer->stop(); hidePopup(true); } @@ -894,106 +692,72 @@ void UserListWidget::applyDisplayMode() void UserListWidget::connectPopupSignals() { - connect(userInfoPopup, &UserInfoPopup::closeRequested, this, [this] { - popupPinned = false; + connect(m_userInfoPopup, &UserInfoPopup::closeRequested, this, [this] { + m_popupPinned = false; hidePopup(true); }); - connect(userInfoPopup, &UserInfoPopup::mouseEnteredPopup, hidePopupTimer, &QTimer::stop); - connect(userInfoPopup, &UserInfoPopup::mouseLeftPopup, this, [this] { - if (!popupPinned) { - hidePopupTimer->start(); + connect(m_userInfoPopup, &UserInfoPopup::mouseEnteredPopup, m_hidePopupTimer, &QTimer::stop); + connect(m_userInfoPopup, &UserInfoPopup::mouseLeftPopup, this, [this] { + if (!m_popupPinned) { + m_hidePopupTimer->start(); } }); // Wire all action signals to UserContextMenu::exec*() - connect(userInfoPopup, &UserInfoPopup::chatRequested, userContextMenu, &UserContextMenu::execChat); - connect(userInfoPopup, &UserInfoPopup::detailsRequested, userContextMenu, &UserContextMenu::execDetails); - connect(userInfoPopup, &UserInfoPopup::showGamesRequested, userContextMenu, &UserContextMenu::execShowGames); - connect(userInfoPopup, &UserInfoPopup::addBuddyRequested, userContextMenu, &UserContextMenu::execAddToBuddy); - connect(userInfoPopup, &UserInfoPopup::removeBuddyRequested, userContextMenu, + connect(m_userInfoPopup, &UserInfoPopup::chatRequested, userContextMenu, &UserContextMenu::execChat); + connect(m_userInfoPopup, &UserInfoPopup::detailsRequested, userContextMenu, &UserContextMenu::execDetails); + connect(m_userInfoPopup, &UserInfoPopup::showGamesRequested, userContextMenu, &UserContextMenu::execShowGames); + connect(m_userInfoPopup, &UserInfoPopup::addBuddyRequested, userContextMenu, &UserContextMenu::execAddToBuddy); + connect(m_userInfoPopup, &UserInfoPopup::removeBuddyRequested, userContextMenu, &UserContextMenu::execRemoveFromBuddy); - connect(userInfoPopup, &UserInfoPopup::addIgnoreRequested, userContextMenu, &UserContextMenu::execAddToIgnore); - connect(userInfoPopup, &UserInfoPopup::removeIgnoreRequested, userContextMenu, + connect(m_userInfoPopup, &UserInfoPopup::addIgnoreRequested, userContextMenu, &UserContextMenu::execAddToIgnore); + connect(m_userInfoPopup, &UserInfoPopup::removeIgnoreRequested, userContextMenu, &UserContextMenu::execRemoveFromIgnore); - connect(userInfoPopup, &UserInfoPopup::banRequested, userContextMenu, &UserContextMenu::execBan); - connect(userInfoPopup, &UserInfoPopup::warnRequested, userContextMenu, &UserContextMenu::execWarn); - connect(userInfoPopup, &UserInfoPopup::banHistoryRequested, userContextMenu, &UserContextMenu::execBanHistory); - connect(userInfoPopup, &UserInfoPopup::warnHistoryRequested, userContextMenu, &UserContextMenu::execWarnHistory); - connect(userInfoPopup, &UserInfoPopup::adminNotesRequested, userContextMenu, &UserContextMenu::execAdminNotes); - connect(userInfoPopup, &UserInfoPopup::promoteToModRequested, this, + connect(m_userInfoPopup, &UserInfoPopup::banRequested, userContextMenu, &UserContextMenu::execBan); + connect(m_userInfoPopup, &UserInfoPopup::warnRequested, userContextMenu, &UserContextMenu::execWarn); + connect(m_userInfoPopup, &UserInfoPopup::banHistoryRequested, userContextMenu, &UserContextMenu::execBanHistory); + connect(m_userInfoPopup, &UserInfoPopup::warnHistoryRequested, userContextMenu, &UserContextMenu::execWarnHistory); + connect(m_userInfoPopup, &UserInfoPopup::adminNotesRequested, userContextMenu, &UserContextMenu::execAdminNotes); + connect(m_userInfoPopup, &UserInfoPopup::promoteToModRequested, this, [this](const QString &n) { userContextMenu->execAdjustMod(n, true); }); - connect(userInfoPopup, &UserInfoPopup::demoteFromModRequested, this, + connect(m_userInfoPopup, &UserInfoPopup::demoteFromModRequested, this, [this](const QString &n) { userContextMenu->execAdjustMod(n, false); }); - connect(userInfoPopup, &UserInfoPopup::promoteToJudgeRequested, this, + connect(m_userInfoPopup, &UserInfoPopup::promoteToJudgeRequested, this, [this](const QString &n) { userContextMenu->execAdjustJudge(n, true); }); - connect(userInfoPopup, &UserInfoPopup::demoteFromJudgeRequested, this, + connect(m_userInfoPopup, &UserInfoPopup::demoteFromJudgeRequested, this, [this](const QString &n) { userContextMenu->execAdjustJudge(n, false); }); } bool UserListWidget::eventFilter(QObject *obj, QEvent *event) { - // A press outside the tree, the popup and any open menu deselects the - // list and closes the popup. The filter is installed application-wide, so - // the target can be any widget in the window or another list. - if (event->type() == QEvent::MouseButtonPress) { - auto *pressTarget = qobject_cast(obj); - if (pressTarget && !isPressInsideListUi(pressTarget)) { - clearSelectionAndClosePopup(); - } - } - - // Keyboard navigation of the section dividers. - // The dividers are selectable so arrow keys land on them. When one is the - // current item, Enter/Space toggle it (like a button) and Left/Right follow - // the tree convention (Left collapses, Right expands). - if (obj == userTree && event->type() == QEvent::KeyPress) { - auto *keyEvent = static_cast(event); - QTreeWidgetItem *current = userTree->currentItem(); - if (sectioned && current && current->type() == SectionItemType) { - const bool toggle = keyEvent->key() == Qt::Key_Return || keyEvent->key() == Qt::Key_Enter || - keyEvent->key() == Qt::Key_Space; - const bool collapse = keyEvent->key() == Qt::Key_Left && current->isExpanded(); - const bool expand = keyEvent->key() == Qt::Key_Right && !current->isExpanded(); - if (toggle || collapse || expand) { - const bool expanded = toggle ? !current->isExpanded() : expand; - setExpandedProgrammatically(current, expanded); - handleSectionExpansion(current, expanded); - return true; - } - } - } - if (obj == userTree->viewport()) { if (event->type() == QEvent::MouseMove) { if (!SettingsCache::instance().appearance().getStyleUserList()) { return QGroupBox::eventFilter(obj, event); } auto *me = static_cast(event); - QTreeWidgetItem *hoveredItem = userTree->itemAt(me->pos()); - QString hovName; - if (hoveredItem && hoveredItem->type() == QTreeWidgetItem::Type) { - hovName = QString::fromStdString(static_cast(hoveredItem)->getUserInfo().name()); - } + auto *twi = static_cast(userTree->itemAt(me->pos())); + const QString hovName = twi ? QString::fromStdString(twi->getUserInfo().name()) : QString{}; - if (hovName != hoveredUser) { - hoveredUser = hovName; + if (hovName != m_hoveredUser) { + m_hoveredUser = hovName; if (!hovName.isEmpty()) { - hidePopupTimer->stop(); - if (!popupPinned) { - showPopupTimer->start(); + m_hidePopupTimer->stop(); + if (!m_popupPinned) { + m_showPopupTimer->start(); } } else { - showPopupTimer->stop(); - if (!popupPinned) { - hidePopupTimer->start(); + m_showPopupTimer->stop(); + if (!m_popupPinned) { + m_hidePopupTimer->start(); } } } } else if (event->type() == QEvent::Leave) { - hoveredUser.clear(); - showPopupTimer->stop(); - if (!popupPinned) { - hidePopupTimer->start(); + m_hoveredUser.clear(); + m_showPopupTimer->stop(); + if (!m_popupPinned) { + m_hidePopupTimer->start(); } } } @@ -1001,13 +765,13 @@ bool UserListWidget::eventFilter(QObject *obj, QEvent *event) return QGroupBox::eventFilter(obj, event); } -void UserListWidget::showPopupForUser(UserListTWI *item) +void UserListWidget::showPopupForUser(const QString &userName) { + UserListTWI *item = users.value(userName); if (!item) { return; } - const QString userName = QString::fromStdString(item->getUserInfo().name()); avatarProvider->requestAvatar(userName); // ensure the hovered user's avatar is fetched promptly const ServerInfo_User &info = item->getUserInfo(); @@ -1015,52 +779,29 @@ void UserListWidget::showPopupForUser(UserListTWI *item) const bool isBuddy = userContextMenu->getUserListProxy()->isUserBuddy(userName); const bool isIgn = userContextMenu->getUserListProxy()->isUserIgnored(userName); - // The popup is already showing this user (e.g. arrow key navigation between - // the online/buddy rows of the same user): just reposition it. - if (userInfoPopup->isVisible() && userInfoPopup->getCurrentUser() == userName) { - positionPopup(item); - return; - } + m_userInfoPopup->showForUser(userName, info, online, isBuddy, isIgn); - // Cancel any pending show/hide so a hover timer that armed before a row - // selected via the keyboard cannot override it, and a pending hide cannot - // kill the popup right after it appears. - showPopupTimer->stop(); - hidePopupTimer->stop(); + // Realize the native window at opacity 0 before positioning so that: + // 1) move() applies to an existing native handle (not overridden by Qt's + // default centering logic on first show) + // 2) adjustSize() inside positionPopup() can measure the final laid-out + // geometry correctly + m_userInfoPopup->setWindowOpacity(0.0); + m_userInfoPopup->show(); + m_userInfoPopup->raise(); - userInfoPopup->showForUser(userName, info, online, isBuddy, isIgn); + positionPopup(userName); // geometry is now accurate; move() sticks - const bool wasVisible = userInfoPopup->isVisible(); - if (!wasVisible) { - // Realize the native window at opacity 0 before positioning so that: - // 1) move() applies to an existing native handle (not overridden by - // Qt's default centering logic on first show) - // 2) adjustSize() inside positionPopup() can measure the final - // laid out geometry correctly - userInfoPopup->setWindowOpacity(0.0); - } - userInfoPopup->show(); - userInfoPopup->raise(); - - positionPopup(item); // geometry is accurate after show, so move() is not overridden - - if (wasVisible) { - // Content swap while already open (hover or arrow key navigation): - // keep the popup opaque instead of flashing through a fade on every - // step. - userInfoPopup->setWindowOpacity(1.0); - return; - } - - auto *fade = new QPropertyAnimation(userInfoPopup, "windowOpacity", userInfoPopup); + auto *fade = new QPropertyAnimation(m_userInfoPopup, "windowOpacity", m_userInfoPopup); fade->setDuration(120); fade->setStartValue(0.0); fade->setEndValue(1.0); fade->start(QAbstractAnimation::DeleteWhenStopped); } -void UserListWidget::positionPopup(UserListTWI *item) +void UserListWidget::positionPopup(const QString &userName) { + UserListTWI *item = users.value(userName); if (!item) { return; } @@ -1071,9 +812,9 @@ void UserListWidget::positionPopup(UserListTWI *item) const QPoint vpTL = vp->mapToGlobal(vp->rect().topLeft()); const QPoint vpTR = vp->mapToGlobal(vp->rect().topRight()); - userInfoPopup->adjustSize(); - const int popW = userInfoPopup->width(); - const int popH = userInfoPopup->height(); + m_userInfoPopup->adjustSize(); + const int popW = m_userInfoPopup->width(); + const int popH = m_userInfoPopup->height(); const int margin = 12; QScreen *activeScreen = QGuiApplication::screenAt(itemTL); @@ -1110,50 +851,31 @@ void UserListWidget::positionPopup(UserListTWI *item) } y = qBound(screen.top() + margin, y, screen.bottom() - popH - margin); - userInfoPopup->move(x, y); + m_userInfoPopup->move(x, y); } void UserListWidget::hidePopup(bool immediate) { - showPopupTimer->stop(); - hidePopupTimer->stop(); - if (!userInfoPopup->isVisible()) { + m_showPopupTimer->stop(); + m_hidePopupTimer->stop(); + if (!m_userInfoPopup->isVisible()) { return; } if (immediate) { - userInfoPopup->hide(); + m_userInfoPopup->hide(); return; } // Fade out - auto *fade = new QPropertyAnimation(userInfoPopup, "windowOpacity", userInfoPopup); + auto *fade = new QPropertyAnimation(m_userInfoPopup, "windowOpacity", m_userInfoPopup); fade->setDuration(100); - fade->setStartValue(userInfoPopup->windowOpacity()); + fade->setStartValue(m_userInfoPopup->windowOpacity()); fade->setEndValue(0.0); - connect(fade, &QPropertyAnimation::finished, userInfoPopup, &QWidget::hide); + connect(fade, &QPropertyAnimation::finished, m_userInfoPopup, &QWidget::hide); fade->start(QAbstractAnimation::DeleteWhenStopped); } -bool UserListWidget::isPressInsideListUi(const QWidget *widget) const -{ - const QWidget *w = widget; - while (w) { - if (w == userTree || w == userInfoPopup || qobject_cast(w)) { - return true; - } - w = w->parentWidget(); - } - return false; -} - -void UserListWidget::clearSelectionAndClosePopup() -{ - popupPinned = false; - hidePopup(true); - userTree->clearSelection(); -} - void UserListWidget::retranslateUi() { userContextMenu->retranslateUi(); @@ -1176,14 +898,13 @@ void UserListWidget::retranslateUi() void UserListWidget::beginBulkLoad() { - bulkLoading = true; + m_bulkLoading = true; } void UserListWidget::endBulkLoad() { - bulkLoading = false; + m_bulkLoading = false; sortItems(); - updateCount(); // divider counts were deferred during the bulk build requestAvatarsForVisibleItems(); userTree->viewport()->update(); } @@ -1199,23 +920,6 @@ bool UserListWidget::isItemNearViewport(const UserListTWI *item) const void UserListWidget::requestAvatarsForVisibleItems() { - if (sectioned) { - // Top level items are dividers, user rows hang below them. - for (const Section section : sectionIds) { - QTreeWidgetItem *divider = sectionItems.value(section); - if (!divider) { - continue; - } - for (int i = 0; i < divider->childCount(); ++i) { - auto *twi = static_cast(divider->child(i)); - if (isItemNearViewport(twi)) { - avatarProvider->requestAvatar(QString::fromStdString(twi->getUserInfo().name())); - } - } - } - return; - } - for (int i = 0; i < userTree->topLevelItemCount(); ++i) { auto *twi = static_cast(userTree->topLevelItem(i)); if (isItemNearViewport(twi)) { @@ -1228,40 +932,13 @@ void UserListWidget::rebuild() { userTree->clear(); users.clear(); - sectionUsers.clear(); cardArtParamsMap.clear(); onlineCount = 0; - if (sectioned) { - createSectionItems(); - } - if (!manager) { return; } - if (sectioned) { - // Every source feeds its own section. Users that belong to several - // sources (an online buddy) get one row per section because - // ensureSectionMembership() creates the row when it is missing. - beginBulkLoad(); - const auto &onlineUsers = manager->getAllUsersList(); - for (auto it = onlineUsers.cbegin(); it != onlineUsers.cend(); ++it) { - processUserInfo(Section::Online, it.value(), true); - } - const auto &buddyUsers = manager->getBuddyList(); - for (auto it = buddyUsers.cbegin(); it != buddyUsers.cend(); ++it) { - processUserInfo(Section::Buddy, it.value(), manager->getOnlineUser(it.key()) != nullptr); - } - const auto &ignoreUsers = manager->getIgnoreList(); - for (auto it = ignoreUsers.cbegin(); it != ignoreUsers.cend(); ++it) { - processUserInfo(Section::Ignore, it.value(), manager->getOnlineUser(it.key()) != nullptr); - } - endBulkLoad(); - applyFilter(); - return; - } - const QMap *source = nullptr; switch (type) { @@ -1282,13 +959,14 @@ void UserListWidget::rebuild() processUserInfo(it.value(), manager->getOnlineUser(it.key()) != nullptr); } endBulkLoad(); - applyFilter(); } -void UserListWidget::updateCardArtParams(const ServerInfo_User &user, const QString &userName) +void UserListWidget::processUserInfo(const ServerInfo_User &user, bool online) { + const QString userName = QString::fromStdString(user.name()); + // Always update params from the latest ServerInfo_User, whether the - // item is new or existing, so a live server push refreshes the rendering. + // item is new or existing, so a live server-push refreshes the rendering. if (user.has_card_art_params()) { const auto &cap = user.card_art_params(); CardArtParams params; @@ -1303,13 +981,6 @@ void UserListWidget::updateCardArtParams(const ServerInfo_User &user, const QStr } else { cardArtParamsMap.remove(userName); // clear stale params on removal } -} - -void UserListWidget::processUserInfo(const ServerInfo_User &user, bool online) -{ - const QString userName = QString::fromStdString(user.name()); - - updateCardArtParams(user, userName); UserListTWI *item = users.value(userName); if (item) { @@ -1322,92 +993,41 @@ void UserListWidget::processUserInfo(const ServerInfo_User &user, bool online) ++onlineCount; } updateCount(); - if (!bulkLoading && isItemNearViewport(item)) { + if (!m_bulkLoading && isItemNearViewport(item)) { avatarProvider->requestAvatar(userName); } } item->setOnline(online); - if (!bulkLoading) { + if (!m_bulkLoading) { sortItems(); - applyFilter(); - userTree->viewport()->update(); - } -} - -void UserListWidget::processUserInfo(Section section, const ServerInfo_User &user, bool online) -{ - ensureSectionMembership(section, user, online); - if (!bulkLoading) { - sortItems(); - applyFilter(); userTree->viewport()->update(); } } bool UserListWidget::deleteUser(const QString &userName) { - if (sectioned) { - // The user may own several rows (one per section). Drop them all. - bool removed = false; - const QList
sections = sectionUsers.keys(); // snapshot: maps mutate - for (const Section section : sections) { - removed = dropSectionMembership(section, userName) || removed; - } - if (removed && !bulkLoading) { - sortItems(); - applyFilter(); - userTree->viewport()->update(); - } - return removed; - } - UserListTWI *twi = users.value(userName); if (!twi) { return false; } users.remove(userName); - if (twi->parent()) { - twi->parent()->removeChild(twi); // sectioned mode: rows hang off a divider - } else { - userTree->takeTopLevelItem(userTree->indexOfTopLevelItem(twi)); - } - if (twi->data(0, UserListRoles::Online).toBool()) { + userTree->takeTopLevelItem(userTree->indexOfTopLevelItem(twi)); + if (twi->data(0, Qt::UserRole + 1).toBool()) { --onlineCount; } delete twi; updateCount(); - applyFilter(); return true; } void UserListWidget::setUserOnline(const QString &userName, bool online) { - if (sectioned) { - // The rows in the "Online" section are created/removed by the presence - // handlers. This only keeps the presence flag of the surviving rows - // (e.g. a buddy row after the user went offline) in sync. - for (auto it = sectionUsers.cbegin(); it != sectionUsers.cend(); ++it) { - UserListTWI *item = it.value().value(userName); - if (item) { - item->setOnline(online); - } - } - return; - } - UserListTWI *twi = users.value(userName); if (!twi) { return; } - // No state change: nothing to resort. This also keeps the presence - // broadcasts cheap (userJoinedOnline fires once per online user) when the - // row already carries the right flag. - if (twi->data(0, UserListRoles::Online).toBool() == online) { - return; - } - twi->setOnline(online); if (online) { ++onlineCount; @@ -1415,123 +1035,26 @@ void UserListWidget::setUserOnline(const QString &userName, bool online) --onlineCount; } updateCount(); - - // Online users sort above offline users (UserListTWI::operator<), so a - // flag change moves the row. Resort to place the user by the new state. - if (!bulkLoading) { - sortItems(); - applyFilter(); - userTree->viewport()->update(); - } } void UserListWidget::updateCount() { - if (sectioned) { - // The dividers carry the section titles - setTitle(QString()); - for (const Section section : sectionIds) { - updateSectionDivider(section); - } - return; + QString str = titleStr; + if ((type == BuddyList) || (type == IgnoreList)) { + str = str.arg(onlineCount); } - - if (showTitle) { - QString str = titleStr; - if ((type == BuddyList) || (type == IgnoreList)) { - str = str.arg(onlineCount); - } - setTitle(str.arg(userTree->topLevelItemCount())); - } else { - setTitle(QString()); - } -} - -void UserListWidget::setShowTitle(bool showTitle) -{ - this->showTitle = showTitle; - updateCount(); -} - -void UserListWidget::setFilterText(const QString &text) -{ - if (filterText == text) { - return; - } - filterText = text; - applyFilter(); -} - -void UserListWidget::applyFilter() -{ - if (sectioned) { - const bool searching = !filterText.isEmpty(); - const QString lower = filterText.toLower(); - for (const Section section : sectionIds) { - QTreeWidgetItem *divider = sectionItems.value(section); - if (!divider) { - continue; - } - int visible = 0; - for (int i = 0; i < divider->childCount(); ++i) { - auto *child = static_cast(divider->child(i)); - const bool match = - !searching || QString::fromStdString(child->getUserInfo().name()).toLower().contains(lower); - child->setHidden(!match); - if (match) { - ++visible; - } - } - if (searching) { - // During a search the sections with matches stay open and empty - // sections disappear entirely. The persisted expansion state is - // untouched and restored when the search is cleared. - divider->setHidden(visible == 0); - setExpandedProgrammatically(divider, visible > 0); - } else { - divider->setHidden(false); - setExpandedProgrammatically(divider, expandedSections.contains(section)); - } - updateSectionDivider(section); - } - requestAvatarsForVisibleItems(); - userTree->viewport()->update(); - return; - } - - if (filterText.isEmpty()) { - for (auto it = users.cbegin(); it != users.cend(); ++it) { - it.value()->setHidden(false); - } - } else { - const QString lower = filterText.toLower(); - for (auto it = users.cbegin(); it != users.cend(); ++it) { - const bool match = QString::fromStdString(it.value()->getUserInfo().name()).toLower().contains(lower); - it.value()->setHidden(!match); - } - } - - requestAvatarsForVisibleItems(); - userTree->viewport()->update(); + setTitle(str.arg(userTree->topLevelItemCount())); } void UserListWidget::userClicked(QTreeWidgetItem *item, int /*column*/) { - if (item->type() != QTreeWidgetItem::Type) { - return; // divider rows open no chat - } emit openMessageDialog(item->data(2, Qt::UserRole).toString(), true); } void UserListWidget::showContextMenu(const QPoint &pos, const QModelIndex &index) { - QTreeWidgetItem *item = userTree->itemFromIndex(index); - if (!item || item->type() != QTreeWidgetItem::Type) { - return; // divider rows have no user menu - } - const auto *userItem = static_cast(item); - const ServerInfo_User &userInfo = userItem->getUserInfo(); - const bool online = userItem->data(0, UserListRoles::Online).toBool(); + const ServerInfo_User &userInfo = static_cast(userTree->topLevelItem(index.row()))->getUserInfo(); + bool online = index.sibling(index.row(), 0).data(Qt::UserRole + 1).toBool(); userContextMenu->showContextMenu(pos, QString::fromStdString(userInfo.name()), UserLevelFlags(userInfo.user_level()), online); @@ -1539,289 +1062,5 @@ void UserListWidget::showContextMenu(const QPoint &pos, const QModelIndex &index void UserListWidget::sortItems() { - if (sectioned) { - // Sorting must stay inside each section so the dividers keep their - // places as top level items. - for (auto it = sectionItems.cbegin(); it != sectionItems.cend(); ++it) { - it.value()->sortChildren(0, Qt::AscendingOrder); - } - return; - } userTree->sortItems(0, Qt::AscendingOrder); } - -// Sectioned mode - -void UserListWidget::setSectioned(const QList
&ids) -{ - if (sectioned || ids.isEmpty()) { - return; - } - - sectioned = true; - sectionIds = ids; - expandedSections.clear(); - for (const Section section : sectionIds) { - expandedSections.insert(section); // everything starts expanded - } - - // The single tree owns scrolling and the dividers carry the section titles, - // so the group box chrome and tree decorations collapse into a flat list. - setFlat(true); - setShowTitle(false); - userTree->setFrameStyle(QFrame::NoFrame); - // No tree branches: the dividers draw their own arrow glyph, so the rows can - // sit flush with the left border. - userTree->setRootIsDecorated(false); - userTree->setIndentation(0); - userTree->setAlternatingRowColors(false); - if (auto *listLayout = layout()) { - listLayout->setContentsMargins(0, 0, 0, 0); - } - - createSectionItems(); - updateCount(); -} - -void UserListWidget::createSectionItems() -{ - sectionItems.clear(); - QSignalBlocker blocker(userTree); // no expansion signals while building - for (const Section section : sectionIds) { - QTreeWidgetItem *divider = createSectionItem(section); - sectionItems.insert(section, divider); - divider->setExpanded(expandedSections.contains(section)); - } -} - -QTreeWidgetItem *UserListWidget::createSectionItem(Section section) -{ - Q_UNUSED(section); - auto *divider = new QTreeWidgetItem(SectionItemType); - // Selectable so keyboard navigation (Up/Down) can land on the dividers. - // They act as collapsible section headers once they have focus. - divider->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable); - - QFont font = userTree->font(); - font.setBold(true); - divider->setFont(0, font); - // A little taller than a plain text row so the header reads as a section - // separator without matching the full user row height. - divider->setSizeHint(0, QSize(0, QFontMetrics(font).height() + 16)); - - userTree->addTopLevelItem(divider); - // QTreeWidgetItem::setFirstColumnSpanned() does nothing while the item is - // detached from the tree (Qt returns early when treeModel() is null), so it - // must be called after addTopLevelItem(). Without the span the divider text - // is confined to column 0 and gets elided in unstyled mode. - divider->setFirstColumnSpanned(true); - return divider; -} - -QString UserListWidget::sectionTitle(Section section) const -{ - switch (section) { - case Section::Buddy: - return tr("Buddies"); - case Section::Online: - return tr("Online"); - case Section::Ignore: - return tr("Ignored"); - } - return {}; -} - -void UserListWidget::updateSectionDivider(Section section) -{ - QTreeWidgetItem *divider = sectionItems.value(section); - if (!divider) { - return; - } - int visible = 0; - for (int i = 0; i < divider->childCount(); ++i) { - if (!divider->child(i)->isHidden()) { - ++visible; - } - } - // The tree draws no branches (rows are flush), so the divider carries its - // own collapse arrow glyph. - const QString arrow = divider->isExpanded() ? QStringLiteral("\u25BE") : QStringLiteral("\u25B8"); - divider->setText(0, tr("%1 %2 (%3)").arg(arrow, sectionTitle(section)).arg(visible)); -} - -void UserListWidget::handleSectionExpansion(QTreeWidgetItem *item, bool expanded) -{ - if (!sectioned || item->type() != SectionItemType) { - return; - } - // Reverse lookup. Only three dividers exist, so a linear scan over the - // section map is cheaper than caching the section on each divider. - auto dividerIt = sectionItems.constBegin(); - while (dividerIt != sectionItems.constEnd() && dividerIt.value() != item) { - ++dividerIt; - } - if (dividerIt == sectionItems.constEnd()) { - return; - } - const Section section = dividerIt.key(); - if (expanded) { - expandedSections.insert(section); - } else { - expandedSections.remove(section); - } - updateSectionDivider(section); // the arrow glyph follows the state - emit sectionExpanded(section, expanded); -} - -void UserListWidget::setExpandedProgrammatically(QTreeWidgetItem *item, bool expanded) -{ - QSignalBlocker blocker(userTree); - item->setExpanded(expanded); -} - -void UserListWidget::setSectionExpanded(Section section, bool expanded) -{ - if (!sectioned) { - return; - } - if (expanded) { - expandedSections.insert(section); - } else { - expandedSections.remove(section); - } - QTreeWidgetItem *divider = sectionItems.value(section); - if (!divider) { - return; - } - QSignalBlocker blocker(userTree); - divider->setExpanded(expanded); - updateSectionDivider(section); // the arrow glyph follows the state - userTree->viewport()->update(); -} - -void UserListWidget::handleOnlineChange(const ServerInfo_User &user) -{ - // A user came online: they get a row in the "Online" section, plus (if - // applicable) a row in the buddy/ignore sections, which flip to online. - const QString name = QString::fromStdString(user.name()); - ensureSectionMembership(Section::Online, user, true); - if (manager->isUserBuddy(name)) { - ensureSectionMembership(Section::Buddy, user, true); - } - if (manager->isUserIgnored(name)) { - ensureSectionMembership(Section::Ignore, user, true); - } - finishSectionedMutation(); -} - -void UserListWidget::handleOnlineChangeLeft(const QString &userName) -{ - // The user is no longer online: their "Online" row disappears. Buddies and - // ignored users keep their own section's row, marked offline. A plain user - // has no rows left. - const bool dropped = dropSectionMembership(Section::Online, userName); - const bool kept = manager->isUserBuddy(userName) || manager->isUserIgnored(userName); - if (kept) { - setUserOnline(userName, false); - } - if (dropped || kept) { - finishSectionedMutation(); - } -} - -void UserListWidget::handleListAdd(Section section, const ServerInfo_User &user) -{ - const QString name = QString::fromStdString(user.name()); - const bool online = manager->getOnlineUser(name) != nullptr; - ensureSectionMembership(section, user, online); - if (online) { - // The user belongs to the "Online" section as well. Make sure the row - // exists even if the join event raced ahead of the list mutation. - ensureSectionMembership(Section::Online, user, true); - } - finishSectionedMutation(); -} - -void UserListWidget::handleListRemove(Section section, const QString &userName) -{ - // Only the row of the removed section disappears: an online user keeps - // their "Online" row, and other list memberships keep theirs. - if (dropSectionMembership(section, userName)) { - finishSectionedMutation(); - } -} - -UserListTWI *UserListWidget::ensureSectionMembership(Section section, const ServerInfo_User &user, bool online) -{ - const QString userName = QString::fromStdString(user.name()); - - updateCardArtParams(user, userName); - - QTreeWidgetItem *divider = sectionItems.value(section); - if (!divider) { - return nullptr; - } - - QMap §ionMap = sectionUsers[section]; - UserListTWI *item = sectionMap.value(userName); - if (!item) { - item = new UserListTWI(user); - sectionMap.insert(userName, item); - divider->addChild(item); - if (!users.contains(userName)) { - users.insert(userName, item); // primary row for lookups by name - } - // The divider counts are refreshed once in endBulkLoad(). Calling - // updateCount() per row during a large rebuild would be quadratic. - if (!bulkLoading) { - updateCount(); // a new row changes the divider's count - } - if (!bulkLoading && isItemNearViewport(item)) { - avatarProvider->requestAvatar(userName); - } - } else { - item->setUserInfo(user); - } - item->setOnline(online); - return item; -} - -bool UserListWidget::dropSectionMembership(Section section, const QString &userName) -{ - QMap §ionMap = sectionUsers[section]; - UserListTWI *item = sectionMap.take(userName); - if (!item) { - return false; - } - - if (item->parent()) { - item->parent()->removeChild(item); - } else { - userTree->takeTopLevelItem(userTree->indexOfTopLevelItem(item)); - } - if (users.value(userName) == item) { - // Repoint the primary row at another surviving row, if any. - UserListTWI *replacement = nullptr; - for (auto it = sectionUsers.cbegin(); it != sectionUsers.cend() && !replacement; ++it) { - replacement = it.value().value(userName); - } - if (replacement) { - users.insert(userName, replacement); - } else { - users.remove(userName); - } - } - delete item; - updateCount(); - return true; -} - -void UserListWidget::finishSectionedMutation() -{ - if (bulkLoading) { - return; - } - sortItems(); - applyFilter(); - userTree->viewport()->update(); -} diff --git a/cockatrice/src/interface/widgets/server/user/user_list_widget.h b/cockatrice/src/interface/widgets/server/user/user_list_widget.h index 298a5f8d8..c98ebebdf 100644 --- a/cockatrice/src/interface/widgets/server/user/user_list_widget.h +++ b/cockatrice/src/interface/widgets/server/user/user_list_widget.h @@ -18,7 +18,6 @@ #include #include #include -#include #include #include #include @@ -104,13 +103,12 @@ public: class UserListItemDelegate : public QStyledItemDelegate { - QTreeWidget *tree; const QMap *avatarCache; const QMap *cardArtCache; const QMap *cardArtParamsMap; public: - explicit UserListItemDelegate(QTreeWidget *tree, + explicit UserListItemDelegate(QObject *const parent, const QMap *avatarCache, const QMap *cardArtCache, const QMap *cardArtParamsMap); @@ -149,12 +147,6 @@ public: BuddyList, IgnoreList }; - enum class Section - { - Buddy, - Online, - Ignore - }; private: UserListManager *manager = nullptr; @@ -162,69 +154,30 @@ private: UserCardArtProvider *cardArtProvider = nullptr; QMap cardArtParamsMap; // ── Hover popup ─────────────────────────────────────────────────────────── - UserInfoPopup *userInfoPopup = nullptr; - QTimer *showPopupTimer = nullptr; - QTimer *hidePopupTimer = nullptr; - QString hoveredUser; - bool popupPinned = false; - bool bulkLoading = false; + UserInfoPopup *m_userInfoPopup = nullptr; + QTimer *m_showPopupTimer = nullptr; + QTimer *m_hidePopupTimer = nullptr; + QString m_hoveredUser; + bool m_popupPinned = false; + bool m_bulkLoading = false; - /** - * Popup functions are anchored on the row, not the user name. In sectioned - * mode a user can own several rows (online + buddy), and the popup must - * follow the hovered/selected row rather than a lookup by name. - */ - void showPopupForUser(UserListTWI *item); + void showPopupForUser(const QString &userName); void hidePopup(bool immediate = false); - void positionPopup(UserListTWI *item); + void positionPopup(const QString &userName); void connectPopupSignals(); - /** True when @p widget is the tree, the popup or an open menu. */ - bool isPressInsideListUi(const QWidget *widget) const; - void clearSelectionAndClosePopup(); bool isItemNearViewport(const UserListTWI *item) const; void requestAvatarsForVisibleItems(); - // Sectioned mode (single tree with inline dividers) - bool sectioned = false; - QList
sectionIds; - QMap sectionItems; - // One row per (section, user): a user that is online AND a buddy appears in - // both the "Online" and the "Buddies" sections, so the same user can own - // several rows, each hanging off its section's divider. - QMap> sectionUsers; - QSet
expandedSections; - void createSectionItems(); - QTreeWidgetItem *createSectionItem(Section section); - [[nodiscard]] QString sectionTitle(Section section) const; - void updateSectionDivider(Section section); - void handleSectionExpansion(QTreeWidgetItem *item, bool expanded); - void setExpandedProgrammatically(QTreeWidgetItem *item, bool expanded); - void handleOnlineChange(const ServerInfo_User &user); - void handleOnlineChangeLeft(const QString &userName); - void handleListAdd(Section section, const ServerInfo_User &user); - void handleListRemove(Section section, const QString &userName); - /** Creates or updates the row for @p user in @p section. */ - UserListTWI *ensureSectionMembership(Section section, const ServerInfo_User &user, bool online); - /** Removes and deletes the row for @p userName in @p section. */ - bool dropSectionMembership(Section section, const QString &userName); - /** Sorts, refilters and repaints after a sectioned mode mutation. */ - void finishSectionedMutation(); - void updateCardArtParams(const ServerInfo_User &user, const QString &userName); - void processUserInfo(Section section, const ServerInfo_User &user, bool online); - QMap users; TabSupervisor *tabSupervisor; AbstractClient *client; UserListType type; - QTreeWidget *userTree = nullptr; + QTreeWidget *userTree; UserListItemDelegate *itemDelegate; UserContextMenu *userContextMenu; int onlineCount; QString titleStr; - QString filterText; - bool showTitle = true; void updateCount(); - void applyFilter(); void refreshPopupButtons(const QString &userName); private slots: void userClicked(QTreeWidgetItem *item, int column); @@ -236,14 +189,12 @@ signals: void addIgnore(const QString &userName); void removeIgnore(const QString &userName); void joinGameRequested(int gameId, int roomId, bool asSpectator); - void sectionExpanded(Section section, bool expanded); public: UserListWidget(TabSupervisor *_tabSupervisor, AbstractClient *_client, UserListType _type, QWidget *parent = nullptr); - ~UserListWidget() override; void bind(UserListManager *mgr); void applyDisplayMode(); void beginBulkLoad(); @@ -254,14 +205,6 @@ public: void processUserInfo(const ServerInfo_User &user, bool online); bool deleteUser(const QString &userName); void setUserOnline(const QString &userName, bool online); - void setFilterText(const QString &text); - void setShowTitle(bool showTitle); - void setSectioned(const QList
&ids); - void setSectionExpanded(Section section, bool expanded); - [[nodiscard]] const QList
&getSectionIds() const - { - return sectionIds; - } [[nodiscard]] const QMap &getUsers() const { return users; diff --git a/cockatrice/src/interface/widgets/settings_page/general_settings_page.cpp b/cockatrice/src/interface/widgets/settings_page/general_settings_page.cpp index a293660f9..91c4943e1 100644 --- a/cockatrice/src/interface/widgets/settings_page/general_settings_page.cpp +++ b/cockatrice/src/interface/widgets/settings_page/general_settings_page.cpp @@ -2,7 +2,6 @@ #include "../../../client/settings/cache_settings.h" #include "../main.h" -#include "../server/user/user_info_connection.h" #include "update/client/release_channel.h" #include @@ -12,7 +11,6 @@ #include #include #include -#include #include #include @@ -123,61 +121,8 @@ GeneralSettingsPage::GeneralSettingsPage() connect(&showTipsOnStartup, &QCheckBox::clicked, &settings.personal(), &PersonalSettings::setShowTipsOnStartup); - // startup destination - for (int i = 0; i < 8; ++i) { - startupTabSelector.addItem(""); // texts set in retranslateUi - } - startupTabSelector.setCurrentIndex(settings.tabs().getStartupTabIndex()); - - connect(&startupTabSelector, qOverload(&QComboBox::currentIndexChanged), &settings.tabs(), - &TabsSettings::setStartupTabIndex); - connect(&startupTabSelector, qOverload(&QComboBox::currentIndexChanged), this, - &GeneralSettingsPage::updateStartupServerControlsVisibility); - - const QString savedHost = settings.tabs().getStartupServerHost(); - const QString savedPort = settings.tabs().getStartupServerPort(); - int startupServerIndex = -1; - UserConnection_Information uci; - for (const auto &savedServer : uci.getServerInfo()) { - const UserConnection_Information &info = savedServer.second; - const QString saveName = info.getSaveName(); - if (saveName.isEmpty()) { - continue; - } - startupServerSelector.addItem(saveName, QVariantList{info.getServer(), info.getPort()}); - if (startupServerIndex == -1 && info.getServer() == savedHost && info.getPort() == savedPort) { - startupServerIndex = startupServerSelector.count() - 1; - } - } - startupServerSelector.setCurrentIndex(startupServerIndex); - - connect(&startupServerSelector, qOverload(&QComboBox::currentIndexChanged), this, [this](int index) { - const QVariantList serverInfo = startupServerSelector.itemData(index).toList(); - if (serverInfo.size() != 2) { - return; - } - TabsSettings &tabs = SettingsCache::instance().tabs(); - tabs.setStartupServerHost(serverInfo[0].toString()); - tabs.setStartupServerPort(serverInfo[1].toString()); - }); - - startupRoomNameEdit = new QLineEdit(settings.tabs().getStartupRoomName()); - // Default (Expanding) would stretch the whole controls column when this row becomes visible, - // so size it like the combo boxes instead: fills the column, never widens it. - startupRoomNameEdit->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed); - connect(startupRoomNameEdit, &QLineEdit::editingFinished, this, - [this] { SettingsCache::instance().tabs().setStartupRoomName(startupRoomNameEdit->text().trimmed()); }); - auto *startupGrid = new QGridLayout; startupGrid->addWidget(&showTipsOnStartup, 0, 0, 1, 2); - startupGrid->addWidget(&startupTabLabel, 1, 0); - startupGrid->addWidget(&startupTabSelector, 1, 1); - startupGrid->addWidget(&startupServerLabel, 2, 0); - startupGrid->addWidget(&startupServerSelector, 2, 1); - startupGrid->addWidget(&startupRoomLabel, 3, 0); - startupGrid->addWidget(startupRoomNameEdit, 3, 1); - - updateStartupServerControlsVisibility(); startupGroupBox = new QGroupBox; startupGroupBox->setLayout(startupGrid); @@ -412,17 +357,6 @@ void GeneralSettingsPage::languageBoxChanged(int index) SettingsCache::instance().personal().setLang(languageBox.itemData(index).toString()); } -void GeneralSettingsPage::updateStartupServerControlsVisibility() -{ - const int index = startupTabSelector.currentIndex(); - const bool serverNeeded = index == StartupTab::StartupTabServer || index == StartupTab::StartupTabServerRoom; - const bool roomNeeded = index == StartupTab::StartupTabServerRoom; - startupServerLabel.setVisible(serverNeeded); - startupServerSelector.setVisible(serverNeeded); - startupRoomLabel.setVisible(roomNeeded); - startupRoomNameEdit->setVisible(roomNeeded); -} - void GeneralSettingsPage::retranslateUi() { languageGroupBox->setTitle(tr("Language settings")); @@ -459,20 +393,6 @@ void GeneralSettingsPage::retranslateUi() updateNotificationCheckBox.setText(tr("Notify if a feature supported by the server is missing in my client")); newVersionOracleCheckBox.setText(tr("Automatically run Oracle when running a new version of Cockatrice")); showTipsOnStartup.setText(tr("Show tips on startup")); - startupTabLabel.setText(tr("Startup tab:")); - startupTabSelector.setItemText(StartupTab::StartupTabHome, tr("Home")); - startupTabSelector.setItemText(StartupTab::StartupTabVisualDeckStorage, tr("Visual Deck Storage")); - startupTabSelector.setItemText(StartupTab::StartupTabDeckStorage, tr("Deck Storage")); - startupTabSelector.setItemText(StartupTab::StartupTabReplays, tr("Game Replays")); - startupTabSelector.setItemText(StartupTab::StartupTabDeckEditor, tr("Deck Editor")); - startupTabSelector.setItemText(StartupTab::StartupTabVisualDeckEditor, tr("Visual Deck Editor")); - startupTabSelector.setItemText(StartupTab::StartupTabServer, tr("Server")); - startupTabSelector.setItemText(StartupTab::StartupTabServerRoom, tr("Server Room")); - startupTabSelector.setToolTip( - tr("The tab shown when Cockatrice starts. If the chosen tab is not open yet, it is opened.")); - startupServerLabel.setText(tr("Server:")); - startupRoomLabel.setText(tr("Room:")); - startupRoomNameEdit->setPlaceholderText(tr("Room name")); resetAllPathsButton->setText(tr("Reset all paths")); const auto &settings = SettingsCache::instance(); diff --git a/cockatrice/src/interface/widgets/settings_page/general_settings_page.h b/cockatrice/src/interface/widgets/settings_page/general_settings_page.h index fbe70a5a4..8aa39ff65 100644 --- a/cockatrice/src/interface/widgets/settings_page/general_settings_page.h +++ b/cockatrice/src/interface/widgets/settings_page/general_settings_page.h @@ -30,7 +30,6 @@ private slots: void tokenDatabasePathButtonClicked(); void resetAllPathsClicked(); void languageBoxChanged(int index); - void updateStartupServerControlsVisibility(); private: QStringList findQmFiles(); @@ -72,12 +71,6 @@ private: QLabel updateReleaseChannelLabel; QLabel advertiseTranslationPageLabel; QCheckBox showTipsOnStartup; - QLabel startupTabLabel; - QComboBox startupTabSelector; - QLabel startupServerLabel; - QComboBox startupServerSelector; - QLabel startupRoomLabel; - QLineEdit *startupRoomNameEdit; }; #endif // COCKATRICE_GENERAL_SETTINGS_PAGE_H diff --git a/cockatrice/src/interface/widgets/settings_page/user_interface_settings_page.cpp b/cockatrice/src/interface/widgets/settings_page/user_interface_settings_page.cpp index 182e75aac..a20d31652 100644 --- a/cockatrice/src/interface/widgets/settings_page/user_interface_settings_page.cpp +++ b/cockatrice/src/interface/widgets/settings_page/user_interface_settings_page.cpp @@ -183,13 +183,6 @@ UserInterfaceSettingsPage::UserInterfaceSettingsPage() connect(&defaultDeckEditorTypeSelector, QOverload::of(&QComboBox::currentIndexChanged), &SettingsCache::instance().deckEditor(), &DeckEditorSettings::setDefaultDeckEditorType); - vdeStartupTabSelector.addItem(""); // these will be set in retranslateUI - vdeStartupTabSelector.addItem(""); - vdeStartupTabSelector.addItem(""); - vdeStartupTabSelector.setCurrentIndex(SettingsCache::instance().deckEditor().getVdeStartupTab()); - connect(&vdeStartupTabSelector, QOverload::of(&QComboBox::currentIndexChanged), - &SettingsCache::instance().deckEditor(), &DeckEditorSettings::setVdeStartupTab); - commanderSpellbookIntegrationUseOfficialBracketNamesExplainer.setText("?"); commanderSpellbookIntegrationUseOfficialBracketNamesExplainer.setAutoRaise(true); commanderSpellbookIntegrationUseOfficialBracketNamesExplainer.setEnabled(false); @@ -249,12 +242,10 @@ UserInterfaceSettingsPage::UserInterfaceSettingsPage() deckEditorGrid->addWidget(&visualDeckStoragePromptForConversionSelector, 3, 1); deckEditorGrid->addWidget(&defaultDeckEditorTypeLabel, 4, 0); deckEditorGrid->addWidget(&defaultDeckEditorTypeSelector, 4, 1); - deckEditorGrid->addWidget(&vdeStartupTabLabel, 5, 0); - deckEditorGrid->addWidget(&vdeStartupTabSelector, 5, 1); - deckEditorGrid->addWidget(&commanderSpellbookIntegrationEnabledLabel, 6, 0); - deckEditorGrid->addWidget(&commanderSpellbookIntegrationEnabledSelector, 6, 1); - deckEditorGrid->addWidget(labelWidget, 7, 0); - deckEditorGrid->addWidget(&commanderSpellbookIntegrationBracketNamingSelector, 7, 1); + deckEditorGrid->addWidget(&commanderSpellbookIntegrationEnabledLabel, 5, 0); + deckEditorGrid->addWidget(&commanderSpellbookIntegrationEnabledSelector, 5, 1); + deckEditorGrid->addWidget(labelWidget, 6, 0); + deckEditorGrid->addWidget(&commanderSpellbookIntegrationBracketNamingSelector, 6, 1); deckEditorGroupBox = new QGroupBox; deckEditorGroupBox->setLayout(deckEditorGrid); @@ -377,12 +368,6 @@ void UserInterfaceSettingsPage::retranslateUi() defaultDeckEditorTypeLabel.setText(tr("Default deck editor type")); defaultDeckEditorTypeSelector.setItemText(TabSupervisor::ClassicDeckEditor, tr("Classic Deck Editor")); defaultDeckEditorTypeSelector.setItemText(TabSupervisor::VisualDeckEditor, tr("Visual Deck Editor")); - vdeStartupTabLabel.setText(tr("Visual deck editor startup tab")); - vdeStartupTabSelector.setItemText(VdeStartupTabContext, tr("Context")); - vdeStartupTabSelector.setItemText(VdeStartupTabDeckDisplay, tr("Deck display")); - vdeStartupTabSelector.setItemText(VdeStartupTabDatabaseDisplay, tr("Database display")); - vdeStartupTabSelector.setToolTip( - tr("Context mode: New decks open on the database display, existing decks open on the deck view.")); commanderSpellbookIntegrationEnabledLabel.setText( tr("CommanderSpellbook integration to estimate commander bracket")); diff --git a/cockatrice/src/interface/widgets/settings_page/user_interface_settings_page.h b/cockatrice/src/interface/widgets/settings_page/user_interface_settings_page.h index 0dc4cf4e8..2b9eba72c 100644 --- a/cockatrice/src/interface/widgets/settings_page/user_interface_settings_page.h +++ b/cockatrice/src/interface/widgets/settings_page/user_interface_settings_page.h @@ -50,8 +50,6 @@ private: QCheckBox visualDeckStorageSelectionAnimationCheckBox; QLabel defaultDeckEditorTypeLabel; QComboBox defaultDeckEditorTypeSelector; - QLabel vdeStartupTabLabel; - QComboBox vdeStartupTabSelector; QLabel commanderSpellbookIntegrationEnabledLabel; QComboBox commanderSpellbookIntegrationEnabledSelector; QLabel commanderSpellbookIntegrationUseOfficialBracketNamesLabel; diff --git a/cockatrice/src/interface/widgets/tabs/abstract_tab_deck_editor.h b/cockatrice/src/interface/widgets/tabs/abstract_tab_deck_editor.h index e1f255199..34c585597 100644 --- a/cockatrice/src/interface/widgets/tabs/abstract_tab_deck_editor.h +++ b/cockatrice/src/interface/widgets/tabs/abstract_tab_deck_editor.h @@ -263,12 +263,12 @@ protected slots: /** @brief Handles dock close events. */ void closeEvent(QCloseEvent *event) override; +private: /** @brief Sets the deck for this tab. * @param _deck The deck object. */ virtual void setDeck(const LoadedDeck &_deck); -private: /** @brief Helper for editing decks from the clipboard. */ void editDeckInClipboard(bool annotated); diff --git a/cockatrice/src/interface/widgets/tabs/tab_room.cpp b/cockatrice/src/interface/widgets/tabs/tab_room.cpp index 9b09ba7bb..705266b1d 100644 --- a/cockatrice/src/interface/widgets/tabs/tab_room.cpp +++ b/cockatrice/src/interface/widgets/tabs/tab_room.cpp @@ -6,7 +6,6 @@ #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_panel_widget.h" #include "../interface/widgets/server/user/user_list_widget.h" #include "../main.h" #include "../utility/completer_utils.h" @@ -61,10 +60,23 @@ TabRoom::TabRoom(TabSupervisor *_tabSupervisor, tempMap.insert(info.room_id(), gameTypes); gameSelector = new GameSelector(client, tabSupervisor, this, QMap(), tempMap, true, true); - userListPanel = new UserListPanelWidget(tabSupervisor, client, this); - userListPanel->bind(tabSupervisor->getUserListManager()); - userList = userListPanel->getUserList(); - connect(userListPanel, &UserListPanelWidget::openMessageDialog, this, &TabRoom::openMessageDialog); + 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); @@ -114,7 +126,7 @@ TabRoom::TabRoom(TabSupervisor *_tabSupervisor, auto *hbox = new QHBoxLayout; hbox->addWidget(splitter, 3); - hbox->addWidget(userListPanel, 1); + hbox->addWidget(tabs, 1); aLeaveRoom = new QAction(this); connect(aLeaveRoom, &QAction::triggered, this, &TabRoom::closeRequest); @@ -169,7 +181,7 @@ void TabRoom::retranslateUi() { gameSelector->retranslateUi(); chatView->retranslateUi(); - userListPanel->retranslateUi(); + userList->retranslateUi(); sayLabel->setText(tr("&Say:")); chatGroupBox->setTitle(tr("Chat")); roomMenu->setTitle(tr("&Room")); diff --git a/cockatrice/src/interface/widgets/tabs/tab_room.h b/cockatrice/src/interface/widgets/tabs/tab_room.h index 7d01d5cf6..dc58b8bf6 100644 --- a/cockatrice/src/interface/widgets/tabs/tab_room.h +++ b/cockatrice/src/interface/widgets/tabs/tab_room.h @@ -27,7 +27,6 @@ class Message; } // namespace google class AbstractClient; class UserListWidget; -class UserListPanelWidget; class QLabel; class ChatView; class QPushButton; @@ -58,8 +57,9 @@ private: QMap gameTypes; GameSelector *gameSelector; - UserListPanelWidget *userListPanel; + UserListWidget *friendsList; UserListWidget *userList; + UserListWidget *ignoreList; const UserListProxy *userListProxy; ChatView *chatView; QLabel *sayLabel; @@ -114,10 +114,6 @@ public: { return roomId; } - [[nodiscard]] QString getRoomName() const - { - return roomName; - } [[nodiscard]] const QMap &getGameTypes() const { return gameTypes; diff --git a/cockatrice/src/interface/widgets/tabs/tab_supervisor.cpp b/cockatrice/src/interface/widgets/tabs/tab_supervisor.cpp index 4100e124a..d8f2e7935 100644 --- a/cockatrice/src/interface/widgets/tabs/tab_supervisor.cpp +++ b/cockatrice/src/interface/widgets/tabs/tab_supervisor.cpp @@ -335,12 +335,7 @@ static void checkAndTrigger(QAction *checkableAction, bool checked) } /** - * Opens the always-available tabs, depending on settings, and lands on the configured startup tab. - * - * The startup destination is a request: tabs that were not open before (deck editors, storage - * tabs disabled in the Tabs menu) are opened as part of the startup flow. Destinations that - * require a server connection (Server, Server Room) are handled asynchronously by MainWindow - * through the intent system, since this class has no RemoteClient. + * Opens the always-available tabs, depending on settings. */ void TabSupervisor::initStartupTabs() { @@ -356,42 +351,6 @@ void TabSupervisor::initStartupTabs() if (SettingsCache::instance().tabs().getTabReplaysOpen()) { openTabReplays(); } - - switch (SettingsCache::instance().tabs().getStartupTabIndex()) { - case StartupTab::StartupTabVisualDeckStorage: - if (!tabVisualDeckStorage) { - openTabVisualDeckStorage(); - } - setCurrentWidget(tabVisualDeckStorage); - break; - case StartupTab::StartupTabDeckStorage: - if (!tabDeckStorage) { - openTabDeckStorage(); - } - setCurrentWidget(tabDeckStorage); - break; - case StartupTab::StartupTabReplays: - if (!tabReplays) { - openTabReplays(); - } - setCurrentWidget(tabReplays); - break; - case StartupTab::StartupTabDeckEditor: - addDeckEditorTab(LoadedDeck()); - break; - case StartupTab::StartupTabVisualDeckEditor: - addVisualDeckEditorTab(LoadedDeck()); - break; - case StartupTab::StartupTabServer: - case StartupTab::StartupTabServerRoom: - // Handled asynchronously by MainWindow::applyStartupDestination(); Home stays selected - // until the server connection succeeds. - break; - case StartupTab::StartupTabHome: - default: - setCurrentWidget(tabHome); - break; - } } /** diff --git a/cockatrice/src/interface/widgets/tabs/tab_supervisor.h b/cockatrice/src/interface/widgets/tabs/tab_supervisor.h index 0c3542cf3..d3c147138 100644 --- a/cockatrice/src/interface/widgets/tabs/tab_supervisor.h +++ b/cockatrice/src/interface/widgets/tabs/tab_supervisor.h @@ -184,7 +184,6 @@ public slots: void actTabVisualDeckStorage(bool checked); void actTabReplays(bool checked); void openTabServer(); - void addRoomTab(const ServerInfo_Room &info, bool setCurrent); private slots: void refreshShortcuts(); @@ -210,6 +209,7 @@ private slots: void gameJoined(const Event_GameJoined &event); void localGameJoined(const Event_GameJoined &event); void gameLeft(TabGame *tab); + void addRoomTab(const ServerInfo_Room &info, bool setCurrent); void roomLeft(TabRoom *tab); TabMessage *addMessageTab(const QString &userName, bool focus); void replayLeft(TabGame *tab); diff --git a/cockatrice/src/interface/widgets/tabs/visual_deck_editor/tab_deck_editor_visual.cpp b/cockatrice/src/interface/widgets/tabs/visual_deck_editor/tab_deck_editor_visual.cpp index 209a30642..c15f614d8 100644 --- a/cockatrice/src/interface/widgets/tabs/visual_deck_editor/tab_deck_editor_visual.cpp +++ b/cockatrice/src/interface/widgets/tabs/visual_deck_editor/tab_deck_editor_visual.cpp @@ -30,7 +30,6 @@ #include #include #include -#include #include /** @@ -99,33 +98,6 @@ void TabDeckEditorVisual::onDeckChanged() tabContainer->sampleHandWidget->setDeckModel(deckStateManager->getModel()); } -/** @brief Sets the deck and selects the startup sub-tab matching the context. */ -void TabDeckEditorVisual::setDeck(const LoadedDeck &_deck) -{ - AbstractTabDeckEditor::setDeck(_deck); - - int startupTab = SettingsCache::instance().deckEditor().getVdeStartupTab(); - if (startupTab == VdeStartupTabContext) { - // New (empty) decks open on the database display so cards can be added - // right away. Existing decks open on the deck view. - startupTab = _deck.isEmpty() ? VdeStartupTabDatabaseDisplay : VdeStartupTabDeckDisplay; - } - - switch (startupTab) { - case VdeStartupTabDatabaseDisplay: - tabContainer->setCurrentIndex(TabDeckEditorVisualTabWidget::TabIndex::VisualDatabaseDisplay); - break; - case VdeStartupTabDeckDisplay: - tabContainer->setCurrentIndex(TabDeckEditorVisualTabWidget::TabIndex::VisualDeckView); - break; - default: - qCWarning(TabSupervisorLog) << "Unknown VdeStartupTab [" << startupTab - << "]; falling back to the deck view"; - tabContainer->setCurrentIndex(TabDeckEditorVisualTabWidget::TabIndex::VisualDeckView); - break; - } -} - /** @brief Creates menus for deck editing and view options, including dock actions. */ void TabDeckEditorVisual::createMenus() { diff --git a/cockatrice/src/interface/widgets/tabs/visual_deck_editor/tab_deck_editor_visual.h b/cockatrice/src/interface/widgets/tabs/visual_deck_editor/tab_deck_editor_visual.h index 21335d2d0..7d7a3f3a2 100644 --- a/cockatrice/src/interface/widgets/tabs/visual_deck_editor/tab_deck_editor_visual.h +++ b/cockatrice/src/interface/widgets/tabs/visual_deck_editor/tab_deck_editor_visual.h @@ -164,15 +164,6 @@ public slots: * @return true if successful, false otherwise. */ bool actSaveDeckAs() override; - -private: - /** - * @brief Sets the deck for this tab and selects the sub-tab to open on - * startup, per the "Visual deck editor startup tab" setting (Context / - * Deck display / Database display). - * @param _deck The deck object. - */ - void setDeck(const LoadedDeck &_deck) override; }; #endif diff --git a/cockatrice/src/interface/widgets/tabs/visual_deck_editor/tab_deck_editor_visual_tab_widget.h b/cockatrice/src/interface/widgets/tabs/visual_deck_editor/tab_deck_editor_visual_tab_widget.h index 4f04b51f6..2aabbb26a 100644 --- a/cockatrice/src/interface/widgets/tabs/visual_deck_editor/tab_deck_editor_visual_tab_widget.h +++ b/cockatrice/src/interface/widgets/tabs/visual_deck_editor/tab_deck_editor_visual_tab_widget.h @@ -49,17 +49,6 @@ class TabDeckEditorVisualTabWidget : public QTabWidget Q_OBJECT public: - /** - * @brief Sub-tab order in the container; addNewTab() is called in this order. - */ - enum TabIndex - { - VisualDeckView, - VisualDatabaseDisplay, - DeckAnalytics, - SampleHand, - }; - /** * @brief Construct the tab widget with required models. * @param parent Parent widget. diff --git a/cockatrice/src/interface/window_main.cpp b/cockatrice/src/interface/window_main.cpp index c083dccf8..44e188760 100644 --- a/cockatrice/src/interface/window_main.cpp +++ b/cockatrice/src/interface/window_main.cpp @@ -32,14 +32,8 @@ #include "../interface/widgets/dialogs/dlg_update.h" #include "../interface/widgets/dialogs/dlg_view_log.h" #include "../interface/widgets/tabs/tab_game.h" -#include "../interface/widgets/tabs/tab_server.h" #include "../interface/widgets/tabs/tab_supervisor.h" #include "../main.h" -#include "intents/contexts/context_connect_to_server.h" -#include "intents/contexts/context_join_room.h" -#include "intents/intent_connect_to_server.h" -#include "intents/intent_login.h" -#include "intents/intent_open_server_room_by_name.h" #include "logger.h" #include "version_string.h" #include "widgets/dialogs/dlg_connect.h" @@ -83,7 +77,6 @@ #include #include #include -#include #include #define GITHUB_PAGES_URL "https://cockatrice.github.io" @@ -547,7 +540,6 @@ MainWindow::MainWindow(QWidget *parent) // run startup check async QTimer::singleShot(0, this, &MainWindow::startupConfigCheck); - QTimer::singleShot(0, this, &MainWindow::applyStartupDestination); } void MainWindow::startupConfigCheck() @@ -656,82 +648,6 @@ void MainWindow::startupConfigCheck() } } -/** - * Drives the server-based startup destinations (Server lobby, Server Room) through the intent - * system: fetch saved credentials, connect to the configured server, then land on the Lobby or - * join the configured room by name. - */ -void MainWindow::applyStartupDestination() -{ - // An explicit command-line connect takes precedence over the startup destination. - if (!connectTo.isEmpty()) { - return; - } - - const int destination = SettingsCache::instance().tabs().getStartupTabIndex(); - if (destination != StartupTab::StartupTabServer && destination != StartupTab::StartupTabServerRoom) { - return; - } - - const QString host = SettingsCache::instance().tabs().getStartupServerHost(); - const QString port = SettingsCache::instance().tabs().getStartupServerPort(); - if (host.isEmpty() || port.isEmpty()) { - qCWarning(WindowMainStartupLog) << "Startup destination needs a configured server"; - return; - } - - auto serverContext = std::make_shared(); - serverContext->hostname = host; - serverContext->port = port; - - auto *credentials = new IntentGetLoginCredentials(serverContext.get()); - auto *connector = new IntentConnectToServer(getRemoteClient(), serverContext.get()); - - connect(credentials, &Intent::finished, connector, &Intent::execute); - connect(credentials, &Intent::failed, this, &MainWindow::startupDestinationFailed); - connect(connector, &Intent::finished, this, - [this, destination, serverContext]() { onStartupDestinationConnected(destination, *serverContext); }); - connect(connector, &Intent::failed, this, &MainWindow::startupDestinationFailed); - - credentials->execute(); -} - -void MainWindow::onStartupDestinationConnected(int destination, const ContextConnectToServer &serverContext) -{ - // The server tab must exist: it is what requests the room list. - if (!tabSupervisor->getTabServer()) { - tabSupervisor->openTabServer(); - } - - if (destination == StartupTab::StartupTabServerRoom) { - auto roomContext = std::make_unique(); - roomContext->serverContext = serverContext; - auto *roomIntent = new IntentOpenServerRoomByName(tabSupervisor, getRemoteClient(), std::move(roomContext), - SettingsCache::instance().tabs().getStartupRoomName()); - roomIntent->setParent(this); - connect(roomIntent, &Intent::failed, this, &MainWindow::startupDestinationFailed); - roomIntent->execute(); - return; - } - - if (tabSupervisor->getTabServer()) { - tabSupervisor->setCurrentWidget(tabSupervisor->getTabServer()); - } else { - qCWarning(WindowMainStartupLog) << "Startup destination: server tab could not be opened"; - } -} - -void MainWindow::startupDestinationFailed(const QString &reason) -{ - qCWarning(WindowMainStartupLog) << "Startup destination failed:" << reason; -} - -bool MainWindow::startupDestinationConnectsToServer() const -{ - const int destination = SettingsCache::instance().tabs().getStartupTabIndex(); - return destination == StartupTab::StartupTabServer || destination == StartupTab::StartupTabServerRoom; -} - void MainWindow::alertForcedOracleRun(const QString &version, bool isUpdate) { if (isUpdate) { @@ -834,8 +750,7 @@ void MainWindow::changeEvent(QEvent *event) connectionController->connectToServerDirect(connectTo.host(), connectTo.port(), connectTo.userName(), connectTo.password()); } else if (SettingsCache::instance().servers().getAutoConnect() && - !SettingsCache::instance().debug().getLocalGameOnStartup() && - !startupDestinationConnectsToServer()) { + !SettingsCache::instance().debug().getLocalGameOnStartup()) { qCInfo(WindowMainStartupAutoconnectLog) << "Attempting auto-connect..."; DlgConnect dlg(this); connectionController->connectToServerDirect(dlg.getHost(), static_cast(dlg.getPort()), diff --git a/cockatrice/src/interface/window_main.h b/cockatrice/src/interface/window_main.h index 73b7c42c5..fa6c79915 100644 --- a/cockatrice/src/interface/window_main.h +++ b/cockatrice/src/interface/window_main.h @@ -55,7 +55,6 @@ class ServerInfo_User; class TabSupervisor; class WndSets; class DlgTipOfTheDay; -struct ContextConnectToServer; class MainWindow : public QMainWindow { @@ -106,11 +105,6 @@ private slots: void startupConfigCheck(); void alertForcedOracleRun(const QString &version, bool isUpdate); - void applyStartupDestination(); - void onStartupDestinationConnected(int destination, const ContextConnectToServer &serverContext); - void startupDestinationFailed(const QString &reason); - [[nodiscard]] bool startupDestinationConnectsToServer() const; - private: static const QString appName; static const QStringList fileNameFilters; diff --git a/libcockatrice_interfaces/libcockatrice/interfaces/interface_interface_settings_provider.h b/libcockatrice_interfaces/libcockatrice/interfaces/interface_interface_settings_provider.h index b77c98357..1f75d3d33 100644 --- a/libcockatrice_interfaces/libcockatrice/interfaces/interface_interface_settings_provider.h +++ b/libcockatrice_interfaces/libcockatrice/interfaces/interface_interface_settings_provider.h @@ -2,7 +2,6 @@ #define COCKATRICE_INTERFACE_INTERFACE_SETTINGS_PROVIDER_H #include -#include class IInterfaceSettingsProvider { @@ -42,7 +41,6 @@ public: [[nodiscard]] virtual bool getShowGameSelectorFilterToolbar() const = 0; [[nodiscard]] virtual bool getLifeCounterAnimationsEnabled() const = 0; [[nodiscard]] virtual bool getBattlefieldFlashEnabled() const = 0; - [[nodiscard]] virtual QStringList getUserListExpandedSections() const = 0; }; #endif // COCKATRICE_INTERFACE_INTERFACE_SETTINGS_PROVIDER_H diff --git a/libcockatrice_interfaces/libcockatrice/interfaces/interface_tabs_settings_provider.h b/libcockatrice_interfaces/libcockatrice/interfaces/interface_tabs_settings_provider.h index bbe475903..4403de569 100644 --- a/libcockatrice_interfaces/libcockatrice/interfaces/interface_tabs_settings_provider.h +++ b/libcockatrice_interfaces/libcockatrice/interfaces/interface_tabs_settings_provider.h @@ -1,17 +1,11 @@ #ifndef COCKATRICE_INTERFACE_TABS_SETTINGS_PROVIDER_H #define COCKATRICE_INTERFACE_TABS_SETTINGS_PROVIDER_H -#include - class ITabsSettingsProvider { public: virtual ~ITabsSettingsProvider() = default; - [[nodiscard]] virtual int getStartupTabIndex() const = 0; - [[nodiscard]] virtual QString getStartupServerHost() const = 0; - [[nodiscard]] virtual QString getStartupServerPort() const = 0; - [[nodiscard]] virtual QString getStartupRoomName() const = 0; [[nodiscard]] virtual bool getTabVisualDeckStorageOpen() const = 0; [[nodiscard]] virtual bool getTabServerOpen() const = 0; [[nodiscard]] virtual bool getTabAccountOpen() const = 0; diff --git a/libcockatrice_settings/libcockatrice/settings/deck_editor_settings.cpp b/libcockatrice_settings/libcockatrice/settings/deck_editor_settings.cpp index 44cdcd86f..65296a450 100644 --- a/libcockatrice_settings/libcockatrice/settings/deck_editor_settings.cpp +++ b/libcockatrice_settings/libcockatrice/settings/deck_editor_settings.cpp @@ -25,11 +25,6 @@ int DeckEditorSettings::getDefaultDeckEditorType() const return getValue("defaultDeckEditorType", QString(), QString(), 1).toInt(); } -int DeckEditorSettings::getVdeStartupTab() const -{ - return getValue("vdeStartupTab", QString(), QString(), VdeStartupTabContext).toInt(); -} - void DeckEditorSettings::setOpenDeckInNewTab(bool _openDeckInNewTab) { setValue(_openDeckInNewTab, "openDeckInNewTab"); @@ -52,12 +47,6 @@ void DeckEditorSettings::setDefaultDeckEditorType(int _defaultDeckEditorType) setValue(_defaultDeckEditorType, "defaultDeckEditorType"); } -void DeckEditorSettings::setVdeStartupTab(int _vdeStartupTab) -{ - setValue(_vdeStartupTab, "vdeStartupTab"); - emit vdeStartupTabChanged(_vdeStartupTab); -} - int DeckEditorSettings::getCommanderSpellbookIntegrationEnabled() const { return getValue("commanderspellbookintegrationenabled", QString(), QString(), diff --git a/libcockatrice_settings/libcockatrice/settings/deck_editor_settings.h b/libcockatrice_settings/libcockatrice/settings/deck_editor_settings.h index 0c87a270a..70f91be9b 100644 --- a/libcockatrice_settings/libcockatrice/settings/deck_editor_settings.h +++ b/libcockatrice_settings/libcockatrice/settings/deck_editor_settings.h @@ -13,13 +13,6 @@ enum commanderSpellbookIntegrationEnabledIndex commanderSpellbookIntegrationEnabledIndexUnprompted, }; -enum VdeStartupTab -{ - VdeStartupTabContext, ///< Match the opened deck: new decks show the database display, existing decks the deck view - VdeStartupTabDeckDisplay, ///< Always open the Visual Deck View - VdeStartupTabDatabaseDisplay, ///< Always open the Visual Database Display -}; - class DeckEditorSettings : public SettingsManager, public IDeckEditorSettingsProvider { Q_OBJECT @@ -30,7 +23,6 @@ public: [[nodiscard]] bool getBannerCardComboBoxVisible() const override; [[nodiscard]] bool getTagsWidgetVisible() const override; [[nodiscard]] int getDefaultDeckEditorType() const override; - [[nodiscard]] int getVdeStartupTab() const; [[nodiscard]] int getCommanderSpellbookIntegrationEnabled() const; [[nodiscard]] bool getCommanderSpellbookIntegrationUseOfficialBracketNames() const; @@ -38,7 +30,6 @@ public: void setBannerCardComboBoxVisible(bool _bannerCardComboBoxVisible); void setTagsWidgetVisible(bool _tagsWidgetVisible); void setDefaultDeckEditorType(int _defaultDeckEditorType); - void setVdeStartupTab(int _vdeStartupTab); void setCommanderSpellbookIntegrationEnabled(int _commanderSpellbookIntegrationEnabled); void setCommanderSpellbookIntegrationUseOfficialBracketNames(bool _useOfficialBracketNames); @@ -47,7 +38,6 @@ signals: void tagsWidgetVisibleChanged(bool visible); void commanderSpellbookIntegrationEnabledChanged(int enabled); void commanderSpellbookIntegrationUseOfficialBracketNamesChanged(bool useOfficialBracketNames); - void vdeStartupTabChanged(int vdeStartupTab); public: explicit DeckEditorSettings(const QString &settingPath, QObject *parent = nullptr); diff --git a/libcockatrice_settings/libcockatrice/settings/interface_settings.cpp b/libcockatrice_settings/libcockatrice/settings/interface_settings.cpp index 2f0718533..4dfc26417 100644 --- a/libcockatrice_settings/libcockatrice/settings/interface_settings.cpp +++ b/libcockatrice_settings/libcockatrice/settings/interface_settings.cpp @@ -170,12 +170,6 @@ bool InterfaceSettings::getBattlefieldFlashEnabled() const return getValue("battlefieldFlashEnabled", QString(), QString(), true).toBool(); } -QStringList InterfaceSettings::getUserListExpandedSections() const -{ - return getValue("userListExpandedSections", QString(), QString(), QStringList({"buddy", "online", "ignore"})) - .toStringList(); -} - void InterfaceSettings::setUseTearOffMenus(bool _useTearOffMenus) { setValue(_useTearOffMenus, "useTearOffMenus"); @@ -354,8 +348,3 @@ void InterfaceSettings::setBattlefieldFlashEnabled(bool _battlefieldFlashEnabled setValue(_battlefieldFlashEnabled, "battlefieldFlashEnabled"); emit battlefieldFlashEnabledChanged(_battlefieldFlashEnabled); } - -void InterfaceSettings::setUserListExpandedSections(const QStringList §ions) -{ - setValue(sections, "userListExpandedSections"); -} diff --git a/libcockatrice_settings/libcockatrice/settings/interface_settings.h b/libcockatrice_settings/libcockatrice/settings/interface_settings.h index 981d28679..df254eb09 100644 --- a/libcockatrice_settings/libcockatrice/settings/interface_settings.h +++ b/libcockatrice_settings/libcockatrice/settings/interface_settings.h @@ -44,7 +44,6 @@ public: [[nodiscard]] bool getShowGameSelectorFilterToolbar() const override; [[nodiscard]] bool getLifeCounterAnimationsEnabled() const override; [[nodiscard]] bool getBattlefieldFlashEnabled() const override; - [[nodiscard]] QStringList getUserListExpandedSections() const override; void setUseTearOffMenus(bool _useTearOffMenus); void setCardViewInitialRowsMax(int _cardViewInitialRowsMax); @@ -79,7 +78,6 @@ public: void setShowGameSelectorFilterToolbar(bool _showGameSelectorFilterToolbar); void setLifeCounterAnimationsEnabled(bool _lifeCounterAnimationsEnabled); void setBattlefieldFlashEnabled(bool _battlefieldFlashEnabled); - void setUserListExpandedSections(const QStringList §ions); signals: void useTearOffMenusChanged(bool state); diff --git a/libcockatrice_settings/libcockatrice/settings/tabs_settings.cpp b/libcockatrice_settings/libcockatrice/settings/tabs_settings.cpp index 78e48ed5b..1838f667e 100644 --- a/libcockatrice_settings/libcockatrice/settings/tabs_settings.cpp +++ b/libcockatrice_settings/libcockatrice/settings/tabs_settings.cpp @@ -5,26 +5,6 @@ TabsSettings::TabsSettings(const QString &settingPath, QObject *parent) { } -int TabsSettings::getStartupTabIndex() const -{ - return getValue("startupTab", QString(), QString(), StartupTab::StartupTabHome).toInt(); -} - -QString TabsSettings::getStartupServerHost() const -{ - return getValue("startupServerHost", QString(), QString(), QString()).toString(); -} - -QString TabsSettings::getStartupServerPort() const -{ - return getValue("startupServerPort", QString(), QString(), QString()).toString(); -} - -QString TabsSettings::getStartupRoomName() const -{ - return getValue("startupRoomName", QString(), QString(), QString()).toString(); -} - bool TabsSettings::getTabVisualDeckStorageOpen() const { return getValue("visualDeckStorage", QString(), QString(), true).toBool(); @@ -60,42 +40,6 @@ bool TabsSettings::getTabLogOpen() const return getValue("log", QString(), QString(), true).toBool(); } -void TabsSettings::setStartupTabIndex(int value) -{ - if (getStartupTabIndex() == value) { - return; - } - setValue(value, "startupTab"); - emit startupTabIndexChanged(value); -} - -void TabsSettings::setStartupServerHost(const QString &host) -{ - if (getStartupServerHost() == host) { - return; - } - setValue(host, "startupServerHost"); - emit startupServerHostChanged(host); -} - -void TabsSettings::setStartupServerPort(const QString &port) -{ - if (getStartupServerPort() == port) { - return; - } - setValue(port, "startupServerPort"); - emit startupServerPortChanged(port); -} - -void TabsSettings::setStartupRoomName(const QString &roomName) -{ - if (getStartupRoomName() == roomName) { - return; - } - setValue(roomName, "startupRoomName"); - emit startupRoomNameChanged(roomName); -} - void TabsSettings::setTabVisualDeckStorageOpen(bool value) { setValue(value, "visualDeckStorage"); diff --git a/libcockatrice_settings/libcockatrice/settings/tabs_settings.h b/libcockatrice_settings/libcockatrice/settings/tabs_settings.h index 0d5da80af..c8e952b87 100644 --- a/libcockatrice_settings/libcockatrice/settings/tabs_settings.h +++ b/libcockatrice_settings/libcockatrice/settings/tabs_settings.h @@ -5,35 +5,12 @@ #include -/** - * @brief The tab the application selects after launch. - * - * The destination is a request: tabs that were not open before (Deck Editor, Server Room, …) - * are opened as part of the startup flow. Destinations that require a server connection use the - * intent system to satisfy their pre-conditions. - */ -enum StartupTab -{ - StartupTabHome, ///< The Home tab - StartupTabVisualDeckStorage, ///< The visual deck storage tab - StartupTabDeckStorage, ///< The deck storage tab - StartupTabReplays, ///< The game replays tab - StartupTabDeckEditor, ///< A fresh classic deck editor tab - StartupTabVisualDeckEditor, ///< A fresh visual deck editor tab - StartupTabServer, ///< The server lobby: connect and select the server tab - StartupTabServerRoom ///< A server room: connect and join the room by name -}; - class TabsSettings : public SettingsManager, public ITabsSettingsProvider { Q_OBJECT friend class SettingsCache; public: - [[nodiscard]] int getStartupTabIndex() const override; - [[nodiscard]] QString getStartupServerHost() const override; - [[nodiscard]] QString getStartupServerPort() const override; - [[nodiscard]] QString getStartupRoomName() const override; [[nodiscard]] bool getTabVisualDeckStorageOpen() const override; [[nodiscard]] bool getTabServerOpen() const override; [[nodiscard]] bool getTabAccountOpen() const override; @@ -42,10 +19,6 @@ public: [[nodiscard]] bool getTabAdminOpen() const override; [[nodiscard]] bool getTabLogOpen() const override; - void setStartupTabIndex(int value); - void setStartupServerHost(const QString &host); - void setStartupServerPort(const QString &port); - void setStartupRoomName(const QString &roomName); void setTabVisualDeckStorageOpen(bool value); void setTabServerOpen(bool value); void setTabAccountOpen(bool value); @@ -54,12 +27,6 @@ public: void setTabAdminOpen(bool value); void setTabLogOpen(bool value); -signals: - void startupTabIndexChanged(int index); - void startupServerHostChanged(const QString &host); - void startupServerPortChanged(const QString &port); - void startupRoomNameChanged(const QString &roomName); - public: explicit TabsSettings(const QString &settingPath, QObject *parent = nullptr); diff --git a/tests/settings/settings_defaults_test.cpp b/tests/settings/settings_defaults_test.cpp index 1a2fc1176..041a60d6f 100644 --- a/tests/settings/settings_defaults_test.cpp +++ b/tests/settings/settings_defaults_test.cpp @@ -188,38 +188,6 @@ TEST_F(SettingsDefaultsTest, Sound_MasterVolume_SetAndGet) // --- TabsSettings --- -TEST_F(SettingsDefaultsTest, Tabs_StartupTab_Default) -{ - TabsSettings s(settingsPath, nullptr); - ASSERT_EQ(s.getStartupTabIndex(), static_cast(StartupTab::StartupTabHome)); -} - -TEST_F(SettingsDefaultsTest, Tabs_StartupTab_SetAndGet) -{ - TabsSettings s(settingsPath, nullptr); - s.setStartupTabIndex(StartupTab::StartupTabServerRoom); - ASSERT_EQ(s.getStartupTabIndex(), static_cast(StartupTab::StartupTabServerRoom)); -} - -TEST_F(SettingsDefaultsTest, Tabs_StartupServer_Default) -{ - TabsSettings s(settingsPath, nullptr); - ASSERT_EQ(s.getStartupServerHost(), QString()); - ASSERT_EQ(s.getStartupServerPort(), QString()); - ASSERT_EQ(s.getStartupRoomName(), QString()); -} - -TEST_F(SettingsDefaultsTest, Tabs_StartupServer_SetAndGet) -{ - TabsSettings s(settingsPath, nullptr); - s.setStartupServerHost("server.cockatrice.us"); - s.setStartupServerPort("4748"); - s.setStartupRoomName("General"); - ASSERT_EQ(s.getStartupServerHost(), QString("server.cockatrice.us")); - ASSERT_EQ(s.getStartupServerPort(), QString("4748")); - ASSERT_EQ(s.getStartupRoomName(), QString("General")); -} - TEST_F(SettingsDefaultsTest, Tabs_AllTabsOpen_Default) { TabsSettings s(settingsPath, nullptr); @@ -432,23 +400,6 @@ TEST_F(SettingsDefaultsTest, DeckEditor_DefaultDeckEditorType_Default) ASSERT_EQ(s.getDefaultDeckEditorType(), 1); } -TEST_F(SettingsDefaultsTest, DeckEditor_VdeStartupTab_Default) -{ - DeckEditorSettings s(settingsPath, nullptr); - ASSERT_EQ(s.getVdeStartupTab(), VdeStartupTabContext); -} - -TEST_F(SettingsDefaultsTest, DeckEditor_VdeStartupTab_SetAndGet) -{ - DeckEditorSettings s(settingsPath, nullptr); - s.setVdeStartupTab(VdeStartupTabDeckDisplay); - ASSERT_EQ(s.getVdeStartupTab(), VdeStartupTabDeckDisplay); - s.setVdeStartupTab(VdeStartupTabDatabaseDisplay); - ASSERT_EQ(s.getVdeStartupTab(), VdeStartupTabDatabaseDisplay); - s.setVdeStartupTab(VdeStartupTabContext); - ASSERT_EQ(s.getVdeStartupTab(), VdeStartupTabContext); -} - // --- NetworkSettings --- TEST_F(SettingsDefaultsTest, Network_ClientID_Default)