mirror of
https://github.com/Cockatrice/Cockatrice.git
synced 2026-06-10 16:24:45 -07:00
[Move refactor] Reparent orphan classes (#6236)
* Move orphaned classes to their correct parent folders. --------- Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
This commit is contained in:
parent
1ef07309d6
commit
d9c65d4ae0
143 changed files with 171 additions and 169 deletions
638
cockatrice/src/interface/widgets/server/chat_view/chat_view.cpp
Normal file
638
cockatrice/src/interface/widgets/server/chat_view/chat_view.cpp
Normal file
|
|
@ -0,0 +1,638 @@
|
|||
#include "chat_view.h"
|
||||
|
||||
#include "../../client/sound_engine.h"
|
||||
#include "../../interface/pixel_map_generator.h"
|
||||
#include "../../tabs/tab_account.h"
|
||||
#include "../user/user_context_menu.h"
|
||||
#include "../user/user_list_manager.h"
|
||||
#include "../user/user_list_proxy.h"
|
||||
|
||||
#include <QApplication>
|
||||
#include <QDateTime>
|
||||
#include <QDesktopServices>
|
||||
#include <QMouseEvent>
|
||||
#include <QScrollBar>
|
||||
#include <libcockatrice/network/server/remote/user_level.h>
|
||||
#include <libcockatrice/settings/cache_settings.h>
|
||||
|
||||
const QColor DEFAULT_MENTION_COLOR = QColor(194, 31, 47);
|
||||
|
||||
UserMessagePosition::UserMessagePosition(QTextCursor &cursor)
|
||||
{
|
||||
block = cursor.block();
|
||||
relativePosition = cursor.position() - block.position();
|
||||
}
|
||||
|
||||
ChatView::ChatView(TabSupervisor *_tabSupervisor, AbstractGame *_game, bool _showTimestamps, QWidget *parent)
|
||||
: QTextBrowser(parent), tabSupervisor(_tabSupervisor), game(_game),
|
||||
userListProxy(_tabSupervisor->getUserListManager()), evenNumber(true), showTimestamps(_showTimestamps),
|
||||
hoveredItemType(HoveredNothing)
|
||||
{
|
||||
if (palette().windowText().color().lightness() > 200) {
|
||||
document()->setDefaultStyleSheet(R"(
|
||||
a { text-decoration: none; color: rgb(71,158,252); }
|
||||
.blue { color: rgb(71,158,252); }
|
||||
)");
|
||||
serverMessageColor = QColor(0xFF, 0x73, 0x83);
|
||||
otherUserColor = otherUserColor.lighter(150);
|
||||
linkColor = QColor(71, 158, 252);
|
||||
} else {
|
||||
document()->setDefaultStyleSheet(R"(
|
||||
a { text-decoration: none; color: blue; }
|
||||
.blue { color: blue }
|
||||
)");
|
||||
linkColor = palette().link().color();
|
||||
}
|
||||
|
||||
userContextMenu = new UserContextMenu(tabSupervisor, this, game);
|
||||
connect(userContextMenu, SIGNAL(openMessageDialog(QString, bool)), this, SIGNAL(openMessageDialog(QString, bool)));
|
||||
|
||||
ownUserName = userListProxy->getOwnUsername();
|
||||
mention = "@" + ownUserName;
|
||||
|
||||
mentionFormat.setFontWeight(QFont::Bold);
|
||||
|
||||
mentionFormatOtherUser.setFontWeight(QFont::Bold);
|
||||
mentionFormatOtherUser.setForeground(linkColor);
|
||||
mentionFormatOtherUser.setAnchor(true);
|
||||
|
||||
viewport()->setCursor(Qt::IBeamCursor);
|
||||
setReadOnly(true);
|
||||
setTextInteractionFlags(Qt::TextSelectableByMouse | Qt::LinksAccessibleByMouse);
|
||||
setOpenLinks(false);
|
||||
connect(this, &ChatView::anchorClicked, this, &ChatView::openLink);
|
||||
}
|
||||
|
||||
void ChatView::retranslateUi()
|
||||
{
|
||||
userContextMenu->retranslateUi();
|
||||
}
|
||||
|
||||
QTextCursor ChatView::prepareBlock(bool same)
|
||||
{
|
||||
lastSender.clear();
|
||||
|
||||
QTextCursor cursor(document());
|
||||
cursor.movePosition(QTextCursor::End);
|
||||
if (same) {
|
||||
cursor.insertHtml("<br>");
|
||||
} else {
|
||||
QTextBlockFormat blockFormat;
|
||||
if ((evenNumber = !evenNumber))
|
||||
blockFormat.setBackground(palette().window());
|
||||
else
|
||||
blockFormat.setBackground(palette().base());
|
||||
|
||||
blockFormat.setForeground(palette().text());
|
||||
blockFormat.setBottomMargin(4);
|
||||
cursor.insertBlock(blockFormat);
|
||||
}
|
||||
|
||||
return cursor;
|
||||
}
|
||||
|
||||
void ChatView::appendHtml(const QString &html)
|
||||
{
|
||||
bool atBottom = verticalScrollBar()->value() >= verticalScrollBar()->maximum();
|
||||
prepareBlock().insertHtml(html);
|
||||
if (atBottom)
|
||||
verticalScrollBar()->setValue(verticalScrollBar()->maximum());
|
||||
}
|
||||
|
||||
void ChatView::appendHtmlServerMessage(const QString &html, bool optionalIsBold, QString optionalFontColor)
|
||||
{
|
||||
bool atBottom = verticalScrollBar()->value() >= verticalScrollBar()->maximum();
|
||||
|
||||
QString htmlText =
|
||||
"<font color=" + ((optionalFontColor.size() > 0) ? optionalFontColor : serverMessageColor.name()) + ">" +
|
||||
QDateTime::currentDateTime().toString("[hh:mm:ss] ") + html + "</font>";
|
||||
|
||||
if (optionalIsBold)
|
||||
htmlText = "<b>" + htmlText + "</b>";
|
||||
|
||||
prepareBlock().insertHtml(htmlText);
|
||||
if (atBottom)
|
||||
verticalScrollBar()->setValue(verticalScrollBar()->maximum());
|
||||
}
|
||||
|
||||
void ChatView::appendCardTag(QTextCursor &cursor, const QString &cardName)
|
||||
{
|
||||
QTextCharFormat oldFormat = cursor.charFormat();
|
||||
QTextCharFormat anchorFormat = oldFormat;
|
||||
anchorFormat.setForeground(linkColor);
|
||||
anchorFormat.setAnchor(true);
|
||||
anchorFormat.setAnchorHref("card://" + cardName);
|
||||
anchorFormat.setFontItalic(true);
|
||||
|
||||
cursor.setCharFormat(anchorFormat);
|
||||
cursor.insertText(cardName);
|
||||
cursor.setCharFormat(oldFormat);
|
||||
}
|
||||
|
||||
void ChatView::appendUrlTag(QTextCursor &cursor, QString url)
|
||||
{
|
||||
if (!url.contains("://"))
|
||||
url.prepend("https://");
|
||||
|
||||
QTextCharFormat oldFormat = cursor.charFormat();
|
||||
QTextCharFormat anchorFormat = oldFormat;
|
||||
anchorFormat.setForeground(linkColor);
|
||||
anchorFormat.setAnchor(true);
|
||||
anchorFormat.setAnchorHref(url);
|
||||
anchorFormat.setUnderlineColor(linkColor);
|
||||
anchorFormat.setFontUnderline(true);
|
||||
|
||||
cursor.setCharFormat(anchorFormat);
|
||||
cursor.insertText(url);
|
||||
cursor.setCharFormat(oldFormat);
|
||||
}
|
||||
|
||||
void ChatView::appendMessage(QString message,
|
||||
RoomMessageTypeFlags messageType,
|
||||
const ServerInfo_User &userInfo,
|
||||
bool playerBold)
|
||||
{
|
||||
const QString userName = QString::fromStdString(userInfo.name());
|
||||
const UserLevelFlags userLevel = UserLevelFlags(userInfo.user_level());
|
||||
|
||||
bool atBottom = verticalScrollBar()->value() >= verticalScrollBar()->maximum();
|
||||
// messageType should be Event_RoomSay::UserMessage though we don't actually check
|
||||
bool isUserMessage = !(userName.toLower() == "servatrice" || userName.isEmpty());
|
||||
bool sameSender = isUserMessage && userName == lastSender;
|
||||
QTextCursor cursor = prepareBlock(sameSender);
|
||||
lastSender = userName;
|
||||
|
||||
// timestamp
|
||||
if (showTimestamps && ((!sameSender && isUserMessage) || userName.toLower() == "servatrice")) {
|
||||
QTextCharFormat timeFormat;
|
||||
timeFormat.setForeground(serverMessageColor);
|
||||
timeFormat.setFontWeight(QFont::Bold);
|
||||
cursor.setCharFormat(timeFormat);
|
||||
cursor.insertText(QDateTime::currentDateTime().toString("[hh:mm:ss] "));
|
||||
}
|
||||
|
||||
// nickname
|
||||
if (isUserMessage) {
|
||||
QTextCharFormat senderFormat;
|
||||
if (userName == ownUserName) {
|
||||
senderFormat.setForeground(QBrush(getCustomMentionColor()));
|
||||
senderFormat.setFontWeight(QFont::Bold);
|
||||
} else {
|
||||
senderFormat.setForeground(QBrush(otherUserColor));
|
||||
if (playerBold) {
|
||||
senderFormat.setFontWeight(QFont::Bold);
|
||||
}
|
||||
}
|
||||
senderFormat.setAnchor(true);
|
||||
senderFormat.setAnchorHref("user://" + QString::number(userLevel) + "_" + userName);
|
||||
if (sameSender) {
|
||||
cursor.insertText(" ");
|
||||
} else {
|
||||
const int pixelSize = QFontInfo(cursor.charFormat().font()).pixelSize();
|
||||
bool isBuddy = userListProxy->isUserBuddy(userName);
|
||||
const QString privLevel = userInfo.has_privlevel() ? QString::fromStdString(userInfo.privlevel()) : "NONE";
|
||||
cursor.insertImage(UserLevelPixmapGenerator::generatePixmap(pixelSize, userLevel, userInfo.pawn_colors(),
|
||||
isBuddy, privLevel)
|
||||
.toImage());
|
||||
cursor.insertText(" ");
|
||||
cursor.setCharFormat(senderFormat);
|
||||
cursor.insertText(userName);
|
||||
cursor.insertText(": ");
|
||||
userMessagePositions[userName].append(cursor);
|
||||
}
|
||||
}
|
||||
|
||||
// use different color for server messages
|
||||
defaultFormat = QTextCharFormat();
|
||||
if (!isUserMessage) {
|
||||
if (messageType == Event_RoomSay::ChatHistory) {
|
||||
defaultFormat.setForeground(Qt::gray); // FIXME : hardcoded color
|
||||
defaultFormat.setFontWeight(QFont::Light);
|
||||
defaultFormat.setFontItalic(true);
|
||||
static const QRegularExpression userNameRegex("^(\\[[^\\]]*\\]\\s)(\\S+):\\s");
|
||||
auto match = userNameRegex.match(message);
|
||||
if (match.hasMatch()) {
|
||||
cursor.setCharFormat(defaultFormat);
|
||||
UserMessagePosition pos(cursor);
|
||||
pos.relativePosition = match.captured(0).length(); // set message start
|
||||
auto before = match.captured(1);
|
||||
auto sentBy = match.captured(2);
|
||||
cursor.insertText(before); // add message timestamp
|
||||
QTextCharFormat senderFormat(defaultFormat);
|
||||
senderFormat.setAnchor(true);
|
||||
// this underscore is important, it is used to add the user level, but in this case the level is
|
||||
// unknown, if the name contains an underscore it would split up the name
|
||||
senderFormat.setAnchorHref("user://_" + sentBy);
|
||||
cursor.setCharFormat(senderFormat);
|
||||
cursor.insertText(sentBy); // add username with href so it shows the menu
|
||||
userMessagePositions[sentBy].append(pos); // save message position
|
||||
message.remove(0, pos.relativePosition - 2); // do not remove semicolon
|
||||
}
|
||||
} else {
|
||||
defaultFormat.setForeground(Qt::darkGreen); // FIXME : hardcoded color
|
||||
defaultFormat.setFontWeight(QFont::Bold);
|
||||
}
|
||||
}
|
||||
cursor.setCharFormat(defaultFormat);
|
||||
|
||||
bool mentionEnabled = SettingsCache::instance().getChatMention();
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0))
|
||||
highlightedWords = SettingsCache::instance().getHighlightWords().split(' ', Qt::SkipEmptyParts);
|
||||
#else
|
||||
highlightedWords = SettingsCache::instance().getHighlightWords().split(' ', QString::SkipEmptyParts);
|
||||
#endif
|
||||
|
||||
// parse the message
|
||||
while (message.size()) {
|
||||
QChar c = message.at(0);
|
||||
switch (c.toLatin1()) {
|
||||
case '[':
|
||||
checkTag(cursor, message);
|
||||
break;
|
||||
case '@':
|
||||
if (mentionEnabled) {
|
||||
checkMention(cursor, message, userName, userLevel);
|
||||
} else {
|
||||
cursor.insertText(c, defaultFormat);
|
||||
message = message.mid(1);
|
||||
}
|
||||
break;
|
||||
case ' ':
|
||||
cursor.insertText(c, defaultFormat);
|
||||
message = message.mid(1);
|
||||
break;
|
||||
default:
|
||||
if (c.isLetterOrNumber()) {
|
||||
checkWord(cursor, message);
|
||||
} else {
|
||||
cursor.insertText(c, defaultFormat);
|
||||
message = message.mid(1);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (atBottom)
|
||||
verticalScrollBar()->setValue(verticalScrollBar()->maximum());
|
||||
}
|
||||
|
||||
void ChatView::checkTag(QTextCursor &cursor, QString &message)
|
||||
{
|
||||
if (message.startsWith("[card]")) {
|
||||
message = message.mid(6);
|
||||
int closeTagIndex = message.indexOf("[/card]");
|
||||
QString cardName = message.left(closeTagIndex);
|
||||
if (closeTagIndex == -1)
|
||||
message.clear();
|
||||
else
|
||||
message = message.mid(closeTagIndex + 7);
|
||||
|
||||
appendCardTag(cursor, cardName);
|
||||
return;
|
||||
}
|
||||
|
||||
if (message.startsWith("[[")) {
|
||||
message = message.mid(2);
|
||||
int closeTagIndex = message.indexOf("]]");
|
||||
QString cardName = message.left(closeTagIndex);
|
||||
if (closeTagIndex == -1)
|
||||
message.clear();
|
||||
else
|
||||
message = message.mid(closeTagIndex + 2);
|
||||
|
||||
appendCardTag(cursor, cardName);
|
||||
return;
|
||||
}
|
||||
|
||||
if (message.startsWith("[url]")) {
|
||||
message = message.mid(5);
|
||||
int closeTagIndex = message.indexOf("[/url]");
|
||||
QString url = message.left(closeTagIndex);
|
||||
if (closeTagIndex == -1)
|
||||
message.clear();
|
||||
else
|
||||
message = message.mid(closeTagIndex + 6);
|
||||
|
||||
appendUrlTag(cursor, url);
|
||||
return;
|
||||
}
|
||||
|
||||
// no valid tag found
|
||||
checkWord(cursor, message);
|
||||
}
|
||||
|
||||
void ChatView::checkMention(QTextCursor &cursor, QString &message, const QString &userName, UserLevelFlags userLevel)
|
||||
{
|
||||
const static auto notALetterOrNumber = QRegularExpression("[^a-zA-Z0-9]");
|
||||
|
||||
int firstSpace = message.indexOf(' ');
|
||||
QString fullMentionUpToSpaceOrEnd = (firstSpace == -1) ? message.mid(1) : message.mid(1, firstSpace - 1);
|
||||
QString mentionIntact = fullMentionUpToSpaceOrEnd;
|
||||
|
||||
while (fullMentionUpToSpaceOrEnd.size()) {
|
||||
const ServerInfo_User *onlineUser = userListProxy->getOnlineUser(fullMentionUpToSpaceOrEnd);
|
||||
if (onlineUser) // Is there a user online named this?
|
||||
{
|
||||
if (ownUserName.toLower() == fullMentionUpToSpaceOrEnd.toLower()) // Is this user you?
|
||||
{
|
||||
// You have received a valid mention!!
|
||||
soundEngine->playSound("chat_mention");
|
||||
mentionFormat.setBackground(QBrush(getCustomMentionColor()));
|
||||
mentionFormat.setForeground(SettingsCache::instance().getChatMentionForeground() ? QBrush(Qt::white)
|
||||
: QBrush(Qt::black));
|
||||
cursor.insertText(mention, mentionFormat);
|
||||
message = message.mid(mention.size());
|
||||
showSystemPopup(userName);
|
||||
} else {
|
||||
QString correctUserName = QString::fromStdString(onlineUser->name());
|
||||
mentionFormatOtherUser.setAnchorHref("user://" + QString::number(onlineUser->user_level()) + "_" +
|
||||
correctUserName);
|
||||
cursor.insertText("@" + correctUserName, mentionFormatOtherUser);
|
||||
|
||||
message = message.mid(correctUserName.size() + 1);
|
||||
}
|
||||
|
||||
cursor.setCharFormat(defaultFormat);
|
||||
return;
|
||||
}
|
||||
|
||||
if (isModeratorSendingGlobal(userLevel, fullMentionUpToSpaceOrEnd)) {
|
||||
// Moderator Sending Global Message
|
||||
soundEngine->playSound("all_mention");
|
||||
mentionFormat.setBackground(QBrush(getCustomMentionColor()));
|
||||
mentionFormat.setForeground(SettingsCache::instance().getChatMentionForeground() ? QBrush(Qt::white)
|
||||
: QBrush(Qt::black));
|
||||
cursor.insertText("@" + fullMentionUpToSpaceOrEnd, mentionFormat);
|
||||
message = message.mid(fullMentionUpToSpaceOrEnd.size() + 1);
|
||||
showSystemPopup(userName);
|
||||
|
||||
cursor.setCharFormat(defaultFormat);
|
||||
return;
|
||||
}
|
||||
|
||||
if (fullMentionUpToSpaceOrEnd.right(1).indexOf(notALetterOrNumber) == -1 ||
|
||||
fullMentionUpToSpaceOrEnd.size() < 2) {
|
||||
cursor.insertText("@" + mentionIntact, defaultFormat);
|
||||
message = message.mid(mentionIntact.size() + 1);
|
||||
cursor.setCharFormat(defaultFormat);
|
||||
return;
|
||||
}
|
||||
|
||||
fullMentionUpToSpaceOrEnd.chop(1);
|
||||
}
|
||||
|
||||
// no valid mention found
|
||||
checkWord(cursor, message);
|
||||
}
|
||||
|
||||
void ChatView::checkWord(QTextCursor &cursor, QString &message)
|
||||
{
|
||||
// extract the first word
|
||||
QString rest;
|
||||
QString fullWordUpToSpaceOrEnd = extractNextWord(message, rest);
|
||||
|
||||
// check urls
|
||||
if (fullWordUpToSpaceOrEnd.startsWith("http://", Qt::CaseInsensitive) ||
|
||||
fullWordUpToSpaceOrEnd.startsWith("https://", Qt::CaseInsensitive) ||
|
||||
fullWordUpToSpaceOrEnd.startsWith("www.", Qt::CaseInsensitive)) {
|
||||
QUrl qUrl(fullWordUpToSpaceOrEnd);
|
||||
if (qUrl.isValid()) {
|
||||
appendUrlTag(cursor, fullWordUpToSpaceOrEnd);
|
||||
cursor.insertText(rest, defaultFormat);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// check word mentions
|
||||
for (const QString &word : highlightedWords) {
|
||||
if (fullWordUpToSpaceOrEnd.compare(word, Qt::CaseInsensitive) == 0) {
|
||||
// You have received a valid mention of custom word!!
|
||||
highlightFormat.setBackground(QBrush(getCustomHighlightColor()));
|
||||
highlightFormat.setForeground(SettingsCache::instance().getChatHighlightForeground() ? QBrush(Qt::white)
|
||||
: QBrush(Qt::black));
|
||||
cursor.insertText(fullWordUpToSpaceOrEnd, highlightFormat);
|
||||
cursor.insertText(rest, defaultFormat);
|
||||
QApplication::alert(this);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// not a special word; just print it
|
||||
cursor.insertText(fullWordUpToSpaceOrEnd + rest, defaultFormat);
|
||||
}
|
||||
|
||||
QString ChatView::extractNextWord(QString &message, QString &rest)
|
||||
{
|
||||
// get the first next space and extract the word
|
||||
QString word;
|
||||
int firstSpace = message.indexOf(' ');
|
||||
if (firstSpace == -1) {
|
||||
word = message;
|
||||
message.clear();
|
||||
} else {
|
||||
word = message.mid(0, firstSpace);
|
||||
message = message.mid(firstSpace);
|
||||
}
|
||||
|
||||
// remove any punctuation from the end and pass it separately
|
||||
for (int len = word.size() - 1; len >= 0; --len) {
|
||||
if (word.at(len).isLetterOrNumber()) {
|
||||
rest = word.mid(len + 1);
|
||||
return word.mid(0, len + 1);
|
||||
}
|
||||
}
|
||||
|
||||
rest = word;
|
||||
return QString();
|
||||
}
|
||||
|
||||
bool ChatView::isModeratorSendingGlobal(QFlags<ServerInfo_User::UserLevelFlag> userLevelFlag, QString message)
|
||||
{
|
||||
int userLevel = QString::number(userLevelFlag).toInt();
|
||||
|
||||
QStringList getAttentionList;
|
||||
getAttentionList << "/all"; // Send a message to all users
|
||||
|
||||
return (getAttentionList.contains(message) &&
|
||||
(userLevel & ServerInfo_User::IsModerator || userLevel & ServerInfo_User::IsAdmin));
|
||||
}
|
||||
|
||||
void ChatView::actMessageClicked()
|
||||
{
|
||||
emit messageClickedSignal();
|
||||
}
|
||||
|
||||
void ChatView::showSystemPopup(const QString &userName)
|
||||
{
|
||||
QApplication::alert(this);
|
||||
if (SettingsCache::instance().getShowMentionPopup()) {
|
||||
emit showMentionPopup(userName);
|
||||
}
|
||||
}
|
||||
|
||||
QColor ChatView::getCustomMentionColor()
|
||||
{
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(6, 4, 0))
|
||||
QColor customColor = QColor::fromString("#" + SettingsCache::instance().getChatMentionColor());
|
||||
#else
|
||||
QColor customColor;
|
||||
customColor.setNamedColor("#" + SettingsCache::instance().getChatMentionColor());
|
||||
#endif
|
||||
return customColor.isValid() ? customColor : DEFAULT_MENTION_COLOR;
|
||||
}
|
||||
|
||||
QColor ChatView::getCustomHighlightColor()
|
||||
{
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(6, 4, 0))
|
||||
QColor customColor = QColor::fromString("#" + SettingsCache::instance().getChatMentionColor());
|
||||
#else
|
||||
QColor customColor;
|
||||
customColor.setNamedColor("#" + SettingsCache::instance().getChatMentionColor());
|
||||
#endif
|
||||
return customColor.isValid() ? customColor : DEFAULT_MENTION_COLOR;
|
||||
}
|
||||
|
||||
void ChatView::clearChat()
|
||||
{
|
||||
document()->clear();
|
||||
lastSender = "";
|
||||
evenNumber = true;
|
||||
}
|
||||
|
||||
void ChatView::redactMessages(const QString &userName, int amount)
|
||||
{
|
||||
auto &messagePositions = userMessagePositions[userName];
|
||||
bool removedLastMessage = false;
|
||||
QTextCursor cursor(document());
|
||||
for (; !messagePositions.isEmpty() && amount != 0; --amount) {
|
||||
auto position = messagePositions.takeLast(); // go backwards from last message
|
||||
cursor.setPosition(position.block.position()); // move to start of block, then continue to start of message
|
||||
cursor.movePosition(QTextCursor::Right, QTextCursor::MoveAnchor, position.relativePosition);
|
||||
cursor.movePosition(QTextCursor::EndOfBlock, QTextCursor::KeepAnchor); // select until end of block
|
||||
cursor.removeSelectedText();
|
||||
// if the cursor is at the end of the text it is possible to add text to this block still
|
||||
removedLastMessage |= cursor.atEnd();
|
||||
// we will readd this position later
|
||||
}
|
||||
if (removedLastMessage) {
|
||||
cursor.movePosition(QTextCursor::End);
|
||||
messagePositions.append(cursor);
|
||||
// note that this message might stay empty, this is not harmful as it will simply remove nothing the next time
|
||||
}
|
||||
}
|
||||
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
|
||||
void ChatView::enterEvent(QEnterEvent * /*event*/)
|
||||
#else
|
||||
void ChatView::enterEvent(QEvent * /*event*/)
|
||||
#endif
|
||||
{
|
||||
setMouseTracking(true);
|
||||
}
|
||||
|
||||
void ChatView::leaveEvent(QEvent * /*event*/)
|
||||
{
|
||||
setMouseTracking(false);
|
||||
}
|
||||
|
||||
QTextFragment ChatView::getFragmentUnderMouse(const QPoint &pos) const
|
||||
{
|
||||
QTextCursor cursor(cursorForPosition(pos));
|
||||
QTextBlock block(cursor.block());
|
||||
QTextBlock::iterator it;
|
||||
for (it = block.begin(); !(it.atEnd()); ++it) {
|
||||
QTextFragment frag = it.fragment();
|
||||
if (frag.contains(cursor.position()))
|
||||
return frag;
|
||||
}
|
||||
return QTextFragment();
|
||||
}
|
||||
|
||||
void ChatView::mouseMoveEvent(QMouseEvent *event)
|
||||
{
|
||||
QString anchorHref = getFragmentUnderMouse(event->pos()).charFormat().anchorHref();
|
||||
if (!anchorHref.isEmpty()) {
|
||||
const int delimiterIndex = anchorHref.indexOf("://");
|
||||
if (delimiterIndex != -1) {
|
||||
const QString scheme = anchorHref.left(delimiterIndex);
|
||||
hoveredContent = anchorHref.mid(delimiterIndex + 3);
|
||||
if (scheme == "card") {
|
||||
hoveredItemType = HoveredCard;
|
||||
emit cardNameHovered(hoveredContent);
|
||||
} else if (scheme == "user")
|
||||
hoveredItemType = HoveredUser;
|
||||
else
|
||||
hoveredItemType = HoveredUrl;
|
||||
viewport()->setCursor(Qt::PointingHandCursor);
|
||||
} else {
|
||||
hoveredItemType = HoveredNothing;
|
||||
viewport()->setCursor(Qt::IBeamCursor);
|
||||
}
|
||||
} else {
|
||||
hoveredItemType = HoveredNothing;
|
||||
viewport()->setCursor(Qt::IBeamCursor);
|
||||
}
|
||||
|
||||
QTextBrowser::mouseMoveEvent(event);
|
||||
}
|
||||
|
||||
void ChatView::mousePressEvent(QMouseEvent *event)
|
||||
{
|
||||
switch (hoveredItemType) {
|
||||
case HoveredCard: {
|
||||
if ((event->button() == Qt::MiddleButton) || (event->button() == Qt::LeftButton))
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
|
||||
emit showCardInfoPopup(event->globalPosition().toPoint(), {hoveredContent});
|
||||
#else
|
||||
emit showCardInfoPopup(event->globalPos(), {hoveredContent});
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
case HoveredUser: {
|
||||
if (event->button() != Qt::MiddleButton) {
|
||||
const int delimiterIndex = hoveredContent.indexOf("_");
|
||||
const QString userName = hoveredContent.mid(delimiterIndex + 1);
|
||||
switch (event->button()) {
|
||||
case Qt::RightButton: {
|
||||
UserLevelFlags userLevel(hoveredContent.left(delimiterIndex).toInt());
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
|
||||
userContextMenu->showContextMenu(event->globalPosition().toPoint(), userName, userLevel, this);
|
||||
#else
|
||||
userContextMenu->showContextMenu(event->globalPos(), userName, userLevel, this);
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
case Qt::LeftButton: {
|
||||
if (event->modifiers() == Qt::ControlModifier) {
|
||||
emit openMessageDialog(userName, true);
|
||||
} else
|
||||
emit addMentionTag("@" + userName);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
QTextBrowser::mousePressEvent(event);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ChatView::mouseReleaseEvent(QMouseEvent *event)
|
||||
{
|
||||
if ((event->button() == Qt::MiddleButton) || (event->button() == Qt::LeftButton))
|
||||
emit deleteCardInfoPopup(QString("_"));
|
||||
|
||||
QTextBrowser::mouseReleaseEvent(event);
|
||||
}
|
||||
|
||||
void ChatView::openLink(const QUrl &link)
|
||||
{
|
||||
if ((link.scheme() == "card") || (link.scheme() == "user"))
|
||||
return;
|
||||
|
||||
QDesktopServices::openUrl(link);
|
||||
}
|
||||
126
cockatrice/src/interface/widgets/server/chat_view/chat_view.h
Normal file
126
cockatrice/src/interface/widgets/server/chat_view/chat_view.h
Normal file
|
|
@ -0,0 +1,126 @@
|
|||
/**
|
||||
* @file chat_view.h
|
||||
* @ingroup NetworkingWidgets
|
||||
* @ingroup Lobby
|
||||
* @brief TODO: Document this.
|
||||
*/
|
||||
|
||||
#ifndef CHATVIEW_H
|
||||
#define CHATVIEW_H
|
||||
|
||||
#include "../../tabs/tab_supervisor.h"
|
||||
#include "../user/user_list_widget.h"
|
||||
|
||||
#include <QAction>
|
||||
#include <QColor>
|
||||
#include <QTextBrowser>
|
||||
#include <QTextCursor>
|
||||
#include <QTextFragment>
|
||||
#include <libcockatrice/network/server/remote/room_message_type.h>
|
||||
#include <libcockatrice/network/server/remote/user_level.h>
|
||||
|
||||
class AbstractGame;
|
||||
class QTextTable;
|
||||
class QMouseEvent;
|
||||
class UserContextMenu;
|
||||
class UserListProxy;
|
||||
|
||||
class UserMessagePosition
|
||||
{
|
||||
public:
|
||||
#if (QT_VERSION < QT_VERSION_CHECK(5, 13, 0))
|
||||
UserMessagePosition() = default; // older qt versions require a default constructor to use in containers
|
||||
#endif
|
||||
UserMessagePosition(QTextCursor &cursor);
|
||||
int relativePosition;
|
||||
QTextBlock block;
|
||||
};
|
||||
|
||||
class ChatView : public QTextBrowser
|
||||
{
|
||||
Q_OBJECT
|
||||
protected:
|
||||
TabSupervisor *const tabSupervisor;
|
||||
AbstractGame *const game;
|
||||
|
||||
private:
|
||||
enum HoveredItemType
|
||||
{
|
||||
HoveredNothing,
|
||||
HoveredUrl,
|
||||
HoveredCard,
|
||||
HoveredUser
|
||||
};
|
||||
const UserListProxy *const userListProxy;
|
||||
UserContextMenu *userContextMenu;
|
||||
QString lastSender;
|
||||
QString ownUserName;
|
||||
QString mention;
|
||||
QTextCharFormat mentionFormat;
|
||||
QTextCharFormat highlightFormat;
|
||||
QTextCharFormat mentionFormatOtherUser;
|
||||
QTextCharFormat defaultFormat;
|
||||
QStringList highlightedWords;
|
||||
bool evenNumber;
|
||||
bool showTimestamps;
|
||||
HoveredItemType hoveredItemType;
|
||||
QString hoveredContent;
|
||||
QAction *messageClicked;
|
||||
QMap<QString, QVector<UserMessagePosition>> userMessagePositions;
|
||||
|
||||
QTextFragment getFragmentUnderMouse(const QPoint &pos) const;
|
||||
QTextCursor prepareBlock(bool same = false);
|
||||
void appendCardTag(QTextCursor &cursor, const QString &cardName);
|
||||
void appendUrlTag(QTextCursor &cursor, QString url);
|
||||
static QColor getCustomMentionColor();
|
||||
static QColor getCustomHighlightColor();
|
||||
void showSystemPopup(const QString &userName);
|
||||
bool isModeratorSendingGlobal(QFlags<ServerInfo_User::UserLevelFlag> userLevelFlag, QString message);
|
||||
void checkTag(QTextCursor &cursor, QString &message);
|
||||
void checkMention(QTextCursor &cursor, QString &message, const QString &userName, UserLevelFlags userLevel);
|
||||
void checkWord(QTextCursor &cursor, QString &message);
|
||||
QString extractNextWord(QString &message, QString &rest);
|
||||
|
||||
QColor otherUserColor = QColor(0, 65, 255); // dark blue
|
||||
QColor serverMessageColor = QColor(0x85, 0x15, 0x15);
|
||||
QColor linkColor;
|
||||
|
||||
private slots:
|
||||
void openLink(const QUrl &link);
|
||||
void actMessageClicked();
|
||||
|
||||
public:
|
||||
ChatView(TabSupervisor *_tabSupervisor, AbstractGame *_game, bool _showTimestamps, QWidget *parent = nullptr);
|
||||
void retranslateUi();
|
||||
void appendHtml(const QString &html);
|
||||
void virtual appendHtmlServerMessage(const QString &html,
|
||||
bool optionalIsBold = false,
|
||||
QString optionalFontColor = QString());
|
||||
void appendMessage(QString message,
|
||||
RoomMessageTypeFlags messageType = {},
|
||||
const ServerInfo_User &userInfo = {},
|
||||
bool playerBold = false);
|
||||
void clearChat();
|
||||
void redactMessages(const QString &userName, int amount);
|
||||
|
||||
protected:
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
|
||||
void enterEvent(QEnterEvent *event) override;
|
||||
#else
|
||||
void enterEvent(QEvent *event) override;
|
||||
#endif
|
||||
void leaveEvent(QEvent *event) override;
|
||||
void mouseMoveEvent(QMouseEvent *event) override;
|
||||
void mousePressEvent(QMouseEvent *event) override;
|
||||
void mouseReleaseEvent(QMouseEvent *event) override;
|
||||
signals:
|
||||
void openMessageDialog(const QString &userName, bool focus);
|
||||
void cardNameHovered(QString cardName);
|
||||
void showCardInfoPopup(const QPoint &pos, const CardRef &cardRef);
|
||||
void deleteCardInfoPopup(QString cardName);
|
||||
void addMentionTag(QString mentionTag);
|
||||
void messageClickedSignal();
|
||||
void showMentionPopup(const QString &userName);
|
||||
};
|
||||
|
||||
#endif
|
||||
393
cockatrice/src/interface/widgets/server/game_selector.cpp
Normal file
393
cockatrice/src/interface/widgets/server/game_selector.cpp
Normal file
|
|
@ -0,0 +1,393 @@
|
|||
#include "game_selector.h"
|
||||
|
||||
#include "../dialogs/dlg_create_game.h"
|
||||
#include "../dialogs/dlg_filter_games.h"
|
||||
#include "../interface/widgets/utility/get_text_with_max.h"
|
||||
#include "../tabs/tab_account.h"
|
||||
#include "../tabs/tab_game.h"
|
||||
#include "../tabs/tab_room.h"
|
||||
#include "../tabs/tab_supervisor.h"
|
||||
#include "games_model.h"
|
||||
#include "user/user_list_manager.h"
|
||||
|
||||
#include <QApplication>
|
||||
#include <QCheckBox>
|
||||
#include <QDebug>
|
||||
#include <QHBoxLayout>
|
||||
#include <QHeaderView>
|
||||
#include <QInputDialog>
|
||||
#include <QLabel>
|
||||
#include <QMessageBox>
|
||||
#include <QPushButton>
|
||||
#include <QTreeView>
|
||||
#include <QVBoxLayout>
|
||||
#include <libcockatrice/network/client/abstract/abstract_client.h>
|
||||
#include <libcockatrice/protocol/pb/response.pb.h>
|
||||
#include <libcockatrice/protocol/pb/room_commands.pb.h>
|
||||
#include <libcockatrice/protocol/pb/serverinfo_game.pb.h>
|
||||
#include <libcockatrice/protocol/pending_command.h>
|
||||
|
||||
GameSelector::GameSelector(AbstractClient *_client,
|
||||
TabSupervisor *_tabSupervisor,
|
||||
TabRoom *_room,
|
||||
const QMap<int, QString> &_rooms,
|
||||
const QMap<int, GameTypeMap> &_gameTypes,
|
||||
const bool restoresettings,
|
||||
const bool _showfilters,
|
||||
QWidget *parent)
|
||||
: QGroupBox(parent), client(_client), tabSupervisor(_tabSupervisor), room(_room), showFilters(_showfilters)
|
||||
{
|
||||
gameListView = new QTreeView;
|
||||
gameListView->setContextMenuPolicy(Qt::CustomContextMenu);
|
||||
connect(gameListView, &QTreeView::customContextMenuRequested, this, &GameSelector::customContextMenu);
|
||||
|
||||
gameListModel = new GamesModel(_rooms, _gameTypes, this);
|
||||
if (showFilters) {
|
||||
gameListProxyModel = new GamesProxyModel(this, tabSupervisor->getUserListManager());
|
||||
gameListProxyModel->setSourceModel(gameListModel);
|
||||
gameListProxyModel->setSortCaseSensitivity(Qt::CaseInsensitive);
|
||||
gameListView->setModel(gameListProxyModel);
|
||||
} else {
|
||||
gameListView->setModel(gameListModel);
|
||||
}
|
||||
gameListView->setIconSize(QSize(13, 13));
|
||||
gameListView->setSortingEnabled(true);
|
||||
gameListView->sortByColumn(gameListModel->startTimeColIndex(), Qt::AscendingOrder);
|
||||
gameListView->setAlternatingRowColors(true);
|
||||
gameListView->setRootIsDecorated(true);
|
||||
// game created width
|
||||
gameListView->setColumnWidth(1, gameListView->columnWidth(2) * 0.7);
|
||||
// players width
|
||||
gameListView->resizeColumnToContents(6);
|
||||
// description width
|
||||
gameListView->setColumnWidth(2, gameListView->columnWidth(2) * 1.7);
|
||||
// creator width
|
||||
gameListView->setColumnWidth(3, gameListView->columnWidth(3) * 1.2);
|
||||
// game type width
|
||||
gameListView->setColumnWidth(4, gameListView->columnWidth(4) * 1.4);
|
||||
if (_room)
|
||||
gameListView->header()->hideSection(gameListModel->roomColIndex());
|
||||
|
||||
if (room)
|
||||
gameTypeMap = gameListModel->getGameTypes().value(room->getRoomId());
|
||||
|
||||
if (showFilters && restoresettings)
|
||||
gameListProxyModel->loadFilterParameters(gameTypeMap);
|
||||
|
||||
gameListView->header()->setSectionResizeMode(0, QHeaderView::ResizeToContents);
|
||||
|
||||
filterButton = new QPushButton;
|
||||
filterButton->setIcon(QPixmap("theme:icons/search"));
|
||||
connect(filterButton, &QPushButton::clicked, this, &GameSelector::actSetFilter);
|
||||
clearFilterButton = new QPushButton;
|
||||
clearFilterButton->setIcon(QPixmap("theme:icons/clearsearch"));
|
||||
bool filtersSetToDefault = showFilters && gameListProxyModel->areFilterParametersSetToDefaults();
|
||||
clearFilterButton->setEnabled(!filtersSetToDefault);
|
||||
connect(clearFilterButton, &QPushButton::clicked, this, &GameSelector::actClearFilter);
|
||||
|
||||
if (room) {
|
||||
createButton = new QPushButton;
|
||||
connect(createButton, &QPushButton::clicked, this, &GameSelector::actCreate);
|
||||
} else {
|
||||
createButton = nullptr;
|
||||
}
|
||||
joinButton = new QPushButton;
|
||||
spectateButton = new QPushButton;
|
||||
|
||||
QHBoxLayout *buttonLayout = new QHBoxLayout;
|
||||
if (showFilters) {
|
||||
buttonLayout->addWidget(filterButton);
|
||||
buttonLayout->addWidget(clearFilterButton);
|
||||
}
|
||||
buttonLayout->addStretch();
|
||||
if (room)
|
||||
buttonLayout->addWidget(createButton);
|
||||
buttonLayout->addWidget(joinButton);
|
||||
buttonLayout->addWidget(spectateButton);
|
||||
buttonLayout->setAlignment(Qt::AlignTop);
|
||||
|
||||
QVBoxLayout *mainLayout = new QVBoxLayout;
|
||||
mainLayout->addWidget(gameListView);
|
||||
mainLayout->addLayout(buttonLayout);
|
||||
|
||||
retranslateUi();
|
||||
setLayout(mainLayout);
|
||||
|
||||
setMinimumWidth((qreal)(gameListView->columnWidth(0) * gameListModel->columnCount()) / 1.5);
|
||||
setMinimumHeight(200);
|
||||
|
||||
connect(joinButton, &QPushButton::clicked, this, &GameSelector::actJoin);
|
||||
connect(spectateButton, &QPushButton::clicked, this, &GameSelector::actSpectate);
|
||||
connect(gameListView->selectionModel(), &QItemSelectionModel::currentRowChanged, this,
|
||||
&GameSelector::actSelectedGameChanged);
|
||||
connect(gameListView, &QTreeView::activated, this, &GameSelector::actJoin);
|
||||
|
||||
connect(client, &AbstractClient::ignoreListReceived, this, &GameSelector::ignoreListReceived);
|
||||
connect(client, &AbstractClient::addToListEventReceived, this, &GameSelector::processAddToListEvent);
|
||||
connect(client, &AbstractClient::removeFromListEventReceived, this, &GameSelector::processRemoveFromListEvent);
|
||||
}
|
||||
|
||||
void GameSelector::ignoreListReceived(const QList<ServerInfo_User> &)
|
||||
{
|
||||
gameListProxyModel->refresh();
|
||||
}
|
||||
|
||||
void GameSelector::processAddToListEvent(const Event_AddToList &event)
|
||||
{
|
||||
if (event.list_name() == "ignore") {
|
||||
gameListProxyModel->refresh();
|
||||
}
|
||||
updateTitle();
|
||||
}
|
||||
|
||||
void GameSelector::processRemoveFromListEvent(const Event_RemoveFromList &event)
|
||||
{
|
||||
if (event.list_name() == "ignore") {
|
||||
gameListProxyModel->refresh();
|
||||
}
|
||||
updateTitle();
|
||||
}
|
||||
|
||||
void GameSelector::actSetFilter()
|
||||
{
|
||||
DlgFilterGames dlg(gameTypeMap, gameListProxyModel, this);
|
||||
|
||||
if (!dlg.exec())
|
||||
return;
|
||||
|
||||
gameListProxyModel->setHideBuddiesOnlyGames(dlg.getHideBuddiesOnlyGames());
|
||||
gameListProxyModel->setHideFullGames(dlg.getHideFullGames());
|
||||
gameListProxyModel->setHideGamesThatStarted(dlg.getHideGamesThatStarted());
|
||||
gameListProxyModel->setHidePasswordProtectedGames(dlg.getHidePasswordProtectedGames());
|
||||
gameListProxyModel->setHideIgnoredUserGames(dlg.getHideIgnoredUserGames());
|
||||
gameListProxyModel->setHideNotBuddyCreatedGames(dlg.getHideNotBuddyCreatedGames());
|
||||
gameListProxyModel->setHideOpenDecklistGames(dlg.getHideOpenDecklistGames());
|
||||
gameListProxyModel->setGameNameFilter(dlg.getGameNameFilter());
|
||||
gameListProxyModel->setCreatorNameFilter(dlg.getCreatorNameFilter());
|
||||
gameListProxyModel->setGameTypeFilter(dlg.getGameTypeFilter());
|
||||
gameListProxyModel->setMaxPlayersFilter(dlg.getMaxPlayersFilterMin(), dlg.getMaxPlayersFilterMax());
|
||||
gameListProxyModel->setMaxGameAge(dlg.getMaxGameAge());
|
||||
gameListProxyModel->setShowOnlyIfSpectatorsCanWatch(dlg.getShowOnlyIfSpectatorsCanWatch());
|
||||
gameListProxyModel->setShowSpectatorPasswordProtected(dlg.getShowSpectatorPasswordProtected());
|
||||
gameListProxyModel->setShowOnlyIfSpectatorsCanChat(dlg.getShowOnlyIfSpectatorsCanChat());
|
||||
gameListProxyModel->setShowOnlyIfSpectatorsCanSeeHands(dlg.getShowOnlyIfSpectatorsCanSeeHands());
|
||||
gameListProxyModel->saveFilterParameters(gameTypeMap);
|
||||
|
||||
clearFilterButton->setEnabled(!gameListProxyModel->areFilterParametersSetToDefaults());
|
||||
|
||||
updateTitle();
|
||||
}
|
||||
|
||||
void GameSelector::actClearFilter()
|
||||
{
|
||||
clearFilterButton->setEnabled(false);
|
||||
|
||||
gameListProxyModel->resetFilterParameters();
|
||||
gameListProxyModel->saveFilterParameters(gameTypeMap);
|
||||
|
||||
updateTitle();
|
||||
}
|
||||
|
||||
void GameSelector::actCreate()
|
||||
{
|
||||
if (room == nullptr) {
|
||||
qWarning() << "Attempted to create game, but the room was null";
|
||||
return;
|
||||
}
|
||||
|
||||
DlgCreateGame dlg(room, room->getGameTypes(), this);
|
||||
dlg.exec();
|
||||
updateTitle();
|
||||
}
|
||||
|
||||
void GameSelector::checkResponse(const Response &response)
|
||||
{
|
||||
// NB: We re-enable buttons for the currently selected game, which may not
|
||||
// be the same game as the one for which we are processing a response.
|
||||
// This could lead to situations where we join a game, select a different
|
||||
// game, join the new game, then receive the response for the original
|
||||
// join, which would re-activate the buttons for the second game too soon.
|
||||
// However, that is better than doing things the other ways, because then
|
||||
// the response to the first game could lock us out of join/join as
|
||||
// spectator for the second game!
|
||||
//
|
||||
// Ideally we should have a way to figure out if the current game is the
|
||||
// same as the one we are getting a response for, but it's probably not
|
||||
// worth the trouble.
|
||||
enableButtons();
|
||||
|
||||
switch (response.response_code()) {
|
||||
case Response::RespNotInRoom:
|
||||
QMessageBox::critical(this, tr("Error"), tr("Please join the appropriate room first."));
|
||||
break;
|
||||
case Response::RespWrongPassword:
|
||||
QMessageBox::critical(this, tr("Error"), tr("Wrong password."));
|
||||
break;
|
||||
case Response::RespSpectatorsNotAllowed:
|
||||
QMessageBox::critical(this, tr("Error"), tr("Spectators are not allowed in this game."));
|
||||
break;
|
||||
case Response::RespGameFull:
|
||||
QMessageBox::critical(this, tr("Error"), tr("The game is already full."));
|
||||
break;
|
||||
case Response::RespNameNotFound:
|
||||
QMessageBox::critical(this, tr("Error"), tr("The game does not exist any more."));
|
||||
break;
|
||||
case Response::RespUserLevelTooLow:
|
||||
QMessageBox::critical(this, tr("Error"), tr("This game is only open to registered users."));
|
||||
break;
|
||||
case Response::RespOnlyBuddies:
|
||||
QMessageBox::critical(this, tr("Error"), tr("This game is only open to its creator's buddies."));
|
||||
break;
|
||||
case Response::RespInIgnoreList:
|
||||
QMessageBox::critical(this, tr("Error"), tr("You are being ignored by the creator of this game."));
|
||||
break;
|
||||
default:;
|
||||
}
|
||||
}
|
||||
|
||||
void GameSelector::actJoin()
|
||||
{
|
||||
return joinGame(false);
|
||||
}
|
||||
|
||||
void GameSelector::actSpectate()
|
||||
{
|
||||
return joinGame(true);
|
||||
}
|
||||
|
||||
void GameSelector::customContextMenu(const QPoint &point)
|
||||
{
|
||||
const auto &index = gameListView->indexAt(point);
|
||||
if (!index.isValid()) {
|
||||
return;
|
||||
}
|
||||
|
||||
QAction joinGame(tr("Join Game"));
|
||||
connect(&joinGame, &QAction::triggered, this, &GameSelector::actJoin);
|
||||
|
||||
QAction spectateGame(tr("Spectate Game"));
|
||||
connect(&spectateGame, &QAction::triggered, this, &GameSelector::actSpectate);
|
||||
|
||||
QAction getGameInfo(tr("Game Information"));
|
||||
connect(&getGameInfo, &QAction::triggered, this, [=, this]() {
|
||||
const ServerInfo_Game &gameInfo = gameListModel->getGame(index.data(Qt::UserRole).toInt());
|
||||
const QMap<int, QString> &gameTypes = gameListModel->getGameTypes().value(gameInfo.room_id());
|
||||
|
||||
DlgCreateGame dlg(gameInfo, gameTypes, this);
|
||||
dlg.exec();
|
||||
});
|
||||
|
||||
QMenu menu;
|
||||
menu.addAction(&joinGame);
|
||||
menu.addAction(&spectateGame);
|
||||
menu.addAction(&getGameInfo);
|
||||
menu.exec(gameListView->mapToGlobal(point));
|
||||
}
|
||||
|
||||
void GameSelector::joinGame(const bool isSpectator)
|
||||
{
|
||||
QModelIndex ind = gameListView->currentIndex();
|
||||
if (!ind.isValid()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const ServerInfo_Game &game = gameListModel->getGame(ind.data(Qt::UserRole).toInt());
|
||||
if (tabSupervisor->switchToGameTabIfAlreadyExists(game.game_id())) {
|
||||
return;
|
||||
}
|
||||
|
||||
bool spectator = isSpectator || game.player_count() == game.max_players();
|
||||
|
||||
bool overrideRestrictions = !tabSupervisor->getAdminLocked();
|
||||
QString password;
|
||||
if (game.with_password() && !(spectator && !game.spectators_need_password()) && !overrideRestrictions) {
|
||||
bool ok;
|
||||
password = getTextWithMax(this, tr("Join game"), tr("Password:"), QLineEdit::Password, QString(), &ok);
|
||||
if (!ok) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Command_JoinGame cmd;
|
||||
cmd.set_game_id(game.game_id());
|
||||
cmd.set_password(password.toStdString());
|
||||
cmd.set_spectator(spectator);
|
||||
cmd.set_override_restrictions(overrideRestrictions);
|
||||
cmd.set_join_as_judge((QApplication::keyboardModifiers() & Qt::ShiftModifier) != 0);
|
||||
|
||||
TabRoom *r = tabSupervisor->getRoomTabs().value(game.room_id());
|
||||
if (!r) {
|
||||
QMessageBox::critical(this, tr("Error"), tr("Please join the respective room first."));
|
||||
return;
|
||||
}
|
||||
|
||||
PendingCommand *pend = r->prepareRoomCommand(cmd);
|
||||
connect(pend, &PendingCommand::finished, this, &GameSelector::checkResponse);
|
||||
r->sendRoomCommand(pend);
|
||||
|
||||
disableButtons();
|
||||
}
|
||||
|
||||
void GameSelector::disableButtons()
|
||||
{
|
||||
if (createButton)
|
||||
createButton->setEnabled(false);
|
||||
|
||||
joinButton->setEnabled(false);
|
||||
spectateButton->setEnabled(false);
|
||||
}
|
||||
|
||||
void GameSelector::enableButtons()
|
||||
{
|
||||
if (createButton)
|
||||
createButton->setEnabled(true);
|
||||
|
||||
// Enable buttons for the currently selected game
|
||||
enableButtonsForIndex(gameListView->currentIndex());
|
||||
}
|
||||
|
||||
void GameSelector::enableButtonsForIndex(const QModelIndex ¤t)
|
||||
{
|
||||
if (!current.isValid())
|
||||
return;
|
||||
|
||||
const ServerInfo_Game &game = gameListModel->getGame(current.data(Qt::UserRole).toInt());
|
||||
bool overrideRestrictions = !tabSupervisor->getAdminLocked();
|
||||
|
||||
spectateButton->setEnabled(game.spectators_allowed() || overrideRestrictions);
|
||||
joinButton->setEnabled(game.player_count() < game.max_players() || overrideRestrictions);
|
||||
}
|
||||
|
||||
void GameSelector::retranslateUi()
|
||||
{
|
||||
filterButton->setText(tr("&Filter games"));
|
||||
clearFilterButton->setText(tr("C&lear filter"));
|
||||
if (createButton)
|
||||
createButton->setText(tr("C&reate"));
|
||||
joinButton->setText(tr("&Join"));
|
||||
spectateButton->setText(tr("J&oin as spectator"));
|
||||
|
||||
updateTitle();
|
||||
}
|
||||
|
||||
void GameSelector::processGameInfo(const ServerInfo_Game &info)
|
||||
{
|
||||
gameListModel->updateGameList(info);
|
||||
updateTitle();
|
||||
}
|
||||
|
||||
void GameSelector::actSelectedGameChanged(const QModelIndex ¤t, const QModelIndex & /* previous */)
|
||||
{
|
||||
enableButtonsForIndex(current);
|
||||
}
|
||||
|
||||
void GameSelector::updateTitle()
|
||||
{
|
||||
if (showFilters) {
|
||||
const int totalGames = gameListModel->rowCount();
|
||||
const int shownGames = totalGames - gameListProxyModel->getNumFilteredGames();
|
||||
setTitle(tr("Games shown: %1 / %2").arg(shownGames).arg(totalGames));
|
||||
} else {
|
||||
setTitle(tr("Games"));
|
||||
}
|
||||
}
|
||||
81
cockatrice/src/interface/widgets/server/game_selector.h
Normal file
81
cockatrice/src/interface/widgets/server/game_selector.h
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
/**
|
||||
* @file game_selector.h
|
||||
* @ingroup Lobby
|
||||
* @brief TODO: Document this.
|
||||
*/
|
||||
|
||||
#ifndef GAMESELECTOR_H
|
||||
#define GAMESELECTOR_H
|
||||
|
||||
#include "game_type_map.h"
|
||||
|
||||
#include <QGroupBox>
|
||||
#include <libcockatrice/protocol/pb/commands.pb.h>
|
||||
#include <libcockatrice/protocol/pb/event_add_to_list.pb.h>
|
||||
#include <libcockatrice/protocol/pb/event_remove_from_list.pb.h>
|
||||
|
||||
class QTreeView;
|
||||
class GamesModel;
|
||||
class GamesProxyModel;
|
||||
class QPushButton;
|
||||
class QCheckBox;
|
||||
class QLabel;
|
||||
class AbstractClient;
|
||||
class TabSupervisor;
|
||||
class TabRoom;
|
||||
class ServerInfo_Game;
|
||||
class Response;
|
||||
|
||||
class GameSelector : public QGroupBox
|
||||
{
|
||||
Q_OBJECT
|
||||
private slots:
|
||||
void actSetFilter();
|
||||
void actClearFilter();
|
||||
void actCreate();
|
||||
|
||||
void actJoin();
|
||||
void actSpectate();
|
||||
void customContextMenu(const QPoint &point);
|
||||
|
||||
void actSelectedGameChanged(const QModelIndex ¤t, const QModelIndex &previous);
|
||||
void checkResponse(const Response &response);
|
||||
|
||||
void ignoreListReceived(const QList<ServerInfo_User> &_ignoreList);
|
||||
void processAddToListEvent(const Event_AddToList &event);
|
||||
void processRemoveFromListEvent(const Event_RemoveFromList &event);
|
||||
signals:
|
||||
void gameJoined(int gameId);
|
||||
|
||||
private:
|
||||
AbstractClient *client;
|
||||
TabSupervisor *tabSupervisor;
|
||||
TabRoom *room;
|
||||
|
||||
QTreeView *gameListView;
|
||||
GamesModel *gameListModel;
|
||||
GamesProxyModel *gameListProxyModel;
|
||||
QPushButton *filterButton, *clearFilterButton, *createButton, *joinButton, *spectateButton;
|
||||
const bool showFilters;
|
||||
GameTypeMap gameTypeMap;
|
||||
|
||||
void updateTitle();
|
||||
void disableButtons();
|
||||
void enableButtons();
|
||||
void enableButtonsForIndex(const QModelIndex ¤t);
|
||||
void joinGame(const bool isSpectator);
|
||||
|
||||
public:
|
||||
GameSelector(AbstractClient *_client,
|
||||
TabSupervisor *_tabSupervisor,
|
||||
TabRoom *_room,
|
||||
const QMap<int, QString> &_rooms,
|
||||
const QMap<int, GameTypeMap> &_gameTypes,
|
||||
const bool restoresettings,
|
||||
const bool _showfilters,
|
||||
QWidget *parent = nullptr);
|
||||
void retranslateUi();
|
||||
void processGameInfo(const ServerInfo_Game &info);
|
||||
};
|
||||
|
||||
#endif
|
||||
8
cockatrice/src/interface/widgets/server/game_type_map.h
Normal file
8
cockatrice/src/interface/widgets/server/game_type_map.h
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
#ifndef GAMETYPEMAP_H
|
||||
#define GAMETYPEMAP_H
|
||||
|
||||
#include <QMap>
|
||||
|
||||
typedef QMap<int, QString> GameTypeMap;
|
||||
|
||||
#endif
|
||||
581
cockatrice/src/interface/widgets/server/games_model.cpp
Normal file
581
cockatrice/src/interface/widgets/server/games_model.cpp
Normal file
|
|
@ -0,0 +1,581 @@
|
|||
#include "games_model.h"
|
||||
|
||||
#include "../interface/pixel_map_generator.h"
|
||||
#include "../tabs/tab_account.h"
|
||||
#include "user/user_list_manager.h"
|
||||
#include "user/user_list_widget.h"
|
||||
|
||||
#include <QDateTime>
|
||||
#include <QDebug>
|
||||
#include <QIcon>
|
||||
#include <QStringList>
|
||||
#include <QTime>
|
||||
#include <QTimeZone>
|
||||
#include <libcockatrice/protocol/pb/serverinfo_game.pb.h>
|
||||
#include <libcockatrice/settings/cache_settings.h>
|
||||
|
||||
enum GameListColumn
|
||||
{
|
||||
ROOM,
|
||||
CREATED,
|
||||
DESCRIPTION,
|
||||
CREATOR,
|
||||
GAME_TYPE,
|
||||
RESTRICTIONS,
|
||||
PLAYERS,
|
||||
SPECTATORS
|
||||
};
|
||||
|
||||
const int DEFAULT_MAX_PLAYERS_MIN = 1;
|
||||
const int DEFAULT_MAX_PLAYERS_MAX = 99;
|
||||
constexpr QTime DEFAULT_MAX_GAME_AGE = QTime();
|
||||
|
||||
const QString GamesModel::getGameCreatedString(const int secs)
|
||||
{
|
||||
static const QTime zeroTime{0, 0};
|
||||
static const int halfHourSecs = zeroTime.secsTo(QTime(1, 0)) / 2;
|
||||
static const int halfMinSecs = zeroTime.secsTo(QTime(0, 1)) / 2;
|
||||
static const int wrapSeconds = zeroTime.secsTo(zeroTime.addSecs(-halfHourSecs)); // round up
|
||||
|
||||
if (secs >= wrapSeconds) { // QTime wraps after a day
|
||||
return tr(">1 day");
|
||||
}
|
||||
|
||||
QTime total = zeroTime.addSecs(secs);
|
||||
QTime totalRounded = total.addSecs(halfMinSecs); // round up
|
||||
QString form;
|
||||
int amount;
|
||||
if (totalRounded.hour()) {
|
||||
amount = total.addSecs(halfHourSecs).hour(); // round up separately
|
||||
form = tr("%1%2 hr", "short age in hours", amount);
|
||||
} else if (total.minute() < 2) { // games are new during their first minute
|
||||
return tr("new");
|
||||
} else {
|
||||
amount = totalRounded.minute();
|
||||
form = tr("%1%2 min", "short age in minutes", amount);
|
||||
}
|
||||
|
||||
for (int aggregate : {40, 20, 10, 5}) { // floor to values in this list
|
||||
if (amount >= aggregate) {
|
||||
return form.arg(">").arg(aggregate);
|
||||
}
|
||||
}
|
||||
return form.arg("").arg(amount);
|
||||
}
|
||||
|
||||
GamesModel::GamesModel(const QMap<int, QString> &_rooms, const QMap<int, GameTypeMap> &_gameTypes, QObject *parent)
|
||||
: QAbstractTableModel(parent), rooms(_rooms), gameTypes(_gameTypes)
|
||||
{
|
||||
}
|
||||
|
||||
QVariant GamesModel::data(const QModelIndex &index, int role) const
|
||||
{
|
||||
if (!index.isValid())
|
||||
return QVariant();
|
||||
if (role == Qt::UserRole)
|
||||
return index.row();
|
||||
if (role != Qt::DisplayRole && role != SORT_ROLE && role != Qt::DecorationRole && role != Qt::TextAlignmentRole)
|
||||
return QVariant();
|
||||
if ((index.row() >= gameList.size()) || (index.column() >= columnCount()))
|
||||
return QVariant();
|
||||
|
||||
const ServerInfo_Game &gameentry = gameList[index.row()];
|
||||
switch (index.column()) {
|
||||
case ROOM:
|
||||
return rooms.value(gameentry.room_id());
|
||||
case CREATED: {
|
||||
switch (role) {
|
||||
case Qt::DisplayRole: {
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 7, 0)
|
||||
auto then = QDateTime::fromSecsSinceEpoch(gameentry.start_time(), QTimeZone::UTC);
|
||||
#else
|
||||
auto then = QDateTime::fromSecsSinceEpoch(gameentry.start_time(), Qt::UTC);
|
||||
#endif
|
||||
int secs = then.secsTo(QDateTime::currentDateTimeUtc());
|
||||
return getGameCreatedString(secs);
|
||||
}
|
||||
case SORT_ROLE:
|
||||
return QVariant(-static_cast<qint64>(gameentry.start_time()));
|
||||
case Qt::TextAlignmentRole:
|
||||
return Qt::AlignCenter;
|
||||
default:
|
||||
return QVariant();
|
||||
}
|
||||
}
|
||||
case DESCRIPTION:
|
||||
switch (role) {
|
||||
case SORT_ROLE:
|
||||
case Qt::DisplayRole:
|
||||
return QString::fromStdString(gameentry.description());
|
||||
case Qt::TextAlignmentRole:
|
||||
return Qt::AlignLeft;
|
||||
default:
|
||||
return QVariant();
|
||||
}
|
||||
case CREATOR: {
|
||||
switch (role) {
|
||||
case SORT_ROLE:
|
||||
case Qt::DisplayRole:
|
||||
return QString::fromStdString(gameentry.creator_info().name());
|
||||
case Qt::DecorationRole: {
|
||||
return UserLevelPixmapGenerator::generateIcon(
|
||||
13, UserLevelFlags(gameentry.creator_info().user_level()),
|
||||
gameentry.creator_info().pawn_colors(), false,
|
||||
QString::fromStdString(gameentry.creator_info().privlevel()));
|
||||
}
|
||||
default:
|
||||
return QVariant();
|
||||
}
|
||||
}
|
||||
case GAME_TYPE:
|
||||
switch (role) {
|
||||
case SORT_ROLE:
|
||||
case Qt::DisplayRole: {
|
||||
QStringList result;
|
||||
GameTypeMap gameTypeMap = gameTypes.value(gameentry.room_id());
|
||||
for (int i = gameentry.game_types_size() - 1; i >= 0; --i)
|
||||
result.append(gameTypeMap.value(gameentry.game_types(i)));
|
||||
return result.join(", ");
|
||||
}
|
||||
case Qt::TextAlignmentRole:
|
||||
return Qt::AlignLeft;
|
||||
default:
|
||||
return QVariant();
|
||||
}
|
||||
case RESTRICTIONS:
|
||||
switch (role) {
|
||||
case SORT_ROLE:
|
||||
case Qt::DisplayRole: {
|
||||
QStringList result;
|
||||
if (gameentry.with_password())
|
||||
result.append(tr("password"));
|
||||
if (gameentry.only_buddies())
|
||||
result.append(tr("buddies only"));
|
||||
if (gameentry.only_registered())
|
||||
result.append(tr("reg. users only"));
|
||||
if (gameentry.share_decklists_on_load())
|
||||
result.append(tr("open decklists"));
|
||||
return result.join(", ");
|
||||
}
|
||||
case Qt::DecorationRole: {
|
||||
return gameentry.with_password() ? QIcon(LockPixmapGenerator::generatePixmap(13)) : QVariant();
|
||||
case Qt::TextAlignmentRole:
|
||||
return Qt::AlignLeft;
|
||||
default:
|
||||
return QVariant();
|
||||
}
|
||||
}
|
||||
case PLAYERS:
|
||||
switch (role) {
|
||||
case SORT_ROLE:
|
||||
case Qt::DisplayRole:
|
||||
return QString("%1/%2").arg(gameentry.player_count()).arg(gameentry.max_players());
|
||||
case Qt::TextAlignmentRole:
|
||||
return Qt::AlignCenter;
|
||||
default:
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
case SPECTATORS:
|
||||
switch (role) {
|
||||
case SORT_ROLE:
|
||||
case Qt::DisplayRole: {
|
||||
if (gameentry.spectators_allowed()) {
|
||||
QString result;
|
||||
result.append(QString::number(gameentry.spectators_count()));
|
||||
|
||||
if (gameentry.spectators_can_chat() && gameentry.spectators_omniscient()) {
|
||||
result.append(" (")
|
||||
.append(tr("can chat"))
|
||||
.append(" & ")
|
||||
.append(tr("see hands"))
|
||||
.append(")");
|
||||
} else if (gameentry.spectators_can_chat()) {
|
||||
result.append(" (").append(tr("can chat")).append(")");
|
||||
} else if (gameentry.spectators_omniscient()) {
|
||||
result.append(" (").append(tr("can see hands")).append(")");
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
return QVariant(tr("not allowed"));
|
||||
}
|
||||
case Qt::TextAlignmentRole:
|
||||
return Qt::AlignLeft;
|
||||
default:
|
||||
return QVariant();
|
||||
}
|
||||
default:
|
||||
return QVariant();
|
||||
}
|
||||
}
|
||||
|
||||
QVariant GamesModel::headerData(int section, Qt::Orientation /*orientation*/, int role) const
|
||||
{
|
||||
if ((role != Qt::DisplayRole) && (role != Qt::TextAlignmentRole))
|
||||
return QVariant();
|
||||
switch (section) {
|
||||
case ROOM:
|
||||
return tr("Room");
|
||||
case CREATED: {
|
||||
switch (role) {
|
||||
case Qt::DisplayRole:
|
||||
return tr("Age");
|
||||
case Qt::TextAlignmentRole:
|
||||
return Qt::AlignCenter;
|
||||
default:
|
||||
return QVariant();
|
||||
}
|
||||
}
|
||||
case DESCRIPTION:
|
||||
return tr("Description");
|
||||
case CREATOR:
|
||||
return tr("Creator");
|
||||
case GAME_TYPE:
|
||||
return tr("Type");
|
||||
case RESTRICTIONS:
|
||||
return tr("Restrictions");
|
||||
case PLAYERS: {
|
||||
switch (role) {
|
||||
case Qt::DisplayRole:
|
||||
return tr("Players");
|
||||
case Qt::TextAlignmentRole:
|
||||
return Qt::AlignCenter;
|
||||
default:
|
||||
return QVariant();
|
||||
}
|
||||
}
|
||||
case SPECTATORS:
|
||||
return tr("Spectators");
|
||||
default:
|
||||
return QVariant();
|
||||
}
|
||||
}
|
||||
|
||||
const ServerInfo_Game &GamesModel::getGame(int row)
|
||||
{
|
||||
Q_ASSERT(row < gameList.size());
|
||||
return gameList[row];
|
||||
}
|
||||
|
||||
void GamesModel::updateGameList(const ServerInfo_Game &game)
|
||||
{
|
||||
for (int i = 0; i < gameList.size(); i++) {
|
||||
if (gameList[i].game_id() == game.game_id()) {
|
||||
if (game.closed()) {
|
||||
beginRemoveRows(QModelIndex(), i, i);
|
||||
gameList.removeAt(i);
|
||||
endRemoveRows();
|
||||
} else {
|
||||
gameList[i].MergeFrom(game);
|
||||
emit dataChanged(index(i, 0), index(i, NUM_COLS - 1));
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
beginInsertRows(QModelIndex(), gameList.size(), gameList.size());
|
||||
gameList.append(game);
|
||||
endInsertRows();
|
||||
}
|
||||
|
||||
GamesProxyModel::GamesProxyModel(QObject *parent, const UserListProxy *_userListProxy)
|
||||
: QSortFilterProxyModel(parent), userListProxy(_userListProxy)
|
||||
{
|
||||
resetFilterParameters();
|
||||
setSortRole(GamesModel::SORT_ROLE);
|
||||
setDynamicSortFilter(true);
|
||||
}
|
||||
|
||||
void GamesProxyModel::setHideBuddiesOnlyGames(bool _showBuddiesOnlyGames)
|
||||
{
|
||||
hideBuddiesOnlyGames = _showBuddiesOnlyGames;
|
||||
invalidateFilter();
|
||||
}
|
||||
|
||||
void GamesProxyModel::setHideIgnoredUserGames(bool _hideIgnoredUserGames)
|
||||
{
|
||||
hideIgnoredUserGames = _hideIgnoredUserGames;
|
||||
invalidateFilter();
|
||||
}
|
||||
|
||||
void GamesProxyModel::setHideFullGames(bool _showFullGames)
|
||||
{
|
||||
hideFullGames = _showFullGames;
|
||||
invalidateFilter();
|
||||
}
|
||||
|
||||
void GamesProxyModel::setHideGamesThatStarted(bool _showGamesThatStarted)
|
||||
{
|
||||
hideGamesThatStarted = _showGamesThatStarted;
|
||||
invalidateFilter();
|
||||
}
|
||||
|
||||
void GamesProxyModel::setHidePasswordProtectedGames(bool _showPasswordProtectedGames)
|
||||
{
|
||||
hidePasswordProtectedGames = _showPasswordProtectedGames;
|
||||
invalidateFilter();
|
||||
}
|
||||
|
||||
void GamesProxyModel::setHideNotBuddyCreatedGames(bool value)
|
||||
{
|
||||
hideNotBuddyCreatedGames = value;
|
||||
invalidateFilter();
|
||||
}
|
||||
|
||||
void GamesProxyModel::setHideOpenDecklistGames(bool _hideOpenDecklistGames)
|
||||
{
|
||||
hideOpenDecklistGames = _hideOpenDecklistGames;
|
||||
invalidateFilter();
|
||||
}
|
||||
|
||||
void GamesProxyModel::setGameNameFilter(const QString &_gameNameFilter)
|
||||
{
|
||||
gameNameFilter = _gameNameFilter;
|
||||
invalidateFilter();
|
||||
}
|
||||
|
||||
void GamesProxyModel::setCreatorNameFilter(const QString &_creatorNameFilter)
|
||||
{
|
||||
creatorNameFilter = _creatorNameFilter;
|
||||
invalidateFilter();
|
||||
}
|
||||
|
||||
void GamesProxyModel::setGameTypeFilter(const QSet<int> &_gameTypeFilter)
|
||||
{
|
||||
gameTypeFilter = _gameTypeFilter;
|
||||
invalidateFilter();
|
||||
}
|
||||
|
||||
void GamesProxyModel::setMaxPlayersFilter(int _maxPlayersFilterMin, int _maxPlayersFilterMax)
|
||||
{
|
||||
maxPlayersFilterMin = _maxPlayersFilterMin;
|
||||
maxPlayersFilterMax = _maxPlayersFilterMax;
|
||||
invalidateFilter();
|
||||
}
|
||||
|
||||
void GamesProxyModel::setMaxGameAge(const QTime &_maxGameAge)
|
||||
{
|
||||
maxGameAge = _maxGameAge;
|
||||
invalidateFilter();
|
||||
}
|
||||
|
||||
void GamesProxyModel::setShowOnlyIfSpectatorsCanWatch(bool _showOnlyIfSpectatorsCanWatch)
|
||||
{
|
||||
showOnlyIfSpectatorsCanWatch = _showOnlyIfSpectatorsCanWatch;
|
||||
invalidateFilter();
|
||||
}
|
||||
|
||||
void GamesProxyModel::setShowSpectatorPasswordProtected(bool _showSpectatorPasswordProtected)
|
||||
{
|
||||
showSpectatorPasswordProtected = _showSpectatorPasswordProtected;
|
||||
invalidateFilter();
|
||||
}
|
||||
|
||||
void GamesProxyModel::setShowOnlyIfSpectatorsCanChat(bool _showOnlyIfSpectatorsCanChat)
|
||||
{
|
||||
showOnlyIfSpectatorsCanChat = _showOnlyIfSpectatorsCanChat;
|
||||
invalidateFilter();
|
||||
}
|
||||
|
||||
void GamesProxyModel::setShowOnlyIfSpectatorsCanSeeHands(bool _showOnlyIfSpectatorsCanSeeHands)
|
||||
{
|
||||
showOnlyIfSpectatorsCanSeeHands = _showOnlyIfSpectatorsCanSeeHands;
|
||||
invalidateFilter();
|
||||
}
|
||||
|
||||
int GamesProxyModel::getNumFilteredGames() const
|
||||
{
|
||||
GamesModel *model = qobject_cast<GamesModel *>(sourceModel());
|
||||
if (!model)
|
||||
return 0;
|
||||
|
||||
int numFilteredGames = 0;
|
||||
for (int row = 0; row < model->rowCount(); ++row) {
|
||||
if (!filterAcceptsRow(row)) {
|
||||
++numFilteredGames;
|
||||
}
|
||||
}
|
||||
return numFilteredGames;
|
||||
}
|
||||
|
||||
void GamesProxyModel::resetFilterParameters()
|
||||
{
|
||||
hideFullGames = false;
|
||||
hideGamesThatStarted = false;
|
||||
hidePasswordProtectedGames = false;
|
||||
hideBuddiesOnlyGames = false;
|
||||
hideIgnoredUserGames = false;
|
||||
hideNotBuddyCreatedGames = false;
|
||||
hideOpenDecklistGames = false;
|
||||
gameNameFilter = QString();
|
||||
creatorNameFilter = QString();
|
||||
gameTypeFilter.clear();
|
||||
maxPlayersFilterMin = DEFAULT_MAX_PLAYERS_MIN;
|
||||
maxPlayersFilterMax = DEFAULT_MAX_PLAYERS_MAX;
|
||||
maxGameAge = DEFAULT_MAX_GAME_AGE;
|
||||
showOnlyIfSpectatorsCanWatch = false;
|
||||
showSpectatorPasswordProtected = false;
|
||||
showOnlyIfSpectatorsCanChat = false;
|
||||
showOnlyIfSpectatorsCanSeeHands = false;
|
||||
|
||||
invalidateFilter();
|
||||
}
|
||||
|
||||
bool GamesProxyModel::areFilterParametersSetToDefaults() const
|
||||
{
|
||||
return !hideFullGames && !hideGamesThatStarted && !hidePasswordProtectedGames && !hideBuddiesOnlyGames &&
|
||||
!hideOpenDecklistGames && !hideIgnoredUserGames && !hideNotBuddyCreatedGames && gameNameFilter.isEmpty() &&
|
||||
creatorNameFilter.isEmpty() && gameTypeFilter.isEmpty() && maxPlayersFilterMin == DEFAULT_MAX_PLAYERS_MIN &&
|
||||
maxPlayersFilterMax == DEFAULT_MAX_PLAYERS_MAX && maxGameAge == DEFAULT_MAX_GAME_AGE &&
|
||||
!showOnlyIfSpectatorsCanWatch && !showSpectatorPasswordProtected && !showOnlyIfSpectatorsCanChat &&
|
||||
!showOnlyIfSpectatorsCanSeeHands;
|
||||
}
|
||||
|
||||
void GamesProxyModel::loadFilterParameters(const QMap<int, QString> &allGameTypes)
|
||||
{
|
||||
GameFiltersSettings &gameFilters = SettingsCache::instance().gameFilters();
|
||||
hideFullGames = gameFilters.isHideFullGames();
|
||||
hideGamesThatStarted = gameFilters.isHideGamesThatStarted();
|
||||
hidePasswordProtectedGames = gameFilters.isHidePasswordProtectedGames();
|
||||
hideIgnoredUserGames = gameFilters.isHideIgnoredUserGames();
|
||||
hideBuddiesOnlyGames = gameFilters.isHideBuddiesOnlyGames();
|
||||
hideNotBuddyCreatedGames = gameFilters.isHideNotBuddyCreatedGames();
|
||||
hideOpenDecklistGames = gameFilters.isHideOpenDecklistGames();
|
||||
gameNameFilter = gameFilters.getGameNameFilter();
|
||||
creatorNameFilter = gameFilters.getCreatorNameFilter();
|
||||
maxPlayersFilterMin = gameFilters.getMinPlayers();
|
||||
maxPlayersFilterMax = gameFilters.getMaxPlayers();
|
||||
maxGameAge = gameFilters.getMaxGameAge();
|
||||
showOnlyIfSpectatorsCanWatch = gameFilters.isShowOnlyIfSpectatorsCanWatch();
|
||||
showSpectatorPasswordProtected = gameFilters.isShowSpectatorPasswordProtected();
|
||||
showOnlyIfSpectatorsCanChat = gameFilters.isShowOnlyIfSpectatorsCanChat();
|
||||
showOnlyIfSpectatorsCanSeeHands = gameFilters.isShowOnlyIfSpectatorsCanSeeHands();
|
||||
|
||||
QMapIterator<int, QString> gameTypesIterator(allGameTypes);
|
||||
while (gameTypesIterator.hasNext()) {
|
||||
gameTypesIterator.next();
|
||||
if (gameFilters.isGameTypeEnabled(gameTypesIterator.value())) {
|
||||
gameTypeFilter.insert(gameTypesIterator.key());
|
||||
}
|
||||
}
|
||||
|
||||
invalidateFilter();
|
||||
}
|
||||
|
||||
void GamesProxyModel::saveFilterParameters(const QMap<int, QString> &allGameTypes)
|
||||
{
|
||||
GameFiltersSettings &gameFilters = SettingsCache::instance().gameFilters();
|
||||
gameFilters.setHideBuddiesOnlyGames(hideBuddiesOnlyGames);
|
||||
gameFilters.setHideFullGames(hideFullGames);
|
||||
gameFilters.setHideGamesThatStarted(hideGamesThatStarted);
|
||||
gameFilters.setHidePasswordProtectedGames(hidePasswordProtectedGames);
|
||||
gameFilters.setHideIgnoredUserGames(hideIgnoredUserGames);
|
||||
gameFilters.setHideNotBuddyCreatedGames(hideNotBuddyCreatedGames);
|
||||
gameFilters.setHideOpenDecklistGames(hideOpenDecklistGames);
|
||||
gameFilters.setGameNameFilter(gameNameFilter);
|
||||
gameFilters.setCreatorNameFilter(creatorNameFilter);
|
||||
|
||||
QMapIterator<int, QString> gameTypeIterator(allGameTypes);
|
||||
while (gameTypeIterator.hasNext()) {
|
||||
gameTypeIterator.next();
|
||||
bool enabled = gameTypeFilter.contains(gameTypeIterator.key());
|
||||
gameFilters.setGameTypeEnabled(gameTypeIterator.value(), enabled);
|
||||
}
|
||||
|
||||
gameFilters.setMinPlayers(maxPlayersFilterMin);
|
||||
gameFilters.setMaxPlayers(maxPlayersFilterMax);
|
||||
gameFilters.setMaxGameAge(maxGameAge);
|
||||
|
||||
gameFilters.setShowOnlyIfSpectatorsCanWatch(showOnlyIfSpectatorsCanWatch);
|
||||
gameFilters.setShowSpectatorPasswordProtected(showSpectatorPasswordProtected);
|
||||
gameFilters.setShowOnlyIfSpectatorsCanChat(showOnlyIfSpectatorsCanChat);
|
||||
gameFilters.setShowOnlyIfSpectatorsCanSeeHands(showOnlyIfSpectatorsCanSeeHands);
|
||||
}
|
||||
|
||||
bool GamesProxyModel::filterAcceptsRow(int sourceRow, const QModelIndex & /*sourceParent*/) const
|
||||
{
|
||||
return filterAcceptsRow(sourceRow);
|
||||
}
|
||||
|
||||
bool GamesProxyModel::filterAcceptsRow(int sourceRow) const
|
||||
{
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 7, 0)
|
||||
static const QDate epochDate = QDateTime::fromSecsSinceEpoch(0, QTimeZone::UTC).date();
|
||||
#elif (QT_VERSION >= QT_VERSION_CHECK(5, 8, 0))
|
||||
static const QDate epochDate = QDateTime::fromSecsSinceEpoch(0, Qt::UTC).date();
|
||||
#else
|
||||
static const QDate epochDate = QDateTime::fromTime_t(0, Qt::UTC).date();
|
||||
#endif
|
||||
auto *model = qobject_cast<GamesModel *>(sourceModel());
|
||||
if (!model)
|
||||
return false;
|
||||
|
||||
const ServerInfo_Game &game = model->getGame(sourceRow);
|
||||
|
||||
if (hideBuddiesOnlyGames && game.only_buddies()) {
|
||||
return false;
|
||||
}
|
||||
if (hideOpenDecklistGames && game.share_decklists_on_load()) {
|
||||
return false;
|
||||
}
|
||||
if (hideIgnoredUserGames && userListProxy->isUserIgnored(QString::fromStdString(game.creator_info().name()))) {
|
||||
return false;
|
||||
}
|
||||
if (hideNotBuddyCreatedGames && !userListProxy->isUserBuddy(QString::fromStdString(game.creator_info().name()))) {
|
||||
return false;
|
||||
}
|
||||
if (hideFullGames && game.player_count() == game.max_players())
|
||||
return false;
|
||||
if (hideGamesThatStarted && game.started())
|
||||
return false;
|
||||
if (!userListProxy->isOwnUserRegistered())
|
||||
if (game.only_registered())
|
||||
return false;
|
||||
if (hidePasswordProtectedGames && game.with_password())
|
||||
return false;
|
||||
if (!gameNameFilter.isEmpty())
|
||||
if (!QString::fromStdString(game.description()).contains(gameNameFilter, Qt::CaseInsensitive))
|
||||
return false;
|
||||
if (!creatorNameFilter.isEmpty())
|
||||
if (!QString::fromStdString(game.creator_info().name()).contains(creatorNameFilter, Qt::CaseInsensitive))
|
||||
return false;
|
||||
|
||||
QSet<int> gameTypes;
|
||||
for (int i = 0; i < game.game_types_size(); ++i)
|
||||
gameTypes.insert(game.game_types(i));
|
||||
if (!gameTypeFilter.isEmpty() && gameTypes.intersect(gameTypeFilter).isEmpty())
|
||||
return false;
|
||||
|
||||
if (game.max_players() < maxPlayersFilterMin)
|
||||
return false;
|
||||
if (game.max_players() > maxPlayersFilterMax)
|
||||
return false;
|
||||
|
||||
if (maxGameAge.isValid()) {
|
||||
QDateTime now = QDateTime::currentDateTimeUtc();
|
||||
qint64 signed_start_time = game.start_time(); // cast to 64 bit value to allow signed value
|
||||
QDateTime total = now.addSecs(-signed_start_time); // a 32 bit value would wrap at 2038-1-19
|
||||
// games shouldn't have negative ages but we'll not filter them
|
||||
// because qtime wraps after a day we consider all games older than a day to be too old
|
||||
if (total.isValid() && total.date() >= epochDate && (total.date() > epochDate || total.time() > maxGameAge)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (showOnlyIfSpectatorsCanWatch) {
|
||||
if (!game.spectators_allowed())
|
||||
return false;
|
||||
if (!showSpectatorPasswordProtected && game.spectators_need_password())
|
||||
return false;
|
||||
if (showOnlyIfSpectatorsCanChat && !game.spectators_can_chat())
|
||||
return false;
|
||||
if (showOnlyIfSpectatorsCanSeeHands && !game.spectators_omniscient())
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void GamesProxyModel::refresh()
|
||||
{
|
||||
invalidateFilter();
|
||||
}
|
||||
198
cockatrice/src/interface/widgets/server/games_model.h
Normal file
198
cockatrice/src/interface/widgets/server/games_model.h
Normal file
|
|
@ -0,0 +1,198 @@
|
|||
/**
|
||||
* @file games_model.h
|
||||
* @ingroup Lobby
|
||||
* @brief TODO: Document this.
|
||||
*/
|
||||
|
||||
#ifndef GAMESMODEL_H
|
||||
#define GAMESMODEL_H
|
||||
|
||||
#include "game_type_map.h"
|
||||
|
||||
#include <QAbstractTableModel>
|
||||
#include <QList>
|
||||
#include <QSet>
|
||||
#include <QSortFilterProxyModel>
|
||||
#include <QStringList>
|
||||
#include <QTime>
|
||||
#include <libcockatrice/protocol/pb/serverinfo_game.pb.h>
|
||||
|
||||
class UserListProxy;
|
||||
|
||||
class GamesModel : public QAbstractTableModel
|
||||
{
|
||||
Q_OBJECT
|
||||
private:
|
||||
QList<ServerInfo_Game> gameList;
|
||||
QMap<int, QString> rooms;
|
||||
QMap<int, GameTypeMap> gameTypes;
|
||||
|
||||
static const int NUM_COLS = 8;
|
||||
|
||||
public:
|
||||
static const int SORT_ROLE = Qt::UserRole + 1;
|
||||
|
||||
GamesModel(const QMap<int, QString> &_rooms, const QMap<int, GameTypeMap> &_gameTypes, QObject *parent = nullptr);
|
||||
int rowCount(const QModelIndex &parent = QModelIndex()) const override
|
||||
{
|
||||
return parent.isValid() ? 0 : gameList.size();
|
||||
}
|
||||
int columnCount(const QModelIndex & /*parent*/ = QModelIndex()) const override
|
||||
{
|
||||
return NUM_COLS;
|
||||
}
|
||||
QVariant data(const QModelIndex &index, int role) const override;
|
||||
QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
|
||||
static const QString getGameCreatedString(const int secs);
|
||||
const ServerInfo_Game &getGame(int row);
|
||||
|
||||
/**
|
||||
* Update game list with a (possibly new) game.
|
||||
*/
|
||||
void updateGameList(const ServerInfo_Game &game);
|
||||
|
||||
int roomColIndex()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
int startTimeColIndex()
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
const QMap<int, GameTypeMap> &getGameTypes()
|
||||
{
|
||||
return gameTypes;
|
||||
}
|
||||
};
|
||||
|
||||
class ServerInfo_User;
|
||||
|
||||
class GamesProxyModel : public QSortFilterProxyModel
|
||||
{
|
||||
Q_OBJECT
|
||||
private:
|
||||
const UserListProxy *userListProxy;
|
||||
|
||||
// If adding any additional filters, make sure to update:
|
||||
// - GamesProxyModel()
|
||||
// - resetFilterParameters()
|
||||
// - areFilterParametersSetToDefaults()
|
||||
// - loadFilterParameters()
|
||||
// - saveFilterParameters()
|
||||
// - filterAcceptsRow()
|
||||
bool hideBuddiesOnlyGames;
|
||||
bool hideIgnoredUserGames;
|
||||
bool hideFullGames;
|
||||
bool hideGamesThatStarted;
|
||||
bool hidePasswordProtectedGames;
|
||||
bool hideNotBuddyCreatedGames;
|
||||
bool hideOpenDecklistGames;
|
||||
QString gameNameFilter, creatorNameFilter;
|
||||
QSet<int> gameTypeFilter;
|
||||
quint32 maxPlayersFilterMin, maxPlayersFilterMax;
|
||||
QTime maxGameAge;
|
||||
bool showOnlyIfSpectatorsCanWatch, showSpectatorPasswordProtected, showOnlyIfSpectatorsCanChat,
|
||||
showOnlyIfSpectatorsCanSeeHands;
|
||||
|
||||
public:
|
||||
explicit GamesProxyModel(QObject *parent = nullptr, const UserListProxy *_userListProxy = nullptr);
|
||||
|
||||
bool getHideBuddiesOnlyGames() const
|
||||
{
|
||||
return hideBuddiesOnlyGames;
|
||||
}
|
||||
void setHideBuddiesOnlyGames(bool _showBuddiesOnlyGames);
|
||||
bool getHideIgnoredUserGames() const
|
||||
{
|
||||
return hideIgnoredUserGames;
|
||||
}
|
||||
void setHideIgnoredUserGames(bool _hideIgnoredUserGames);
|
||||
bool getHideFullGames() const
|
||||
{
|
||||
return hideFullGames;
|
||||
}
|
||||
void setHideFullGames(bool _showFullGames);
|
||||
bool getHideGamesThatStarted() const
|
||||
{
|
||||
return hideGamesThatStarted;
|
||||
}
|
||||
void setHideGamesThatStarted(bool _showGamesThatStarted);
|
||||
bool getHidePasswordProtectedGames() const
|
||||
{
|
||||
return hidePasswordProtectedGames;
|
||||
}
|
||||
void setHidePasswordProtectedGames(bool _showPasswordProtectedGames);
|
||||
bool getHideNotBuddyCreatedGames() const
|
||||
{
|
||||
return hideNotBuddyCreatedGames;
|
||||
}
|
||||
void setHideNotBuddyCreatedGames(bool value);
|
||||
bool getHideOpenDecklistGames() const
|
||||
{
|
||||
return hideOpenDecklistGames;
|
||||
}
|
||||
void setHideOpenDecklistGames(bool _hideOpenDecklistGames);
|
||||
QString getGameNameFilter() const
|
||||
{
|
||||
return gameNameFilter;
|
||||
}
|
||||
void setGameNameFilter(const QString &_gameNameFilter);
|
||||
QString getCreatorNameFilter() const
|
||||
{
|
||||
return creatorNameFilter;
|
||||
}
|
||||
void setCreatorNameFilter(const QString &_creatorNameFilter);
|
||||
QSet<int> getGameTypeFilter() const
|
||||
{
|
||||
return gameTypeFilter;
|
||||
}
|
||||
void setGameTypeFilter(const QSet<int> &_gameTypeFilter);
|
||||
int getMaxPlayersFilterMin() const
|
||||
{
|
||||
return maxPlayersFilterMin;
|
||||
}
|
||||
int getMaxPlayersFilterMax() const
|
||||
{
|
||||
return maxPlayersFilterMax;
|
||||
}
|
||||
void setMaxPlayersFilter(int _maxPlayersFilterMin, int _maxPlayersFilterMax);
|
||||
const QTime &getMaxGameAge() const
|
||||
{
|
||||
return maxGameAge;
|
||||
}
|
||||
void setMaxGameAge(const QTime &_maxGameAge);
|
||||
bool getShowOnlyIfSpectatorsCanWatch() const
|
||||
{
|
||||
return showOnlyIfSpectatorsCanWatch;
|
||||
}
|
||||
void setShowOnlyIfSpectatorsCanWatch(bool _showOnlyIfSpectatorsCanWatch);
|
||||
bool getShowSpectatorPasswordProtected() const
|
||||
{
|
||||
return showSpectatorPasswordProtected;
|
||||
}
|
||||
void setShowSpectatorPasswordProtected(bool _showSpectatorPasswordProtected);
|
||||
bool getShowOnlyIfSpectatorsCanChat() const
|
||||
{
|
||||
return showOnlyIfSpectatorsCanChat;
|
||||
}
|
||||
void setShowOnlyIfSpectatorsCanChat(bool _showOnlyIfSpectatorsCanChat);
|
||||
bool getShowOnlyIfSpectatorsCanSeeHands() const
|
||||
{
|
||||
return showOnlyIfSpectatorsCanSeeHands;
|
||||
}
|
||||
void setShowOnlyIfSpectatorsCanSeeHands(bool _showOnlyIfSpectatorsCanSeeHands);
|
||||
|
||||
int getNumFilteredGames() const;
|
||||
void resetFilterParameters();
|
||||
bool areFilterParametersSetToDefaults() const;
|
||||
void loadFilterParameters(const QMap<int, QString> &allGameTypes);
|
||||
void saveFilterParameters(const QMap<int, QString> &allGameTypes);
|
||||
void refresh();
|
||||
|
||||
protected:
|
||||
bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override;
|
||||
bool filterAcceptsRow(int sourceRow) const;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,109 @@
|
|||
#include "handle_public_servers.h"
|
||||
|
||||
#include <QJsonDocument>
|
||||
#include <QMessageBox>
|
||||
#include <QNetworkAccessManager>
|
||||
#include <QNetworkReply>
|
||||
#include <QUrl>
|
||||
#include <libcockatrice/settings/cache_settings.h>
|
||||
|
||||
#define PUBLIC_SERVERS_JSON "https://cockatrice.github.io/public-servers.json"
|
||||
|
||||
HandlePublicServers::HandlePublicServers(QObject *parent)
|
||||
: QObject(parent), nam(new QNetworkAccessManager(this)), reply(nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
void HandlePublicServers::downloadPublicServers()
|
||||
{
|
||||
QUrl url(QString(PUBLIC_SERVERS_JSON));
|
||||
reply = nam->get(QNetworkRequest(url));
|
||||
connect(reply, &QNetworkReply::finished, this, &HandlePublicServers::actFinishParsingDownloadedData);
|
||||
}
|
||||
|
||||
void HandlePublicServers::actFinishParsingDownloadedData()
|
||||
{
|
||||
reply = dynamic_cast<QNetworkReply *>(sender());
|
||||
QNetworkReply::NetworkError errorCode = reply->error();
|
||||
|
||||
if (errorCode == QNetworkReply::NoError) {
|
||||
// Get current saved hosts
|
||||
UserConnection_Information uci;
|
||||
savedHostList = uci.getServerInfo();
|
||||
|
||||
// Downloaded data from GitHub
|
||||
QJsonParseError parseError{};
|
||||
QJsonDocument jsonResponse = QJsonDocument::fromJson(reply->readAll(), &parseError);
|
||||
if (parseError.error == QJsonParseError::NoError) {
|
||||
QVariantMap jsonMap = jsonResponse.toVariant().toMap();
|
||||
updateServerINISettings(jsonMap);
|
||||
} else {
|
||||
qDebug() << "[PUBLIC SERVER HANDLER]"
|
||||
<< "JSON Parsing Error:" << parseError.errorString();
|
||||
emit sigPublicServersDownloadedUnsuccessfully(errorCode);
|
||||
}
|
||||
} else {
|
||||
qDebug() << "[PUBLIC SERVER HANDLER]"
|
||||
<< "Error Downloading Public Servers" << errorCode;
|
||||
emit sigPublicServersDownloadedUnsuccessfully(errorCode);
|
||||
}
|
||||
|
||||
reply->deleteLater(); // After an emit() occurs, this object will be deleted
|
||||
}
|
||||
|
||||
void HandlePublicServers::updateServerINISettings(QMap<QString, QVariant> jsonMap)
|
||||
{
|
||||
// Servers available
|
||||
auto publicServersJSONList = jsonMap["servers"].toList();
|
||||
|
||||
for (const auto &server : publicServersJSONList) {
|
||||
// Data inside one server at a time
|
||||
// server: [{ ... }, ..., { ... }]
|
||||
const auto serverMap = server.toMap();
|
||||
|
||||
QString serverAddress = serverMap["host"].toString();
|
||||
|
||||
if (serverMap["isInactive"].toBool()) {
|
||||
publicServersToRemove.append(serverAddress);
|
||||
continue;
|
||||
}
|
||||
|
||||
QString serverName = serverMap["name"].toString();
|
||||
QString serverPort = serverMap["port"].toString();
|
||||
QString serverSite = serverMap["site"].toString();
|
||||
|
||||
if (serverMap.contains("websocketPort")) {
|
||||
serverPort = serverMap["websocketPort"].toString();
|
||||
}
|
||||
|
||||
bool serverFound = false;
|
||||
for (const auto &iter : savedHostList) {
|
||||
// If the URL/IP matches
|
||||
if (iter.second.getServer() == serverAddress) {
|
||||
serverFound = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (serverFound) {
|
||||
SettingsCache::instance().servers().updateExistingServerWithoutLoss(serverName, serverAddress, serverPort,
|
||||
serverSite);
|
||||
} else {
|
||||
SettingsCache::instance().servers().addNewServer(serverName, serverAddress, serverPort, "", "", false,
|
||||
serverSite);
|
||||
}
|
||||
}
|
||||
|
||||
// If a server was removed from the public list,
|
||||
// we will delete it from the local system.
|
||||
// Will not delete "unofficial" servers
|
||||
for (const auto &pair : savedHostList) {
|
||||
QString serverAddr = pair.first;
|
||||
|
||||
if (publicServersToRemove.indexOf(serverAddr) != -1) {
|
||||
SettingsCache::instance().servers().removeServer(serverAddr);
|
||||
}
|
||||
}
|
||||
|
||||
emit sigPublicServersDownloadedSuccessfully();
|
||||
}
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
/**
|
||||
* @file handle_public_servers.h
|
||||
* @ingroup Server
|
||||
* @brief TODO: Document this.
|
||||
*/
|
||||
|
||||
#ifndef COCKATRICE_HANDLE_PUBLIC_SERVERS_H
|
||||
#define COCKATRICE_HANDLE_PUBLIC_SERVERS_H
|
||||
|
||||
#include "user/user_info_connection.h"
|
||||
|
||||
class QNetworkReply;
|
||||
class QNetworkAccessManager;
|
||||
|
||||
/**
|
||||
* This class is used to update the servers.ini file and ensure
|
||||
* the list of public servers has up-to-date information.
|
||||
* Servers that are added manually by users are not modified.
|
||||
*/
|
||||
class HandlePublicServers : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
signals:
|
||||
void sigPublicServersDownloadedSuccessfully();
|
||||
void sigPublicServersDownloadedUnsuccessfully(int);
|
||||
|
||||
public:
|
||||
explicit HandlePublicServers(QObject *parent = nullptr);
|
||||
~HandlePublicServers() override = default;
|
||||
|
||||
public slots:
|
||||
void downloadPublicServers();
|
||||
|
||||
private slots:
|
||||
void actFinishParsingDownloadedData();
|
||||
|
||||
private:
|
||||
void updateServerINISettings(QMap<QString, QVariant>);
|
||||
|
||||
QStringList publicServersToRemove;
|
||||
QMap<QString, std::pair<QString, UserConnection_Information>> savedHostList;
|
||||
QNetworkAccessManager *nam;
|
||||
QNetworkReply *reply;
|
||||
};
|
||||
|
||||
#endif // COCKATRICE_HANDLE_PUBLIC_SERVERS_H
|
||||
|
|
@ -0,0 +1,368 @@
|
|||
#include "remote_decklist_tree_widget.h"
|
||||
|
||||
#include <QFileIconProvider>
|
||||
#include <QHeaderView>
|
||||
#include <QSortFilterProxyModel>
|
||||
#include <libcockatrice/network/client/abstract/abstract_client.h>
|
||||
#include <libcockatrice/protocol/pb/command_deck_list.pb.h>
|
||||
#include <libcockatrice/protocol/pb/response_deck_list.pb.h>
|
||||
#include <libcockatrice/protocol/pb/serverinfo_deckstorage.pb.h>
|
||||
#include <libcockatrice/protocol/pending_command.h>
|
||||
|
||||
RemoteDeckList_TreeModel::DirectoryNode::DirectoryNode(const QString &_name,
|
||||
RemoteDeckList_TreeModel::DirectoryNode *_parent)
|
||||
: RemoteDeckList_TreeModel::Node(_name, _parent)
|
||||
{
|
||||
}
|
||||
|
||||
RemoteDeckList_TreeModel::DirectoryNode::~DirectoryNode()
|
||||
{
|
||||
clearTree();
|
||||
}
|
||||
|
||||
void RemoteDeckList_TreeModel::DirectoryNode::clearTree()
|
||||
{
|
||||
for (int i = 0; i < size(); ++i)
|
||||
delete at(i);
|
||||
clear();
|
||||
}
|
||||
|
||||
QString RemoteDeckList_TreeModel::DirectoryNode::getPath() const
|
||||
{
|
||||
if (parent) {
|
||||
QString parentPath = parent->getPath();
|
||||
if (parentPath.isEmpty())
|
||||
return name;
|
||||
else
|
||||
return parentPath + "/" + name;
|
||||
} else
|
||||
return name;
|
||||
}
|
||||
|
||||
RemoteDeckList_TreeModel::DirectoryNode *RemoteDeckList_TreeModel::DirectoryNode::getNodeByPath(QStringList path)
|
||||
{
|
||||
QString pathItem;
|
||||
if (parent) {
|
||||
if (path.isEmpty())
|
||||
return this;
|
||||
pathItem = path.takeFirst();
|
||||
if (pathItem.isEmpty() && name.isEmpty())
|
||||
return this;
|
||||
}
|
||||
|
||||
for (int i = 0; i < size(); ++i) {
|
||||
DirectoryNode *node = dynamic_cast<DirectoryNode *>(at(i));
|
||||
if (!node)
|
||||
continue;
|
||||
if (node->getName() == pathItem)
|
||||
return node->getNodeByPath(path);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
RemoteDeckList_TreeModel::FileNode *RemoteDeckList_TreeModel::DirectoryNode::getNodeById(int id) const
|
||||
{
|
||||
for (int i = 0; i < size(); ++i) {
|
||||
DirectoryNode *node = dynamic_cast<DirectoryNode *>(at(i));
|
||||
if (node) {
|
||||
FileNode *result = node->getNodeById(id);
|
||||
if (result)
|
||||
return result;
|
||||
} else {
|
||||
FileNode *file = dynamic_cast<FileNode *>(at(i));
|
||||
if (file->getId() == id)
|
||||
return file;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
RemoteDeckList_TreeModel::RemoteDeckList_TreeModel(AbstractClient *_client, QObject *parent)
|
||||
: QAbstractItemModel(parent), client(_client)
|
||||
{
|
||||
QFileIconProvider fip;
|
||||
dirIcon = fip.icon(QFileIconProvider::Folder);
|
||||
fileIcon = fip.icon(QFileIconProvider::File);
|
||||
|
||||
root = new DirectoryNode;
|
||||
}
|
||||
|
||||
RemoteDeckList_TreeModel::~RemoteDeckList_TreeModel()
|
||||
{
|
||||
delete root;
|
||||
}
|
||||
|
||||
int RemoteDeckList_TreeModel::rowCount(const QModelIndex &parent) const
|
||||
{
|
||||
DirectoryNode *node = getNode<DirectoryNode *>(parent);
|
||||
if (node)
|
||||
return node->size();
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
|
||||
int RemoteDeckList_TreeModel::columnCount(const QModelIndex & /*parent*/) const
|
||||
{
|
||||
return 3;
|
||||
}
|
||||
|
||||
QVariant RemoteDeckList_TreeModel::data(const QModelIndex &index, int role) const
|
||||
{
|
||||
if (!index.isValid())
|
||||
return QVariant();
|
||||
if (index.column() >= 3)
|
||||
return QVariant();
|
||||
|
||||
Node *temp = static_cast<Node *>(index.internalPointer());
|
||||
FileNode *file = dynamic_cast<FileNode *>(temp);
|
||||
if (!file) {
|
||||
DirectoryNode *node = dynamic_cast<DirectoryNode *>(temp);
|
||||
switch (role) {
|
||||
case Qt::DisplayRole: {
|
||||
switch (index.column()) {
|
||||
case 0:
|
||||
return node->getName();
|
||||
default:
|
||||
return QVariant();
|
||||
}
|
||||
}
|
||||
case Qt::DecorationRole:
|
||||
return index.column() == 0 ? dirIcon : QVariant();
|
||||
default:
|
||||
return QVariant();
|
||||
}
|
||||
} else {
|
||||
switch (role) {
|
||||
case Qt::DisplayRole: {
|
||||
switch (index.column()) {
|
||||
case 0:
|
||||
return file->getName();
|
||||
case 1:
|
||||
return file->getId();
|
||||
case 2:
|
||||
return file->getUploadTime();
|
||||
default:
|
||||
return QVariant();
|
||||
}
|
||||
}
|
||||
case Qt::DecorationRole:
|
||||
return index.column() == 0 ? fileIcon : QVariant();
|
||||
case Qt::TextAlignmentRole:
|
||||
return index.column() == 1 ? Qt::AlignRight : Qt::AlignLeft;
|
||||
default:
|
||||
return QVariant();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QVariant RemoteDeckList_TreeModel::headerData(int section, Qt::Orientation orientation, int role) const
|
||||
{
|
||||
if (orientation != Qt::Horizontal)
|
||||
return QVariant();
|
||||
switch (role) {
|
||||
case Qt::TextAlignmentRole:
|
||||
return section == 1 ? Qt::AlignRight : Qt::AlignLeft;
|
||||
case Qt::DisplayRole: {
|
||||
switch (section) {
|
||||
case 0:
|
||||
return tr("Name");
|
||||
case 1:
|
||||
return tr("ID");
|
||||
case 2:
|
||||
return tr("Upload time");
|
||||
default:
|
||||
return QVariant();
|
||||
}
|
||||
}
|
||||
default:
|
||||
return QVariant();
|
||||
}
|
||||
}
|
||||
|
||||
QModelIndex RemoteDeckList_TreeModel::index(int row, int column, const QModelIndex &parent) const
|
||||
{
|
||||
if (!hasIndex(row, column, parent))
|
||||
return QModelIndex();
|
||||
|
||||
DirectoryNode *parentNode = getNode<DirectoryNode *>(parent);
|
||||
if (row >= parentNode->size())
|
||||
return QModelIndex();
|
||||
|
||||
return createIndex(row, column, parentNode->at(row));
|
||||
}
|
||||
|
||||
QModelIndex RemoteDeckList_TreeModel::parent(const QModelIndex &ind) const
|
||||
{
|
||||
if (!ind.isValid())
|
||||
return QModelIndex();
|
||||
|
||||
return nodeToIndex(static_cast<Node *>(ind.internalPointer())->getParent());
|
||||
}
|
||||
|
||||
Qt::ItemFlags RemoteDeckList_TreeModel::flags(const QModelIndex &index) const
|
||||
{
|
||||
if (!index.isValid()) {
|
||||
return Qt::NoItemFlags;
|
||||
}
|
||||
|
||||
return Qt::ItemIsEnabled | Qt::ItemIsSelectable;
|
||||
}
|
||||
|
||||
QModelIndex RemoteDeckList_TreeModel::nodeToIndex(Node *node) const
|
||||
{
|
||||
if (node == nullptr || node == root)
|
||||
return QModelIndex();
|
||||
return createIndex(node->getParent()->indexOf(node), 0, node);
|
||||
}
|
||||
|
||||
void RemoteDeckList_TreeModel::addFileToTree(const ServerInfo_DeckStorage_TreeItem &file, DirectoryNode *parent)
|
||||
{
|
||||
const ServerInfo_DeckStorage_File &fileInfo = file.file();
|
||||
QDateTime time;
|
||||
time.setSecsSinceEpoch(fileInfo.creation_time());
|
||||
|
||||
beginInsertRows(nodeToIndex(parent), parent->size(), parent->size());
|
||||
parent->append(new FileNode(QString::fromStdString(file.name()), file.id(), time, parent));
|
||||
endInsertRows();
|
||||
}
|
||||
|
||||
void RemoteDeckList_TreeModel::addFolderToTree(const ServerInfo_DeckStorage_TreeItem &folder, DirectoryNode *parent)
|
||||
{
|
||||
DirectoryNode *newItem = addNamedFolderToTree(QString::fromStdString(folder.name()), parent);
|
||||
const ServerInfo_DeckStorage_Folder &folderInfo = folder.folder();
|
||||
const int folderItemsSize = folderInfo.items_size();
|
||||
for (int i = 0; i < folderItemsSize; ++i) {
|
||||
const ServerInfo_DeckStorage_TreeItem &subItem = folderInfo.items(i);
|
||||
if (subItem.has_folder())
|
||||
addFolderToTree(subItem, newItem);
|
||||
else
|
||||
addFileToTree(subItem, newItem);
|
||||
}
|
||||
}
|
||||
|
||||
RemoteDeckList_TreeModel::DirectoryNode *RemoteDeckList_TreeModel::addNamedFolderToTree(const QString &name,
|
||||
DirectoryNode *parent)
|
||||
{
|
||||
DirectoryNode *newItem = new DirectoryNode(name, parent);
|
||||
beginInsertRows(nodeToIndex(parent), parent->size(), parent->size());
|
||||
parent->append(newItem);
|
||||
endInsertRows();
|
||||
return newItem;
|
||||
}
|
||||
|
||||
void RemoteDeckList_TreeModel::removeNode(RemoteDeckList_TreeModel::Node *node)
|
||||
{
|
||||
int ind = node->getParent()->indexOf(node);
|
||||
beginRemoveRows(nodeToIndex(node->getParent()), ind, ind);
|
||||
node->getParent()->removeAt(ind);
|
||||
endRemoveRows();
|
||||
delete node;
|
||||
}
|
||||
|
||||
void RemoteDeckList_TreeModel::refreshTree()
|
||||
{
|
||||
PendingCommand *pend = client->prepareSessionCommand(Command_DeckList());
|
||||
connect(pend, &PendingCommand::finished, this, &RemoteDeckList_TreeModel::deckListFinished);
|
||||
|
||||
client->sendCommand(pend);
|
||||
}
|
||||
|
||||
void RemoteDeckList_TreeModel::clearTree()
|
||||
{
|
||||
beginResetModel();
|
||||
root->clearTree();
|
||||
endResetModel();
|
||||
}
|
||||
|
||||
void RemoteDeckList_TreeModel::deckListFinished(const Response &r)
|
||||
{
|
||||
const Response_DeckList &resp = r.GetExtension(Response_DeckList::ext);
|
||||
|
||||
clearTree();
|
||||
|
||||
ServerInfo_DeckStorage_TreeItem tempRoot;
|
||||
tempRoot.set_id(0);
|
||||
tempRoot.mutable_folder()->CopyFrom(resp.root());
|
||||
addFolderToTree(tempRoot, root);
|
||||
|
||||
emit treeRefreshed();
|
||||
}
|
||||
|
||||
RemoteDeckList_TreeWidget::RemoteDeckList_TreeWidget(AbstractClient *_client, QWidget *parent) : QTreeView(parent)
|
||||
{
|
||||
treeModel = new RemoteDeckList_TreeModel(_client, this);
|
||||
proxyModel = new QSortFilterProxyModel(this);
|
||||
proxyModel->setSourceModel(treeModel);
|
||||
proxyModel->setDynamicSortFilter(true);
|
||||
proxyModel->setSortCaseSensitivity(Qt::CaseInsensitive);
|
||||
setModel(proxyModel);
|
||||
connect(treeModel, &RemoteDeckList_TreeModel::treeRefreshed, this, &RemoteDeckList_TreeWidget::expandAll);
|
||||
|
||||
header()->setSectionResizeMode(QHeaderView::ResizeToContents);
|
||||
setUniformRowHeights(true);
|
||||
setSelectionMode(QAbstractItemView::ExtendedSelection);
|
||||
setSortingEnabled(true);
|
||||
proxyModel->sort(0, Qt::AscendingOrder);
|
||||
header()->setSortIndicator(0, Qt::AscendingOrder);
|
||||
}
|
||||
|
||||
RemoteDeckList_TreeModel::Node *RemoteDeckList_TreeWidget::getNode(const QModelIndex &ind) const
|
||||
{
|
||||
return treeModel->getNode<RemoteDeckList_TreeModel::Node *>(proxyModel->mapToSource(ind));
|
||||
}
|
||||
|
||||
RemoteDeckList_TreeModel::Node *RemoteDeckList_TreeWidget::getCurrentItem() const
|
||||
{
|
||||
return getNode(selectionModel()->currentIndex());
|
||||
}
|
||||
|
||||
QList<RemoteDeckList_TreeModel::Node *> RemoteDeckList_TreeWidget::getCurrentSelection() const
|
||||
{
|
||||
auto list = QList<RemoteDeckList_TreeModel::Node *>();
|
||||
for (const auto &row : selectionModel()->selectedRows()) {
|
||||
list << getNode(row);
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
RemoteDeckList_TreeModel::DirectoryNode *RemoteDeckList_TreeWidget::getNodeByPath(const QString &path) const
|
||||
{
|
||||
return treeModel->getRoot()->getNodeByPath(path.split("/"));
|
||||
}
|
||||
|
||||
RemoteDeckList_TreeModel::FileNode *RemoteDeckList_TreeWidget::getNodeById(int id) const
|
||||
{
|
||||
return treeModel->getRoot()->getNodeById(id);
|
||||
}
|
||||
|
||||
void RemoteDeckList_TreeWidget::addFileToTree(const ServerInfo_DeckStorage_TreeItem &file,
|
||||
RemoteDeckList_TreeModel::DirectoryNode *parent)
|
||||
{
|
||||
treeModel->addFileToTree(file, parent);
|
||||
}
|
||||
|
||||
void RemoteDeckList_TreeWidget::addFolderToTree(const ServerInfo_DeckStorage_TreeItem &folder,
|
||||
RemoteDeckList_TreeModel::DirectoryNode *parent)
|
||||
{
|
||||
treeModel->addFolderToTree(folder, parent);
|
||||
}
|
||||
|
||||
void RemoteDeckList_TreeWidget::addFolderToTree(const QString &name, RemoteDeckList_TreeModel::DirectoryNode *parent)
|
||||
{
|
||||
treeModel->addNamedFolderToTree(name, parent);
|
||||
}
|
||||
|
||||
void RemoteDeckList_TreeWidget::removeNode(RemoteDeckList_TreeModel::Node *node)
|
||||
{
|
||||
treeModel->removeNode(node);
|
||||
}
|
||||
|
||||
void RemoteDeckList_TreeWidget::refreshTree()
|
||||
{
|
||||
treeModel->refreshTree();
|
||||
}
|
||||
|
||||
void RemoteDeckList_TreeWidget::clearTree()
|
||||
{
|
||||
treeModel->clearTree();
|
||||
}
|
||||
|
|
@ -0,0 +1,141 @@
|
|||
/**
|
||||
* @file remote_decklist_tree_widget.h
|
||||
* @ingroup NetworkingWidgets
|
||||
* @ingroup DeckStorageWidgets
|
||||
* @brief TODO: Document this.
|
||||
*/
|
||||
|
||||
#ifndef REMOTEDECKLIST_TREEWIDGET_H
|
||||
#define REMOTEDECKLIST_TREEWIDGET_H
|
||||
|
||||
#include <QAbstractItemModel>
|
||||
#include <QDateTime>
|
||||
#include <QTreeView>
|
||||
|
||||
class Response;
|
||||
class AbstractClient;
|
||||
class QSortFilterProxyModel;
|
||||
class ServerInfo_DeckStorage_TreeItem;
|
||||
|
||||
class RemoteDeckList_TreeModel : public QAbstractItemModel
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
class DirectoryNode;
|
||||
class FileNode;
|
||||
class Node
|
||||
{
|
||||
protected:
|
||||
DirectoryNode *parent;
|
||||
QString name;
|
||||
|
||||
public:
|
||||
explicit Node(const QString &_name, DirectoryNode *_parent = nullptr) : parent(_parent), name(_name)
|
||||
{
|
||||
}
|
||||
virtual ~Node() = default;
|
||||
DirectoryNode *getParent() const
|
||||
{
|
||||
return parent;
|
||||
}
|
||||
QString getName() const
|
||||
{
|
||||
return name;
|
||||
}
|
||||
};
|
||||
class DirectoryNode : public Node, public QList<Node *>
|
||||
{
|
||||
public:
|
||||
explicit DirectoryNode(const QString &_name = QString(), DirectoryNode *_parent = nullptr);
|
||||
~DirectoryNode() override;
|
||||
void clearTree();
|
||||
QString getPath() const;
|
||||
DirectoryNode *getNodeByPath(QStringList path);
|
||||
FileNode *getNodeById(int id) const;
|
||||
};
|
||||
class FileNode : public Node
|
||||
{
|
||||
private:
|
||||
int id;
|
||||
QDateTime uploadTime;
|
||||
|
||||
public:
|
||||
FileNode(const QString &_name, int _id, const QDateTime &_uploadTime, DirectoryNode *_parent = nullptr)
|
||||
: Node(_name, _parent), id(_id), uploadTime(_uploadTime)
|
||||
{
|
||||
}
|
||||
int getId() const
|
||||
{
|
||||
return id;
|
||||
}
|
||||
QDateTime getUploadTime() const
|
||||
{
|
||||
return uploadTime;
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T> T getNode(const QModelIndex &index) const
|
||||
{
|
||||
if (!index.isValid())
|
||||
return dynamic_cast<T>(root);
|
||||
return dynamic_cast<T>(static_cast<Node *>(index.internalPointer()));
|
||||
}
|
||||
|
||||
private:
|
||||
AbstractClient *client;
|
||||
DirectoryNode *root;
|
||||
|
||||
QIcon fileIcon, dirIcon;
|
||||
|
||||
QModelIndex nodeToIndex(Node *node) const;
|
||||
signals:
|
||||
void treeRefreshed();
|
||||
private slots:
|
||||
void deckListFinished(const Response &r);
|
||||
|
||||
public:
|
||||
explicit RemoteDeckList_TreeModel(AbstractClient *_client, QObject *parent = nullptr);
|
||||
~RemoteDeckList_TreeModel() override;
|
||||
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||
int columnCount(const QModelIndex & /*parent*/ = QModelIndex()) const override;
|
||||
QVariant data(const QModelIndex &index, int role) const override;
|
||||
QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
|
||||
QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override;
|
||||
QModelIndex parent(const QModelIndex &index) const override;
|
||||
Qt::ItemFlags flags(const QModelIndex &index) const override;
|
||||
|
||||
DirectoryNode *getRoot() const
|
||||
{
|
||||
return root;
|
||||
}
|
||||
void addFileToTree(const ServerInfo_DeckStorage_TreeItem &file, DirectoryNode *parent);
|
||||
void addFolderToTree(const ServerInfo_DeckStorage_TreeItem &folder, DirectoryNode *parent);
|
||||
DirectoryNode *addNamedFolderToTree(const QString &name, DirectoryNode *parent);
|
||||
void removeNode(Node *node);
|
||||
void refreshTree();
|
||||
void clearTree();
|
||||
};
|
||||
|
||||
class RemoteDeckList_TreeWidget : public QTreeView
|
||||
{
|
||||
private:
|
||||
RemoteDeckList_TreeModel *treeModel;
|
||||
QSortFilterProxyModel *proxyModel;
|
||||
|
||||
public:
|
||||
explicit RemoteDeckList_TreeWidget(AbstractClient *_client, QWidget *parent = nullptr);
|
||||
RemoteDeckList_TreeModel::Node *getNode(const QModelIndex &ind) const;
|
||||
RemoteDeckList_TreeModel::Node *getCurrentItem() const;
|
||||
QList<RemoteDeckList_TreeModel::Node *> getCurrentSelection() const;
|
||||
RemoteDeckList_TreeModel::DirectoryNode *getNodeByPath(const QString &path) const;
|
||||
RemoteDeckList_TreeModel::FileNode *getNodeById(int id) const;
|
||||
void addFileToTree(const ServerInfo_DeckStorage_TreeItem &file, RemoteDeckList_TreeModel::DirectoryNode *parent);
|
||||
void addFolderToTree(const ServerInfo_DeckStorage_TreeItem &folder,
|
||||
RemoteDeckList_TreeModel::DirectoryNode *parent);
|
||||
void addFolderToTree(const QString &name, RemoteDeckList_TreeModel::DirectoryNode *parent);
|
||||
void removeNode(RemoteDeckList_TreeModel::Node *node);
|
||||
void refreshTree();
|
||||
void clearTree();
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,383 @@
|
|||
#include "remote_replay_list_tree_widget.h"
|
||||
|
||||
#include <QFileIconProvider>
|
||||
#include <QHeaderView>
|
||||
#include <QSortFilterProxyModel>
|
||||
#include <libcockatrice/network/client/abstract/abstract_client.h>
|
||||
#include <libcockatrice/protocol/pb/command_replay_list.pb.h>
|
||||
#include <libcockatrice/protocol/pb/response_replay_list.pb.h>
|
||||
#include <libcockatrice/protocol/pb/serverinfo_replay.pb.h>
|
||||
#include <libcockatrice/protocol/pending_command.h>
|
||||
|
||||
const int RemoteReplayList_TreeModel::numberOfColumns = 6;
|
||||
|
||||
RemoteReplayList_TreeModel::MatchNode::MatchNode(const ServerInfo_ReplayMatch &_matchInfo)
|
||||
: RemoteReplayList_TreeModel::Node(QString::fromStdString(_matchInfo.game_name())), matchInfo(_matchInfo)
|
||||
{
|
||||
for (int i = 0; i < matchInfo.replay_list_size(); ++i)
|
||||
append(new ReplayNode(matchInfo.replay_list(i), this));
|
||||
}
|
||||
|
||||
RemoteReplayList_TreeModel::MatchNode::~MatchNode()
|
||||
{
|
||||
for (int i = 0; i < size(); ++i)
|
||||
delete at(i);
|
||||
}
|
||||
|
||||
void RemoteReplayList_TreeModel::MatchNode::updateMatchInfo(const ServerInfo_ReplayMatch &_matchInfo)
|
||||
{
|
||||
matchInfo.MergeFrom(_matchInfo);
|
||||
}
|
||||
|
||||
RemoteReplayList_TreeModel::RemoteReplayList_TreeModel(AbstractClient *_client, QObject *parent)
|
||||
: QAbstractItemModel(parent), client(_client)
|
||||
{
|
||||
QFileIconProvider fip;
|
||||
dirIcon = fip.icon(QFileIconProvider::Folder);
|
||||
fileIcon = fip.icon(QFileIconProvider::File);
|
||||
lockIcon = QPixmap("theme:icons/lock");
|
||||
}
|
||||
|
||||
RemoteReplayList_TreeModel::~RemoteReplayList_TreeModel()
|
||||
{
|
||||
clearAll();
|
||||
}
|
||||
|
||||
int RemoteReplayList_TreeModel::rowCount(const QModelIndex &parent) const
|
||||
{
|
||||
if (!parent.isValid())
|
||||
return replayMatches.size();
|
||||
|
||||
MatchNode *matchNode = dynamic_cast<MatchNode *>(static_cast<Node *>(parent.internalPointer()));
|
||||
if (matchNode)
|
||||
return matchNode->size();
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
|
||||
QVariant RemoteReplayList_TreeModel::data(const QModelIndex &index, int role) const
|
||||
{
|
||||
if (!index.isValid())
|
||||
return QVariant();
|
||||
if (index.column() >= numberOfColumns)
|
||||
return QVariant();
|
||||
|
||||
ReplayNode *replayNode = dynamic_cast<ReplayNode *>(static_cast<Node *>(index.internalPointer()));
|
||||
if (replayNode) {
|
||||
const ServerInfo_Replay &replayInfo = replayNode->getReplayInfo();
|
||||
switch (role) {
|
||||
case Qt::TextAlignmentRole:
|
||||
return index.column() == 0 ? Qt::AlignRight : Qt::AlignLeft;
|
||||
case Qt::DisplayRole: {
|
||||
switch (index.column()) {
|
||||
case 0:
|
||||
return replayInfo.replay_id();
|
||||
case 1:
|
||||
return QString::fromStdString(replayInfo.replay_name());
|
||||
case 5:
|
||||
return replayInfo.duration();
|
||||
default:
|
||||
return QVariant();
|
||||
}
|
||||
}
|
||||
case Qt::DecorationRole:
|
||||
return index.column() == 0 ? fileIcon : QVariant();
|
||||
}
|
||||
} else {
|
||||
MatchNode *matchNode = dynamic_cast<MatchNode *>(static_cast<Node *>(index.internalPointer()));
|
||||
const ServerInfo_ReplayMatch &matchInfo = matchNode->getMatchInfo();
|
||||
switch (role) {
|
||||
case Qt::TextAlignmentRole:
|
||||
switch (index.column()) {
|
||||
case 0:
|
||||
case 5:
|
||||
return Qt::AlignRight;
|
||||
default:
|
||||
return Qt::AlignLeft;
|
||||
}
|
||||
case Qt::DisplayRole: {
|
||||
switch (index.column()) {
|
||||
case 0:
|
||||
return matchInfo.game_id();
|
||||
case 1:
|
||||
return QString::fromStdString(matchInfo.game_name());
|
||||
case 2: {
|
||||
QStringList playerList;
|
||||
for (int i = 0; i < matchInfo.player_names_size(); ++i)
|
||||
playerList.append(QString::fromStdString(matchInfo.player_names(i)));
|
||||
return playerList.join(", ");
|
||||
}
|
||||
case 4:
|
||||
return QDateTime::fromSecsSinceEpoch(matchInfo.time_started());
|
||||
case 5:
|
||||
return matchInfo.length();
|
||||
default:
|
||||
return QVariant();
|
||||
}
|
||||
}
|
||||
case Qt::DecorationRole:
|
||||
switch (index.column()) {
|
||||
case 0:
|
||||
return dirIcon;
|
||||
case 3:
|
||||
return matchInfo.do_not_hide() ? lockIcon : QVariant();
|
||||
default:
|
||||
return QVariant();
|
||||
}
|
||||
}
|
||||
}
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
QVariant RemoteReplayList_TreeModel::headerData(int section, Qt::Orientation orientation, int role) const
|
||||
{
|
||||
if (orientation != Qt::Horizontal)
|
||||
return QVariant();
|
||||
switch (role) {
|
||||
case Qt::TextAlignmentRole:
|
||||
switch (section) {
|
||||
case 0:
|
||||
case 5:
|
||||
return Qt::AlignRight;
|
||||
default:
|
||||
return Qt::AlignLeft;
|
||||
}
|
||||
case Qt::DisplayRole: {
|
||||
switch (section) {
|
||||
case 0:
|
||||
return tr("ID");
|
||||
case 1:
|
||||
return tr("Name");
|
||||
case 2:
|
||||
return tr("Players");
|
||||
case 3:
|
||||
return tr("Keep");
|
||||
case 4:
|
||||
return tr("Time started");
|
||||
case 5:
|
||||
return tr("Duration (sec)");
|
||||
default:
|
||||
return QVariant();
|
||||
}
|
||||
}
|
||||
default:
|
||||
return QVariant();
|
||||
}
|
||||
}
|
||||
|
||||
QModelIndex RemoteReplayList_TreeModel::index(int row, int column, const QModelIndex &parent) const
|
||||
{
|
||||
if (!hasIndex(row, column, parent))
|
||||
return QModelIndex();
|
||||
|
||||
MatchNode *matchNode = dynamic_cast<MatchNode *>(static_cast<Node *>(parent.internalPointer()));
|
||||
if (matchNode) {
|
||||
if (row >= matchNode->size())
|
||||
return QModelIndex();
|
||||
return createIndex(row, column, (void *)matchNode->at(row));
|
||||
} else {
|
||||
if (row >= replayMatches.size())
|
||||
return QModelIndex();
|
||||
return createIndex(row, column, (void *)replayMatches[row]);
|
||||
}
|
||||
}
|
||||
|
||||
QModelIndex RemoteReplayList_TreeModel::parent(const QModelIndex &ind) const
|
||||
{
|
||||
MatchNode const *matchNode = dynamic_cast<MatchNode *>(static_cast<Node *>(ind.internalPointer()));
|
||||
if (matchNode)
|
||||
return QModelIndex();
|
||||
else {
|
||||
ReplayNode *replayNode = dynamic_cast<ReplayNode *>(static_cast<Node *>(ind.internalPointer()));
|
||||
return createIndex(replayNode->getParent()->indexOf(replayNode), 0, replayNode->getParent());
|
||||
}
|
||||
}
|
||||
|
||||
Qt::ItemFlags RemoteReplayList_TreeModel::flags(const QModelIndex &index) const
|
||||
{
|
||||
if (!index.isValid())
|
||||
return Qt::NoItemFlags;
|
||||
|
||||
return Qt::ItemIsEnabled | Qt::ItemIsSelectable;
|
||||
}
|
||||
|
||||
ServerInfo_Replay const *RemoteReplayList_TreeModel::getReplay(const QModelIndex &index) const
|
||||
{
|
||||
if (!index.isValid())
|
||||
return 0;
|
||||
|
||||
ReplayNode *node = dynamic_cast<ReplayNode *>(static_cast<Node *>(index.internalPointer()));
|
||||
if (!node)
|
||||
return 0;
|
||||
return &node->getReplayInfo();
|
||||
}
|
||||
|
||||
ServerInfo_ReplayMatch const *RemoteReplayList_TreeModel::getReplayMatch(const QModelIndex &index) const
|
||||
{
|
||||
if (!index.isValid())
|
||||
return nullptr;
|
||||
|
||||
auto *node = dynamic_cast<MatchNode *>(static_cast<Node *>(index.internalPointer()));
|
||||
if (!node)
|
||||
return nullptr;
|
||||
|
||||
return &node->getMatchInfo();
|
||||
}
|
||||
|
||||
ServerInfo_ReplayMatch const *RemoteReplayList_TreeModel::getEnclosingReplayMatch(const QModelIndex &index) const
|
||||
{
|
||||
if (!index.isValid())
|
||||
return nullptr;
|
||||
|
||||
auto *node = dynamic_cast<MatchNode *>(static_cast<Node *>(index.internalPointer()));
|
||||
if (!node) {
|
||||
auto *_node = dynamic_cast<ReplayNode *>(static_cast<Node *>(index.internalPointer()));
|
||||
if (!_node)
|
||||
return nullptr;
|
||||
return &_node->getParent()->getMatchInfo();
|
||||
}
|
||||
return &node->getMatchInfo();
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes all items in the model
|
||||
*/
|
||||
void RemoteReplayList_TreeModel::clearAll()
|
||||
{
|
||||
for (int i = 0; i < replayMatches.size(); ++i)
|
||||
delete replayMatches[i];
|
||||
replayMatches.clear();
|
||||
}
|
||||
|
||||
void RemoteReplayList_TreeModel::refreshTree()
|
||||
{
|
||||
PendingCommand *pend = client->prepareSessionCommand(Command_ReplayList());
|
||||
connect(pend, &PendingCommand::finished, this, &RemoteReplayList_TreeModel::replayListFinished);
|
||||
|
||||
client->sendCommand(pend);
|
||||
}
|
||||
|
||||
void RemoteReplayList_TreeModel::clearTree()
|
||||
{
|
||||
beginResetModel();
|
||||
clearAll();
|
||||
endResetModel();
|
||||
}
|
||||
|
||||
void RemoteReplayList_TreeModel::addMatchInfo(const ServerInfo_ReplayMatch &matchInfo)
|
||||
{
|
||||
beginInsertRows(QModelIndex(), replayMatches.size(), replayMatches.size());
|
||||
replayMatches.append(new MatchNode(matchInfo));
|
||||
endInsertRows();
|
||||
|
||||
emit treeRefreshed();
|
||||
}
|
||||
|
||||
void RemoteReplayList_TreeModel::updateMatchInfo(int gameId, const ServerInfo_ReplayMatch &matchInfo)
|
||||
{
|
||||
for (int i = 0; i < replayMatches.size(); ++i)
|
||||
if (replayMatches[i]->getMatchInfo().game_id() == gameId) {
|
||||
replayMatches[i]->updateMatchInfo(matchInfo);
|
||||
emit dataChanged(createIndex(i, 0, (void *)replayMatches[i]),
|
||||
createIndex(i, numberOfColumns - 1, (void *)replayMatches[i]));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void RemoteReplayList_TreeModel::removeMatchInfo(int gameId)
|
||||
{
|
||||
for (int i = 0; i < replayMatches.size(); ++i)
|
||||
if (replayMatches[i]->getMatchInfo().game_id() == gameId) {
|
||||
beginRemoveRows(QModelIndex(), i, i);
|
||||
replayMatches.removeAt(i);
|
||||
endRemoveRows();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void RemoteReplayList_TreeModel::replayListFinished(const Response &r)
|
||||
{
|
||||
const Response_ReplayList &resp = r.GetExtension(Response_ReplayList::ext);
|
||||
|
||||
beginResetModel();
|
||||
clearAll();
|
||||
|
||||
for (int i = 0; i < resp.match_list_size(); ++i)
|
||||
replayMatches.append(new MatchNode(resp.match_list(i)));
|
||||
|
||||
endResetModel();
|
||||
emit treeRefreshed();
|
||||
}
|
||||
|
||||
RemoteReplayList_TreeWidget::RemoteReplayList_TreeWidget(AbstractClient *_client, QWidget *parent) : QTreeView(parent)
|
||||
{
|
||||
treeModel = new RemoteReplayList_TreeModel(_client, this);
|
||||
proxyModel = new QSortFilterProxyModel(this);
|
||||
proxyModel->setSourceModel(treeModel);
|
||||
proxyModel->setDynamicSortFilter(true);
|
||||
proxyModel->setSortCaseSensitivity(Qt::CaseInsensitive);
|
||||
setModel(proxyModel);
|
||||
|
||||
header()->setSectionResizeMode(QHeaderView::ResizeToContents);
|
||||
header()->setStretchLastSection(false);
|
||||
setUniformRowHeights(true);
|
||||
setSortingEnabled(true);
|
||||
proxyModel->sort(0, Qt::AscendingOrder);
|
||||
header()->setSortIndicator(0, Qt::AscendingOrder);
|
||||
setSelectionMode(QAbstractItemView::ExtendedSelection);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the replay at the given index
|
||||
* @return The replay. Returns nullptr if there is no replay at the index.
|
||||
*/
|
||||
ServerInfo_Replay const *RemoteReplayList_TreeWidget::getReplay(const QModelIndex &ind) const
|
||||
{
|
||||
return treeModel->getReplay(proxyModel->mapToSource(ind));
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the replay match at the given index
|
||||
* @return The replay match. Returns nullptr if there is no replay match at the index.
|
||||
*/
|
||||
ServerInfo_ReplayMatch const *RemoteReplayList_TreeWidget::getReplayMatch(const QModelIndex &ind) const
|
||||
{
|
||||
return treeModel->getReplayMatch(proxyModel->mapToSource(ind));
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all currently selected replays.
|
||||
* Any selection that isn't a replay file (e.g. a folder) will appear as a nullptr in the list.
|
||||
* Make sure to check the list for nullptr before using it.
|
||||
*
|
||||
* @return A List of pointers to the selected replays, as well as nullptr for any selection that isn't a replay.
|
||||
*/
|
||||
QList<ServerInfo_Replay const *> RemoteReplayList_TreeWidget::getSelectedReplays() const
|
||||
{
|
||||
const auto selection = selectionModel()->selectedRows();
|
||||
auto replays = QList<ServerInfo_Replay const *>();
|
||||
for (const auto &row : selection) {
|
||||
replays << treeModel->getReplay(proxyModel->mapToSource(row));
|
||||
}
|
||||
|
||||
return replays;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all currently selected replayMatches.
|
||||
* If a non-folder node is selected, it will return the parent folder of that node.
|
||||
*
|
||||
* @return A Set of pointers to the selected replayMatches.
|
||||
*/
|
||||
QSet<ServerInfo_ReplayMatch const *> RemoteReplayList_TreeWidget::getSelectedReplayMatches() const
|
||||
{
|
||||
const auto selection = selectionModel()->selectedRows();
|
||||
auto replayMatches = QSet<ServerInfo_ReplayMatch const *>();
|
||||
for (const auto &row : selection) {
|
||||
if (const auto replayMatch = treeModel->getEnclosingReplayMatch(proxyModel->mapToSource(row))) {
|
||||
replayMatches << replayMatch;
|
||||
}
|
||||
}
|
||||
|
||||
return replayMatches;
|
||||
}
|
||||
|
|
@ -0,0 +1,148 @@
|
|||
/**
|
||||
* @file remote_replay_list_tree_widget.h
|
||||
* @ingroup DeckStorageWidgets
|
||||
* @ingroup Replays
|
||||
* @ingroup NetworkingWidgets
|
||||
* @brief TODO: Document this.
|
||||
*/
|
||||
|
||||
#ifndef REMOTEREPLAYLIST_TREEWIDGET_H
|
||||
#define REMOTEREPLAYLIST_TREEWIDGET_H
|
||||
|
||||
#include <QAbstractItemModel>
|
||||
#include <QDateTime>
|
||||
#include <QTreeView>
|
||||
#include <libcockatrice/protocol/pb/serverinfo_replay.pb.h>
|
||||
#include <libcockatrice/protocol/pb/serverinfo_replay_match.pb.h>
|
||||
|
||||
class Response;
|
||||
class AbstractClient;
|
||||
class QSortFilterProxyModel;
|
||||
|
||||
class RemoteReplayList_TreeModel : public QAbstractItemModel
|
||||
{
|
||||
Q_OBJECT
|
||||
private:
|
||||
class MatchNode;
|
||||
class ReplayNode;
|
||||
class Node
|
||||
{
|
||||
protected:
|
||||
QString name;
|
||||
|
||||
public:
|
||||
explicit Node(const QString &_name) : name(_name)
|
||||
{
|
||||
}
|
||||
virtual ~Node() = default;
|
||||
QString getName() const
|
||||
{
|
||||
return name;
|
||||
}
|
||||
};
|
||||
class MatchNode : public Node, public QList<ReplayNode *>
|
||||
{
|
||||
private:
|
||||
ServerInfo_ReplayMatch matchInfo;
|
||||
|
||||
public:
|
||||
explicit MatchNode(const ServerInfo_ReplayMatch &_matchInfo);
|
||||
~MatchNode() override;
|
||||
void clearTree();
|
||||
const ServerInfo_ReplayMatch &getMatchInfo()
|
||||
{
|
||||
return matchInfo;
|
||||
}
|
||||
void updateMatchInfo(const ServerInfo_ReplayMatch &_matchInfo);
|
||||
};
|
||||
class ReplayNode : public Node
|
||||
{
|
||||
private:
|
||||
MatchNode *parent;
|
||||
ServerInfo_Replay replayInfo;
|
||||
|
||||
public:
|
||||
ReplayNode(const ServerInfo_Replay &_replayInfo, MatchNode *_parent)
|
||||
: Node(QString::fromStdString(_replayInfo.replay_name())), parent(_parent), replayInfo(_replayInfo)
|
||||
{
|
||||
}
|
||||
MatchNode *getParent() const
|
||||
{
|
||||
return parent;
|
||||
}
|
||||
const ServerInfo_Replay &getReplayInfo()
|
||||
{
|
||||
return replayInfo;
|
||||
}
|
||||
};
|
||||
|
||||
AbstractClient *client;
|
||||
QList<MatchNode *> replayMatches;
|
||||
|
||||
QIcon dirIcon, fileIcon, lockIcon;
|
||||
void clearAll();
|
||||
|
||||
static const int numberOfColumns;
|
||||
signals:
|
||||
void treeRefreshed();
|
||||
private slots:
|
||||
void replayListFinished(const Response &r);
|
||||
|
||||
public:
|
||||
explicit RemoteReplayList_TreeModel(AbstractClient *_client, QObject *parent = nullptr);
|
||||
~RemoteReplayList_TreeModel() override;
|
||||
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||
int columnCount(const QModelIndex & /*parent*/ = QModelIndex()) const override
|
||||
{
|
||||
return numberOfColumns;
|
||||
}
|
||||
QVariant data(const QModelIndex &index, int role) const override;
|
||||
QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
|
||||
QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override;
|
||||
QModelIndex parent(const QModelIndex &index) const override;
|
||||
Qt::ItemFlags flags(const QModelIndex &index) const override;
|
||||
void clearTree();
|
||||
void refreshTree();
|
||||
ServerInfo_Replay const *getReplay(const QModelIndex &index) const;
|
||||
ServerInfo_ReplayMatch const *getReplayMatch(const QModelIndex &index) const;
|
||||
ServerInfo_ReplayMatch const *getEnclosingReplayMatch(const QModelIndex &index) const;
|
||||
void addMatchInfo(const ServerInfo_ReplayMatch &matchInfo);
|
||||
void updateMatchInfo(int gameId, const ServerInfo_ReplayMatch &matchInfo);
|
||||
void removeMatchInfo(int gameId);
|
||||
};
|
||||
|
||||
class RemoteReplayList_TreeWidget : public QTreeView
|
||||
{
|
||||
private:
|
||||
RemoteReplayList_TreeModel *treeModel;
|
||||
QSortFilterProxyModel *proxyModel;
|
||||
|
||||
public:
|
||||
explicit RemoteReplayList_TreeWidget(AbstractClient *_client, QWidget *parent = nullptr);
|
||||
ServerInfo_Replay const *getReplay(const QModelIndex &ind) const;
|
||||
ServerInfo_ReplayMatch const *getReplayMatch(const QModelIndex &ind) const;
|
||||
QList<ServerInfo_Replay const *> getSelectedReplays() const;
|
||||
QSet<ServerInfo_ReplayMatch const *> getSelectedReplayMatches() const;
|
||||
void clearTree()
|
||||
{
|
||||
treeModel->clearTree();
|
||||
}
|
||||
void refreshTree()
|
||||
{
|
||||
treeModel->refreshTree();
|
||||
}
|
||||
void addMatchInfo(const ServerInfo_ReplayMatch &matchInfo)
|
||||
{
|
||||
treeModel->addMatchInfo(matchInfo);
|
||||
}
|
||||
void updateMatchInfo(int gameId, const ServerInfo_ReplayMatch &matchInfo)
|
||||
{
|
||||
treeModel->updateMatchInfo(gameId, matchInfo);
|
||||
}
|
||||
void removeMatchInfo(int gameId)
|
||||
{
|
||||
treeModel->removeMatchInfo(gameId);
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,536 @@
|
|||
#include "user_context_menu.h"
|
||||
|
||||
#include "../../tabs/tab_account.h"
|
||||
#include "../../tabs/tab_game.h"
|
||||
#include "../../tabs/tab_supervisor.h"
|
||||
#include "../chat_view/chat_view.h"
|
||||
#include "../game_selector.h"
|
||||
#include "user_info_box.h"
|
||||
#include "user_list_manager.h"
|
||||
#include "user_list_proxy.h"
|
||||
#include "user_list_widget.h"
|
||||
|
||||
#include <QAction>
|
||||
#include <QMenu>
|
||||
#include <QMessageBox>
|
||||
#include <QSignalMapper>
|
||||
#include <QtGui>
|
||||
#include <QtWidgets>
|
||||
#include <libcockatrice/network/client/abstract/abstract_client.h>
|
||||
#include <libcockatrice/protocol/pb/command_kick_from_game.pb.h>
|
||||
#include <libcockatrice/protocol/pb/commands.pb.h>
|
||||
#include <libcockatrice/protocol/pb/moderator_commands.pb.h>
|
||||
#include <libcockatrice/protocol/pb/response_ban_history.pb.h>
|
||||
#include <libcockatrice/protocol/pb/response_get_admin_notes.pb.h>
|
||||
#include <libcockatrice/protocol/pb/response_get_games_of_user.pb.h>
|
||||
#include <libcockatrice/protocol/pb/response_get_user_info.pb.h>
|
||||
#include <libcockatrice/protocol/pb/response_warn_history.pb.h>
|
||||
#include <libcockatrice/protocol/pb/response_warn_list.pb.h>
|
||||
#include <libcockatrice/protocol/pb/session_commands.pb.h>
|
||||
#include <libcockatrice/protocol/pending_command.h>
|
||||
|
||||
UserContextMenu::UserContextMenu(TabSupervisor *_tabSupervisor, QWidget *parent, AbstractGame *_game)
|
||||
: QObject(parent), client(_tabSupervisor->getClient()), tabSupervisor(_tabSupervisor),
|
||||
userListProxy(_tabSupervisor->getUserListManager()), game(_game)
|
||||
{
|
||||
aUserName = new QAction(QString(), this);
|
||||
aUserName->setEnabled(false);
|
||||
aDetails = new QAction(QString(), this);
|
||||
aChat = new QAction(QString(), this);
|
||||
aShowGames = new QAction(QString(), this);
|
||||
aAddToBuddyList = new QAction(QString(), this);
|
||||
aRemoveFromBuddyList = new QAction(QString(), this);
|
||||
aAddToIgnoreList = new QAction(QString(), this);
|
||||
aRemoveFromIgnoreList = new QAction(QString(), this);
|
||||
aKick = new QAction(QString(), this);
|
||||
aWarnUser = new QAction(QString(), this);
|
||||
aWarnHistory = new QAction(QString(), this);
|
||||
aBan = new QAction(QString(), this);
|
||||
aBanHistory = new QAction(QString(), this);
|
||||
aPromoteToMod = new QAction(QString(), this);
|
||||
aDemoteFromMod = new QAction(QString(), this);
|
||||
aPromoteToJudge = new QAction(QString(), this);
|
||||
aDemoteFromJudge = new QAction(QString(), this);
|
||||
aGetAdminNotes = new QAction(QString(), this);
|
||||
|
||||
retranslateUi();
|
||||
}
|
||||
|
||||
void UserContextMenu::retranslateUi()
|
||||
{
|
||||
aDetails->setText(tr("User &details"));
|
||||
aChat->setText(tr("Private &chat"));
|
||||
aShowGames->setText(tr("Show this user's &games"));
|
||||
aAddToBuddyList->setText(tr("Add to &buddy list"));
|
||||
aRemoveFromBuddyList->setText(tr("Remove from &buddy list"));
|
||||
aAddToIgnoreList->setText(tr("Add to &ignore list"));
|
||||
aRemoveFromIgnoreList->setText(tr("Remove from &ignore list"));
|
||||
aKick->setText(tr("Kick from &game"));
|
||||
aWarnUser->setText(tr("Warn user"));
|
||||
aWarnHistory->setText(tr("View user's war&n history"));
|
||||
aBan->setText(tr("Ban from &server"));
|
||||
aBanHistory->setText(tr("View user's &ban history"));
|
||||
aPromoteToMod->setText(tr("&Promote user to moderator"));
|
||||
aDemoteFromMod->setText(tr("Dem&ote user from moderator"));
|
||||
aPromoteToJudge->setText(tr("Promote user to &judge"));
|
||||
aDemoteFromJudge->setText(tr("Demote user from judge"));
|
||||
aGetAdminNotes->setText(tr("View admin notes"));
|
||||
}
|
||||
|
||||
void UserContextMenu::gamesOfUserReceived(const Response &resp, const CommandContainer &commandContainer)
|
||||
{
|
||||
const Command_GetGamesOfUser &cmd = commandContainer.session_command(0).GetExtension(Command_GetGamesOfUser::ext);
|
||||
if (resp.response_code() == Response::RespNameNotFound) {
|
||||
QMessageBox::critical(static_cast<QWidget *>(parent()), tr("Error"), tr("This user does not exist."));
|
||||
return;
|
||||
} else if (resp.response_code() == Response::RespInIgnoreList) {
|
||||
QMessageBox::critical(
|
||||
static_cast<QWidget *>(parent()), tr("Error"),
|
||||
tr("You are being ignored by %1 and can't see their games.").arg(QString::fromStdString(cmd.user_name())));
|
||||
return;
|
||||
} else if (resp.response_code() != Response::RespOk) {
|
||||
QMessageBox::critical(static_cast<QWidget *>(parent()), tr("Error"),
|
||||
tr("Could not get %1's games.").arg(QString::fromStdString(cmd.user_name())));
|
||||
return;
|
||||
}
|
||||
const Response_GetGamesOfUser &response = resp.GetExtension(Response_GetGamesOfUser::ext);
|
||||
|
||||
QMap<int, GameTypeMap> gameTypeMap;
|
||||
QMap<int, QString> roomMap;
|
||||
const int roomListSize = response.room_list_size();
|
||||
for (int i = 0; i < roomListSize; ++i) {
|
||||
const ServerInfo_Room &roomInfo = response.room_list(i);
|
||||
roomMap.insert(roomInfo.room_id(), QString::fromStdString(roomInfo.name()));
|
||||
GameTypeMap tempMap;
|
||||
const int gameTypeListSize = roomInfo.gametype_list_size();
|
||||
for (int j = 0; j < gameTypeListSize; ++j) {
|
||||
const ServerInfo_GameType &gameTypeInfo = roomInfo.gametype_list(j);
|
||||
tempMap.insert(gameTypeInfo.game_type_id(), QString::fromStdString(gameTypeInfo.description()));
|
||||
}
|
||||
gameTypeMap.insert(roomInfo.room_id(), tempMap);
|
||||
}
|
||||
|
||||
GameSelector *selector = new GameSelector(client, tabSupervisor, nullptr, roomMap, gameTypeMap, false, false);
|
||||
selector->setParent(static_cast<QWidget *>(parent()), Qt::Window);
|
||||
const int gameListSize = response.game_list_size();
|
||||
for (int i = 0; i < gameListSize; ++i) {
|
||||
selector->processGameInfo(response.game_list(i));
|
||||
}
|
||||
|
||||
selector->setWindowTitle(tr("%1's games").arg(QString::fromStdString(cmd.user_name())));
|
||||
selector->setMinimumWidth(800);
|
||||
selector->setAttribute(Qt::WA_DeleteOnClose);
|
||||
selector->show();
|
||||
}
|
||||
|
||||
void UserContextMenu::banUser_processUserInfoResponse(const Response &r)
|
||||
{
|
||||
const Response_GetUserInfo &response = r.GetExtension(Response_GetUserInfo::ext);
|
||||
|
||||
// The dialog needs to be non-modal in order to not block the event queue of the client.
|
||||
BanDialog *dlg = new BanDialog(response.user_info(), static_cast<QWidget *>(parent()));
|
||||
connect(dlg, &QDialog::accepted, this, &UserContextMenu::banUser_dialogFinished);
|
||||
dlg->show();
|
||||
}
|
||||
|
||||
void UserContextMenu::warnUser_processGetWarningsListResponse(const Response &r)
|
||||
{
|
||||
const Response_WarnList &response = r.GetExtension(Response_WarnList::ext);
|
||||
|
||||
QString user = QString::fromStdString(response.user_name()).simplified();
|
||||
QString clientid = QString::fromStdString(response.user_clientid()).simplified();
|
||||
|
||||
// The dialog needs to be non-modal in order to not block the event queue of the client.
|
||||
WarningDialog *dlg = new WarningDialog(user, clientid, static_cast<QWidget *>(parent()));
|
||||
connect(dlg, &QDialog::accepted, this, &UserContextMenu::warnUser_dialogFinished);
|
||||
|
||||
if (response.warning_size() > 0) {
|
||||
for (int i = 0; i < response.warning_size(); ++i) {
|
||||
dlg->addWarningOption(QString::fromStdString(response.warning(i)).simplified());
|
||||
}
|
||||
}
|
||||
dlg->show();
|
||||
}
|
||||
|
||||
void UserContextMenu::warnUser_processUserInfoResponse(const Response &resp)
|
||||
{
|
||||
const Response_GetUserInfo &response = resp.GetExtension(Response_GetUserInfo::ext);
|
||||
ServerInfo_User userInfo = response.user_info();
|
||||
|
||||
Command_GetWarnList cmd;
|
||||
cmd.set_user_name(userInfo.name());
|
||||
cmd.set_user_clientid(userInfo.clientid());
|
||||
PendingCommand *pend = client->prepareModeratorCommand(cmd);
|
||||
connect(pend, &PendingCommand::finished, this, &UserContextMenu::warnUser_processGetWarningsListResponse);
|
||||
client->sendCommand(pend);
|
||||
}
|
||||
|
||||
void UserContextMenu::banUserHistory_processResponse(const Response &resp)
|
||||
{
|
||||
const Response_BanHistory &response = resp.GetExtension(Response_BanHistory::ext);
|
||||
if (resp.response_code() == Response::RespOk) {
|
||||
|
||||
if (response.ban_list_size() > 0) {
|
||||
QTableWidget *table = new QTableWidget();
|
||||
table->setWindowTitle(tr("Ban History"));
|
||||
table->setRowCount(response.ban_list_size());
|
||||
table->setColumnCount(5);
|
||||
table->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
|
||||
|
||||
table->setHorizontalHeaderLabels(
|
||||
QString(tr("Ban Time;Moderator;Ban Length;Ban Reason;Visible Reason")).split(";"));
|
||||
|
||||
ServerInfo_Ban ban;
|
||||
for (int i = 0; i < response.ban_list_size(); ++i) {
|
||||
ban = response.ban_list(i);
|
||||
table->setItem(i, 0, new QTableWidgetItem(QString::fromStdString(ban.ban_time())));
|
||||
table->setItem(i, 1, new QTableWidgetItem(QString::fromStdString(ban.admin_name())));
|
||||
table->setItem(i, 2, new QTableWidgetItem(QString::fromStdString(ban.ban_length())));
|
||||
table->setItem(i, 3, new QTableWidgetItem(QString::fromStdString(ban.ban_reason())));
|
||||
table->setItem(i, 4, new QTableWidgetItem(QString::fromStdString(ban.visible_reason())));
|
||||
}
|
||||
|
||||
table->resizeColumnsToContents();
|
||||
table->setMinimumSize(table->horizontalHeader()->length() + (table->columnCount() * 5),
|
||||
table->verticalHeader()->length() + (table->rowCount() * 3));
|
||||
table->show();
|
||||
} else
|
||||
QMessageBox::information(static_cast<QWidget *>(parent()), tr("Ban History"),
|
||||
tr("User has never been banned."));
|
||||
|
||||
} else
|
||||
QMessageBox::critical(static_cast<QWidget *>(parent()), tr("Ban History"),
|
||||
tr("Failed to collect ban information."));
|
||||
}
|
||||
|
||||
void UserContextMenu::warnUserHistory_processResponse(const Response &resp)
|
||||
{
|
||||
const Response_WarnHistory &response = resp.GetExtension(Response_WarnHistory::ext);
|
||||
if (resp.response_code() == Response::RespOk) {
|
||||
|
||||
if (response.warn_list_size() > 0) {
|
||||
QTableWidget *table = new QTableWidget();
|
||||
table->setWindowTitle(tr("Warning History"));
|
||||
table->setRowCount(response.warn_list_size());
|
||||
table->setColumnCount(4);
|
||||
table->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
|
||||
|
||||
table->setHorizontalHeaderLabels(QString(tr("Warning Time;Moderator;User Name;Reason")).split(";"));
|
||||
|
||||
ServerInfo_Warning warn;
|
||||
for (int i = 0; i < response.warn_list_size(); ++i) {
|
||||
warn = response.warn_list(i);
|
||||
table->setItem(i, 0, new QTableWidgetItem(QString::fromStdString(warn.time_of())));
|
||||
table->setItem(i, 1, new QTableWidgetItem(QString::fromStdString(warn.admin_name())));
|
||||
table->setItem(i, 2, new QTableWidgetItem(QString::fromStdString(warn.user_name())));
|
||||
table->setItem(i, 3, new QTableWidgetItem(QString::fromStdString(warn.reason())));
|
||||
}
|
||||
|
||||
table->resizeColumnsToContents();
|
||||
table->setMinimumSize(table->horizontalHeader()->length() + (table->columnCount() * 5),
|
||||
table->verticalHeader()->length() + (table->rowCount() * 3));
|
||||
table->show();
|
||||
} else
|
||||
QMessageBox::information(static_cast<QWidget *>(parent()), tr("Warning History"),
|
||||
tr("User has never been warned."));
|
||||
|
||||
} else
|
||||
QMessageBox::critical(static_cast<QWidget *>(parent()), tr("Warning History"),
|
||||
tr("Failed to collect warning information."));
|
||||
}
|
||||
|
||||
void UserContextMenu::getAdminNotes_processResponse(const Response &resp)
|
||||
{
|
||||
const Response_GetAdminNotes &response = resp.GetExtension(Response_GetAdminNotes::ext);
|
||||
|
||||
if (resp.response_code() != Response::RespOk) {
|
||||
QMessageBox::information(static_cast<QWidget *>(parent()), tr("Failed"), tr("Failed to get admin notes."));
|
||||
return;
|
||||
}
|
||||
|
||||
auto *dlg = new AdminNotesDialog(QString::fromStdString(response.user_name()),
|
||||
QString::fromStdString(response.notes()), static_cast<QWidget *>(parent()));
|
||||
connect(dlg, &AdminNotesDialog::accepted, this, &UserContextMenu::updateAdminNotes_dialogFinished);
|
||||
dlg->show();
|
||||
}
|
||||
|
||||
void UserContextMenu::adjustMod_processUserResponse(const Response &resp, const CommandContainer &commandContainer)
|
||||
{
|
||||
|
||||
const Command_AdjustMod &cmd = commandContainer.admin_command(0).GetExtension(Command_AdjustMod::ext);
|
||||
|
||||
if (resp.response_code() == Response::RespOk) {
|
||||
if (cmd.should_be_mod() || cmd.should_be_judge()) {
|
||||
QMessageBox::information(static_cast<QWidget *>(parent()), tr("Success"),
|
||||
tr("Successfully promoted user."));
|
||||
} else {
|
||||
QMessageBox::information(static_cast<QWidget *>(parent()), tr("Success"), tr("Successfully demoted user."));
|
||||
}
|
||||
|
||||
} else {
|
||||
if (cmd.should_be_mod() || cmd.should_be_judge()) {
|
||||
QMessageBox::information(static_cast<QWidget *>(parent()), tr("Failed"), tr("Failed to promote user."));
|
||||
} else {
|
||||
QMessageBox::information(static_cast<QWidget *>(parent()), tr("Failed"), tr("Failed to demote user."));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void UserContextMenu::banUser_dialogFinished()
|
||||
{
|
||||
BanDialog *dlg = static_cast<BanDialog *>(sender());
|
||||
|
||||
Command_BanFromServer cmd;
|
||||
cmd.set_user_name(dlg->getBanName().toStdString());
|
||||
cmd.set_address(dlg->getBanIP().toStdString());
|
||||
cmd.set_minutes(dlg->getMinutes());
|
||||
cmd.set_reason(dlg->getReason().toStdString());
|
||||
cmd.set_visible_reason(dlg->getVisibleReason().toStdString());
|
||||
cmd.set_clientid(dlg->getBanId().toStdString());
|
||||
int removeAmount = dlg->getDeleteMessages();
|
||||
if (removeAmount != 0) {
|
||||
cmd.set_remove_messages(removeAmount);
|
||||
}
|
||||
|
||||
client->sendCommand(client->prepareModeratorCommand(cmd));
|
||||
}
|
||||
|
||||
void UserContextMenu::warnUser_dialogFinished()
|
||||
{
|
||||
WarningDialog *dlg = static_cast<WarningDialog *>(sender());
|
||||
|
||||
if (dlg->getName().isEmpty() || userListProxy->getOwnUsername().simplified().isEmpty())
|
||||
return;
|
||||
|
||||
Command_WarnUser cmd;
|
||||
cmd.set_user_name(dlg->getName().toStdString());
|
||||
cmd.set_reason(dlg->getReason().toStdString());
|
||||
cmd.set_clientid(dlg->getWarnID().toStdString());
|
||||
int removeAmount = dlg->getDeleteMessages();
|
||||
if (removeAmount != 0) {
|
||||
cmd.set_remove_messages(removeAmount);
|
||||
}
|
||||
|
||||
client->sendCommand(client->prepareModeratorCommand(cmd));
|
||||
}
|
||||
|
||||
void UserContextMenu::updateAdminNotes_dialogFinished()
|
||||
{
|
||||
auto *dlg = static_cast<AdminNotesDialog *>(sender());
|
||||
|
||||
Command_UpdateAdminNotes cmd;
|
||||
cmd.set_user_name(dlg->getName().toStdString());
|
||||
cmd.set_notes(dlg->getNotes().toStdString());
|
||||
|
||||
client->sendCommand(client->prepareModeratorCommand(cmd));
|
||||
}
|
||||
|
||||
void UserContextMenu::showContextMenu(const QPoint &pos,
|
||||
const QString &userName,
|
||||
UserLevelFlags userLevel,
|
||||
bool online,
|
||||
int playerId)
|
||||
{
|
||||
showContextMenu(pos, userName, userLevel, online, playerId, QString(), nullptr);
|
||||
}
|
||||
|
||||
void UserContextMenu::showContextMenu(const QPoint &pos,
|
||||
const QString &userName,
|
||||
UserLevelFlags userLevel,
|
||||
ChatView *chatView)
|
||||
{
|
||||
showContextMenu(pos, userName, userLevel, true, -1, QString(), chatView);
|
||||
}
|
||||
|
||||
void UserContextMenu::showContextMenu(const QPoint &pos,
|
||||
const QString &userName,
|
||||
UserLevelFlags userLevel,
|
||||
bool online,
|
||||
int playerId,
|
||||
const QString &deckHash,
|
||||
ChatView *chatView)
|
||||
{
|
||||
QAction *aCopyToClipBoard = nullptr, *aRemoveMessages = nullptr;
|
||||
aUserName->setText(userName);
|
||||
|
||||
auto *menu = new QMenu(static_cast<QWidget *>(parent()));
|
||||
menu->addAction(aUserName);
|
||||
menu->addSeparator();
|
||||
if (!deckHash.isEmpty()) {
|
||||
aCopyToClipBoard = new QAction(tr("Copy hash to clipboard"), this);
|
||||
menu->addAction(aCopyToClipBoard);
|
||||
}
|
||||
menu->addAction(aDetails);
|
||||
menu->addAction(aShowGames);
|
||||
menu->addAction(aChat);
|
||||
if (userLevel.testFlag(ServerInfo_User::IsRegistered) && userListProxy->isOwnUserRegistered()) {
|
||||
menu->addSeparator();
|
||||
if (userListProxy->isUserBuddy(userName)) {
|
||||
menu->addAction(aRemoveFromBuddyList);
|
||||
} else {
|
||||
menu->addAction(aAddToBuddyList);
|
||||
}
|
||||
if (userListProxy->isUserIgnored(userName)) {
|
||||
menu->addAction(aRemoveFromIgnoreList);
|
||||
} else {
|
||||
menu->addAction(aAddToIgnoreList);
|
||||
}
|
||||
}
|
||||
if (chatView != nullptr) {
|
||||
aRemoveMessages = new QAction(tr("Remove this user's messages"), this);
|
||||
menu->addAction(aRemoveMessages);
|
||||
}
|
||||
if (game && (game->isHost() || !tabSupervisor->getAdminLocked())) {
|
||||
menu->addSeparator();
|
||||
menu->addAction(aKick);
|
||||
}
|
||||
if (!tabSupervisor->getAdminLocked()) {
|
||||
menu->addSeparator();
|
||||
menu->addAction(aWarnUser);
|
||||
menu->addAction(aWarnHistory);
|
||||
menu->addSeparator();
|
||||
menu->addAction(aBan);
|
||||
menu->addAction(aBanHistory);
|
||||
menu->addSeparator();
|
||||
menu->addAction(aGetAdminNotes);
|
||||
|
||||
menu->addSeparator();
|
||||
if (userLevel.testFlag(ServerInfo_User::IsModerator) &&
|
||||
(tabSupervisor->getUserInfo()->user_level() & ServerInfo_User::IsAdmin)) {
|
||||
menu->addAction(aDemoteFromMod);
|
||||
|
||||
} else if (userLevel.testFlag(ServerInfo_User::IsRegistered) &&
|
||||
(tabSupervisor->getUserInfo()->user_level() & ServerInfo_User::IsAdmin)) {
|
||||
menu->addAction(aPromoteToMod);
|
||||
}
|
||||
|
||||
if (userLevel.testFlag(ServerInfo_User::IsJudge) &&
|
||||
(tabSupervisor->getUserInfo()->user_level() & ServerInfo_User::IsAdmin)) {
|
||||
menu->addAction(aDemoteFromJudge);
|
||||
|
||||
} else if (userLevel.testFlag(ServerInfo_User::IsRegistered) &&
|
||||
(tabSupervisor->getUserInfo()->user_level() & ServerInfo_User::IsAdmin)) {
|
||||
menu->addAction(aPromoteToJudge);
|
||||
}
|
||||
}
|
||||
bool anotherUser = userName != userListProxy->getOwnUsername();
|
||||
aDetails->setEnabled(true);
|
||||
aChat->setEnabled(anotherUser && online);
|
||||
aShowGames->setEnabled(online);
|
||||
aAddToBuddyList->setEnabled(anotherUser);
|
||||
aRemoveFromBuddyList->setEnabled(anotherUser);
|
||||
aAddToIgnoreList->setEnabled(anotherUser);
|
||||
aRemoveFromIgnoreList->setEnabled(anotherUser);
|
||||
aKick->setEnabled(anotherUser);
|
||||
aWarnUser->setEnabled(anotherUser);
|
||||
aWarnHistory->setEnabled(anotherUser);
|
||||
aBan->setEnabled(anotherUser);
|
||||
aBanHistory->setEnabled(anotherUser);
|
||||
aGetAdminNotes->setEnabled(anotherUser);
|
||||
aPromoteToMod->setEnabled(anotherUser);
|
||||
aDemoteFromMod->setEnabled(anotherUser);
|
||||
|
||||
QAction *actionClicked = menu->exec(pos);
|
||||
if (actionClicked == nullptr) {
|
||||
} else if (actionClicked == aDetails) {
|
||||
UserInfoBox *infoWidget =
|
||||
new UserInfoBox(client, false, static_cast<QWidget *>(parent()),
|
||||
Qt::Dialog | Qt::WindowTitleHint | Qt::CustomizeWindowHint | Qt::WindowCloseButtonHint);
|
||||
infoWidget->setAttribute(Qt::WA_DeleteOnClose);
|
||||
infoWidget->updateInfo(userName);
|
||||
} else if (actionClicked == aChat) {
|
||||
emit openMessageDialog(userName, true);
|
||||
} else if (actionClicked == aShowGames) {
|
||||
Command_GetGamesOfUser cmd;
|
||||
cmd.set_user_name(userName.toStdString());
|
||||
|
||||
PendingCommand *pend = client->prepareSessionCommand(cmd);
|
||||
connect(pend, &PendingCommand::finished, this, &UserContextMenu::gamesOfUserReceived);
|
||||
|
||||
client->sendCommand(pend);
|
||||
} else if (actionClicked == aAddToBuddyList) {
|
||||
Command_AddToList cmd;
|
||||
cmd.set_list("buddy");
|
||||
cmd.set_user_name(userName.toStdString());
|
||||
|
||||
client->sendCommand(client->prepareSessionCommand(cmd));
|
||||
} else if (actionClicked == aRemoveFromBuddyList) {
|
||||
Command_RemoveFromList cmd;
|
||||
cmd.set_list("buddy");
|
||||
cmd.set_user_name(userName.toStdString());
|
||||
|
||||
client->sendCommand(client->prepareSessionCommand(cmd));
|
||||
} else if (actionClicked == aAddToIgnoreList) {
|
||||
Command_AddToList cmd;
|
||||
cmd.set_list("ignore");
|
||||
cmd.set_user_name(userName.toStdString());
|
||||
|
||||
client->sendCommand(client->prepareSessionCommand(cmd));
|
||||
} else if (actionClicked == aRemoveFromIgnoreList) {
|
||||
Command_RemoveFromList cmd;
|
||||
cmd.set_list("ignore");
|
||||
cmd.set_user_name(userName.toStdString());
|
||||
|
||||
client->sendCommand(client->prepareSessionCommand(cmd));
|
||||
} else if (actionClicked == aKick) {
|
||||
Command_KickFromGame cmd;
|
||||
cmd.set_player_id(playerId);
|
||||
|
||||
game->getGameEventHandler()->sendGameCommand(cmd);
|
||||
} else if (actionClicked == aBan) {
|
||||
Command_GetUserInfo cmd;
|
||||
cmd.set_user_name(userName.toStdString());
|
||||
|
||||
PendingCommand *pend = client->prepareSessionCommand(cmd);
|
||||
connect(pend, &PendingCommand::finished, this, &UserContextMenu::banUser_processUserInfoResponse);
|
||||
client->sendCommand(pend);
|
||||
} else if (actionClicked == aPromoteToMod || actionClicked == aDemoteFromMod) {
|
||||
Command_AdjustMod cmd;
|
||||
cmd.set_user_name(userName.toStdString());
|
||||
cmd.set_should_be_mod(actionClicked == aPromoteToMod);
|
||||
|
||||
PendingCommand *pend = client->prepareAdminCommand(cmd);
|
||||
connect(pend, &PendingCommand::finished, this, &UserContextMenu::adjustMod_processUserResponse);
|
||||
client->sendCommand(pend);
|
||||
} else if (actionClicked == aPromoteToJudge || actionClicked == aDemoteFromJudge) {
|
||||
Command_AdjustMod cmd;
|
||||
cmd.set_user_name(userName.toStdString());
|
||||
cmd.set_should_be_judge(actionClicked == aPromoteToJudge);
|
||||
|
||||
PendingCommand *pend = client->prepareAdminCommand(cmd);
|
||||
connect(pend, &PendingCommand::finished, this, &UserContextMenu::adjustMod_processUserResponse);
|
||||
client->sendCommand(pend);
|
||||
} else if (actionClicked == aBanHistory) {
|
||||
Command_GetBanHistory cmd;
|
||||
cmd.set_user_name(userName.toStdString());
|
||||
PendingCommand *pend = client->prepareModeratorCommand(cmd);
|
||||
connect(pend, &PendingCommand::finished, this, &UserContextMenu::banUserHistory_processResponse);
|
||||
client->sendCommand(pend);
|
||||
} else if (actionClicked == aWarnUser) {
|
||||
Command_GetUserInfo cmd;
|
||||
cmd.set_user_name(userName.toStdString());
|
||||
PendingCommand *pend = client->prepareSessionCommand(cmd);
|
||||
connect(pend, &PendingCommand::finished, this, &UserContextMenu::warnUser_processUserInfoResponse);
|
||||
client->sendCommand(pend);
|
||||
} else if (actionClicked == aWarnHistory) {
|
||||
Command_GetWarnHistory cmd;
|
||||
cmd.set_user_name(userName.toStdString());
|
||||
PendingCommand *pend = client->prepareModeratorCommand(cmd);
|
||||
connect(pend, &PendingCommand::finished, this, &UserContextMenu::warnUserHistory_processResponse);
|
||||
client->sendCommand(pend);
|
||||
} else if (actionClicked == aGetAdminNotes) {
|
||||
Command_GetAdminNotes cmd;
|
||||
cmd.set_user_name(userName.toStdString());
|
||||
auto *pend = client->prepareModeratorCommand(cmd);
|
||||
connect(pend, &PendingCommand::finished, this, &UserContextMenu::getAdminNotes_processResponse);
|
||||
client->sendCommand(pend);
|
||||
|
||||
} else if (actionClicked == aCopyToClipBoard) {
|
||||
QClipboard *clipboard = QGuiApplication::clipboard();
|
||||
clipboard->setText(deckHash);
|
||||
} else if (actionClicked == aRemoveMessages) {
|
||||
chatView->redactMessages(userName, -1);
|
||||
}
|
||||
|
||||
delete menu;
|
||||
}
|
||||
|
|
@ -0,0 +1,79 @@
|
|||
/**
|
||||
* @file user_context_menu.h
|
||||
* @ingroup Lobby
|
||||
* @brief TODO: Document this.
|
||||
*/
|
||||
|
||||
#ifndef USER_CONTEXT_MENU_H
|
||||
#define USER_CONTEXT_MENU_H
|
||||
|
||||
#include <QObject>
|
||||
#include <libcockatrice/network/server/remote/user_level.h>
|
||||
|
||||
class AbstractGame;
|
||||
class UserListProxy;
|
||||
class AbstractClient;
|
||||
class ChatView;
|
||||
class CommandContainer;
|
||||
class QAction;
|
||||
class QMenu;
|
||||
class QPoint;
|
||||
class Response;
|
||||
class ServerInfo_User;
|
||||
class TabSupervisor;
|
||||
|
||||
class UserContextMenu : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
private:
|
||||
AbstractClient *client;
|
||||
TabSupervisor *tabSupervisor;
|
||||
const UserListProxy *userListProxy;
|
||||
AbstractGame *game;
|
||||
|
||||
QAction *aUserName;
|
||||
QAction *aDetails;
|
||||
QAction *aShowGames;
|
||||
QAction *aChat;
|
||||
QAction *aAddToBuddyList, *aRemoveFromBuddyList;
|
||||
QAction *aAddToIgnoreList, *aRemoveFromIgnoreList;
|
||||
QAction *aKick;
|
||||
QAction *aBan, *aBanHistory;
|
||||
QAction *aPromoteToMod, *aDemoteFromMod;
|
||||
QAction *aPromoteToJudge, *aDemoteFromJudge;
|
||||
QAction *aWarnUser, *aWarnHistory;
|
||||
QAction *aGetAdminNotes;
|
||||
signals:
|
||||
void openMessageDialog(const QString &userName, bool focus);
|
||||
private slots:
|
||||
void banUser_processUserInfoResponse(const Response &resp);
|
||||
void warnUser_processGetWarningsListResponse(const Response &r);
|
||||
void warnUser_processUserInfoResponse(const Response &resp);
|
||||
void banUserHistory_processResponse(const Response &resp);
|
||||
void warnUserHistory_processResponse(const Response &resp);
|
||||
void getAdminNotes_processResponse(const Response &resp);
|
||||
void adjustMod_processUserResponse(const Response &resp, const CommandContainer &commandContainer);
|
||||
void banUser_dialogFinished();
|
||||
void warnUser_dialogFinished();
|
||||
void updateAdminNotes_dialogFinished();
|
||||
void gamesOfUserReceived(const Response &resp, const CommandContainer &commandContainer);
|
||||
|
||||
public:
|
||||
UserContextMenu(TabSupervisor *_tabSupervisor, QWidget *_parent, AbstractGame *_game = 0);
|
||||
void retranslateUi();
|
||||
void showContextMenu(const QPoint &pos,
|
||||
const QString &userName,
|
||||
UserLevelFlags userLevel,
|
||||
bool online = true,
|
||||
int playerId = -1);
|
||||
void showContextMenu(const QPoint &pos, const QString &userName, UserLevelFlags userLevel, ChatView *chatView);
|
||||
void showContextMenu(const QPoint &pos,
|
||||
const QString &userName,
|
||||
UserLevelFlags userLevel,
|
||||
bool online,
|
||||
int playerId,
|
||||
const QString &deckHash,
|
||||
ChatView *chatView = nullptr);
|
||||
};
|
||||
|
||||
#endif
|
||||
385
cockatrice/src/interface/widgets/server/user/user_info_box.cpp
Normal file
385
cockatrice/src/interface/widgets/server/user/user_info_box.cpp
Normal file
|
|
@ -0,0 +1,385 @@
|
|||
#include "user_info_box.h"
|
||||
|
||||
#include "../../dialogs/dlg_edit_avatar.h"
|
||||
#include "../../dialogs/dlg_edit_password.h"
|
||||
#include "../../dialogs/dlg_edit_user.h"
|
||||
#include "../../interface/pixel_map_generator.h"
|
||||
#include "../../interface/widgets/utility/get_text_with_max.h"
|
||||
|
||||
#include <QDateTime>
|
||||
#include <QGridLayout>
|
||||
#include <QHBoxLayout>
|
||||
#include <QInputDialog>
|
||||
#include <QLabel>
|
||||
#include <QMessageBox>
|
||||
#include <libcockatrice/network/client/abstract/abstract_client.h>
|
||||
#include <libcockatrice/protocol/pb/response_get_user_info.pb.h>
|
||||
#include <libcockatrice/protocol/pb/serverinfo_user.pb.h>
|
||||
#include <libcockatrice/protocol/pb/session_commands.pb.h>
|
||||
#include <libcockatrice/protocol/pending_command.h>
|
||||
#include <libcockatrice/utility/passwordhasher.h>
|
||||
|
||||
UserInfoBox::UserInfoBox(AbstractClient *_client, bool _editable, QWidget *parent, Qt::WindowFlags flags)
|
||||
: QWidget(parent, flags), client(_client), editable(_editable)
|
||||
{
|
||||
QFont nameFont = nameLabel.font();
|
||||
nameFont.setBold(true);
|
||||
nameFont.setPointSizeF(nameFont.pointSizeF() * 1.5);
|
||||
nameLabel.setFont(nameFont);
|
||||
|
||||
auto *userIconAndNameLayout = new QHBoxLayout;
|
||||
userIconAndNameLayout->addWidget(&userLevelIcon, 0, Qt::AlignCenter);
|
||||
userIconAndNameLayout->addWidget(&nameLabel, 1);
|
||||
userIconAndNameLayout->addStretch();
|
||||
|
||||
avatarPic.setMinimumSize(200, 200);
|
||||
avatarPic.setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
|
||||
avatarPic.setAlignment(Qt::AlignCenter);
|
||||
|
||||
auto *avatarLayout = new QHBoxLayout;
|
||||
avatarLayout->setContentsMargins(0, 0, 0, 0);
|
||||
avatarLayout->addStretch(1);
|
||||
avatarLayout->addWidget(&avatarPic, 3);
|
||||
avatarLayout->addStretch(1);
|
||||
|
||||
auto *mainLayout = new QGridLayout;
|
||||
mainLayout->addLayout(avatarLayout, 0, 0, 1, 3);
|
||||
mainLayout->addLayout(userIconAndNameLayout, 1, 0, 1, 3);
|
||||
mainLayout->addWidget(&realNameLabel1, 2, 0, 1, 1);
|
||||
mainLayout->addWidget(&realNameLabel2, 2, 1, 1, 2);
|
||||
mainLayout->addWidget(&countryLabel1, 3, 0, 1, 1);
|
||||
mainLayout->addWidget(&countryLabel2, 3, 1, 1, 1, Qt::AlignCenter);
|
||||
mainLayout->addWidget(&countryLabel3, 3, 2, 1, 1);
|
||||
mainLayout->addWidget(&userLevelLabel1, 4, 0, 1, 1);
|
||||
mainLayout->addWidget(&userLevelLabel2, 4, 1, 1, 2);
|
||||
mainLayout->addWidget(&accountAgeLabel1, 5, 0, 1, 1);
|
||||
mainLayout->addWidget(&accountAgeLabel2, 5, 1, 1, 2);
|
||||
mainLayout->setColumnStretch(2, 10);
|
||||
|
||||
if (editable) {
|
||||
auto *buttonsLayout = new QHBoxLayout;
|
||||
buttonsLayout->addWidget(&editButton);
|
||||
buttonsLayout->addWidget(&passwordButton);
|
||||
buttonsLayout->addWidget(&avatarButton);
|
||||
mainLayout->addLayout(buttonsLayout, 7, 0, 1, 3);
|
||||
|
||||
connect(&editButton, &QPushButton::clicked, this, &UserInfoBox::actEdit);
|
||||
connect(&passwordButton, &QPushButton::clicked, this, &UserInfoBox::actPassword);
|
||||
connect(&avatarButton, &QPushButton::clicked, this, &UserInfoBox::actAvatar);
|
||||
}
|
||||
|
||||
setWindowTitle(tr("User Information"));
|
||||
setLayout(mainLayout);
|
||||
retranslateUi();
|
||||
}
|
||||
|
||||
void UserInfoBox::retranslateUi()
|
||||
{
|
||||
realNameLabel1.setText(tr("Real Name:"));
|
||||
countryLabel1.setText(tr("Location:"));
|
||||
userLevelLabel1.setText(tr("User Level:"));
|
||||
accountAgeLabel1.setText(tr("Account Age:"));
|
||||
|
||||
editButton.setText(tr("Edit"));
|
||||
passwordButton.setText(tr("Change password"));
|
||||
avatarButton.setText(tr("Change avatar"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the default profile pic that is used when the user doesn't have a custom pic
|
||||
*/
|
||||
static QPixmap createDefaultAvatar(int height, const ServerInfo_User &user)
|
||||
{
|
||||
return UserLevelPixmapGenerator::generatePixmap(height, UserLevelFlags(user.user_level()), user.pawn_colors(),
|
||||
false, QString::fromStdString(user.privlevel()));
|
||||
}
|
||||
|
||||
void UserInfoBox::updateInfo(const ServerInfo_User &user)
|
||||
{
|
||||
currentUserInfo = &user;
|
||||
|
||||
const UserLevelFlags userLevel(user.user_level());
|
||||
|
||||
const std::string &bmp = user.avatar_bmp();
|
||||
if (!avatarPixmap.loadFromData((const uchar *)bmp.data(), static_cast<uint>(bmp.size()))) {
|
||||
avatarPixmap = createDefaultAvatar(64, user);
|
||||
hasAvatar = false;
|
||||
} else {
|
||||
hasAvatar = true;
|
||||
}
|
||||
|
||||
nameLabel.setText(QString::fromStdString(user.name()));
|
||||
realNameLabel2.setText(QString::fromStdString(user.real_name()));
|
||||
QString country = QString::fromStdString(user.country());
|
||||
|
||||
if (country.length() != 0) {
|
||||
countryLabel2.setPixmap(CountryPixmapGenerator::generatePixmap(13, country));
|
||||
countryLabel3.setText(QString("%1").arg(country.toUpper()));
|
||||
} else {
|
||||
countryLabel2.setText("");
|
||||
countryLabel3.setText("");
|
||||
}
|
||||
|
||||
userLevelIcon.setPixmap(UserLevelPixmapGenerator::generatePixmap(15, userLevel, user.pawn_colors(), false,
|
||||
QString::fromStdString(user.privlevel())));
|
||||
QString userLevelText;
|
||||
if (userLevel.testFlag(ServerInfo_User::IsAdmin))
|
||||
userLevelText = tr("Administrator");
|
||||
else if (userLevel.testFlag(ServerInfo_User::IsModerator))
|
||||
userLevelText = tr("Moderator");
|
||||
else if (userLevel.testFlag(ServerInfo_User::IsRegistered))
|
||||
userLevelText = tr("Registered user");
|
||||
else
|
||||
userLevelText = tr("Unregistered user");
|
||||
|
||||
if (userLevel.testFlag(ServerInfo_User::IsJudge))
|
||||
userLevelText += " | " + tr("Judge");
|
||||
|
||||
if (user.has_privlevel() && user.privlevel() != "NONE") {
|
||||
userLevelText += " | " + QString("%1").arg(user.privlevel().c_str());
|
||||
}
|
||||
|
||||
userLevelLabel2.setText(userLevelText);
|
||||
|
||||
QString accountAgeString = tr("Unregistered user");
|
||||
if (userLevel.testFlag(ServerInfo_User::IsAdmin) || userLevel.testFlag(ServerInfo_User::IsModerator) ||
|
||||
userLevel.testFlag(ServerInfo_User::IsRegistered)) {
|
||||
accountAgeString = getAgeString(user.accountage_secs());
|
||||
}
|
||||
accountAgeLabel2.setText(accountAgeString);
|
||||
}
|
||||
|
||||
QString UserInfoBox::getAgeString(int ageSeconds)
|
||||
{
|
||||
QString accountAgeString = tr("Unknown");
|
||||
if (ageSeconds <= 0)
|
||||
return accountAgeString;
|
||||
|
||||
// secsSinceEpoch is in utc
|
||||
auto secsSinceEpoch = QDateTime::currentSecsSinceEpoch() - ageSeconds;
|
||||
// the date is in local time, fromSecsSinceEpoch expects a timestamp from utc and converts it to local time
|
||||
auto date = QDateTime::fromSecsSinceEpoch(secsSinceEpoch).date();
|
||||
if (!date.isValid())
|
||||
return accountAgeString;
|
||||
|
||||
// now can be local time as the date is also local time
|
||||
auto now = QDate::currentDate();
|
||||
auto daysAndYears = getDaysAndYearsBetween(date, now);
|
||||
|
||||
QString dateString = QLocale().toString(date, QLocale::ShortFormat);
|
||||
QString yearString;
|
||||
if (daysAndYears.second > 0) {
|
||||
yearString = tr("%n Year(s), ", "amount of years (only shown if more than 0)", daysAndYears.second);
|
||||
}
|
||||
accountAgeString =
|
||||
tr("%10%n Day(s) %20", "amount of years (if more than 0), amount of days, date in local short format",
|
||||
daysAndYears.first)
|
||||
.arg(yearString)
|
||||
.arg(dateString);
|
||||
return accountAgeString;
|
||||
}
|
||||
|
||||
void UserInfoBox::updateInfo(const QString &userName)
|
||||
{
|
||||
Command_GetUserInfo cmd;
|
||||
cmd.set_user_name(userName.toStdString());
|
||||
|
||||
PendingCommand *pend = client->prepareSessionCommand(cmd);
|
||||
connect(pend, &PendingCommand::finished, this, &UserInfoBox::processResponse);
|
||||
|
||||
client->sendCommand(pend);
|
||||
}
|
||||
|
||||
void UserInfoBox::processResponse(const Response &r)
|
||||
{
|
||||
const Response_GetUserInfo &response = r.GetExtension(Response_GetUserInfo::ext);
|
||||
updateInfo(response.user_info());
|
||||
resize(sizeHint());
|
||||
show();
|
||||
}
|
||||
|
||||
void UserInfoBox::actEdit()
|
||||
{
|
||||
Command_GetUserInfo cmd;
|
||||
|
||||
PendingCommand *pend = client->prepareSessionCommand(cmd);
|
||||
connect(pend, &PendingCommand::finished, this, &UserInfoBox::actEditInternal);
|
||||
|
||||
client->sendCommand(pend);
|
||||
}
|
||||
|
||||
void UserInfoBox::actEditInternal(const Response &r)
|
||||
{
|
||||
const Response_GetUserInfo &response = r.GetExtension(Response_GetUserInfo::ext);
|
||||
const ServerInfo_User &user = response.user_info();
|
||||
|
||||
QString email = QString::fromStdString(user.email());
|
||||
QString country = QString::fromStdString(user.country());
|
||||
QString realName = QString::fromStdString(user.real_name());
|
||||
|
||||
DlgEditUser dlg(this, email, country, realName);
|
||||
if (!dlg.exec())
|
||||
return;
|
||||
|
||||
Command_AccountEdit cmd;
|
||||
cmd.set_real_name(dlg.getRealName().toStdString());
|
||||
if (client->getServerSupportsPasswordHash()) {
|
||||
if (email != dlg.getEmail()) {
|
||||
// real password is required to change email
|
||||
bool ok = false;
|
||||
QString password =
|
||||
getTextWithMax(this, tr("Enter Password"),
|
||||
tr("Password verification is required in order to change your email address"),
|
||||
QLineEdit::Password, "", &ok);
|
||||
if (!ok)
|
||||
return;
|
||||
cmd.set_password_check(password.toStdString());
|
||||
cmd.set_email(dlg.getEmail().toStdString());
|
||||
} // servers that support password hash do not require all fields to be filled anymore
|
||||
} else {
|
||||
cmd.set_email(dlg.getEmail().toStdString());
|
||||
}
|
||||
cmd.set_country(dlg.getCountry().toStdString());
|
||||
|
||||
PendingCommand *pend = client->prepareSessionCommand(cmd);
|
||||
connect(pend, &PendingCommand::finished, this, &UserInfoBox::processEditResponse);
|
||||
|
||||
client->sendCommand(pend);
|
||||
}
|
||||
|
||||
void UserInfoBox::actPassword()
|
||||
{
|
||||
DlgEditPassword dlg(this);
|
||||
if (!dlg.exec())
|
||||
return;
|
||||
|
||||
auto oldPassword = dlg.getOldPassword();
|
||||
auto newPassword = dlg.getNewPassword();
|
||||
|
||||
if (client->getServerSupportsPasswordHash()) {
|
||||
Command_RequestPasswordSalt cmd;
|
||||
cmd.set_user_name(client->getUserName().toStdString());
|
||||
|
||||
PendingCommand *pend = client->prepareSessionCommand(cmd);
|
||||
connect(pend, &PendingCommand::finished, this, [=, this](const Response &response) {
|
||||
if (response.response_code() == Response::RespOk) {
|
||||
changePassword(oldPassword, newPassword);
|
||||
} else {
|
||||
QMessageBox::critical(this, tr("Error"),
|
||||
tr("An error occurred while trying to update your user information."));
|
||||
}
|
||||
});
|
||||
client->sendCommand(pend);
|
||||
} else {
|
||||
changePassword(oldPassword, newPassword);
|
||||
}
|
||||
}
|
||||
|
||||
void UserInfoBox::changePassword(const QString &oldPassword, const QString &newPassword)
|
||||
{
|
||||
Command_AccountPassword cmd;
|
||||
cmd.set_old_password(oldPassword.toStdString());
|
||||
if (client->getServerSupportsPasswordHash()) {
|
||||
auto passwordSalt = PasswordHasher::generateRandomSalt();
|
||||
QString hashedPassword = PasswordHasher::computeHash(newPassword, passwordSalt);
|
||||
cmd.set_hashed_new_password(hashedPassword.toStdString());
|
||||
} else {
|
||||
cmd.set_new_password(newPassword.toStdString());
|
||||
}
|
||||
|
||||
PendingCommand *pend = client->prepareSessionCommand(cmd);
|
||||
connect(pend, &PendingCommand::finished, this, &UserInfoBox::processPasswordResponse);
|
||||
|
||||
client->sendCommand(pend);
|
||||
}
|
||||
|
||||
void UserInfoBox::actAvatar()
|
||||
{
|
||||
DlgEditAvatar dlg(this);
|
||||
if (!dlg.exec())
|
||||
return;
|
||||
|
||||
Command_AccountImage cmd;
|
||||
cmd.set_image(dlg.getImage().data(), dlg.getImage().size());
|
||||
|
||||
PendingCommand *pend = client->prepareSessionCommand(cmd);
|
||||
connect(pend, &PendingCommand::finished, this, &UserInfoBox::processAvatarResponse);
|
||||
|
||||
client->sendCommand(pend);
|
||||
}
|
||||
|
||||
void UserInfoBox::processEditResponse(const Response &r)
|
||||
{
|
||||
switch (r.response_code()) {
|
||||
case Response::RespOk:
|
||||
updateInfo(nameLabel.text());
|
||||
QMessageBox::information(this, tr("Information"), tr("User information updated."));
|
||||
break;
|
||||
case Response::RespFunctionNotAllowed:
|
||||
QMessageBox::critical(this, tr("Error"),
|
||||
tr("This server does not permit you to update your user informations."));
|
||||
break;
|
||||
case Response::RespWrongPassword:
|
||||
QMessageBox::critical(this, tr("Error"), tr("The entered password does not match your account."));
|
||||
break;
|
||||
case Response::RespInternalError:
|
||||
default:
|
||||
QMessageBox::critical(this, tr("Error"),
|
||||
tr("An error occurred while trying to update your user information."));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void UserInfoBox::processPasswordResponse(const Response &r)
|
||||
{
|
||||
switch (r.response_code()) {
|
||||
case Response::RespOk:
|
||||
QMessageBox::information(this, tr("Information"), tr("Password changed."));
|
||||
break;
|
||||
case Response::RespFunctionNotAllowed:
|
||||
QMessageBox::critical(this, tr("Error"), tr("This server does not permit you to change your password."));
|
||||
break;
|
||||
case Response::RespPasswordTooShort:
|
||||
QMessageBox::critical(this, tr("Error"), tr("The new password is too short."));
|
||||
break;
|
||||
case Response::RespWrongPassword:
|
||||
QMessageBox::critical(this, tr("Error"), tr("The old password is incorrect."));
|
||||
break;
|
||||
case Response::RespInternalError:
|
||||
default:
|
||||
QMessageBox::critical(this, tr("Error"),
|
||||
tr("An error occurred while trying to update your user information."));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void UserInfoBox::processAvatarResponse(const Response &r)
|
||||
{
|
||||
switch (r.response_code()) {
|
||||
case Response::RespOk:
|
||||
updateInfo(nameLabel.text());
|
||||
QMessageBox::information(this, tr("Information"), tr("Avatar updated."));
|
||||
break;
|
||||
case Response::RespFunctionNotAllowed:
|
||||
QMessageBox::critical(this, tr("Error"), tr("This server does not permit you to update your avatar."));
|
||||
break;
|
||||
case Response::RespInternalError:
|
||||
default:
|
||||
QMessageBox::critical(this, tr("Error"), tr("An error occured while trying to updater your avatar."));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void UserInfoBox::resizeEvent(QResizeEvent *event)
|
||||
{
|
||||
QPixmap resizedPixmap;
|
||||
if (hasAvatar) {
|
||||
resizedPixmap = avatarPixmap.scaled(avatarPic.size(), Qt::KeepAspectRatio, Qt::SmoothTransformation);
|
||||
} else {
|
||||
int height = qMin(avatarPic.size().width(), avatarPic.size().height());
|
||||
resizedPixmap = createDefaultAvatar(height, *currentUserInfo);
|
||||
}
|
||||
avatarPic.setPixmap(resizedPixmap);
|
||||
|
||||
QWidget::resizeEvent(event);
|
||||
}
|
||||
63
cockatrice/src/interface/widgets/server/user/user_info_box.h
Normal file
63
cockatrice/src/interface/widgets/server/user/user_info_box.h
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
/**
|
||||
* @file user_info_box.h
|
||||
* @ingroup Lobby
|
||||
* @brief TODO: Document this.
|
||||
*/
|
||||
|
||||
#ifndef USERINFOBOX_H
|
||||
#define USERINFOBOX_H
|
||||
|
||||
#include <QDateTime>
|
||||
#include <QLabel>
|
||||
#include <QPushButton>
|
||||
#include <QWidget>
|
||||
|
||||
class ServerInfo_User;
|
||||
class AbstractClient;
|
||||
class Response;
|
||||
|
||||
class UserInfoBox : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
private:
|
||||
AbstractClient *client;
|
||||
bool editable;
|
||||
QLabel avatarPic, userLevelIcon, nameLabel, realNameLabel1, realNameLabel2, countryLabel1, countryLabel2,
|
||||
countryLabel3, userLevelLabel1, userLevelLabel2, accountAgeLabel1, accountAgeLabel2;
|
||||
QPushButton editButton, passwordButton, avatarButton;
|
||||
QPixmap avatarPixmap;
|
||||
bool hasAvatar;
|
||||
const ServerInfo_User *currentUserInfo;
|
||||
|
||||
static QString getAgeString(int ageSeconds);
|
||||
|
||||
public:
|
||||
UserInfoBox(AbstractClient *_client, bool editable, QWidget *parent = nullptr, Qt::WindowFlags flags = {});
|
||||
void retranslateUi();
|
||||
|
||||
inline static QPair<int, int> getDaysAndYearsBetween(const QDate &then, const QDate &now)
|
||||
{
|
||||
int years = now.addDays(1 - then.dayOfYear()).year() - then.year(); // there is no yearsTo
|
||||
int days = then.addYears(years).daysTo(now);
|
||||
return {days, years};
|
||||
}
|
||||
private slots:
|
||||
void processResponse(const Response &r);
|
||||
void processEditResponse(const Response &r);
|
||||
void processPasswordResponse(const Response &r);
|
||||
void processAvatarResponse(const Response &r);
|
||||
|
||||
void actEdit();
|
||||
void actEditInternal(const Response &r);
|
||||
void actPassword();
|
||||
void actAvatar();
|
||||
public slots:
|
||||
void updateInfo(const ServerInfo_User &user);
|
||||
void updateInfo(const QString &userName);
|
||||
|
||||
private:
|
||||
void resizeEvent(QResizeEvent *event) override;
|
||||
void changePassword(const QString &oldPassword, const QString &newPassword);
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,79 @@
|
|||
#include "user_info_connection.h"
|
||||
|
||||
#include <QDebug>
|
||||
#include <libcockatrice/settings/cache_settings.h>
|
||||
#include <utility>
|
||||
|
||||
UserConnection_Information::UserConnection_Information() = default;
|
||||
|
||||
UserConnection_Information::UserConnection_Information(QString _saveName,
|
||||
QString _serverName,
|
||||
QString _portNum,
|
||||
QString _userName,
|
||||
QString _pass,
|
||||
bool _savePass,
|
||||
QString _site)
|
||||
: saveName(std::move(_saveName)), server(std::move(_serverName)), port(std::move(_portNum)),
|
||||
username(std::move(_userName)), password(std::move(_pass)), savePassword(_savePass), site(std::move(_site))
|
||||
{
|
||||
}
|
||||
|
||||
QMap<QString, std::pair<QString, UserConnection_Information>> UserConnection_Information::getServerInfo()
|
||||
{
|
||||
QMap<QString, std::pair<QString, UserConnection_Information>> serverList;
|
||||
|
||||
ServersSettings &servers = SettingsCache::instance().servers();
|
||||
|
||||
int size = servers.getValue("totalServers", "server", "server_details").toInt() + 1;
|
||||
|
||||
for (int i = 0; i < size; i++) {
|
||||
QString _saveName = servers.getValue(QString("saveName%1").arg(i), "server", "server_details").toString();
|
||||
QString serverName = servers.getValue(QString("server%1").arg(i), "server", "server_details").toString();
|
||||
QString portNum = servers.getValue(QString("port%1").arg(i), "server", "server_details").toString();
|
||||
QString userName = servers.getValue(QString("username%1").arg(i), "server", "server_details").toString();
|
||||
QString pass = servers.getValue(QString("password%1").arg(i), "server", "server_details").toString();
|
||||
bool savePass = servers.getValue(QString("savePassword%1").arg(i), "server", "server_details").toBool();
|
||||
QString _site = servers.getValue(QString("site%1").arg(i), "server", "server_details").toString();
|
||||
|
||||
UserConnection_Information userInfo(_saveName, serverName, portNum, userName, pass, savePass, _site);
|
||||
serverList.insert(_saveName, std::make_pair(serverName, userInfo));
|
||||
}
|
||||
|
||||
return serverList;
|
||||
}
|
||||
|
||||
QStringList UserConnection_Information::getServerInfo(const QString &find)
|
||||
{
|
||||
QStringList _server;
|
||||
|
||||
ServersSettings &servers = SettingsCache::instance().servers();
|
||||
|
||||
int size = servers.getValue("totalServers", "server", "server_details").toInt() + 1;
|
||||
for (int i = 0; i < size; i++) {
|
||||
QString _saveName = servers.getValue(QString("saveName%1").arg(i), "server", "server_details").toString();
|
||||
|
||||
if (find != _saveName)
|
||||
continue;
|
||||
|
||||
QString serverName = servers.getValue(QString("server%1").arg(i), "server", "server_details").toString();
|
||||
QString portNum = servers.getValue(QString("port%1").arg(i), "server", "server_details").toString();
|
||||
QString userName = servers.getValue(QString("username%1").arg(i), "server", "server_details").toString();
|
||||
QString pass = servers.getValue(QString("password%1").arg(i), "server", "server_details").toString();
|
||||
bool savePass = servers.getValue(QString("savePassword%1").arg(i), "server", "server_details").toBool();
|
||||
QString _site = servers.getValue(QString("site%1").arg(i), "server", "server_details").toString();
|
||||
|
||||
_server.append(_saveName);
|
||||
_server.append(serverName);
|
||||
_server.append(portNum);
|
||||
_server.append(userName);
|
||||
_server.append(pass);
|
||||
_server.append(savePass ? "1" : "0");
|
||||
_server.append(_site);
|
||||
break;
|
||||
}
|
||||
|
||||
if (_server.empty())
|
||||
qCWarning(UserInfoConnectionLog) << "There was a problem!";
|
||||
|
||||
return _server;
|
||||
}
|
||||
|
|
@ -0,0 +1,64 @@
|
|||
/**
|
||||
* @file user_info_connection.h
|
||||
* @ingroup Client
|
||||
* @brief TODO: Document this.
|
||||
*/
|
||||
|
||||
#ifndef USERCONNECTION_INFORMATION_H
|
||||
#define USERCONNECTION_INFORMATION_H
|
||||
|
||||
#include <QApplication>
|
||||
#include <QDir>
|
||||
#include <QFile>
|
||||
#include <QLoggingCategory>
|
||||
#include <QSettings>
|
||||
#include <QStandardPaths>
|
||||
|
||||
inline Q_LOGGING_CATEGORY(UserInfoConnectionLog, "user_info_connection");
|
||||
|
||||
class UserConnection_Information
|
||||
{
|
||||
private:
|
||||
QString saveName;
|
||||
QString server;
|
||||
QString port;
|
||||
QString username;
|
||||
QString password;
|
||||
bool savePassword;
|
||||
QString site;
|
||||
|
||||
public:
|
||||
UserConnection_Information();
|
||||
UserConnection_Information(QString, QString, QString, QString, QString, bool, QString);
|
||||
QString getSaveName() const
|
||||
{
|
||||
return saveName;
|
||||
}
|
||||
QString getServer() const
|
||||
{
|
||||
return server;
|
||||
}
|
||||
QString getPort() const
|
||||
{
|
||||
return port;
|
||||
}
|
||||
QString getUsername() const
|
||||
{
|
||||
return username;
|
||||
}
|
||||
QString getPassword() const
|
||||
{
|
||||
return password;
|
||||
}
|
||||
bool getSavePassword() const
|
||||
{
|
||||
return savePassword;
|
||||
}
|
||||
QString getSite() const
|
||||
{
|
||||
return site;
|
||||
}
|
||||
QMap<QString, std::pair<QString, UserConnection_Information>> getServerInfo();
|
||||
QStringList getServerInfo(const QString &find);
|
||||
};
|
||||
#endif
|
||||
|
|
@ -0,0 +1,170 @@
|
|||
#include "user_list_manager.h"
|
||||
|
||||
#include "../../client/sound_engine.h"
|
||||
#include "user_info_box.h"
|
||||
|
||||
#include <libcockatrice/network/client/abstract/abstract_client.h>
|
||||
#include <libcockatrice/protocol/pb/event_add_to_list.pb.h>
|
||||
#include <libcockatrice/protocol/pb/event_remove_from_list.pb.h>
|
||||
#include <libcockatrice/protocol/pb/event_user_joined.pb.h>
|
||||
#include <libcockatrice/protocol/pb/event_user_left.pb.h>
|
||||
#include <libcockatrice/protocol/pb/response_list_users.pb.h>
|
||||
#include <libcockatrice/protocol/pb/session_commands.pb.h>
|
||||
#include <libcockatrice/protocol/pending_command.h>
|
||||
|
||||
UserListManager::UserListManager(AbstractClient *_client, QObject *parent)
|
||||
: QObject(parent), client(_client), ownUserInfo(nullptr)
|
||||
{
|
||||
connect(client, &AbstractClient::userJoinedEventReceived, this, &UserListManager::processUserJoinedEvent);
|
||||
connect(client, &AbstractClient::userLeftEventReceived, this, &UserListManager::processUserLeftEvent);
|
||||
connect(client, &AbstractClient::buddyListReceived, this, &UserListManager::buddyListReceived);
|
||||
connect(client, &AbstractClient::ignoreListReceived, this, &UserListManager::ignoreListReceived);
|
||||
connect(client, &AbstractClient::addToListEventReceived, this, &UserListManager::processAddToListEvent);
|
||||
connect(client, &AbstractClient::removeFromListEventReceived, this, &UserListManager::processRemoveFromListEvent);
|
||||
connect(client, &AbstractClient::userInfoChanged, this, &UserListManager::setOwnUserInfo);
|
||||
}
|
||||
|
||||
UserListManager::~UserListManager()
|
||||
{
|
||||
handleDisconnect();
|
||||
}
|
||||
|
||||
void UserListManager::handleConnect()
|
||||
{
|
||||
populateInitialOnlineUsers();
|
||||
}
|
||||
|
||||
void UserListManager::handleDisconnect()
|
||||
{
|
||||
onlineUsers.clear();
|
||||
buddyUsers.clear();
|
||||
ignoredUsers.clear();
|
||||
|
||||
delete ownUserInfo;
|
||||
ownUserInfo = nullptr;
|
||||
}
|
||||
|
||||
void UserListManager::setOwnUserInfo(const ServerInfo_User &userInfo)
|
||||
{
|
||||
ownUserInfo = new ServerInfo_User(userInfo);
|
||||
}
|
||||
|
||||
void UserListManager::populateInitialOnlineUsers()
|
||||
{
|
||||
PendingCommand *pend = client->prepareSessionCommand(Command_ListUsers());
|
||||
connect(pend, &PendingCommand::finished, this, &UserListManager::processListUsersResponse);
|
||||
client->sendCommand(pend);
|
||||
}
|
||||
|
||||
void UserListManager::processListUsersResponse(const Response &response)
|
||||
{
|
||||
const auto &resp = response.GetExtension(Response_ListUsers::ext);
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
void UserListManager::processUserJoinedEvent(const Event_UserJoined &event)
|
||||
{
|
||||
const auto &info = event.user_info();
|
||||
const QString &userName = QString::fromStdString(info.name());
|
||||
onlineUsers.insert(userName, info);
|
||||
}
|
||||
|
||||
void UserListManager::processUserLeftEvent(const Event_UserLeft &event)
|
||||
{
|
||||
const auto &userName = QString::fromStdString(event.name());
|
||||
onlineUsers.remove(userName);
|
||||
}
|
||||
|
||||
void UserListManager::buddyListReceived(const QList<ServerInfo_User> &_buddyList)
|
||||
{
|
||||
for (const auto &user : _buddyList) {
|
||||
const auto &userName = QString::fromStdString(user.name());
|
||||
buddyUsers.insert(userName, user);
|
||||
}
|
||||
}
|
||||
|
||||
void UserListManager::ignoreListReceived(const QList<ServerInfo_User> &_ignoreList)
|
||||
{
|
||||
for (const auto &user : _ignoreList) {
|
||||
const auto &userName = QString::fromStdString(user.name());
|
||||
ignoredUsers.insert(userName, user);
|
||||
}
|
||||
}
|
||||
|
||||
void UserListManager::processAddToListEvent(const Event_AddToList &event)
|
||||
{
|
||||
const auto &user = event.user_info();
|
||||
const auto &userName = QString::fromStdString(user.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;
|
||||
}
|
||||
|
||||
userMap->insert(userName, user);
|
||||
}
|
||||
|
||||
void UserListManager::processRemoveFromListEvent(const Event_RemoveFromList &event)
|
||||
{
|
||||
const auto &userListType = QString::fromStdString(event.list_name());
|
||||
const auto &userName = QString::fromStdString(event.user_name());
|
||||
|
||||
QMap<QString, ServerInfo_User> *userMap;
|
||||
if (userListType == "buddy") {
|
||||
userMap = &buddyUsers;
|
||||
} else if (userListType == "ignore") {
|
||||
userMap = &ignoredUsers;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
userMap->remove(userName);
|
||||
}
|
||||
|
||||
bool UserListManager::isOwnUserRegistered() const
|
||||
{
|
||||
return ownUserInfo != nullptr && (ownUserInfo->user_level() & ServerInfo_User::IsRegistered) != 0;
|
||||
}
|
||||
|
||||
QString UserListManager::getOwnUsername() const
|
||||
{
|
||||
return ownUserInfo != nullptr ? QString::fromStdString(ownUserInfo->name()) : QString();
|
||||
}
|
||||
|
||||
bool UserListManager::isUserBuddy(const QString &userName) const
|
||||
{
|
||||
return buddyUsers.contains(userName);
|
||||
}
|
||||
|
||||
bool UserListManager::isUserIgnored(const QString &userName) const
|
||||
{
|
||||
return ignoredUsers.contains(userName);
|
||||
}
|
||||
|
||||
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;
|
||||
};
|
||||
|
|
@ -0,0 +1,78 @@
|
|||
/**
|
||||
* @file user_list_manager.h
|
||||
* @ingroup Lobby
|
||||
* @brief TODO: Document this.
|
||||
*/
|
||||
|
||||
#ifndef COCKATRICE_USER_LIST_MANAGER_H
|
||||
#define COCKATRICE_USER_LIST_MANAGER_H
|
||||
|
||||
#include "user_list_proxy.h"
|
||||
|
||||
#include <QMap>
|
||||
#include <QWidget>
|
||||
#include <libcockatrice/protocol/pb/serverinfo_user.pb.h>
|
||||
|
||||
class AbstractClient;
|
||||
class Event_AddToList;
|
||||
class Event_ListRooms;
|
||||
class Event_RemoveFromList;
|
||||
class Event_UserJoined;
|
||||
class Event_UserLeft;
|
||||
class Response;
|
||||
class ServerInfo_User;
|
||||
class TabSupervisor;
|
||||
|
||||
class UserListManager : public QObject, public UserListProxy
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
private:
|
||||
AbstractClient *client;
|
||||
ServerInfo_User *ownUserInfo;
|
||||
QMap<QString, ServerInfo_User> onlineUsers, buddyUsers, ignoredUsers;
|
||||
|
||||
private slots:
|
||||
void setOwnUserInfo(const ServerInfo_User &userInfo);
|
||||
void populateInitialOnlineUsers();
|
||||
void processListUsersResponse(const Response &response);
|
||||
void processUserJoinedEvent(const Event_UserJoined &event);
|
||||
void processUserLeftEvent(const Event_UserLeft &event);
|
||||
void buddyListReceived(const QList<ServerInfo_User> &_buddyList);
|
||||
void ignoreListReceived(const QList<ServerInfo_User> &_ignoreList);
|
||||
void processAddToListEvent(const Event_AddToList &event);
|
||||
void processRemoveFromListEvent(const Event_RemoveFromList &event);
|
||||
|
||||
public:
|
||||
explicit UserListManager(AbstractClient *_client, QObject *parent = nullptr);
|
||||
~UserListManager() override;
|
||||
|
||||
[[nodiscard]] QMap<QString, ServerInfo_User> getAllUsersList() const
|
||||
{
|
||||
return onlineUsers;
|
||||
}
|
||||
[[nodiscard]] QMap<QString, ServerInfo_User> getBuddyList() const
|
||||
{
|
||||
return buddyUsers;
|
||||
}
|
||||
[[nodiscard]] QMap<QString, ServerInfo_User> getIgnoreList() const
|
||||
{
|
||||
return ignoredUsers;
|
||||
}
|
||||
|
||||
bool isOwnUserRegistered() const override;
|
||||
QString getOwnUsername() const override;
|
||||
bool isUserBuddy(const QString &userName) const override;
|
||||
bool isUserIgnored(const QString &userName) const override;
|
||||
const ServerInfo_User *getOnlineUser(const QString &userName) const override;
|
||||
|
||||
public slots:
|
||||
void handleConnect();
|
||||
void handleDisconnect();
|
||||
|
||||
signals:
|
||||
void userLeft(const QString &userName);
|
||||
void userJoined(const ServerInfo_User &userInfo);
|
||||
};
|
||||
|
||||
#endif // COCKATRICE_USER_LIST_MANAGER_H
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
/**
|
||||
* @file user_list_proxy.h
|
||||
* @ingroup UI
|
||||
* @ingroup Lobby
|
||||
* @brief TODO: Document this.
|
||||
*/
|
||||
|
||||
#ifndef COCKATRICE_USERLISTPROXY_H
|
||||
#define COCKATRICE_USERLISTPROXY_H
|
||||
|
||||
class QString;
|
||||
class ServerInfo_User;
|
||||
|
||||
/**
|
||||
* Responsible for providing a bare-bones minimal interface into userlist information,
|
||||
* including your current connection to the server as well as buddy/ignore/alluser lists.
|
||||
*/
|
||||
class UserListProxy
|
||||
{
|
||||
public:
|
||||
virtual bool isOwnUserRegistered() const = 0;
|
||||
virtual QString getOwnUsername() const = 0;
|
||||
virtual bool isUserBuddy(const QString &userName) const = 0;
|
||||
virtual bool isUserIgnored(const QString &userName) const = 0;
|
||||
virtual const ServerInfo_User *getOnlineUser(const QString &userName) const = 0; // Can return nullptr
|
||||
};
|
||||
|
||||
#endif // COCKATRICE_USERLISTPROXY_H
|
||||
|
|
@ -0,0 +1,539 @@
|
|||
#include "user_list_widget.h"
|
||||
|
||||
#include "../../interface/pixel_map_generator.h"
|
||||
#include "../../tabs/tab_account.h"
|
||||
#include "../../tabs/tab_supervisor.h"
|
||||
#include "../game_selector.h"
|
||||
#include "user_context_menu.h"
|
||||
#include "user_list_manager.h"
|
||||
|
||||
#include <QApplication>
|
||||
#include <QCheckBox>
|
||||
#include <QHBoxLayout>
|
||||
#include <QHeaderView>
|
||||
#include <QInputDialog>
|
||||
#include <QLabel>
|
||||
#include <QLineEdit>
|
||||
#include <QMenu>
|
||||
#include <QMessageBox>
|
||||
#include <QMouseEvent>
|
||||
#include <QPlainTextEdit>
|
||||
#include <QPushButton>
|
||||
#include <QRadioButton>
|
||||
#include <QSpinBox>
|
||||
#include <QVBoxLayout>
|
||||
#include <QWidget>
|
||||
#include <libcockatrice/network/client/abstract/abstract_client.h>
|
||||
#include <libcockatrice/protocol/pb/moderator_commands.pb.h>
|
||||
#include <libcockatrice/protocol/pb/response_get_games_of_user.pb.h>
|
||||
#include <libcockatrice/protocol/pb/response_get_user_info.pb.h>
|
||||
#include <libcockatrice/protocol/pb/session_commands.pb.h>
|
||||
#include <libcockatrice/protocol/pending_command.h>
|
||||
#include <libcockatrice/utility/trice_limits.h>
|
||||
|
||||
BanDialog::BanDialog(const ServerInfo_User &info, QWidget *parent) : QDialog(parent)
|
||||
{
|
||||
setAttribute(Qt::WA_DeleteOnClose);
|
||||
|
||||
nameBanCheckBox = new QCheckBox(tr("ban &user name"));
|
||||
nameBanCheckBox->setChecked(true);
|
||||
nameBanEdit = new QLineEdit(QString::fromStdString(info.name()));
|
||||
nameBanEdit->setMaxLength(MAX_NAME_LENGTH);
|
||||
ipBanCheckBox = new QCheckBox(tr("ban &IP address"));
|
||||
ipBanCheckBox->setChecked(true);
|
||||
ipBanEdit = new QLineEdit(QString::fromStdString(info.address()));
|
||||
ipBanEdit->setMaxLength(MAX_NAME_LENGTH);
|
||||
idBanCheckBox = new QCheckBox(tr("ban client I&D"));
|
||||
idBanCheckBox->setChecked(true);
|
||||
idBanEdit = new QLineEdit(QString::fromStdString(info.clientid()));
|
||||
idBanEdit->setMaxLength(MAX_NAME_LENGTH);
|
||||
if (QString::fromStdString(info.clientid()).isEmpty())
|
||||
idBanCheckBox->setChecked(false);
|
||||
|
||||
QGridLayout *banTypeGrid = new QGridLayout;
|
||||
banTypeGrid->addWidget(nameBanCheckBox, 0, 0);
|
||||
banTypeGrid->addWidget(nameBanEdit, 0, 1);
|
||||
banTypeGrid->addWidget(ipBanCheckBox, 1, 0);
|
||||
banTypeGrid->addWidget(ipBanEdit, 1, 1);
|
||||
banTypeGrid->addWidget(idBanCheckBox, 2, 0);
|
||||
banTypeGrid->addWidget(idBanEdit, 2, 1);
|
||||
QGroupBox *banTypeGroupBox = new QGroupBox(tr("Ban type"));
|
||||
banTypeGroupBox->setLayout(banTypeGrid);
|
||||
|
||||
permanentRadio = new QRadioButton(tr("&permanent ban"));
|
||||
temporaryRadio = new QRadioButton(tr("&temporary ban"));
|
||||
temporaryRadio->setChecked(true);
|
||||
connect(temporaryRadio, &QRadioButton::toggled, this, &BanDialog::enableTemporaryEdits);
|
||||
daysLabel = new QLabel(tr("&Days:"));
|
||||
daysEdit = new QSpinBox;
|
||||
daysEdit->setMinimum(0);
|
||||
daysEdit->setValue(0);
|
||||
daysEdit->setMaximum(10000);
|
||||
daysLabel->setBuddy(daysEdit);
|
||||
hoursLabel = new QLabel(tr("&Hours:"));
|
||||
hoursEdit = new QSpinBox;
|
||||
hoursEdit->setMinimum(0);
|
||||
hoursEdit->setValue(0);
|
||||
hoursEdit->setMaximum(24);
|
||||
hoursLabel->setBuddy(hoursEdit);
|
||||
minutesLabel = new QLabel(tr("&Minutes:"));
|
||||
minutesEdit = new QSpinBox;
|
||||
minutesEdit->setMinimum(0);
|
||||
minutesEdit->setValue(5);
|
||||
minutesEdit->setMaximum(60);
|
||||
minutesLabel->setBuddy(minutesEdit);
|
||||
QGridLayout *durationLayout = new QGridLayout;
|
||||
durationLayout->addWidget(permanentRadio, 0, 0, 1, 6);
|
||||
durationLayout->addWidget(temporaryRadio, 1, 0, 1, 6);
|
||||
durationLayout->addWidget(daysLabel, 2, 0);
|
||||
durationLayout->addWidget(daysEdit, 2, 1);
|
||||
durationLayout->addWidget(hoursLabel, 2, 2);
|
||||
durationLayout->addWidget(hoursEdit, 2, 3);
|
||||
durationLayout->addWidget(minutesLabel, 2, 4);
|
||||
durationLayout->addWidget(minutesEdit, 2, 5);
|
||||
QGroupBox *durationGroupBox = new QGroupBox(tr("Duration of the ban"));
|
||||
durationGroupBox->setLayout(durationLayout);
|
||||
|
||||
QLabel *reasonLabel = new QLabel(tr("Please enter the reason for the ban.\nThis is only saved for moderators and "
|
||||
"cannot be seen by the banned person."));
|
||||
reasonEdit = new QPlainTextEdit;
|
||||
|
||||
QLabel *visibleReasonLabel =
|
||||
new QLabel(tr("Please enter the reason for the ban that will be visible to the banned person."));
|
||||
visibleReasonEdit = new QPlainTextEdit;
|
||||
|
||||
deleteMessages = new QCheckBox(tr("Redact all messages from this user in all rooms"));
|
||||
|
||||
QPushButton *okButton = new QPushButton(tr("&OK"));
|
||||
okButton->setAutoDefault(true);
|
||||
connect(okButton, &QPushButton::clicked, this, &BanDialog::okClicked);
|
||||
QPushButton *cancelButton = new QPushButton(tr("&Cancel"));
|
||||
connect(cancelButton, &QPushButton::clicked, this, &BanDialog::reject);
|
||||
|
||||
QHBoxLayout *buttonLayout = new QHBoxLayout;
|
||||
buttonLayout->addStretch();
|
||||
buttonLayout->addWidget(okButton);
|
||||
buttonLayout->addWidget(cancelButton);
|
||||
|
||||
QVBoxLayout *vbox = new QVBoxLayout;
|
||||
vbox->addWidget(banTypeGroupBox);
|
||||
vbox->addWidget(durationGroupBox);
|
||||
vbox->addWidget(reasonLabel);
|
||||
vbox->addWidget(reasonEdit);
|
||||
vbox->addWidget(visibleReasonLabel);
|
||||
vbox->addWidget(visibleReasonEdit);
|
||||
vbox->addWidget(deleteMessages);
|
||||
vbox->addLayout(buttonLayout);
|
||||
|
||||
setLayout(vbox);
|
||||
setWindowTitle(tr("Ban user from server"));
|
||||
}
|
||||
|
||||
WarningDialog::WarningDialog(const QString userName, const QString clientID, QWidget *parent) : QDialog(parent)
|
||||
{
|
||||
setAttribute(Qt::WA_DeleteOnClose);
|
||||
descriptionLabel = new QLabel(tr("Which warning would you like to send?"));
|
||||
nameWarning = new QLineEdit(userName);
|
||||
nameWarning->setMaxLength(MAX_NAME_LENGTH);
|
||||
warnClientID = new QLineEdit(clientID);
|
||||
warnClientID->setMaxLength(MAX_NAME_LENGTH);
|
||||
warningOption = new QComboBox();
|
||||
warningOption->addItem("");
|
||||
|
||||
deleteMessages = new QCheckBox(tr("Redact all messages from this user in all rooms"));
|
||||
|
||||
QPushButton *okButton = new QPushButton(tr("&OK"));
|
||||
okButton->setAutoDefault(true);
|
||||
connect(okButton, &QPushButton::clicked, this, &WarningDialog::okClicked);
|
||||
QPushButton *cancelButton = new QPushButton(tr("&Cancel"));
|
||||
connect(cancelButton, &QPushButton::clicked, this, &WarningDialog::reject);
|
||||
|
||||
QHBoxLayout *buttonLayout = new QHBoxLayout;
|
||||
buttonLayout->addStretch();
|
||||
buttonLayout->addWidget(okButton);
|
||||
buttonLayout->addWidget(cancelButton);
|
||||
|
||||
QVBoxLayout *vbox = new QVBoxLayout;
|
||||
vbox->addWidget(descriptionLabel);
|
||||
vbox->addWidget(nameWarning);
|
||||
vbox->addWidget(warningOption);
|
||||
vbox->addWidget(deleteMessages);
|
||||
vbox->addLayout(buttonLayout);
|
||||
setLayout(vbox);
|
||||
setWindowTitle(tr("Warn user for misconduct"));
|
||||
}
|
||||
|
||||
void WarningDialog::okClicked()
|
||||
{
|
||||
if (nameWarning->text().simplified().isEmpty()) {
|
||||
QMessageBox::critical(this, tr("Error"),
|
||||
tr("User name to send a warning to can not be blank, please specify a user to warn."));
|
||||
return;
|
||||
}
|
||||
|
||||
if (warningOption->currentText().simplified().isEmpty()) {
|
||||
QMessageBox::critical(this, tr("Error"),
|
||||
tr("Warning to use can not be blank, please select a valid warning to send."));
|
||||
return;
|
||||
}
|
||||
|
||||
accept();
|
||||
}
|
||||
|
||||
QString WarningDialog::getName() const
|
||||
{
|
||||
return nameWarning->text().simplified();
|
||||
}
|
||||
|
||||
QString WarningDialog::getWarnID() const
|
||||
{
|
||||
return warnClientID->text().simplified();
|
||||
}
|
||||
|
||||
QString WarningDialog::getReason() const
|
||||
{
|
||||
return warningOption->currentText().simplified();
|
||||
}
|
||||
|
||||
int WarningDialog::getDeleteMessages() const
|
||||
{
|
||||
return deleteMessages->isChecked() ? -1 : 0;
|
||||
}
|
||||
|
||||
void WarningDialog::addWarningOption(const QString warning)
|
||||
{
|
||||
warningOption->addItem(warning);
|
||||
}
|
||||
|
||||
void BanDialog::okClicked()
|
||||
{
|
||||
if (!nameBanCheckBox->isChecked() && !ipBanCheckBox->isChecked() && !idBanCheckBox->isChecked()) {
|
||||
QMessageBox::critical(this, tr("Error"),
|
||||
tr("You have to select a name-based, IP-based, clientId based, or some combination of "
|
||||
"the three to place a ban."));
|
||||
return;
|
||||
}
|
||||
|
||||
if (nameBanCheckBox->isChecked())
|
||||
if (nameBanEdit->text().simplified() == "") {
|
||||
QMessageBox::critical(this, tr("Error"),
|
||||
tr("You must have a value in the name ban when selecting the name ban checkbox."));
|
||||
return;
|
||||
}
|
||||
|
||||
if (ipBanCheckBox->isChecked())
|
||||
if (ipBanEdit->text().simplified() == "") {
|
||||
QMessageBox::critical(this, tr("Error"),
|
||||
tr("You must have a value in the ip ban when selecting the ip ban checkbox."));
|
||||
return;
|
||||
}
|
||||
|
||||
if (idBanCheckBox->isChecked())
|
||||
if (idBanEdit->text().simplified() == "") {
|
||||
QMessageBox::critical(
|
||||
this, tr("Error"),
|
||||
tr("You must have a value in the clientid ban when selecting the clientid ban checkbox."));
|
||||
return;
|
||||
}
|
||||
|
||||
accept();
|
||||
}
|
||||
|
||||
void BanDialog::enableTemporaryEdits(bool enabled)
|
||||
{
|
||||
daysLabel->setEnabled(enabled);
|
||||
daysEdit->setEnabled(enabled);
|
||||
hoursLabel->setEnabled(enabled);
|
||||
hoursEdit->setEnabled(enabled);
|
||||
minutesLabel->setEnabled(enabled);
|
||||
minutesEdit->setEnabled(enabled);
|
||||
}
|
||||
|
||||
QString BanDialog::getBanId() const
|
||||
{
|
||||
return idBanCheckBox->isChecked() ? idBanEdit->text() : QString();
|
||||
}
|
||||
|
||||
QString BanDialog::getBanName() const
|
||||
{
|
||||
return nameBanCheckBox->isChecked() ? nameBanEdit->text() : QString();
|
||||
}
|
||||
|
||||
QString BanDialog::getBanIP() const
|
||||
{
|
||||
return ipBanCheckBox->isChecked() ? ipBanEdit->text() : QString();
|
||||
}
|
||||
|
||||
int BanDialog::getMinutes() const
|
||||
{
|
||||
return permanentRadio->isChecked() ? 0
|
||||
: (daysEdit->value() * 24 * 60 + hoursEdit->value() * 60 + minutesEdit->value());
|
||||
}
|
||||
|
||||
QString BanDialog::getReason() const
|
||||
{
|
||||
return reasonEdit->toPlainText();
|
||||
}
|
||||
|
||||
QString BanDialog::getVisibleReason() const
|
||||
{
|
||||
return visibleReasonEdit->toPlainText();
|
||||
}
|
||||
|
||||
int BanDialog::getDeleteMessages() const
|
||||
{
|
||||
return deleteMessages->isChecked() ? -1 : 0;
|
||||
}
|
||||
|
||||
AdminNotesDialog::AdminNotesDialog(const QString &_userName, const QString &_notes, QWidget *_parent)
|
||||
: QDialog(_parent), userName(_userName)
|
||||
{
|
||||
setAttribute(Qt::WA_DeleteOnClose);
|
||||
|
||||
auto *updateButton = new QPushButton(tr("Update Notes"));
|
||||
updateButton->setEnabled(false);
|
||||
connect(updateButton, &QPushButton::clicked, this, &AdminNotesDialog::accept);
|
||||
|
||||
notes = new QPlainTextEdit(_notes);
|
||||
notes->setMinimumWidth(500);
|
||||
connect(notes, &QPlainTextEdit::textChanged, this, [=]() { updateButton->setEnabled(true); });
|
||||
|
||||
auto *vbox = new QVBoxLayout;
|
||||
vbox->addWidget(notes);
|
||||
vbox->addWidget(updateButton);
|
||||
|
||||
setLayout(vbox);
|
||||
setWindowTitle(tr("Admin Notes for %1").arg(_userName));
|
||||
}
|
||||
|
||||
QString AdminNotesDialog::getNotes() const
|
||||
{
|
||||
return notes->toPlainText();
|
||||
}
|
||||
|
||||
UserListItemDelegate::UserListItemDelegate(QObject *const parent) : QStyledItemDelegate(parent)
|
||||
{
|
||||
}
|
||||
|
||||
bool UserListItemDelegate::editorEvent(QEvent *event,
|
||||
QAbstractItemModel *model,
|
||||
const QStyleOptionViewItem &option,
|
||||
const QModelIndex &index)
|
||||
{
|
||||
if ((event->type() == QEvent::MouseButtonPress) && index.isValid()) {
|
||||
QMouseEvent *const mouseEvent = static_cast<QMouseEvent *>(event);
|
||||
if (mouseEvent->button() == Qt::RightButton) {
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
|
||||
static_cast<UserListWidget *>(parent())->showContextMenu(mouseEvent->globalPosition().toPoint(), index);
|
||||
#else
|
||||
static_cast<UserListWidget *>(parent())->showContextMenu(mouseEvent->globalPos(), index);
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return QStyledItemDelegate::editorEvent(event, model, option, index);
|
||||
}
|
||||
|
||||
UserListTWI::UserListTWI(const ServerInfo_User &_userInfo) : QTreeWidgetItem(Type)
|
||||
{
|
||||
setUserInfo(_userInfo);
|
||||
}
|
||||
|
||||
void UserListTWI::setUserInfo(const ServerInfo_User &_userInfo)
|
||||
{
|
||||
userInfo = _userInfo;
|
||||
|
||||
setData(0, Qt::UserRole, userInfo.user_level());
|
||||
setIcon(0, UserLevelPixmapGenerator::generateIcon(18, UserLevelFlags(userInfo.user_level()), userInfo.pawn_colors(),
|
||||
false, QString::fromStdString(userInfo.privlevel())));
|
||||
setIcon(1, QIcon(CountryPixmapGenerator::generatePixmap(18, QString::fromStdString(userInfo.country()))));
|
||||
setData(2, Qt::UserRole, QString::fromStdString(userInfo.name()));
|
||||
setData(2, Qt::DisplayRole, QString::fromStdString(userInfo.name()));
|
||||
setData(3, Qt::InitialSortOrderRole, QString::fromStdString(userInfo.privlevel()));
|
||||
}
|
||||
|
||||
void UserListTWI::setOnline(bool online)
|
||||
{
|
||||
setData(0, Qt::UserRole + 1, online);
|
||||
setData(2, Qt::ForegroundRole, online ? qApp->palette().brush(QPalette::WindowText) : QBrush(Qt::gray));
|
||||
}
|
||||
|
||||
/**
|
||||
* Sort Users in the following order
|
||||
* 1) Online Users > Offline Users
|
||||
* 2) Admins, judge/vip/donator status ignored
|
||||
* 3) Moderators, judge/vip/donator status ignored
|
||||
* 4) Judges
|
||||
* 5) VIPs
|
||||
* 6) Donators
|
||||
* 7) Everyone else
|
||||
* @param other RHS to compare to
|
||||
* @return Left is less than the Right
|
||||
*/
|
||||
bool UserListTWI::operator<(const QTreeWidgetItem &other) const
|
||||
{
|
||||
// Sort by online/offline
|
||||
if (data(0, Qt::UserRole + 1) != other.data(0, Qt::UserRole + 1)) {
|
||||
return data(0, Qt::UserRole + 1).toBool();
|
||||
}
|
||||
|
||||
const auto &lhsUserLevelFlags = UserLevelFlags(data(0, Qt::UserRole).toInt());
|
||||
const auto &rhsUserLevelFlags = UserLevelFlags(other.data(0, Qt::UserRole).toInt());
|
||||
|
||||
// Admins & Mods need no additional comparison checks, just to see if they're an admin or a moderator
|
||||
static const QList<ServerInfo_User_UserLevelFlag> userLevelWithNoOtherPrefOrder = {
|
||||
ServerInfo_User_UserLevelFlag_IsAdmin, ServerInfo_User_UserLevelFlag_IsModerator};
|
||||
for (const auto &userLevelEntry : userLevelWithNoOtherPrefOrder) {
|
||||
if (lhsUserLevelFlags.testFlag(userLevelEntry) &&
|
||||
lhsUserLevelFlags.testFlag(userLevelEntry) == rhsUserLevelFlags.testFlag(userLevelEntry)) {
|
||||
return QString::localeAwareCompare(data(2, Qt::UserRole).toString(),
|
||||
other.data(2, Qt::UserRole).toString()) < 0;
|
||||
} else if (lhsUserLevelFlags.testFlag(userLevelEntry) != rhsUserLevelFlags.testFlag(userLevelEntry)) {
|
||||
return lhsUserLevelFlags.testFlag(userLevelEntry) > rhsUserLevelFlags.testFlag(userLevelEntry);
|
||||
}
|
||||
}
|
||||
|
||||
// Judges can be sorted by their additional ranks
|
||||
static const QList<ServerInfo_User_UserLevelFlag> userLevelOrder = {ServerInfo_User_UserLevelFlag_IsJudge,
|
||||
ServerInfo_User_UserLevelFlag_IsRegistered,
|
||||
ServerInfo_User_UserLevelFlag_IsUser};
|
||||
for (const auto &userLevelEntry : userLevelOrder) {
|
||||
if (lhsUserLevelFlags.testFlag(userLevelEntry) != rhsUserLevelFlags.testFlag(userLevelEntry)) {
|
||||
return lhsUserLevelFlags.testFlag(userLevelEntry) > rhsUserLevelFlags.testFlag(userLevelEntry);
|
||||
}
|
||||
}
|
||||
|
||||
// Sort by VIP > Donator > None
|
||||
static const QMap<QString, int> privilegeOrder = {{"VIP", 3}, {"DONATOR", 2}, {"NONE", 1}, {"UNKNOWN", 0}};
|
||||
const auto &lhsUserPrivLevel = privilegeOrder.value(data(3, Qt::InitialSortOrderRole).toString(), 0);
|
||||
const auto &rhsUserPrivLevel = privilegeOrder.value(other.data(3, Qt::InitialSortOrderRole).toString(), 0);
|
||||
if (lhsUserPrivLevel != rhsUserPrivLevel) {
|
||||
return lhsUserPrivLevel > rhsUserPrivLevel;
|
||||
}
|
||||
|
||||
// Sort by name
|
||||
return QString::localeAwareCompare(data(2, Qt::UserRole).toString(), other.data(2, Qt::UserRole).toString()) < 0;
|
||||
}
|
||||
|
||||
UserListWidget::UserListWidget(TabSupervisor *_tabSupervisor,
|
||||
AbstractClient *_client,
|
||||
UserListType _type,
|
||||
QWidget *parent)
|
||||
: QGroupBox(parent), tabSupervisor(_tabSupervisor), client(_client), type(_type), onlineCount(0)
|
||||
{
|
||||
itemDelegate = new UserListItemDelegate(this);
|
||||
userContextMenu = new UserContextMenu(tabSupervisor, this);
|
||||
connect(userContextMenu, &UserContextMenu::openMessageDialog, this, &UserListWidget::openMessageDialog);
|
||||
|
||||
userTree = new QTreeWidget;
|
||||
userTree->setColumnCount(3);
|
||||
userTree->header()->setSectionResizeMode(QHeaderView::ResizeToContents);
|
||||
userTree->header()->setMinimumSectionSize(0);
|
||||
userTree->setHeaderHidden(true);
|
||||
userTree->setRootIsDecorated(false);
|
||||
userTree->setIconSize(QSize(20, 18));
|
||||
userTree->setItemDelegate(itemDelegate);
|
||||
userTree->setAlternatingRowColors(true);
|
||||
connect(userTree, &QTreeWidget::itemActivated, this, &UserListWidget::userClicked);
|
||||
|
||||
QVBoxLayout *vbox = new QVBoxLayout;
|
||||
vbox->addWidget(userTree);
|
||||
|
||||
setLayout(vbox);
|
||||
|
||||
retranslateUi();
|
||||
}
|
||||
|
||||
void UserListWidget::retranslateUi()
|
||||
{
|
||||
userContextMenu->retranslateUi();
|
||||
switch (type) {
|
||||
case AllUsersList:
|
||||
titleStr = tr("Users connected to server: %1");
|
||||
break;
|
||||
case RoomList:
|
||||
titleStr = tr("Users in this room: %1");
|
||||
break;
|
||||
case BuddyList:
|
||||
titleStr = tr("Buddies online: %1 / %2");
|
||||
break;
|
||||
case IgnoreList:
|
||||
titleStr = tr("Ignored users online: %1 / %2");
|
||||
break;
|
||||
}
|
||||
updateCount();
|
||||
}
|
||||
|
||||
void UserListWidget::processUserInfo(const ServerInfo_User &user, bool online)
|
||||
{
|
||||
const QString userName = QString::fromStdString(user.name());
|
||||
UserListTWI *item = users.value(userName);
|
||||
if (item)
|
||||
item->setUserInfo(user);
|
||||
else {
|
||||
item = new UserListTWI(user);
|
||||
users.insert(userName, item);
|
||||
userTree->addTopLevelItem(item);
|
||||
if (online)
|
||||
++onlineCount;
|
||||
updateCount();
|
||||
}
|
||||
item->setOnline(online);
|
||||
}
|
||||
|
||||
bool UserListWidget::deleteUser(const QString &userName)
|
||||
{
|
||||
UserListTWI *twi = users.value(userName);
|
||||
if (twi) {
|
||||
users.remove(userName);
|
||||
userTree->takeTopLevelItem(userTree->indexOfTopLevelItem(twi));
|
||||
if (twi->data(0, Qt::UserRole + 1).toBool())
|
||||
--onlineCount;
|
||||
delete twi;
|
||||
updateCount();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void UserListWidget::setUserOnline(const QString &userName, bool online)
|
||||
{
|
||||
UserListTWI *twi = users.value(userName);
|
||||
if (!twi)
|
||||
return;
|
||||
|
||||
twi->setOnline(online);
|
||||
if (online)
|
||||
++onlineCount;
|
||||
else
|
||||
--onlineCount;
|
||||
updateCount();
|
||||
}
|
||||
|
||||
void UserListWidget::updateCount()
|
||||
{
|
||||
QString str = titleStr;
|
||||
if ((type == BuddyList) || (type == IgnoreList))
|
||||
str = str.arg(onlineCount);
|
||||
setTitle(str.arg(userTree->topLevelItemCount()));
|
||||
}
|
||||
|
||||
void UserListWidget::userClicked(QTreeWidgetItem *item, int /*column*/)
|
||||
{
|
||||
emit openMessageDialog(item->data(2, Qt::UserRole).toString(), true);
|
||||
}
|
||||
|
||||
void UserListWidget::showContextMenu(const QPoint &pos, const QModelIndex &index)
|
||||
{
|
||||
const ServerInfo_User &userInfo = static_cast<UserListTWI *>(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);
|
||||
}
|
||||
|
||||
void UserListWidget::sortItems()
|
||||
{
|
||||
userTree->sortItems(1, Qt::AscendingOrder);
|
||||
}
|
||||
170
cockatrice/src/interface/widgets/server/user/user_list_widget.h
Normal file
170
cockatrice/src/interface/widgets/server/user/user_list_widget.h
Normal file
|
|
@ -0,0 +1,170 @@
|
|||
/**
|
||||
* @file user_list_widget.h
|
||||
* @ingroup Lobby
|
||||
* @brief TODO: Document this.
|
||||
*/
|
||||
|
||||
#ifndef USERLIST_H
|
||||
#define USERLIST_H
|
||||
|
||||
#include <QComboBox>
|
||||
#include <QDialog>
|
||||
#include <QGroupBox>
|
||||
#include <QStyledItemDelegate>
|
||||
#include <QTextEdit>
|
||||
#include <QTreeWidgetItem>
|
||||
#include <libcockatrice/network/server/remote/user_level.h>
|
||||
#include <libcockatrice/protocol/pb/moderator_commands.pb.h>
|
||||
|
||||
class QTreeWidget;
|
||||
class ServerInfo_User;
|
||||
class AbstractClient;
|
||||
class TabSupervisor;
|
||||
class QLabel;
|
||||
class QCheckBox;
|
||||
class QSpinBox;
|
||||
class QRadioButton;
|
||||
class QPlainTextEdit;
|
||||
class Response;
|
||||
class CommandContainer;
|
||||
class UserContextMenu;
|
||||
|
||||
class BanDialog : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
private:
|
||||
QLabel *daysLabel, *hoursLabel, *minutesLabel;
|
||||
QCheckBox *nameBanCheckBox, *ipBanCheckBox, *idBanCheckBox, *deleteMessages;
|
||||
QLineEdit *nameBanEdit, *ipBanEdit, *idBanEdit;
|
||||
QSpinBox *daysEdit, *hoursEdit, *minutesEdit;
|
||||
QRadioButton *permanentRadio, *temporaryRadio;
|
||||
QPlainTextEdit *reasonEdit, *visibleReasonEdit;
|
||||
private slots:
|
||||
void okClicked();
|
||||
void enableTemporaryEdits(bool enabled);
|
||||
|
||||
public:
|
||||
explicit BanDialog(const ServerInfo_User &info, QWidget *parent = nullptr);
|
||||
QString getBanName() const;
|
||||
QString getBanIP() const;
|
||||
QString getBanId() const;
|
||||
int getMinutes() const;
|
||||
QString getReason() const;
|
||||
QString getVisibleReason() const;
|
||||
int getDeleteMessages() const;
|
||||
};
|
||||
|
||||
class WarningDialog : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
private:
|
||||
QLabel *descriptionLabel;
|
||||
QLineEdit *nameWarning;
|
||||
QComboBox *warningOption;
|
||||
QLineEdit *warnClientID;
|
||||
QCheckBox *deleteMessages;
|
||||
private slots:
|
||||
void okClicked();
|
||||
|
||||
public:
|
||||
WarningDialog(const QString userName, const QString clientID, QWidget *parent = nullptr);
|
||||
QString getName() const;
|
||||
QString getWarnID() const;
|
||||
QString getReason() const;
|
||||
int getDeleteMessages() const;
|
||||
void addWarningOption(const QString warning);
|
||||
};
|
||||
|
||||
class AdminNotesDialog : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
private:
|
||||
QString userName;
|
||||
QPlainTextEdit *notes;
|
||||
|
||||
public:
|
||||
explicit AdminNotesDialog(const QString &_userName, const QString &_notes, QWidget *_parent = nullptr);
|
||||
QString getName() const
|
||||
{
|
||||
return userName;
|
||||
}
|
||||
QString getNotes() const;
|
||||
};
|
||||
|
||||
class UserListItemDelegate : public QStyledItemDelegate
|
||||
{
|
||||
public:
|
||||
explicit UserListItemDelegate(QObject *const parent);
|
||||
bool editorEvent(QEvent *event,
|
||||
QAbstractItemModel *model,
|
||||
const QStyleOptionViewItem &option,
|
||||
const QModelIndex &index) override;
|
||||
};
|
||||
|
||||
class UserListTWI : public QTreeWidgetItem
|
||||
{
|
||||
private:
|
||||
ServerInfo_User userInfo;
|
||||
|
||||
public:
|
||||
explicit UserListTWI(const ServerInfo_User &_userInfo);
|
||||
const ServerInfo_User &getUserInfo() const
|
||||
{
|
||||
return userInfo;
|
||||
}
|
||||
void setUserInfo(const ServerInfo_User &_userInfo);
|
||||
void setOnline(bool online);
|
||||
bool operator<(const QTreeWidgetItem &other) const override;
|
||||
};
|
||||
|
||||
class UserListWidget : public QGroupBox
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
enum UserListType
|
||||
{
|
||||
AllUsersList,
|
||||
RoomList,
|
||||
BuddyList,
|
||||
IgnoreList
|
||||
};
|
||||
|
||||
private:
|
||||
QMap<QString, UserListTWI *> users;
|
||||
TabSupervisor *tabSupervisor;
|
||||
AbstractClient *client;
|
||||
UserListType type;
|
||||
QTreeWidget *userTree;
|
||||
UserListItemDelegate *itemDelegate;
|
||||
UserContextMenu *userContextMenu;
|
||||
int onlineCount;
|
||||
QString titleStr;
|
||||
void updateCount();
|
||||
private slots:
|
||||
void userClicked(QTreeWidgetItem *item, int column);
|
||||
signals:
|
||||
void openMessageDialog(const QString &userName, bool focus);
|
||||
void addBuddy(const QString &userName);
|
||||
void removeBuddy(const QString &userName);
|
||||
void addIgnore(const QString &userName);
|
||||
void removeIgnore(const QString &userName);
|
||||
|
||||
public:
|
||||
UserListWidget(TabSupervisor *_tabSupervisor,
|
||||
AbstractClient *_client,
|
||||
UserListType _type,
|
||||
QWidget *parent = nullptr);
|
||||
void retranslateUi();
|
||||
void processUserInfo(const ServerInfo_User &user, bool online);
|
||||
bool deleteUser(const QString &userName);
|
||||
void setUserOnline(const QString &userName, bool online);
|
||||
const QMap<QString, UserListTWI *> &getUsers() const
|
||||
{
|
||||
return users;
|
||||
}
|
||||
void showContextMenu(const QPoint &pos, const QModelIndex &index);
|
||||
void sortItems();
|
||||
};
|
||||
|
||||
#endif
|
||||
Loading…
Add table
Add a link
Reference in a new issue