mirror of
https://github.com/Cockatrice/Cockatrice.git
synced 2026-06-25 16:13:54 -07:00
More granular signals, popup for user info.
Took 25 minutes Took 8 seconds Took 16 minutes
This commit is contained in:
parent
8007d40a90
commit
bf04a5b86a
6 changed files with 246 additions and 67 deletions
|
|
@ -329,6 +329,9 @@ void UserInfoPopup::buildUi()
|
|||
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(m_gamesView);
|
||||
|
||||
// Close button — positioned absolutely in the top-right corner
|
||||
|
|
@ -477,6 +480,51 @@ void UserInfoPopup::rebuildActionButtons(const ServerInfo_User &userInfo, bool o
|
|||
m_actionArea->adjustSize();
|
||||
}
|
||||
|
||||
void UserInfoPopup::updateActionButtons(const ServerInfo_User &userInfo, bool online, bool isBuddy, bool isIgnored)
|
||||
{
|
||||
rebuildActionButtons(userInfo, online, isBuddy, isIgnored);
|
||||
adjustSize();
|
||||
}
|
||||
|
||||
void UserInfoPopup::onGamesContextMenu(const QPoint &pos)
|
||||
{
|
||||
const QModelIndex idx = m_gamesView->indexAt(pos);
|
||||
if (!idx.isValid()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const QVariant var = idx.data(PopupRoles::GameData);
|
||||
if (!var.isValid()) {
|
||||
return;
|
||||
}
|
||||
const ServerInfo_Game game = var.value<ServerInfo_Game>();
|
||||
|
||||
QMenu menu(this);
|
||||
menu.setStyleSheet(
|
||||
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"));
|
||||
join->setEnabled(canJoin);
|
||||
|
||||
QAction *spec = nullptr;
|
||||
if (game.spectators_allowed()) {
|
||||
spec = menu.addAction(tr("Spectate"));
|
||||
}
|
||||
|
||||
const QAction *chosen = menu.exec(m_gamesView->viewport()->mapToGlobal(pos));
|
||||
if (!chosen) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (chosen == join) {
|
||||
emit joinGameRequested(game.game_id(), game.room_id(), false);
|
||||
} else if (spec && chosen == spec) {
|
||||
emit joinGameRequested(game.game_id(), game.room_id(), true);
|
||||
}
|
||||
}
|
||||
|
||||
// ── showForUser ───────────────────────────────────────────────────────────────
|
||||
|
||||
void UserInfoPopup::showForUser(const QString &userName,
|
||||
|
|
@ -565,6 +613,15 @@ void UserInfoPopup::onGamesReceived(const Response &r, const QString &forUser)
|
|||
|
||||
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 = m_gamesModel->rowCount();
|
||||
const int visible = qMin(count, maxRows);
|
||||
m_gamesView->setFixedHeight(visible * rowH + 2);
|
||||
m_gamesView->setVerticalScrollBarPolicy(count > maxRows ? Qt::ScrollBarAlwaysOn : Qt::ScrollBarAlwaysOff);
|
||||
|
||||
adjustSize();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -113,11 +113,17 @@ public:
|
|||
return m_currentUser;
|
||||
}
|
||||
|
||||
/** Called when buddy/ignore status changes externally while popup is open. */
|
||||
void updateActionButtons(const ServerInfo_User &userInfo, bool online, bool isBuddy, bool isIgnored);
|
||||
|
||||
signals:
|
||||
void mouseEnteredPopup();
|
||||
void mouseLeftPopup();
|
||||
void closeRequested();
|
||||
|
||||
/** Emitted when the user requests joining or spectating a game in the list. */
|
||||
void joinGameRequested(int gameId, int roomId, bool asSpectator);
|
||||
|
||||
// ── Action signals — connect to UserContextMenu::exec*() ──────────────────
|
||||
void chatRequested(const QString &userName);
|
||||
void detailsRequested(const QString &userName);
|
||||
|
|
@ -147,6 +153,7 @@ protected:
|
|||
private slots:
|
||||
void refreshGames();
|
||||
void onGamesReceived(const Response &r, const QString &forUser);
|
||||
void onGamesContextMenu(const QPoint &pos);
|
||||
|
||||
private:
|
||||
void buildUi();
|
||||
|
|
|
|||
|
|
@ -42,7 +42,9 @@ void UserListManager::handleDisconnect()
|
|||
|
||||
delete ownUserInfo;
|
||||
ownUserInfo = nullptr;
|
||||
emit listsChanged();
|
||||
|
||||
// Full rebuild — all lists are gone
|
||||
emit listReset();
|
||||
}
|
||||
|
||||
void UserListManager::setOwnUserInfo(const ServerInfo_User &userInfo)
|
||||
|
|
@ -64,81 +66,77 @@ void UserListManager::processListUsersResponse(const Response &response)
|
|||
const int userListSize = resp.user_list_size();
|
||||
for (int i = 0; i < userListSize; ++i) {
|
||||
const ServerInfo_User &info = resp.user_list(i);
|
||||
const QString &userName = QString::fromStdString(info.name());
|
||||
onlineUsers.insert(userName, info);
|
||||
onlineUsers.insert(QString::fromStdString(info.name()), info);
|
||||
}
|
||||
emit listsChanged();
|
||||
|
||||
// Bulk load complete — widgets rebuild once from the now-populated map
|
||||
emit listReset();
|
||||
}
|
||||
|
||||
void UserListManager::processUserJoinedEvent(const Event_UserJoined &event)
|
||||
{
|
||||
const auto &info = event.user_info();
|
||||
const QString &userName = QString::fromStdString(info.name());
|
||||
onlineUsers.insert(userName, info);
|
||||
emit listsChanged();
|
||||
const QString name = QString::fromStdString(info.name());
|
||||
onlineUsers.insert(name, info);
|
||||
|
||||
emit userJoinedOnline(info);
|
||||
}
|
||||
|
||||
void UserListManager::processUserLeftEvent(const Event_UserLeft &event)
|
||||
{
|
||||
const auto &userName = QString::fromStdString(event.name());
|
||||
onlineUsers.remove(userName);
|
||||
emit listsChanged();
|
||||
const QString name = QString::fromStdString(event.name());
|
||||
onlineUsers.remove(name);
|
||||
|
||||
emit userLeftOnline(name);
|
||||
}
|
||||
|
||||
void UserListManager::buddyListReceived(const QList<ServerInfo_User> &_buddyList)
|
||||
{
|
||||
for (const auto &user : _buddyList) {
|
||||
const auto &userName = QString::fromStdString(user.name());
|
||||
buddyUsers.insert(userName, user);
|
||||
emit listsChanged();
|
||||
buddyUsers.insert(QString::fromStdString(user.name()), user);
|
||||
}
|
||||
|
||||
// Bulk load — one reset covers all newly added entries
|
||||
emit listReset();
|
||||
}
|
||||
|
||||
void UserListManager::ignoreListReceived(const QList<ServerInfo_User> &_ignoreList)
|
||||
{
|
||||
for (const auto &user : _ignoreList) {
|
||||
const auto &userName = QString::fromStdString(user.name());
|
||||
ignoredUsers.insert(userName, user);
|
||||
emit listsChanged();
|
||||
ignoredUsers.insert(QString::fromStdString(user.name()), user);
|
||||
}
|
||||
|
||||
// Bulk load — one reset covers all newly added entries
|
||||
emit listReset();
|
||||
}
|
||||
|
||||
void UserListManager::processAddToListEvent(const Event_AddToList &event)
|
||||
{
|
||||
const auto &user = event.user_info();
|
||||
const auto &userName = QString::fromStdString(user.name());
|
||||
const QString userName = QString::fromStdString(user.name());
|
||||
const QString listType = QString::fromStdString(event.list_name());
|
||||
|
||||
const auto &userListType = QString::fromStdString(event.list_name());
|
||||
|
||||
QMap<QString, ServerInfo_User> *userMap;
|
||||
if (userListType == "buddy") {
|
||||
userMap = &buddyUsers;
|
||||
} else if (userListType == "ignore") {
|
||||
userMap = &ignoredUsers;
|
||||
} else {
|
||||
return;
|
||||
if (listType == "buddy") {
|
||||
buddyUsers.insert(userName, user);
|
||||
emit addedToBuddyList(user);
|
||||
} else if (listType == "ignore") {
|
||||
ignoredUsers.insert(userName, user);
|
||||
emit addedToIgnoreList(user);
|
||||
}
|
||||
|
||||
userMap->insert(userName, user);
|
||||
emit listsChanged();
|
||||
}
|
||||
|
||||
void UserListManager::processRemoveFromListEvent(const Event_RemoveFromList &event)
|
||||
{
|
||||
const auto &userListType = QString::fromStdString(event.list_name());
|
||||
const auto &userName = QString::fromStdString(event.user_name());
|
||||
const QString listType = QString::fromStdString(event.list_name());
|
||||
const QString userName = QString::fromStdString(event.user_name());
|
||||
|
||||
QMap<QString, ServerInfo_User> *userMap;
|
||||
if (userListType == "buddy") {
|
||||
userMap = &buddyUsers;
|
||||
} else if (userListType == "ignore") {
|
||||
userMap = &ignoredUsers;
|
||||
} else {
|
||||
return;
|
||||
if (listType == "buddy") {
|
||||
buddyUsers.remove(userName);
|
||||
emit removedFromBuddyList(userName);
|
||||
} else if (listType == "ignore") {
|
||||
ignoredUsers.remove(userName);
|
||||
emit removedFromIgnoreList(userName);
|
||||
}
|
||||
|
||||
userMap->remove(userName);
|
||||
emit listsChanged();
|
||||
}
|
||||
|
||||
bool UserListManager::isOwnUserRegistered() const
|
||||
|
|
@ -163,16 +161,9 @@ bool UserListManager::isUserIgnored(const QString &userName) const
|
|||
|
||||
const ServerInfo_User *UserListManager::getOnlineUser(const QString &userName) const
|
||||
{
|
||||
const QString &userNameToMatchLower = userName.toLower();
|
||||
|
||||
const auto it =
|
||||
std::find_if(onlineUsers.begin(), onlineUsers.end(), [&userNameToMatchLower](const ServerInfo_User &user) {
|
||||
return userNameToMatchLower == QString::fromStdString(user.name()).toLower();
|
||||
});
|
||||
|
||||
if (it != onlineUsers.end()) {
|
||||
return &*it;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
const QString lower = userName.toLower();
|
||||
const auto it = std::find_if(onlineUsers.begin(), onlineUsers.end(), [&lower](const ServerInfo_User &user) {
|
||||
return lower == QString::fromStdString(user.name()).toLower();
|
||||
});
|
||||
return it != onlineUsers.end() ? &*it : nullptr;
|
||||
}
|
||||
|
|
@ -73,9 +73,26 @@ public slots:
|
|||
void handleDisconnect();
|
||||
|
||||
signals:
|
||||
void userLeft(const QString &userName);
|
||||
void userJoined(const ServerInfo_User &userInfo);
|
||||
void listsChanged();
|
||||
/**
|
||||
* The entire list needs to be rebuilt from scratch.
|
||||
* Fired on disconnect, reconnect, and initial bulk loads
|
||||
* (Command_ListUsers response, initial buddy/ignore lists).
|
||||
*/
|
||||
void listReset();
|
||||
|
||||
// ── Online user presence ──────────────────────────────────────────────────
|
||||
/** A user came online (or joined the room). Full ServerInfo_User available. */
|
||||
void userJoinedOnline(const ServerInfo_User &user);
|
||||
/** A user went offline (or left the room). */
|
||||
void userLeftOnline(const QString &userName);
|
||||
|
||||
// ── Buddy list mutations (individual, post-login) ─────────────────────────
|
||||
void addedToBuddyList(const ServerInfo_User &user);
|
||||
void removedFromBuddyList(const QString &userName);
|
||||
|
||||
// ── Ignore list mutations (individual, post-login) ────────────────────────
|
||||
void addedToIgnoreList(const ServerInfo_User &user);
|
||||
void removedFromIgnoreList(const QString &userName);
|
||||
};
|
||||
|
||||
#endif // COCKATRICE_USER_LIST_MANAGER_H
|
||||
|
|
|
|||
|
|
@ -533,15 +533,24 @@ UserListWidget::UserListWidget(TabSupervisor *_tabSupervisor,
|
|||
m_popupPinned = true; // pin after showing
|
||||
});
|
||||
|
||||
// Unpin when selection cleared
|
||||
connect(userTree->selectionModel(), &QItemSelectionModel::selectionChanged, this,
|
||||
[this](const QItemSelection &sel, const QItemSelection &) {
|
||||
// if (m_rebuildingTree) return;
|
||||
if (sel.isEmpty() && m_popupPinned) {
|
||||
m_popupPinned = false;
|
||||
hidePopup();
|
||||
}
|
||||
});
|
||||
|
||||
// Hide popup when list scrolls (reference row has moved)
|
||||
connect(userTree->verticalScrollBar(), &QScrollBar::valueChanged, this, [this] {
|
||||
m_showPopupTimer->stop();
|
||||
hidePopup(true);
|
||||
});
|
||||
|
||||
// Forward join requests from popup upward
|
||||
connect(m_userInfoPopup, &UserInfoPopup::joinGameRequested, this, &UserListWidget::joinGameRequested);
|
||||
|
||||
connect(avatarProvider, &UserAvatarProvider::avatarUpdated, this,
|
||||
[this](const QString &) { userTree->viewport()->update(); });
|
||||
connect(cardArtProvider, &UserCardArtProvider::cardArtUpdated, this,
|
||||
|
|
@ -562,11 +571,94 @@ void UserListWidget::bind(UserListManager *mgr)
|
|||
{
|
||||
manager = mgr;
|
||||
|
||||
connect(manager, &UserListManager::listsChanged, this, &UserListWidget::rebuild);
|
||||
// ── Full rebuild: disconnect / reconnect / bulk initial load ──────────────
|
||||
connect(manager, &UserListManager::listReset, this, &UserListWidget::rebuild);
|
||||
|
||||
// ── 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); });
|
||||
}
|
||||
|
||||
// ── Popup button refresh ──────────────────────────────────────────────────
|
||||
// Any buddy/ignore mutation while the popup is open refreshes its buttons
|
||||
auto refreshIfPopupOpen = [this](const QString &name) {
|
||||
if (m_userInfoPopup && m_userInfoPopup->isVisible() && m_userInfoPopup->currentUser() == name) {
|
||||
refreshPopupButtons(name);
|
||||
}
|
||||
};
|
||||
auto refreshCurrentPopup = [refreshIfPopupOpen](const ServerInfo_User &u) {
|
||||
refreshIfPopupOpen(QString::fromStdString(u.name()));
|
||||
};
|
||||
|
||||
connect(manager, &UserListManager::addedToBuddyList, this, refreshCurrentPopup);
|
||||
connect(manager, &UserListManager::removedFromBuddyList, this, refreshIfPopupOpen);
|
||||
connect(manager, &UserListManager::addedToIgnoreList, this, refreshCurrentPopup);
|
||||
connect(manager, &UserListManager::removedFromIgnoreList, this, refreshIfPopupOpen);
|
||||
connect(manager, &UserListManager::userJoinedOnline, this, refreshCurrentPopup);
|
||||
connect(manager, &UserListManager::userLeftOnline, this, refreshIfPopupOpen);
|
||||
|
||||
rebuild();
|
||||
}
|
||||
|
||||
void UserListWidget::refreshPopupButtons(const QString &userName)
|
||||
{
|
||||
UserListTWI *item = users.value(userName);
|
||||
if (!item) {
|
||||
return;
|
||||
}
|
||||
|
||||
const UserListProxy *proxy = tabSupervisor->getUserListManager();
|
||||
const bool online = item->data(0, UserListRoles::Online).toBool();
|
||||
const bool isBuddy = proxy->isUserBuddy(userName);
|
||||
const bool isIgn = proxy->isUserIgnored(userName);
|
||||
|
||||
m_userInfoPopup->updateActionButtons(item->getUserInfo(), online, isBuddy, isIgn);
|
||||
positionPopup(userName); // height may have changed — reposition
|
||||
}
|
||||
|
||||
void UserListWidget::hideEvent(QHideEvent *e)
|
||||
{
|
||||
QGroupBox::hideEvent(e);
|
||||
m_showPopupTimer->stop();
|
||||
m_hidePopupTimer->stop();
|
||||
hidePopup(true);
|
||||
}
|
||||
|
||||
void UserListWidget::applyDisplayMode()
|
||||
{
|
||||
const bool styled = SettingsCache::instance().getStyleUserList();
|
||||
|
|
@ -698,23 +790,32 @@ void UserListWidget::positionPopup(const QString &userName)
|
|||
|
||||
QWidget *vp = userTree->viewport();
|
||||
const QRect itemR = userTree->visualItemRect(item);
|
||||
const QPoint itemTL = vp->mapToGlobal(itemR.topLeft());
|
||||
const QPoint itemBR = vp->mapToGlobal(itemR.bottomRight());
|
||||
const QPoint vpTL = vp->mapToGlobal(vp->rect().topLeft());
|
||||
const QPoint vpTR = vp->mapToGlobal(vp->rect().topRight());
|
||||
|
||||
// Force a fresh size calculation so popH is accurate
|
||||
m_userInfoPopup->adjustSize();
|
||||
const int popW = m_userInfoPopup->width();
|
||||
const int popH = m_userInfoPopup->sizeHint().height();
|
||||
const int popH = m_userInfoPopup->height();
|
||||
const int margin = 12;
|
||||
|
||||
// Go left of the list if there's room, otherwise right
|
||||
int x =
|
||||
(vpTL.x() >= popW + margin) ? vpTL.x() - popW - margin : vp->mapToGlobal(vp->rect().topRight()).x() + margin;
|
||||
|
||||
// Align top with the hovered row, clamped to available screen space
|
||||
const QRect screen = QGuiApplication::primaryScreen()->availableGeometry();
|
||||
int y = itemTL.y();
|
||||
y = qMin(y, screen.bottom() - popH - margin);
|
||||
|
||||
// ── X: left of the list if there's room, otherwise right ─────────────────
|
||||
int x = (vpTL.x() >= popW + margin) ? vpTL.x() - popW - margin : vpTR.x() + margin;
|
||||
x = qBound(screen.left() + margin, x, screen.right() - popW - margin);
|
||||
|
||||
// ── Y: bottom of popup aligns with bottom of hovered row, grows upward ───
|
||||
int y = itemBR.y() - popH;
|
||||
|
||||
// Clamp: never above the screen top
|
||||
y = qMax(y, screen.top() + margin);
|
||||
|
||||
// Clamp: never below the screen bottom (e.g. if the popup is taller
|
||||
// than the space above the row, let it spill downward rather than clip)
|
||||
y = qMin(y, screen.bottom() - popH - margin);
|
||||
|
||||
m_userInfoPopup->move(x, y);
|
||||
}
|
||||
|
||||
|
|
@ -827,6 +928,7 @@ void UserListWidget::processUserInfo(const ServerInfo_User &user, bool online)
|
|||
avatarProvider->requestAvatar(userName);
|
||||
}
|
||||
item->setOnline(online);
|
||||
sortItems();
|
||||
userTree->viewport()->update();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -174,6 +174,7 @@ private:
|
|||
int onlineCount;
|
||||
QString titleStr;
|
||||
void updateCount();
|
||||
void refreshPopupButtons(const QString &userName);
|
||||
private slots:
|
||||
void userClicked(QTreeWidgetItem *item, int column);
|
||||
signals:
|
||||
|
|
@ -182,6 +183,7 @@ signals:
|
|||
void removeBuddy(const QString &userName);
|
||||
void addIgnore(const QString &userName);
|
||||
void removeIgnore(const QString &userName);
|
||||
void joinGameRequested(int gameId, int roomId, bool asSpectator);
|
||||
|
||||
public:
|
||||
UserListWidget(TabSupervisor *_tabSupervisor,
|
||||
|
|
@ -202,6 +204,9 @@ public:
|
|||
}
|
||||
void showContextMenu(const QPoint &pos, const QModelIndex &index);
|
||||
void sortItems();
|
||||
|
||||
protected:
|
||||
void hideEvent(QHideEvent *e) override;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue