mirror of
https://github.com/Cockatrice/Cockatrice.git
synced 2026-06-12 09:04:53 -07:00
Major Directory Refactoring (#5118)
* refactored cardzone.cpp, added doc and changed if to switch case * started moving every files into different folders * remove undercase to match with other files naming convention * refactored dialog files * ran format.sh * refactored client/tabs folder * refactored client/tabs folder * refactored client/tabs folder * refactored client folder * refactored carddbparser * refactored dialogs * Create sonar-project.properties temporary file for lint * Create build.yml temporary file for lint * removed all files from root directory * removed all files from root directory * added current branch to workflow * fixed most broken import * fixed issues while renaming files * fixed oracle importer * fixed dbconverter * updated translations * made sub-folders for client * removed linter * removed linter folder * fixed oracle import * revert card_zone documentation * renamed db parser files name and deck_view imports * fixed dlg file issue * ran format file and fixed test file * fixed carddb test files * moved player folder in game * updated translations and format files * fixed peglib import * format cmake files * removing vcpkg to try to add it back later * tried fixing vcpkg file * renamed filter to filters and moved database parser to cards folder * reverted translation files * reverted oracle translated * Update cockatrice/src/dialogs/dlg_register.cpp Co-authored-by: tooomm <tooomm@users.noreply.github.com> * Update cockatrice/src/client/ui/window_main.cpp Co-authored-by: tooomm <tooomm@users.noreply.github.com> * removed empty line at file start * removed useless include from tab_supervisor.cpp * refactored cardzone.cpp, added doc and changed if to switch case * started moving every files into different folders * remove undercase to match with other files naming convention * refactored dialog files * ran format.sh * refactored client/tabs folder * refactored client folder * refactored carddbparser * refactored dialogs * removed all files from root directory * Create sonar-project.properties temporary file for lint * Create build.yml temporary file for lint * added current branch to workflow * fixed most broken import * fixed issues while renaming files * fixed oracle importer * fixed dbconverter * updated translations * made sub-folders for client * removed linter * removed linter folder * fixed oracle import * revert card_zone documentation * renamed db parser files name and deck_view imports * fixed dlg file issue * ran format file and fixed test file * fixed carddb test files * moved player folder in game * updated translations and format files * fixed peglib import * reverted translation files * format cmake files * removing vcpkg to try to add it back later * tried fixing vcpkg file * pre-updating of cockatrice changes * removed empty line at file start * reverted oracle translated * Update cockatrice/src/dialogs/dlg_register.cpp Co-authored-by: tooomm <tooomm@users.noreply.github.com> * Update cockatrice/src/client/ui/window_main.cpp Co-authored-by: tooomm <tooomm@users.noreply.github.com> * removed useless include from tab_supervisor.cpp --------- Co-authored-by: tooomm <tooomm@users.noreply.github.com>
This commit is contained in:
parent
d1e0f9dfc5
commit
fa999880ee
261 changed files with 735 additions and 729 deletions
198
cockatrice/src/client/game_logic/abstract_client.cpp
Normal file
198
cockatrice/src/client/game_logic/abstract_client.cpp
Normal file
|
|
@ -0,0 +1,198 @@
|
|||
#include "abstract_client.h"
|
||||
|
||||
#include "../../server/pending_command.h"
|
||||
#include "featureset.h"
|
||||
#include "get_pb_extension.h"
|
||||
#include "pb/commands.pb.h"
|
||||
#include "pb/event_add_to_list.pb.h"
|
||||
#include "pb/event_connection_closed.pb.h"
|
||||
#include "pb/event_game_joined.pb.h"
|
||||
#include "pb/event_list_rooms.pb.h"
|
||||
#include "pb/event_notify_user.pb.h"
|
||||
#include "pb/event_remove_from_list.pb.h"
|
||||
#include "pb/event_replay_added.pb.h"
|
||||
#include "pb/event_server_identification.pb.h"
|
||||
#include "pb/event_server_message.pb.h"
|
||||
#include "pb/event_server_shutdown.pb.h"
|
||||
#include "pb/event_user_joined.pb.h"
|
||||
#include "pb/event_user_left.pb.h"
|
||||
#include "pb/event_user_message.pb.h"
|
||||
#include "pb/server_message.pb.h"
|
||||
|
||||
#include <google/protobuf/descriptor.h>
|
||||
|
||||
AbstractClient::AbstractClient(QObject *parent)
|
||||
: QObject(parent), nextCmdId(0), status(StatusDisconnected), serverSupportsPasswordHash(false)
|
||||
{
|
||||
qRegisterMetaType<QVariant>("QVariant");
|
||||
qRegisterMetaType<CommandContainer>("CommandContainer");
|
||||
qRegisterMetaType<Response>("Response");
|
||||
qRegisterMetaType<Response::ResponseCode>("Response::ResponseCode");
|
||||
qRegisterMetaType<ClientStatus>("ClientStatus");
|
||||
qRegisterMetaType<RoomEvent>("RoomEvent");
|
||||
qRegisterMetaType<GameEventContainer>("GameEventContainer");
|
||||
qRegisterMetaType<Event_ServerIdentification>("Event_ServerIdentification");
|
||||
qRegisterMetaType<Event_ConnectionClosed>("Event_ConnectionClosed");
|
||||
qRegisterMetaType<Event_ServerShutdown>("Event_ServerShutdown");
|
||||
qRegisterMetaType<Event_AddToList>("Event_AddToList");
|
||||
qRegisterMetaType<Event_RemoveFromList>("Event_RemoveFromList");
|
||||
qRegisterMetaType<Event_UserJoined>("Event_UserJoined");
|
||||
qRegisterMetaType<Event_UserLeft>("Event_UserLeft");
|
||||
qRegisterMetaType<Event_ServerMessage>("Event_ServerMessage");
|
||||
qRegisterMetaType<Event_ListRooms>("Event_ListRooms");
|
||||
qRegisterMetaType<Event_GameJoined>("Event_GameJoined");
|
||||
qRegisterMetaType<Event_UserMessage>("Event_UserMessage");
|
||||
qRegisterMetaType<Event_NotifyUser>("Event_NotifyUser");
|
||||
qRegisterMetaType<ServerInfo_User>("ServerInfo_User");
|
||||
qRegisterMetaType<QList<ServerInfo_User>>("QList<ServerInfo_User>");
|
||||
qRegisterMetaType<Event_ReplayAdded>("Event_ReplayAdded");
|
||||
qRegisterMetaType<QList<QString>>("missingFeatures");
|
||||
qRegisterMetaType<PendingCommand *>("pendingCommand");
|
||||
|
||||
FeatureSet features;
|
||||
features.initalizeFeatureList(clientFeatures);
|
||||
|
||||
connect(this, SIGNAL(sigQueuePendingCommand(PendingCommand *)), this, SLOT(queuePendingCommand(PendingCommand *)));
|
||||
}
|
||||
|
||||
AbstractClient::~AbstractClient()
|
||||
{
|
||||
}
|
||||
|
||||
void AbstractClient::processProtocolItem(const ServerMessage &item)
|
||||
{
|
||||
switch (item.message_type()) {
|
||||
case ServerMessage::RESPONSE: {
|
||||
const Response &response = item.response();
|
||||
const int cmdId = response.cmd_id();
|
||||
|
||||
PendingCommand *pend = pendingCommands.value(cmdId, 0);
|
||||
if (!pend)
|
||||
return;
|
||||
pendingCommands.remove(cmdId);
|
||||
|
||||
pend->processResponse(response);
|
||||
pend->deleteLater();
|
||||
break;
|
||||
}
|
||||
case ServerMessage::SESSION_EVENT: {
|
||||
const SessionEvent &event = item.session_event();
|
||||
switch ((SessionEvent::SessionEventType)getPbExtension(event)) {
|
||||
case SessionEvent::SERVER_IDENTIFICATION:
|
||||
emit serverIdentificationEventReceived(event.GetExtension(Event_ServerIdentification::ext));
|
||||
break;
|
||||
case SessionEvent::SERVER_MESSAGE:
|
||||
emit serverMessageEventReceived(event.GetExtension(Event_ServerMessage::ext));
|
||||
break;
|
||||
case SessionEvent::SERVER_SHUTDOWN:
|
||||
emit serverShutdownEventReceived(event.GetExtension(Event_ServerShutdown::ext));
|
||||
break;
|
||||
case SessionEvent::CONNECTION_CLOSED:
|
||||
emit connectionClosedEventReceived(event.GetExtension(Event_ConnectionClosed::ext));
|
||||
break;
|
||||
case SessionEvent::USER_MESSAGE:
|
||||
emit userMessageEventReceived(event.GetExtension(Event_UserMessage::ext));
|
||||
break;
|
||||
case SessionEvent::NOTIFY_USER:
|
||||
emit notifyUserEventReceived(event.GetExtension(Event_NotifyUser::ext));
|
||||
break;
|
||||
case SessionEvent::LIST_ROOMS:
|
||||
emit listRoomsEventReceived(event.GetExtension(Event_ListRooms::ext));
|
||||
break;
|
||||
case SessionEvent::ADD_TO_LIST:
|
||||
emit addToListEventReceived(event.GetExtension(Event_AddToList::ext));
|
||||
break;
|
||||
case SessionEvent::REMOVE_FROM_LIST:
|
||||
emit removeFromListEventReceived(event.GetExtension(Event_RemoveFromList::ext));
|
||||
break;
|
||||
case SessionEvent::USER_JOINED:
|
||||
emit userJoinedEventReceived(event.GetExtension(Event_UserJoined::ext));
|
||||
break;
|
||||
case SessionEvent::USER_LEFT:
|
||||
emit userLeftEventReceived(event.GetExtension(Event_UserLeft::ext));
|
||||
break;
|
||||
case SessionEvent::GAME_JOINED:
|
||||
emit gameJoinedEventReceived(event.GetExtension(Event_GameJoined::ext));
|
||||
break;
|
||||
case SessionEvent::REPLAY_ADDED:
|
||||
emit replayAddedEventReceived(event.GetExtension(Event_ReplayAdded::ext));
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ServerMessage::GAME_EVENT_CONTAINER: {
|
||||
emit gameEventContainerReceived(item.game_event_container());
|
||||
break;
|
||||
}
|
||||
case ServerMessage::ROOM_EVENT: {
|
||||
emit roomEventReceived(item.room_event());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AbstractClient::setStatus(const ClientStatus _status)
|
||||
{
|
||||
QMutexLocker locker(&clientMutex);
|
||||
if (_status != status) {
|
||||
status = _status;
|
||||
emit statusChanged(_status);
|
||||
}
|
||||
}
|
||||
|
||||
void AbstractClient::sendCommand(const CommandContainer &cont)
|
||||
{
|
||||
sendCommand(new PendingCommand(cont));
|
||||
}
|
||||
|
||||
void AbstractClient::sendCommand(PendingCommand *pend)
|
||||
{
|
||||
pend->moveToThread(thread());
|
||||
emit sigQueuePendingCommand(pend);
|
||||
}
|
||||
|
||||
void AbstractClient::queuePendingCommand(PendingCommand *pend)
|
||||
{
|
||||
// This function is always called from the client thread via signal/slot.
|
||||
const int cmdId = getNewCmdId();
|
||||
pend->getCommandContainer().set_cmd_id(cmdId);
|
||||
|
||||
pendingCommands.insert(cmdId, pend);
|
||||
|
||||
sendCommandContainer(pend->getCommandContainer());
|
||||
}
|
||||
|
||||
PendingCommand *AbstractClient::prepareSessionCommand(const ::google::protobuf::Message &cmd)
|
||||
{
|
||||
CommandContainer cont;
|
||||
SessionCommand *c = cont.add_session_command();
|
||||
c->GetReflection()->MutableMessage(c, cmd.GetDescriptor()->FindExtensionByName("ext"))->CopyFrom(cmd);
|
||||
return new PendingCommand(cont);
|
||||
}
|
||||
|
||||
PendingCommand *AbstractClient::prepareRoomCommand(const ::google::protobuf::Message &cmd, int roomId)
|
||||
{
|
||||
CommandContainer cont;
|
||||
RoomCommand *c = cont.add_room_command();
|
||||
cont.set_room_id(roomId);
|
||||
c->GetReflection()->MutableMessage(c, cmd.GetDescriptor()->FindExtensionByName("ext"))->CopyFrom(cmd);
|
||||
return new PendingCommand(cont);
|
||||
}
|
||||
|
||||
PendingCommand *AbstractClient::prepareModeratorCommand(const ::google::protobuf::Message &cmd)
|
||||
{
|
||||
CommandContainer cont;
|
||||
ModeratorCommand *c = cont.add_moderator_command();
|
||||
c->GetReflection()->MutableMessage(c, cmd.GetDescriptor()->FindExtensionByName("ext"))->CopyFrom(cmd);
|
||||
return new PendingCommand(cont);
|
||||
}
|
||||
|
||||
PendingCommand *AbstractClient::prepareAdminCommand(const ::google::protobuf::Message &cmd)
|
||||
{
|
||||
CommandContainer cont;
|
||||
AdminCommand *c = cont.add_admin_command();
|
||||
c->GetReflection()->MutableMessage(c, cmd.GetDescriptor()->FindExtensionByName("ext"))->CopyFrom(cmd);
|
||||
return new PendingCommand(cont);
|
||||
}
|
||||
128
cockatrice/src/client/game_logic/abstract_client.h
Normal file
128
cockatrice/src/client/game_logic/abstract_client.h
Normal file
|
|
@ -0,0 +1,128 @@
|
|||
#ifndef ABSTRACTCLIENT_H
|
||||
#define ABSTRACTCLIENT_H
|
||||
|
||||
#include "pb/response.pb.h"
|
||||
#include "pb/serverinfo_user.pb.h"
|
||||
|
||||
#include <QMutex>
|
||||
#include <QObject>
|
||||
#include <QVariant>
|
||||
|
||||
class PendingCommand;
|
||||
class CommandContainer;
|
||||
class RoomEvent;
|
||||
class GameEventContainer;
|
||||
class ServerMessage;
|
||||
class Event_ServerIdentification;
|
||||
class Event_AddToList;
|
||||
class Event_RemoveFromList;
|
||||
class Event_UserJoined;
|
||||
class Event_UserLeft;
|
||||
class Event_ServerMessage;
|
||||
class Event_ListRooms;
|
||||
class Event_GameJoined;
|
||||
class Event_UserMessage;
|
||||
class Event_NotifyUser;
|
||||
class Event_ConnectionClosed;
|
||||
class Event_ServerShutdown;
|
||||
class Event_ReplayAdded;
|
||||
class FeatureSet;
|
||||
|
||||
enum ClientStatus
|
||||
{
|
||||
StatusDisconnected,
|
||||
StatusDisconnecting,
|
||||
StatusConnecting,
|
||||
StatusRegistering,
|
||||
StatusActivating,
|
||||
StatusLoggingIn,
|
||||
StatusLoggedIn,
|
||||
StatusRequestingForgotPassword,
|
||||
StatusSubmitForgotPasswordReset,
|
||||
StatusSubmitForgotPasswordChallenge,
|
||||
StatusGettingPasswordSalt,
|
||||
};
|
||||
|
||||
class AbstractClient : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
signals:
|
||||
void statusChanged(ClientStatus _status);
|
||||
|
||||
// Room events
|
||||
void roomEventReceived(const RoomEvent &event);
|
||||
// Game events
|
||||
void gameEventContainerReceived(const GameEventContainer &event);
|
||||
// Session events
|
||||
void serverIdentificationEventReceived(const Event_ServerIdentification &event);
|
||||
void connectionClosedEventReceived(const Event_ConnectionClosed &event);
|
||||
void serverShutdownEventReceived(const Event_ServerShutdown &event);
|
||||
void addToListEventReceived(const Event_AddToList &event);
|
||||
void removeFromListEventReceived(const Event_RemoveFromList &event);
|
||||
void userJoinedEventReceived(const Event_UserJoined &event);
|
||||
void userLeftEventReceived(const Event_UserLeft &event);
|
||||
void serverMessageEventReceived(const Event_ServerMessage &event);
|
||||
void listRoomsEventReceived(const Event_ListRooms &event);
|
||||
void gameJoinedEventReceived(const Event_GameJoined &event);
|
||||
void userMessageEventReceived(const Event_UserMessage &event);
|
||||
void notifyUserEventReceived(const Event_NotifyUser &event);
|
||||
void userInfoChanged(const ServerInfo_User &userInfo);
|
||||
void buddyListReceived(const QList<ServerInfo_User> &buddyList);
|
||||
void ignoreListReceived(const QList<ServerInfo_User> &ignoreList);
|
||||
void replayAddedEventReceived(const Event_ReplayAdded &event);
|
||||
void registerAccepted();
|
||||
void registerAcceptedNeedsActivate();
|
||||
void activateAccepted();
|
||||
|
||||
void sigQueuePendingCommand(PendingCommand *pend);
|
||||
|
||||
private:
|
||||
int nextCmdId;
|
||||
mutable QMutex clientMutex;
|
||||
ClientStatus status;
|
||||
private slots:
|
||||
void queuePendingCommand(PendingCommand *pend);
|
||||
protected slots:
|
||||
void processProtocolItem(const ServerMessage &item);
|
||||
|
||||
protected:
|
||||
QMap<int, PendingCommand *> pendingCommands;
|
||||
QString userName, password, email, country, realName, token;
|
||||
bool serverSupportsPasswordHash;
|
||||
void setStatus(ClientStatus _status);
|
||||
int getNewCmdId()
|
||||
{
|
||||
return nextCmdId++;
|
||||
}
|
||||
virtual void sendCommandContainer(const CommandContainer &cont) = 0;
|
||||
|
||||
public:
|
||||
AbstractClient(QObject *parent = nullptr);
|
||||
~AbstractClient();
|
||||
|
||||
ClientStatus getStatus() const
|
||||
{
|
||||
QMutexLocker locker(&clientMutex);
|
||||
return status;
|
||||
}
|
||||
void sendCommand(const CommandContainer &cont);
|
||||
void sendCommand(PendingCommand *pend);
|
||||
|
||||
bool getServerSupportsPasswordHash() const
|
||||
{
|
||||
return serverSupportsPasswordHash;
|
||||
}
|
||||
const QString &getUserName() const
|
||||
{
|
||||
return userName;
|
||||
}
|
||||
|
||||
static PendingCommand *prepareSessionCommand(const ::google::protobuf::Message &cmd);
|
||||
static PendingCommand *prepareRoomCommand(const ::google::protobuf::Message &cmd, int roomId);
|
||||
static PendingCommand *prepareModeratorCommand(const ::google::protobuf::Message &cmd);
|
||||
static PendingCommand *prepareAdminCommand(const ::google::protobuf::Message &cmd);
|
||||
|
||||
QMap<QString, bool> clientFeatures;
|
||||
};
|
||||
|
||||
#endif
|
||||
74
cockatrice/src/client/game_logic/key_signals.cpp
Normal file
74
cockatrice/src/client/game_logic/key_signals.cpp
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
#include "key_signals.h"
|
||||
|
||||
#include <QKeyEvent>
|
||||
|
||||
bool KeySignals::eventFilter(QObject * /*object*/, QEvent *event)
|
||||
{
|
||||
QKeyEvent *kevent;
|
||||
|
||||
if (event->type() != QEvent::KeyPress)
|
||||
return false;
|
||||
|
||||
kevent = static_cast<QKeyEvent *>(event);
|
||||
switch (kevent->key()) {
|
||||
case Qt::Key_Return:
|
||||
case Qt::Key_Enter:
|
||||
if (kevent->modifiers().testFlag(Qt::AltModifier) && kevent->modifiers().testFlag(Qt::ControlModifier))
|
||||
emit onCtrlAltEnter();
|
||||
else if (kevent->modifiers() & Qt::ControlModifier)
|
||||
emit onCtrlEnter();
|
||||
else
|
||||
emit onEnter();
|
||||
|
||||
break;
|
||||
case Qt::Key_Right:
|
||||
if (kevent->modifiers() & Qt::ShiftModifier)
|
||||
emit onShiftRight();
|
||||
|
||||
break;
|
||||
case Qt::Key_Left:
|
||||
if (kevent->modifiers() & Qt::ShiftModifier)
|
||||
emit onShiftLeft();
|
||||
|
||||
break;
|
||||
case Qt::Key_Delete:
|
||||
case Qt::Key_Backspace:
|
||||
emit onDelete();
|
||||
|
||||
break;
|
||||
case Qt::Key_Minus:
|
||||
if (kevent->modifiers().testFlag(Qt::AltModifier) && kevent->modifiers().testFlag(Qt::ControlModifier))
|
||||
emit onCtrlAltMinus();
|
||||
|
||||
break;
|
||||
case Qt::Key_Equal:
|
||||
if (kevent->modifiers().testFlag(Qt::AltModifier) && kevent->modifiers().testFlag(Qt::ControlModifier))
|
||||
emit onCtrlAltEqual();
|
||||
|
||||
break;
|
||||
case Qt::Key_BracketLeft:
|
||||
if (kevent->modifiers().testFlag(Qt::AltModifier) && kevent->modifiers().testFlag(Qt::ControlModifier))
|
||||
emit onCtrlAltLBracket();
|
||||
|
||||
break;
|
||||
case Qt::Key_BracketRight:
|
||||
if (kevent->modifiers().testFlag(Qt::AltModifier) && kevent->modifiers().testFlag(Qt::ControlModifier))
|
||||
emit onCtrlAltRBracket();
|
||||
|
||||
break;
|
||||
case Qt::Key_S:
|
||||
if (kevent->modifiers() & Qt::ShiftModifier)
|
||||
emit onShiftS();
|
||||
|
||||
break;
|
||||
case Qt::Key_C:
|
||||
if (kevent->modifiers() & Qt::ControlModifier)
|
||||
emit onCtrlC();
|
||||
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
29
cockatrice/src/client/game_logic/key_signals.h
Normal file
29
cockatrice/src/client/game_logic/key_signals.h
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
#ifndef KEYSIGNALS_H
|
||||
#define KEYSIGNALS_H
|
||||
|
||||
#include <QEvent>
|
||||
#include <QObject>
|
||||
|
||||
class KeySignals : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
signals:
|
||||
void onEnter();
|
||||
void onCtrlEnter();
|
||||
void onCtrlAltEnter();
|
||||
void onShiftLeft();
|
||||
void onShiftRight();
|
||||
void onDelete();
|
||||
void onCtrlAltMinus();
|
||||
void onCtrlAltEqual();
|
||||
void onCtrlAltLBracket();
|
||||
void onCtrlAltRBracket();
|
||||
void onShiftS();
|
||||
void onCtrlC();
|
||||
|
||||
protected:
|
||||
virtual bool eventFilter(QObject *, QEvent *event);
|
||||
};
|
||||
|
||||
#endif
|
||||
32
cockatrice/src/client/get_text_with_max.cpp
Normal file
32
cockatrice/src/client/get_text_with_max.cpp
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
#include "get_text_with_max.h"
|
||||
|
||||
QString getTextWithMax(QWidget *parent,
|
||||
const QString &title,
|
||||
const QString &label,
|
||||
QLineEdit::EchoMode mode,
|
||||
const QString &text,
|
||||
bool *ok,
|
||||
int max,
|
||||
Qt::WindowFlags flags,
|
||||
Qt::InputMethodHints inputMethodHints)
|
||||
{
|
||||
auto *dialog = new QInputDialog(parent, flags);
|
||||
dialog->setWindowTitle(title);
|
||||
dialog->setLabelText(label);
|
||||
dialog->setTextValue(text);
|
||||
dialog->setTextEchoMode(mode);
|
||||
dialog->setInputMethodHints(inputMethodHints);
|
||||
|
||||
// find the qlineedit that this dialog holds, there should be only one
|
||||
dialog->findChild<QLineEdit *>()->setMaxLength(max);
|
||||
|
||||
const int ret = dialog->exec();
|
||||
if (ok != nullptr) {
|
||||
*ok = !!ret;
|
||||
}
|
||||
if (ret) {
|
||||
return dialog->textValue();
|
||||
} else {
|
||||
return QString();
|
||||
}
|
||||
}
|
||||
23
cockatrice/src/client/get_text_with_max.h
Normal file
23
cockatrice/src/client/get_text_with_max.h
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
// custom QInputDialog::getText implementation that allows configuration of the max length
|
||||
#ifndef GETTEXTWITHMAX_H
|
||||
#define GETTEXTWITHMAX_H
|
||||
|
||||
#include "trice_limits.h"
|
||||
|
||||
#include <QInputDialog>
|
||||
|
||||
QString getTextWithMax(QWidget *parent,
|
||||
const QString &title,
|
||||
const QString &label,
|
||||
QLineEdit::EchoMode echo = QLineEdit::Normal,
|
||||
const QString &text = QString(),
|
||||
bool *ok = nullptr,
|
||||
int max = MAX_NAME_LENGTH,
|
||||
Qt::WindowFlags flags = Qt::WindowFlags(),
|
||||
Qt::InputMethodHints inputMethodHints = Qt::ImhNone);
|
||||
static inline QString getTextWithMax(QWidget *parent, const QString &title, const QString &label, int max)
|
||||
{
|
||||
return getTextWithMax(parent, title, label, QLineEdit::Normal, QString(), nullptr, max);
|
||||
}
|
||||
|
||||
#endif // GETTEXTWITHMAX_H
|
||||
304
cockatrice/src/client/network/release_channel.cpp
Normal file
304
cockatrice/src/client/network/release_channel.cpp
Normal file
|
|
@ -0,0 +1,304 @@
|
|||
#include "release_channel.h"
|
||||
|
||||
#include "version_string.h"
|
||||
|
||||
#include <QJsonArray>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QMessageBox>
|
||||
#include <QNetworkReply>
|
||||
#include <QRegularExpression>
|
||||
#include <QSysInfo>
|
||||
#include <QtGlobal>
|
||||
|
||||
#define STABLERELEASE_URL "https://api.github.com/repos/Cockatrice/Cockatrice/releases/latest"
|
||||
#define STABLEMANUALDOWNLOAD_URL "https://github.com/Cockatrice/Cockatrice/releases/latest"
|
||||
#define STABLETAG_URL "https://api.github.com/repos/Cockatrice/Cockatrice/git/refs/tags/"
|
||||
|
||||
#define BETARELEASE_URL "https://api.github.com/repos/Cockatrice/Cockatrice/releases"
|
||||
#define BETAMANUALDOWNLOAD_URL "https://github.com/Cockatrice/Cockatrice/releases/"
|
||||
#define BETARELEASE_CHANGESURL "https://github.com/Cockatrice/Cockatrice/compare/%1...%2"
|
||||
|
||||
#define GIT_SHORT_HASH_LEN 7
|
||||
|
||||
int ReleaseChannel::sharedIndex = 0;
|
||||
|
||||
ReleaseChannel::ReleaseChannel() : netMan(new QNetworkAccessManager(this)), response(nullptr), lastRelease(nullptr)
|
||||
{
|
||||
index = sharedIndex++;
|
||||
}
|
||||
|
||||
ReleaseChannel::~ReleaseChannel()
|
||||
{
|
||||
netMan->deleteLater();
|
||||
}
|
||||
|
||||
void ReleaseChannel::checkForUpdates()
|
||||
{
|
||||
QString releaseChannelUrl = getReleaseChannelUrl();
|
||||
qDebug() << "Searching for updates on the channel: " << releaseChannelUrl;
|
||||
response = netMan->get(QNetworkRequest(releaseChannelUrl));
|
||||
connect(response, SIGNAL(finished()), this, SLOT(releaseListFinished()));
|
||||
}
|
||||
|
||||
// Different release channel checking functions for different operating systems
|
||||
#if defined(Q_OS_MACOS)
|
||||
bool ReleaseChannel::downloadMatchesCurrentOS(const QString &fileName)
|
||||
{
|
||||
static QRegularExpression version_regex("macOS-(\\d+)\\.(\\d+)");
|
||||
auto match = version_regex.match(fileName);
|
||||
if (!match.hasMatch()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// older(smaller) releases are compatible with a newer or the same system version
|
||||
int sys_maj = QSysInfo::productVersion().split(".")[0].toInt();
|
||||
int sys_min = QSysInfo::productVersion().split(".")[1].toInt();
|
||||
int rel_maj = match.captured(1).toInt();
|
||||
int rel_min = match.captured(2).toInt();
|
||||
return rel_maj < sys_maj || (rel_maj == sys_maj && rel_min <= sys_min);
|
||||
}
|
||||
#elif defined(Q_OS_WIN)
|
||||
bool ReleaseChannel::downloadMatchesCurrentOS(const QString &fileName)
|
||||
{
|
||||
#if Q_PROCESSOR_WORDSIZE == 4
|
||||
return fileName.contains("32bit");
|
||||
#elif Q_PROCESSOR_WORDSIZE == 8
|
||||
const QString &version = QSysInfo::productVersion();
|
||||
if (version.startsWith("7") || version.startsWith("8")) {
|
||||
return fileName.contains("Win7");
|
||||
} else {
|
||||
return fileName.contains("Win10");
|
||||
}
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
#else
|
||||
bool ReleaseChannel::downloadMatchesCurrentOS(const QString &)
|
||||
{
|
||||
// If the OS doesn't fit one of the above #defines, then it will never match
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
QString StableReleaseChannel::getManualDownloadUrl() const
|
||||
{
|
||||
return QString(STABLEMANUALDOWNLOAD_URL);
|
||||
}
|
||||
|
||||
QString StableReleaseChannel::getName() const
|
||||
{
|
||||
return tr("Stable Releases");
|
||||
}
|
||||
|
||||
QString StableReleaseChannel::getReleaseChannelUrl() const
|
||||
{
|
||||
return QString(STABLERELEASE_URL);
|
||||
}
|
||||
|
||||
void StableReleaseChannel::releaseListFinished()
|
||||
{
|
||||
auto *reply = static_cast<QNetworkReply *>(sender());
|
||||
QJsonParseError parseError{};
|
||||
QJsonDocument jsonResponse = QJsonDocument::fromJson(reply->readAll(), &parseError);
|
||||
reply->deleteLater();
|
||||
if (parseError.error != QJsonParseError::NoError) {
|
||||
qWarning() << "No reply received from the release update server.";
|
||||
emit error(tr("No reply received from the release update server."));
|
||||
return;
|
||||
}
|
||||
|
||||
QVariantMap resultMap = jsonResponse.toVariant().toMap();
|
||||
if (!(resultMap.contains("name") && resultMap.contains("html_url") && resultMap.contains("tag_name") &&
|
||||
resultMap.contains("published_at"))) {
|
||||
qWarning() << "Invalid received from the release update server.";
|
||||
emit error(tr("Invalid reply received from the release update server."));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!lastRelease)
|
||||
lastRelease = new Release;
|
||||
|
||||
lastRelease->setName(resultMap["name"].toString());
|
||||
lastRelease->setDescriptionUrl(resultMap["html_url"].toString());
|
||||
lastRelease->setPublishDate(resultMap["published_at"].toDate());
|
||||
|
||||
if (resultMap.contains("assets")) {
|
||||
auto rawAssets = resultMap["assets"].toList();
|
||||
// [(name, url)]
|
||||
QVector<std::pair<QString, QString>> assets;
|
||||
std::transform(rawAssets.begin(), rawAssets.end(), std::back_inserter(assets), [](QVariant _asset) {
|
||||
QVariantMap asset = _asset.toMap();
|
||||
QString name = asset["name"].toString();
|
||||
QString url = asset["browser_download_url"].toString();
|
||||
return std::make_pair(name, url);
|
||||
});
|
||||
|
||||
auto _releaseAsset = std::find_if(assets.begin(), assets.end(), [](std::pair<QString, QString> nameAndUrl) {
|
||||
return downloadMatchesCurrentOS(nameAndUrl.first);
|
||||
});
|
||||
|
||||
if (_releaseAsset != assets.end()) {
|
||||
std::pair<QString, QString> releaseAsset = *_releaseAsset;
|
||||
auto releaseUrl = releaseAsset.second;
|
||||
lastRelease->setDownloadUrl(releaseUrl);
|
||||
}
|
||||
}
|
||||
|
||||
QString shortHash = lastRelease->getCommitHash().left(GIT_SHORT_HASH_LEN);
|
||||
QString myHash = QString(VERSION_COMMIT);
|
||||
qDebug() << "Current hash=" << myHash << "update hash=" << shortHash;
|
||||
|
||||
qDebug() << "Got reply from release server, name=" << lastRelease->getName()
|
||||
<< "desc=" << lastRelease->getDescriptionUrl() << "date=" << lastRelease->getPublishDate()
|
||||
<< "url=" << lastRelease->getDownloadUrl();
|
||||
|
||||
const QString &tagName = resultMap["tag_name"].toString();
|
||||
QString url = QString(STABLETAG_URL) + tagName;
|
||||
qDebug() << "Searching for commit hash corresponding to stable channel tag: " << tagName;
|
||||
response = netMan->get(QNetworkRequest(url));
|
||||
connect(response, SIGNAL(finished()), this, SLOT(tagListFinished()));
|
||||
}
|
||||
|
||||
void StableReleaseChannel::tagListFinished()
|
||||
{
|
||||
auto *reply = static_cast<QNetworkReply *>(sender());
|
||||
QJsonParseError parseError{};
|
||||
QJsonDocument jsonResponse = QJsonDocument::fromJson(reply->readAll(), &parseError);
|
||||
reply->deleteLater();
|
||||
if (parseError.error != QJsonParseError::NoError) {
|
||||
qWarning() << "No reply received from the tag update server.";
|
||||
emit error(tr("No reply received from the tag update server."));
|
||||
return;
|
||||
}
|
||||
|
||||
QVariantMap resultMap = jsonResponse.toVariant().toMap();
|
||||
if (!(resultMap.contains("object") && resultMap["object"].toMap().contains("sha"))) {
|
||||
qWarning() << "Invalid received from the tag update server.";
|
||||
emit error(tr("Invalid reply received from the tag update server."));
|
||||
return;
|
||||
}
|
||||
|
||||
lastRelease->setCommitHash(resultMap["object"].toMap()["sha"].toString());
|
||||
qDebug() << "Got reply from tag server, commit=" << lastRelease->getCommitHash();
|
||||
|
||||
QString shortHash = lastRelease->getCommitHash().left(GIT_SHORT_HASH_LEN);
|
||||
QString myHash = QString(VERSION_COMMIT);
|
||||
qDebug() << "Current hash=" << myHash << "update hash=" << shortHash;
|
||||
const bool needToUpdate = (QString::compare(shortHash, myHash, Qt::CaseInsensitive) != 0);
|
||||
|
||||
emit finishedCheck(needToUpdate, lastRelease->isCompatibleVersionFound(), lastRelease);
|
||||
}
|
||||
|
||||
void StableReleaseChannel::fileListFinished()
|
||||
{
|
||||
// Only implemented to satisfy interface
|
||||
}
|
||||
|
||||
QString BetaReleaseChannel::getManualDownloadUrl() const
|
||||
{
|
||||
return QString(BETAMANUALDOWNLOAD_URL);
|
||||
}
|
||||
|
||||
QString BetaReleaseChannel::getName() const
|
||||
{
|
||||
return tr("Beta Releases");
|
||||
}
|
||||
|
||||
QString BetaReleaseChannel::getReleaseChannelUrl() const
|
||||
{
|
||||
return QString(BETARELEASE_URL);
|
||||
}
|
||||
|
||||
void BetaReleaseChannel::releaseListFinished()
|
||||
{
|
||||
auto *reply = static_cast<QNetworkReply *>(sender());
|
||||
QByteArray jsonData = reply->readAll();
|
||||
reply->deleteLater();
|
||||
|
||||
QJsonDocument doc = QJsonDocument::fromJson(jsonData);
|
||||
QJsonArray array = doc.array();
|
||||
|
||||
/*
|
||||
* Get the latest release on GitHub
|
||||
* This can be either a pre-release or a published release
|
||||
* depending on timing. Both are acceptable.
|
||||
*/
|
||||
QVariantMap resultMap = array.at(0).toObject().toVariantMap();
|
||||
|
||||
if (array.empty() || resultMap.empty()) {
|
||||
qWarning() << "No reply received from the release update server:" << QString(jsonData);
|
||||
emit error(tr("No reply received from the release update server."));
|
||||
return;
|
||||
}
|
||||
|
||||
// Make sure resultMap has all elements we'll need
|
||||
if (!resultMap.contains("assets") || !resultMap.contains("author") || !resultMap.contains("tag_name") ||
|
||||
!resultMap.contains("target_commitish") || !resultMap.contains("assets_url") ||
|
||||
!resultMap.contains("published_at")) {
|
||||
qWarning() << "Invalid received from the release update server:" << resultMap;
|
||||
emit error(tr("Invalid reply received from the release update server."));
|
||||
return;
|
||||
}
|
||||
|
||||
if (lastRelease == nullptr)
|
||||
lastRelease = new Release;
|
||||
|
||||
lastRelease->setCommitHash(resultMap["target_commitish"].toString());
|
||||
lastRelease->setPublishDate(resultMap["published_at"].toDate());
|
||||
|
||||
QString shortHash = lastRelease->getCommitHash().left(GIT_SHORT_HASH_LEN);
|
||||
lastRelease->setName(QString("%1 (%2)").arg(resultMap["tag_name"].toString()).arg(shortHash));
|
||||
lastRelease->setDescriptionUrl(QString(BETARELEASE_CHANGESURL).arg(VERSION_COMMIT, shortHash));
|
||||
|
||||
qDebug() << "Got reply from release server, size=" << resultMap.size() << "name=" << lastRelease->getName()
|
||||
<< "desc=" << lastRelease->getDescriptionUrl() << "commit=" << lastRelease->getCommitHash()
|
||||
<< "date=" << lastRelease->getPublishDate();
|
||||
|
||||
QString betaBuildDownloadUrl = resultMap["assets_url"].toString();
|
||||
|
||||
qDebug() << "Searching for a corresponding file on the beta channel: " << betaBuildDownloadUrl;
|
||||
response = netMan->get(QNetworkRequest(betaBuildDownloadUrl));
|
||||
connect(response, SIGNAL(finished()), this, SLOT(fileListFinished()));
|
||||
}
|
||||
|
||||
void BetaReleaseChannel::fileListFinished()
|
||||
{
|
||||
auto *reply = static_cast<QNetworkReply *>(sender());
|
||||
QJsonParseError parseError{};
|
||||
QJsonDocument jsonResponse = QJsonDocument::fromJson(reply->readAll(), &parseError);
|
||||
reply->deleteLater();
|
||||
if (parseError.error != QJsonParseError::NoError) {
|
||||
qWarning() << "No reply received from the file update server.";
|
||||
emit error(tr("No reply received from the file update server."));
|
||||
return;
|
||||
}
|
||||
|
||||
QVariantList resultList = jsonResponse.toVariant().toList();
|
||||
QString shortHash = lastRelease->getCommitHash().left(GIT_SHORT_HASH_LEN);
|
||||
QString myHash = QString(VERSION_COMMIT);
|
||||
qDebug() << "Current hash=" << myHash << "update hash=" << shortHash;
|
||||
|
||||
bool needToUpdate = (QString::compare(shortHash, myHash, Qt::CaseInsensitive) != 0);
|
||||
bool compatibleVersion = false;
|
||||
|
||||
QStringList resultUrlList{};
|
||||
for (QVariant file : resultList) {
|
||||
QVariantMap map = file.toMap();
|
||||
resultUrlList << map["browser_download_url"].toString();
|
||||
}
|
||||
|
||||
resultUrlList.sort();
|
||||
// iterate in reverse so the first item is the latest os version
|
||||
for (auto url = resultUrlList.rbegin(); url < resultUrlList.rend(); ++url) {
|
||||
if (downloadMatchesCurrentOS(*url)) {
|
||||
compatibleVersion = true;
|
||||
lastRelease->setDownloadUrl(*url);
|
||||
qDebug() << "Found compatible version url=" << *url;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
emit finishedCheck(needToUpdate, compatibleVersion, lastRelease);
|
||||
}
|
||||
157
cockatrice/src/client/network/release_channel.h
Normal file
157
cockatrice/src/client/network/release_channel.h
Normal file
|
|
@ -0,0 +1,157 @@
|
|||
#ifndef RELEASECHANNEL_H
|
||||
#define RELEASECHANNEL_H
|
||||
|
||||
#include <QDate>
|
||||
#include <QObject>
|
||||
#include <QString>
|
||||
#include <QVariantMap>
|
||||
#include <utility>
|
||||
|
||||
class QNetworkReply;
|
||||
class QNetworkAccessManager;
|
||||
|
||||
class Release
|
||||
{
|
||||
friend class StableReleaseChannel;
|
||||
friend class BetaReleaseChannel;
|
||||
|
||||
public:
|
||||
Release() = default;
|
||||
~Release() = default;
|
||||
|
||||
private:
|
||||
QString name, descriptionUrl, downloadUrl, commitHash;
|
||||
QDate publishDate;
|
||||
bool compatibleVersionFound = false;
|
||||
|
||||
protected:
|
||||
void setName(QString _name)
|
||||
{
|
||||
name = std::move(_name);
|
||||
}
|
||||
void setDescriptionUrl(QString _descriptionUrl)
|
||||
{
|
||||
descriptionUrl = std::move(_descriptionUrl);
|
||||
}
|
||||
void setDownloadUrl(QString _downloadUrl)
|
||||
{
|
||||
downloadUrl = std::move(_downloadUrl);
|
||||
compatibleVersionFound = true;
|
||||
}
|
||||
void setCommitHash(QString _commitHash)
|
||||
{
|
||||
commitHash = std::move(_commitHash);
|
||||
}
|
||||
void setPublishDate(QDate _publishDate)
|
||||
{
|
||||
publishDate = _publishDate;
|
||||
}
|
||||
|
||||
public:
|
||||
QString getName() const
|
||||
{
|
||||
return name;
|
||||
}
|
||||
QString getDescriptionUrl() const
|
||||
{
|
||||
return descriptionUrl;
|
||||
}
|
||||
QString getDownloadUrl() const
|
||||
{
|
||||
return downloadUrl;
|
||||
}
|
||||
QString getCommitHash() const
|
||||
{
|
||||
return commitHash;
|
||||
}
|
||||
QDate getPublishDate() const
|
||||
{
|
||||
return publishDate;
|
||||
}
|
||||
bool isCompatibleVersionFound() const
|
||||
{
|
||||
return compatibleVersionFound;
|
||||
}
|
||||
};
|
||||
|
||||
class ReleaseChannel : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit ReleaseChannel();
|
||||
~ReleaseChannel() override;
|
||||
|
||||
protected:
|
||||
// shared by all instances
|
||||
static int sharedIndex;
|
||||
int index;
|
||||
QNetworkAccessManager *netMan;
|
||||
QNetworkReply *response;
|
||||
Release *lastRelease;
|
||||
|
||||
protected:
|
||||
static bool downloadMatchesCurrentOS(const QString &fileName);
|
||||
virtual QString getReleaseChannelUrl() const = 0;
|
||||
|
||||
public:
|
||||
int getIndex() const
|
||||
{
|
||||
return index;
|
||||
}
|
||||
Release *getLastRelease()
|
||||
{
|
||||
return lastRelease;
|
||||
}
|
||||
virtual QString getManualDownloadUrl() const = 0;
|
||||
virtual QString getName() const = 0;
|
||||
void checkForUpdates();
|
||||
signals:
|
||||
void finishedCheck(bool needToUpdate, bool isCompatible, Release *release);
|
||||
void error(QString errorString);
|
||||
protected slots:
|
||||
virtual void releaseListFinished() = 0;
|
||||
virtual void fileListFinished() = 0;
|
||||
};
|
||||
|
||||
class StableReleaseChannel : public ReleaseChannel
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit StableReleaseChannel() = default;
|
||||
~StableReleaseChannel() override = default;
|
||||
|
||||
QString getManualDownloadUrl() const override;
|
||||
|
||||
QString getName() const override;
|
||||
|
||||
protected:
|
||||
QString getReleaseChannelUrl() const override;
|
||||
protected slots:
|
||||
|
||||
void releaseListFinished() override;
|
||||
void tagListFinished();
|
||||
|
||||
void fileListFinished() override;
|
||||
};
|
||||
|
||||
class BetaReleaseChannel : public ReleaseChannel
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
BetaReleaseChannel() = default;
|
||||
~BetaReleaseChannel() override = default;
|
||||
|
||||
QString getManualDownloadUrl() const override;
|
||||
|
||||
QString getName() const override;
|
||||
|
||||
protected:
|
||||
QString getReleaseChannelUrl() const override;
|
||||
protected slots:
|
||||
|
||||
void releaseListFinished() override;
|
||||
|
||||
void fileListFinished() override;
|
||||
};
|
||||
|
||||
#endif
|
||||
121
cockatrice/src/client/network/replay_timeline_widget.cpp
Normal file
121
cockatrice/src/client/network/replay_timeline_widget.cpp
Normal file
|
|
@ -0,0 +1,121 @@
|
|||
#include "replay_timeline_widget.h"
|
||||
|
||||
#include <QPainter>
|
||||
#include <QPainterPath>
|
||||
#include <QPalette>
|
||||
#include <QTimer>
|
||||
|
||||
ReplayTimelineWidget::ReplayTimelineWidget(QWidget *parent)
|
||||
: QWidget(parent), maxBinValue(1), maxTime(1), timeScaleFactor(1.0), currentTime(0), currentEvent(0)
|
||||
{
|
||||
replayTimer = new QTimer(this);
|
||||
connect(replayTimer, SIGNAL(timeout()), this, SLOT(replayTimerTimeout()));
|
||||
}
|
||||
|
||||
const int ReplayTimelineWidget::binLength = 5000;
|
||||
|
||||
void ReplayTimelineWidget::setTimeline(const QList<int> &_replayTimeline)
|
||||
{
|
||||
replayTimeline = _replayTimeline;
|
||||
histogram.clear();
|
||||
int binEndTime = binLength - 1;
|
||||
int binValue = 0;
|
||||
for (int i : replayTimeline) {
|
||||
if (i > binEndTime) {
|
||||
histogram.append(binValue);
|
||||
if (binValue > maxBinValue)
|
||||
maxBinValue = binValue;
|
||||
while (i > binEndTime + binLength) {
|
||||
histogram.append(0);
|
||||
binEndTime += binLength;
|
||||
}
|
||||
binValue = 1;
|
||||
binEndTime += binLength;
|
||||
} else
|
||||
++binValue;
|
||||
}
|
||||
histogram.append(binValue);
|
||||
if (!replayTimeline.isEmpty())
|
||||
maxTime = replayTimeline.last();
|
||||
|
||||
update();
|
||||
}
|
||||
|
||||
void ReplayTimelineWidget::paintEvent(QPaintEvent * /* event */)
|
||||
{
|
||||
QPainter painter(this);
|
||||
painter.drawRect(0, 0, width() - 1, height() - 1);
|
||||
|
||||
qreal binWidth = (qreal)width() / histogram.size();
|
||||
QPainterPath path;
|
||||
path.moveTo(0, height() - 1);
|
||||
for (int i = 0; i < histogram.size(); ++i)
|
||||
path.lineTo(qRound(i * binWidth), (height() - 1) * (1.0 - (qreal)histogram[i] / maxBinValue));
|
||||
path.lineTo(width() - 1, height() - 1);
|
||||
path.lineTo(0, height() - 1);
|
||||
painter.fillPath(path, Qt::black);
|
||||
|
||||
const QColor barColor = QColor::fromHsv(120, 255, 255, 100);
|
||||
quint64 w = (quint64)(width() - 1) * (quint64)currentTime / maxTime;
|
||||
painter.fillRect(0, 0, static_cast<int>(w), height() - 1, barColor);
|
||||
}
|
||||
|
||||
void ReplayTimelineWidget::mousePressEvent(QMouseEvent *event)
|
||||
{
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
|
||||
int newTime = static_cast<int>((qint64)maxTime * (qint64)event->position().x() / width());
|
||||
#else
|
||||
int newTime = static_cast<int>((qint64)maxTime * (qint64)event->x() / width());
|
||||
#endif
|
||||
newTime -= newTime % 200; // Time should always be a multiple of 200
|
||||
if (newTime < currentTime) {
|
||||
currentTime = 0;
|
||||
currentEvent = 0;
|
||||
emit rewound();
|
||||
}
|
||||
currentTime = newTime - 200; // 200 is added back in replayTimerTimeout
|
||||
replayTimerTimeout();
|
||||
update();
|
||||
}
|
||||
|
||||
QSize ReplayTimelineWidget::sizeHint() const
|
||||
{
|
||||
return {-1, 50};
|
||||
}
|
||||
|
||||
QSize ReplayTimelineWidget::minimumSizeHint() const
|
||||
{
|
||||
return {400, 50};
|
||||
}
|
||||
|
||||
void ReplayTimelineWidget::replayTimerTimeout()
|
||||
{
|
||||
currentTime += 200;
|
||||
while ((currentEvent < replayTimeline.size()) && (replayTimeline[currentEvent] < currentTime)) {
|
||||
emit processNextEvent();
|
||||
++currentEvent;
|
||||
}
|
||||
if (currentEvent == replayTimeline.size()) {
|
||||
emit replayFinished();
|
||||
replayTimer->stop();
|
||||
}
|
||||
|
||||
if (!(currentTime % 1000))
|
||||
update();
|
||||
}
|
||||
|
||||
void ReplayTimelineWidget::setTimeScaleFactor(qreal _timeScaleFactor)
|
||||
{
|
||||
timeScaleFactor = _timeScaleFactor;
|
||||
replayTimer->setInterval(static_cast<int>(200 / timeScaleFactor));
|
||||
}
|
||||
|
||||
void ReplayTimelineWidget::startReplay()
|
||||
{
|
||||
replayTimer->start(static_cast<int>(200 / timeScaleFactor));
|
||||
}
|
||||
|
||||
void ReplayTimelineWidget::stopReplay()
|
||||
{
|
||||
replayTimer->stop();
|
||||
}
|
||||
50
cockatrice/src/client/network/replay_timeline_widget.h
Normal file
50
cockatrice/src/client/network/replay_timeline_widget.h
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
#ifndef REPLAY_TIMELINE_WIDGET
|
||||
#define REPLAY_TIMELINE_WIDGET
|
||||
|
||||
#include <QList>
|
||||
#include <QMouseEvent>
|
||||
#include <QWidget>
|
||||
|
||||
class QPaintEvent;
|
||||
class QTimer;
|
||||
|
||||
class ReplayTimelineWidget : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
signals:
|
||||
void processNextEvent();
|
||||
void replayFinished();
|
||||
void rewound();
|
||||
|
||||
private:
|
||||
QTimer *replayTimer;
|
||||
QList<int> replayTimeline;
|
||||
QList<int> histogram;
|
||||
static const int binLength;
|
||||
int maxBinValue, maxTime;
|
||||
qreal timeScaleFactor;
|
||||
int currentTime;
|
||||
int currentEvent;
|
||||
private slots:
|
||||
void replayTimerTimeout();
|
||||
|
||||
public:
|
||||
explicit ReplayTimelineWidget(QWidget *parent = nullptr);
|
||||
void setTimeline(const QList<int> &_replayTimeline);
|
||||
QSize sizeHint() const override;
|
||||
QSize minimumSizeHint() const override;
|
||||
void setTimeScaleFactor(qreal _timeScaleFactor);
|
||||
int getCurrentEvent() const
|
||||
{
|
||||
return currentEvent;
|
||||
}
|
||||
public slots:
|
||||
void startReplay();
|
||||
void stopReplay();
|
||||
|
||||
protected:
|
||||
void paintEvent(QPaintEvent *event) override;
|
||||
void mousePressEvent(QMouseEvent *event) override;
|
||||
};
|
||||
|
||||
#endif
|
||||
296
cockatrice/src/client/network/sets_model.cpp
Normal file
296
cockatrice/src/client/network/sets_model.cpp
Normal file
|
|
@ -0,0 +1,296 @@
|
|||
#include "sets_model.h"
|
||||
|
||||
#include <QSortFilterProxyModel>
|
||||
|
||||
SetsModel::SetsModel(CardDatabase *_db, QObject *parent) : QAbstractTableModel(parent), sets(_db->getSetList())
|
||||
{
|
||||
sets.sortByKey();
|
||||
for (const CardSetPtr &set : sets) {
|
||||
if (set->getEnabled())
|
||||
enabledSets.insert(set);
|
||||
}
|
||||
}
|
||||
|
||||
SetsModel::~SetsModel() = default;
|
||||
|
||||
int SetsModel::rowCount(const QModelIndex &parent) const
|
||||
{
|
||||
if (parent.isValid())
|
||||
return 0;
|
||||
else
|
||||
return sets.size();
|
||||
}
|
||||
|
||||
QVariant SetsModel::data(const QModelIndex &index, int role) const
|
||||
{
|
||||
if (!index.isValid() || (index.column() >= NUM_COLS) || (index.row() >= rowCount()))
|
||||
return QVariant();
|
||||
|
||||
CardSetPtr set = sets[index.row()];
|
||||
|
||||
if (index.column() == EnabledCol) {
|
||||
switch (role) {
|
||||
case SortRole:
|
||||
return enabledSets.contains(set) ? "1" : "0";
|
||||
case Qt::CheckStateRole:
|
||||
return static_cast<int>(enabledSets.contains(set) ? Qt::Checked : Qt::Unchecked);
|
||||
case Qt::DisplayRole:
|
||||
default:
|
||||
return QVariant();
|
||||
}
|
||||
}
|
||||
|
||||
if (role != Qt::DisplayRole && role != SortRole)
|
||||
return QVariant();
|
||||
|
||||
switch (index.column()) {
|
||||
case SortKeyCol:
|
||||
return QString("%1").arg(set->getSortKey(), 8, 10, QChar('0'));
|
||||
case IsKnownCol:
|
||||
return set->getIsKnown();
|
||||
case SetTypeCol:
|
||||
return set->getSetType();
|
||||
case ShortNameCol:
|
||||
return set->getShortName();
|
||||
case LongNameCol:
|
||||
return set->getLongName();
|
||||
case ReleaseDateCol:
|
||||
return set->getReleaseDate().toString(Qt::ISODate);
|
||||
default:
|
||||
return QVariant();
|
||||
}
|
||||
}
|
||||
|
||||
bool SetsModel::setData(const QModelIndex &index, const QVariant &value, int role)
|
||||
{
|
||||
if (role == Qt::CheckStateRole && index.column() == EnabledCol) {
|
||||
toggleRow(index.row(), value == Qt::Checked);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
QVariant SetsModel::headerData(int section, Qt::Orientation orientation, int role) const
|
||||
{
|
||||
if ((role != Qt::DisplayRole) || (orientation != Qt::Horizontal))
|
||||
return QVariant();
|
||||
switch (section) {
|
||||
case SortKeyCol:
|
||||
return QString("Key"); /* no tr() for translations needed, column just used for sorting --> hidden */
|
||||
case IsKnownCol:
|
||||
return QString(
|
||||
"Is known"); /* no tr() for translations needed, column is just used for sorting --> hidden */
|
||||
case EnabledCol:
|
||||
return tr("Enabled");
|
||||
case SetTypeCol:
|
||||
return tr("Set type");
|
||||
case ShortNameCol:
|
||||
return tr("Set code");
|
||||
case LongNameCol:
|
||||
return tr("Long name");
|
||||
case ReleaseDateCol:
|
||||
return tr("Release date");
|
||||
default:
|
||||
return QVariant();
|
||||
}
|
||||
}
|
||||
|
||||
Qt::ItemFlags SetsModel::flags(const QModelIndex &index) const
|
||||
{
|
||||
if (!index.isValid())
|
||||
return Qt::NoItemFlags;
|
||||
|
||||
Qt::ItemFlags flags = QAbstractTableModel::flags(index) | Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled;
|
||||
|
||||
if (index.column() == EnabledCol)
|
||||
flags |= Qt::ItemIsUserCheckable;
|
||||
|
||||
return flags;
|
||||
}
|
||||
|
||||
Qt::DropActions SetsModel::supportedDropActions() const
|
||||
{
|
||||
return Qt::MoveAction;
|
||||
}
|
||||
|
||||
QMimeData *SetsModel::mimeData(const QModelIndexList &indexes) const
|
||||
{
|
||||
if (indexes.isEmpty())
|
||||
return 0;
|
||||
|
||||
SetsMimeData *result = new SetsMimeData(indexes[0].row());
|
||||
return qobject_cast<QMimeData *>(result);
|
||||
}
|
||||
|
||||
bool SetsModel::dropMimeData(const QMimeData *data,
|
||||
Qt::DropAction action,
|
||||
int row,
|
||||
int /*column*/,
|
||||
const QModelIndex &parent)
|
||||
{
|
||||
if (action != Qt::MoveAction)
|
||||
return false;
|
||||
if (row == -1) {
|
||||
if (!parent.isValid())
|
||||
return false;
|
||||
row = parent.row();
|
||||
}
|
||||
int oldRow = qobject_cast<const SetsMimeData *>(data)->getOldRow();
|
||||
if (oldRow < row)
|
||||
row--;
|
||||
|
||||
swapRows(oldRow, row);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void SetsModel::toggleRow(int row, bool enable)
|
||||
{
|
||||
CardSetPtr temp = sets.at(row);
|
||||
|
||||
if (enable)
|
||||
enabledSets.insert(temp);
|
||||
else
|
||||
enabledSets.remove(temp);
|
||||
|
||||
emit dataChanged(index(row, EnabledCol), index(row, EnabledCol));
|
||||
}
|
||||
|
||||
void SetsModel::toggleRow(int row)
|
||||
{
|
||||
CardSetPtr tmp = sets.at(row);
|
||||
|
||||
if (tmp == nullptr)
|
||||
return;
|
||||
|
||||
if (enabledSets.contains(tmp))
|
||||
enabledSets.remove(tmp);
|
||||
else
|
||||
enabledSets.insert(tmp);
|
||||
|
||||
emit dataChanged(index(row, EnabledCol), index(row, EnabledCol));
|
||||
}
|
||||
|
||||
void SetsModel::toggleAll(bool enabled)
|
||||
{
|
||||
enabledSets.clear();
|
||||
|
||||
if (enabled)
|
||||
for (CardSetPtr set : sets)
|
||||
enabledSets.insert(set);
|
||||
|
||||
emit dataChanged(index(0, 0), index(rowCount() - 1, columnCount() - 1));
|
||||
}
|
||||
|
||||
void SetsModel::swapRows(int oldRow, int newRow)
|
||||
{
|
||||
beginRemoveRows(QModelIndex(), oldRow, oldRow);
|
||||
CardSetPtr temp = sets.takeAt(oldRow);
|
||||
endRemoveRows();
|
||||
|
||||
beginInsertRows(QModelIndex(), newRow, newRow);
|
||||
sets.insert(newRow, temp);
|
||||
endInsertRows();
|
||||
|
||||
emit dataChanged(index(0, 0), index(rowCount() - 1, columnCount() - 1));
|
||||
}
|
||||
|
||||
void SetsModel::sort(int column, Qt::SortOrder order)
|
||||
{
|
||||
QMultiMap<QString, CardSetPtr> setMap;
|
||||
int numRows = rowCount();
|
||||
int row;
|
||||
|
||||
for (row = 0; row < numRows; ++row)
|
||||
setMap.insert(index(row, column).data(SetsModel::SortRole).toString(), sets.at(row));
|
||||
|
||||
QList<CardSetPtr> tmp = setMap.values();
|
||||
sets.clear();
|
||||
if (order == Qt::AscendingOrder) {
|
||||
for (row = 0; row < tmp.size(); row++) {
|
||||
sets.append(tmp.at(row));
|
||||
}
|
||||
} else {
|
||||
for (row = tmp.size() - 1; row >= 0; row--) {
|
||||
sets.append(tmp.at(row));
|
||||
}
|
||||
}
|
||||
|
||||
emit dataChanged(index(0, 0), index(numRows - 1, columnCount() - 1));
|
||||
}
|
||||
|
||||
void SetsModel::save(CardDatabase *db)
|
||||
{
|
||||
// order
|
||||
for (int i = 0; i < sets.size(); i++)
|
||||
sets[i]->setSortKey(static_cast<unsigned int>(i + 1));
|
||||
|
||||
// enabled sets
|
||||
for (const CardSetPtr &set : sets)
|
||||
set->setEnabled(enabledSets.contains(set));
|
||||
|
||||
sets.sortByKey();
|
||||
|
||||
db->notifyEnabledSetsChanged();
|
||||
}
|
||||
|
||||
void SetsModel::restore(CardDatabase *db)
|
||||
{
|
||||
// order
|
||||
sets = db->getSetList();
|
||||
sets.sortByKey();
|
||||
|
||||
// enabled sets
|
||||
enabledSets.clear();
|
||||
for (const CardSetPtr &set : sets) {
|
||||
if (set->getEnabled())
|
||||
enabledSets.insert(set);
|
||||
}
|
||||
|
||||
emit dataChanged(index(0, 0), index(rowCount() - 1, columnCount() - 1));
|
||||
}
|
||||
|
||||
QStringList SetsModel::mimeTypes() const
|
||||
{
|
||||
return QStringList() << "application/x-cockatricecardset";
|
||||
}
|
||||
|
||||
SetsDisplayModel::SetsDisplayModel(QObject *parent) : QSortFilterProxyModel(parent)
|
||||
{
|
||||
setFilterCaseSensitivity(Qt::CaseInsensitive);
|
||||
setSortCaseSensitivity(Qt::CaseInsensitive);
|
||||
}
|
||||
|
||||
void SetsDisplayModel::fetchMore(const QModelIndex &index)
|
||||
{
|
||||
int itemsToFetch = sourceModel()->rowCount(index);
|
||||
|
||||
beginInsertRows(QModelIndex(), 0, itemsToFetch - 1);
|
||||
|
||||
endInsertRows();
|
||||
}
|
||||
|
||||
bool SetsDisplayModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const
|
||||
{
|
||||
auto typeIndex = sourceModel()->index(sourceRow, SetsModel::SetTypeCol, sourceParent);
|
||||
auto nameIndex = sourceModel()->index(sourceRow, SetsModel::LongNameCol, sourceParent);
|
||||
auto shortNameIndex = sourceModel()->index(sourceRow, SetsModel::ShortNameCol, sourceParent);
|
||||
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(5, 12, 0))
|
||||
const auto filter = filterRegularExpression();
|
||||
#else
|
||||
const auto filter = filterRegExp();
|
||||
#endif
|
||||
|
||||
return (sourceModel()->data(typeIndex).toString().contains(filter) ||
|
||||
sourceModel()->data(nameIndex).toString().contains(filter) ||
|
||||
sourceModel()->data(shortNameIndex).toString().contains(filter));
|
||||
}
|
||||
|
||||
bool SetsDisplayModel::lessThan(const QModelIndex &left, const QModelIndex &right) const
|
||||
{
|
||||
QString leftString = sourceModel()->data(left, SetsModel::SortRole).toString();
|
||||
QString rightString = sourceModel()->data(right, SetsModel::SortRole).toString();
|
||||
|
||||
return QString::localeAwareCompare(leftString, rightString) < 0;
|
||||
}
|
||||
97
cockatrice/src/client/network/sets_model.h
Normal file
97
cockatrice/src/client/network/sets_model.h
Normal file
|
|
@ -0,0 +1,97 @@
|
|||
#ifndef SETSMODEL_H
|
||||
#define SETSMODEL_H
|
||||
|
||||
#include "../../game/cards/card_database.h"
|
||||
|
||||
#include <QAbstractTableModel>
|
||||
#include <QMimeData>
|
||||
#include <QSet>
|
||||
#include <QSortFilterProxyModel>
|
||||
|
||||
class SetsProxyModel;
|
||||
|
||||
class SetsMimeData : public QMimeData
|
||||
{
|
||||
Q_OBJECT
|
||||
private:
|
||||
int oldRow;
|
||||
|
||||
public:
|
||||
SetsMimeData(int _oldRow) : oldRow(_oldRow)
|
||||
{
|
||||
}
|
||||
int getOldRow() const
|
||||
{
|
||||
return oldRow;
|
||||
}
|
||||
QStringList formats() const
|
||||
{
|
||||
return QStringList() << "application/x-cockatricecardset";
|
||||
}
|
||||
};
|
||||
|
||||
class SetsModel : public QAbstractTableModel
|
||||
{
|
||||
Q_OBJECT
|
||||
friend class SetsProxyModel;
|
||||
|
||||
private:
|
||||
static const int NUM_COLS = 7;
|
||||
SetList sets;
|
||||
QSet<CardSetPtr> enabledSets;
|
||||
|
||||
public:
|
||||
enum SetsColumns
|
||||
{
|
||||
SortKeyCol,
|
||||
IsKnownCol,
|
||||
EnabledCol,
|
||||
LongNameCol,
|
||||
ShortNameCol,
|
||||
SetTypeCol,
|
||||
ReleaseDateCol
|
||||
};
|
||||
enum Role
|
||||
{
|
||||
SortRole = Qt::UserRole
|
||||
};
|
||||
|
||||
SetsModel(CardDatabase *_db, QObject *parent = nullptr);
|
||||
~SetsModel();
|
||||
int rowCount(const QModelIndex &parent = QModelIndex()) const;
|
||||
int columnCount(const QModelIndex &parent = QModelIndex()) const
|
||||
{
|
||||
Q_UNUSED(parent);
|
||||
return NUM_COLS;
|
||||
}
|
||||
QVariant data(const QModelIndex &index, int role) const;
|
||||
bool setData(const QModelIndex &index, const QVariant &value, int role);
|
||||
QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const;
|
||||
Qt::ItemFlags flags(const QModelIndex &index) const;
|
||||
Qt::DropActions supportedDropActions() const;
|
||||
|
||||
QMimeData *mimeData(const QModelIndexList &indexes) const;
|
||||
bool dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent);
|
||||
QStringList mimeTypes() const;
|
||||
void swapRows(int oldRow, int newRow);
|
||||
void toggleRow(int row, bool enable);
|
||||
void toggleRow(int row);
|
||||
void toggleAll(bool);
|
||||
void sort(int column, Qt::SortOrder order = Qt::AscendingOrder);
|
||||
void save(CardDatabase *db);
|
||||
void restore(CardDatabase *db);
|
||||
};
|
||||
|
||||
class SetsDisplayModel : public QSortFilterProxyModel
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
SetsDisplayModel(QObject *parent = NULL);
|
||||
|
||||
protected:
|
||||
bool lessThan(const QModelIndex &left, const QModelIndex &right) const override;
|
||||
bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override;
|
||||
void fetchMore(const QModelIndex &index) override;
|
||||
};
|
||||
|
||||
#endif
|
||||
222
cockatrice/src/client/network/spoiler_background_updater.cpp
Normal file
222
cockatrice/src/client/network/spoiler_background_updater.cpp
Normal file
|
|
@ -0,0 +1,222 @@
|
|||
#include "spoiler_background_updater.h"
|
||||
|
||||
#include "../../game/cards/card_database.h"
|
||||
#include "../../main.h"
|
||||
#include "../../settings/cache_settings.h"
|
||||
#include "../ui/window_main.h"
|
||||
|
||||
#include <QApplication>
|
||||
#include <QCryptographicHash>
|
||||
#include <QDateTime>
|
||||
#include <QDebug>
|
||||
#include <QFile>
|
||||
#include <QLocale>
|
||||
#include <QMessageBox>
|
||||
#include <QNetworkReply>
|
||||
#include <QUrl>
|
||||
#include <QtConcurrent>
|
||||
|
||||
#define SPOILERS_STATUS_URL "https://raw.githubusercontent.com/Cockatrice/Magic-Spoiler/files/SpoilerSeasonEnabled"
|
||||
#define SPOILERS_URL "https://raw.githubusercontent.com/Cockatrice/Magic-Spoiler/files/spoiler.xml"
|
||||
|
||||
SpoilerBackgroundUpdater::SpoilerBackgroundUpdater(QObject *apParent) : QObject(apParent), cardUpdateProcess(nullptr)
|
||||
{
|
||||
isSpoilerDownloadEnabled = SettingsCache::instance().getDownloadSpoilersStatus();
|
||||
if (isSpoilerDownloadEnabled) {
|
||||
// Start the process of checking if we're in spoiler season
|
||||
// File exists means we're in spoiler season
|
||||
startSpoilerDownloadProcess(SPOILERS_STATUS_URL, false);
|
||||
} else {
|
||||
qDebug() << "Spoilers Disabled";
|
||||
}
|
||||
}
|
||||
|
||||
void SpoilerBackgroundUpdater::startSpoilerDownloadProcess(QString url, bool saveResults)
|
||||
{
|
||||
auto spoilerURL = QUrl(url);
|
||||
downloadFromURL(spoilerURL, saveResults);
|
||||
}
|
||||
|
||||
void SpoilerBackgroundUpdater::downloadFromURL(QUrl url, bool saveResults)
|
||||
{
|
||||
auto *nam = new QNetworkAccessManager(this);
|
||||
QNetworkReply *reply = nam->get(QNetworkRequest(url));
|
||||
|
||||
if (saveResults) {
|
||||
// This will write out to the file (used for spoiler.xml)
|
||||
connect(reply, SIGNAL(finished()), this, SLOT(actDownloadFinishedSpoilersFile()));
|
||||
} else {
|
||||
// This will check the status (used to see if we're in spoiler season or not)
|
||||
connect(reply, SIGNAL(finished()), this, SLOT(actCheckIfSpoilerSeasonEnabled()));
|
||||
}
|
||||
}
|
||||
|
||||
void SpoilerBackgroundUpdater::actDownloadFinishedSpoilersFile()
|
||||
{
|
||||
// Check for server reply
|
||||
auto *reply = dynamic_cast<QNetworkReply *>(sender());
|
||||
QNetworkReply::NetworkError errorCode = reply->error();
|
||||
|
||||
if (errorCode == QNetworkReply::NoError) {
|
||||
spoilerData = reply->readAll();
|
||||
|
||||
// Save the spoiler.xml file to the disk
|
||||
saveDownloadedFile(spoilerData);
|
||||
|
||||
reply->deleteLater();
|
||||
emit spoilerCheckerDone();
|
||||
} else {
|
||||
qDebug() << "Error downloading spoilers file" << errorCode;
|
||||
emit spoilerCheckerDone();
|
||||
}
|
||||
}
|
||||
|
||||
bool SpoilerBackgroundUpdater::deleteSpoilerFile()
|
||||
{
|
||||
QString fileName = SettingsCache::instance().getSpoilerCardDatabasePath();
|
||||
QFileInfo fi(fileName);
|
||||
QDir fileDir(fi.path());
|
||||
QFile file(fileName);
|
||||
|
||||
// Delete the spoiler.xml file
|
||||
if (file.exists() && file.remove()) {
|
||||
qDebug() << "Deleting spoiler.xml";
|
||||
return true;
|
||||
}
|
||||
|
||||
qDebug() << "Error: Spoiler.xml not found or not deleted";
|
||||
return false;
|
||||
}
|
||||
|
||||
void SpoilerBackgroundUpdater::actCheckIfSpoilerSeasonEnabled()
|
||||
{
|
||||
auto *response = dynamic_cast<QNetworkReply *>(sender());
|
||||
QNetworkReply::NetworkError errorCode = response->error();
|
||||
|
||||
if (errorCode == QNetworkReply::ContentNotFoundError) {
|
||||
// Spoiler season is offline at this point, so the spoiler.xml file can be safely deleted
|
||||
// The user should run Oracle to get the latest card information
|
||||
if (deleteSpoilerFile() && trayIcon) {
|
||||
trayIcon->showMessage(tr("Spoilers season has ended"), tr("Deleting spoiler.xml. Please run Oracle"));
|
||||
}
|
||||
|
||||
qDebug() << "Spoiler Season Offline";
|
||||
emit spoilerCheckerDone();
|
||||
} else if (errorCode == QNetworkReply::NoError) {
|
||||
qDebug() << "Spoiler Service Online";
|
||||
startSpoilerDownloadProcess(SPOILERS_URL, true);
|
||||
} else if (errorCode == QNetworkReply::HostNotFoundError) {
|
||||
if (trayIcon) {
|
||||
trayIcon->showMessage(tr("Spoilers download failed"), tr("No internet connection"));
|
||||
}
|
||||
|
||||
qDebug() << "Spoiler download failed due to no internet connection";
|
||||
emit spoilerCheckerDone();
|
||||
} else {
|
||||
if (trayIcon) {
|
||||
trayIcon->showMessage(tr("Spoilers download failed"), tr("Error") + " " + (short)errorCode);
|
||||
}
|
||||
|
||||
qDebug() << "Spoiler download failed with reason" << errorCode;
|
||||
emit spoilerCheckerDone();
|
||||
}
|
||||
}
|
||||
|
||||
bool SpoilerBackgroundUpdater::saveDownloadedFile(QByteArray data)
|
||||
{
|
||||
QString fileName = SettingsCache::instance().getSpoilerCardDatabasePath();
|
||||
QFileInfo fi(fileName);
|
||||
QDir fileDir(fi.path());
|
||||
|
||||
if (!fileDir.exists() && !fileDir.mkpath(fileDir.absolutePath())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if the data matches. If it does, then spoilers are up to date.
|
||||
if (getHash(fileName) == getHash(data)) {
|
||||
if (trayIcon) {
|
||||
trayIcon->showMessage(tr("Spoilers already up to date"), tr("No new spoilers added"));
|
||||
}
|
||||
|
||||
qDebug() << "Spoilers Up to Date";
|
||||
return false;
|
||||
}
|
||||
|
||||
QFile file(fileName);
|
||||
if (!file.open(QIODevice::WriteOnly)) {
|
||||
qDebug() << "Spoiler Service Error: File open (w) failed for" << fileName;
|
||||
file.close();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (file.write(data) == -1) {
|
||||
qDebug() << "Spoiler Service Error: File write (w) failed for" << fileName;
|
||||
file.close();
|
||||
return false;
|
||||
}
|
||||
|
||||
file.close();
|
||||
|
||||
// Data written, so reload the card database
|
||||
qDebug() << "Spoiler Service Data Written";
|
||||
const auto reloadOk = QtConcurrent::run([] { db->loadCardDatabases(); });
|
||||
|
||||
// If the user has notifications enabled, let them know
|
||||
// when the database was last updated
|
||||
if (trayIcon) {
|
||||
QList<QByteArray> lines = data.split('\n');
|
||||
|
||||
foreach (QByteArray line, lines) {
|
||||
if (line.contains("Created At:")) {
|
||||
QString timeStamp = QString(line).replace("Created At:", "").trimmed();
|
||||
timeStamp.chop(6); // Remove " (UTC)"
|
||||
|
||||
auto utcTime = QLocale().toDateTime(timeStamp, "ddd, MMM dd yyyy, hh:mm:ss");
|
||||
utcTime.setTimeSpec(Qt::UTC);
|
||||
|
||||
QString localTime = utcTime.toLocalTime().toString("MMM d, hh:mm");
|
||||
|
||||
trayIcon->showMessage(tr("Spoilers have been updated!"), tr("Last change:") + " " + localTime);
|
||||
emit spoilersUpdatedSuccessfully();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
QByteArray SpoilerBackgroundUpdater::getHash(const QString fileName)
|
||||
{
|
||||
QFile file(fileName);
|
||||
|
||||
if (file.open(QFile::ReadOnly)) {
|
||||
// Only read the first 512 bytes (enough to get the "created" tag)
|
||||
const QByteArray bytes = file.read(512);
|
||||
|
||||
QCryptographicHash hash(QCryptographicHash::Algorithm::Md5);
|
||||
hash.addData(bytes);
|
||||
|
||||
qDebug() << "File Hash =" << hash.result();
|
||||
|
||||
file.close();
|
||||
return hash.result();
|
||||
} else {
|
||||
qDebug() << "getHash ReadOnly failed!";
|
||||
file.close();
|
||||
return QByteArray();
|
||||
}
|
||||
}
|
||||
|
||||
QByteArray SpoilerBackgroundUpdater::getHash(QByteArray data)
|
||||
{
|
||||
// Only read the first 512 bytes (enough to get the "created" tag)
|
||||
const QByteArray bytes = data.left(512);
|
||||
|
||||
QCryptographicHash hash(QCryptographicHash::Algorithm::Md5);
|
||||
hash.addData(bytes);
|
||||
|
||||
qDebug() << "Data Hash =" << hash.result();
|
||||
|
||||
return hash.result();
|
||||
}
|
||||
38
cockatrice/src/client/network/spoiler_background_updater.h
Normal file
38
cockatrice/src/client/network/spoiler_background_updater.h
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
#ifndef COCKATRICE_SPOILER_DOWNLOADER_H
|
||||
#define COCKATRICE_SPOILER_DOWNLOADER_H
|
||||
|
||||
#include <QByteArray>
|
||||
#include <QObject>
|
||||
#include <QProcess>
|
||||
|
||||
class SpoilerBackgroundUpdater : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit SpoilerBackgroundUpdater(QObject *apParent = nullptr);
|
||||
inline QString getCardUpdaterBinaryName()
|
||||
{
|
||||
return "oracle";
|
||||
};
|
||||
QByteArray getHash(const QString fileName);
|
||||
QByteArray getHash(QByteArray data);
|
||||
static bool deleteSpoilerFile();
|
||||
|
||||
private slots:
|
||||
void actDownloadFinishedSpoilersFile();
|
||||
void actCheckIfSpoilerSeasonEnabled();
|
||||
|
||||
private:
|
||||
bool isSpoilerDownloadEnabled;
|
||||
QProcess *cardUpdateProcess;
|
||||
QByteArray spoilerData;
|
||||
void startSpoilerDownloadProcess(QString url, bool saveResults);
|
||||
void downloadFromURL(QUrl url, bool saveResults);
|
||||
bool saveDownloadedFile(QByteArray data);
|
||||
|
||||
signals:
|
||||
void spoilersUpdatedSuccessfully();
|
||||
void spoilerCheckerDone();
|
||||
};
|
||||
|
||||
#endif // COCKATRICE_SPOILER_DOWNLOADER_H
|
||||
158
cockatrice/src/client/sound_engine.cpp
Normal file
158
cockatrice/src/client/sound_engine.cpp
Normal file
|
|
@ -0,0 +1,158 @@
|
|||
#include "sound_engine.h"
|
||||
|
||||
#include "../settings/cache_settings.h"
|
||||
|
||||
#include <QDir>
|
||||
#include <QMediaPlayer>
|
||||
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
|
||||
#include <QAudioOutput>
|
||||
#endif
|
||||
|
||||
#define DEFAULT_THEME_NAME "Default"
|
||||
#define TEST_SOUND_FILENAME "player_join"
|
||||
|
||||
SoundEngine::SoundEngine(QObject *parent) : QObject(parent), player(nullptr)
|
||||
{
|
||||
ensureThemeDirectoryExists();
|
||||
connect(&SettingsCache::instance(), SIGNAL(soundThemeChanged()), this, SLOT(themeChangedSlot()));
|
||||
connect(&SettingsCache::instance(), SIGNAL(soundEnabledChanged()), this, SLOT(soundEnabledChanged()));
|
||||
|
||||
soundEnabledChanged();
|
||||
themeChangedSlot();
|
||||
}
|
||||
|
||||
SoundEngine::~SoundEngine()
|
||||
{
|
||||
if (player) {
|
||||
player->deleteLater();
|
||||
player = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void SoundEngine::soundEnabledChanged()
|
||||
{
|
||||
if (SettingsCache::instance().getSoundEnabled()) {
|
||||
qDebug() << "SoundEngine: enabling sound with" << audioData.size() << "sounds";
|
||||
if (!player) {
|
||||
player = new QMediaPlayer;
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
|
||||
auto qAudioOutput = new QAudioOutput;
|
||||
player->setAudioOutput(qAudioOutput);
|
||||
#endif
|
||||
}
|
||||
} else {
|
||||
qDebug() << "SoundEngine: disabling sound";
|
||||
if (player) {
|
||||
player->stop();
|
||||
player->deleteLater();
|
||||
player = nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SoundEngine::playSound(const QString &fileName)
|
||||
{
|
||||
if (!player) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!audioData.contains(fileName)) {
|
||||
return;
|
||||
}
|
||||
|
||||
player->stop();
|
||||
int volumeSliderValue = SettingsCache::instance().getMasterVolume();
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
|
||||
player->audioOutput()->setVolume(qreal(volumeSliderValue) / 100);
|
||||
player->setSource(QUrl::fromLocalFile(audioData[fileName]));
|
||||
#else
|
||||
player->setVolume(volumeSliderValue);
|
||||
player->setMedia(QUrl::fromLocalFile(audioData[fileName]));
|
||||
#endif
|
||||
player->play();
|
||||
}
|
||||
|
||||
void SoundEngine::testSound()
|
||||
{
|
||||
playSound(TEST_SOUND_FILENAME);
|
||||
}
|
||||
|
||||
void SoundEngine::ensureThemeDirectoryExists()
|
||||
{
|
||||
if (SettingsCache::instance().getSoundThemeName().isEmpty() ||
|
||||
!getAvailableThemes().contains(SettingsCache::instance().getSoundThemeName())) {
|
||||
qDebug() << "Sounds theme name not set, setting default value";
|
||||
SettingsCache::instance().setSoundThemeName(DEFAULT_THEME_NAME);
|
||||
}
|
||||
}
|
||||
|
||||
QStringMap &SoundEngine::getAvailableThemes()
|
||||
{
|
||||
QDir dir;
|
||||
availableThemes.clear();
|
||||
|
||||
// load themes from user profile dir
|
||||
|
||||
dir.setPath(SettingsCache::instance().getDataPath() + "/sounds");
|
||||
|
||||
for (const QString &themeName : dir.entryList(QDir::AllDirs | QDir::NoDotAndDotDot, QDir::Name)) {
|
||||
if (!availableThemes.contains(themeName))
|
||||
availableThemes.insert(themeName, dir.absoluteFilePath(themeName));
|
||||
}
|
||||
|
||||
// load themes from cockatrice system dir
|
||||
dir.setPath(qApp->applicationDirPath() +
|
||||
#ifdef Q_OS_MAC
|
||||
"/../Resources/sounds"
|
||||
#elif defined(Q_OS_WIN)
|
||||
"/sounds"
|
||||
#else // linux
|
||||
"/../share/cockatrice/sounds"
|
||||
#endif
|
||||
);
|
||||
|
||||
for (const QString &themeName : dir.entryList(QDir::AllDirs | QDir::NoDotAndDotDot, QDir::Name)) {
|
||||
if (!availableThemes.contains(themeName))
|
||||
availableThemes.insert(themeName, dir.absoluteFilePath(themeName));
|
||||
}
|
||||
|
||||
return availableThemes;
|
||||
}
|
||||
|
||||
void SoundEngine::themeChangedSlot()
|
||||
{
|
||||
QString themeName = SettingsCache::instance().getSoundThemeName();
|
||||
qDebug() << "Sound theme changed:" << themeName;
|
||||
|
||||
QDir dir = getAvailableThemes().value(themeName);
|
||||
|
||||
audioData.clear();
|
||||
|
||||
static const QStringList extensions = {".wav", ".mp3", ".ogg"};
|
||||
static const QStringList fileNames = {
|
||||
// Phases
|
||||
"untap_step", "upkeep_step", "draw_step", "main_1", "start_combat", "attack_step", "block_step", "damage_step",
|
||||
"end_combat", "main_2", "end_step",
|
||||
// Game Actions
|
||||
"draw_card", "play_card", "tap_card", "untap_card", "shuffle", "roll_dice", "life_change",
|
||||
// Player
|
||||
"player_join", "player_leave", "player_disconnect", "player_reconnect", "player_concede",
|
||||
// Spectator
|
||||
"spectator_join", "spectator_leave",
|
||||
// Buddy
|
||||
"buddy_join", "buddy_leave",
|
||||
// Chat & UI
|
||||
"chat_mention", "all_mention", "private_message"};
|
||||
|
||||
for (const QString &extension : extensions) {
|
||||
for (const QString &name : fileNames) {
|
||||
QFile file(dir.filePath(name + extension));
|
||||
if (file.exists()) {
|
||||
audioData.insert(name, file.fileName());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
soundEnabledChanged();
|
||||
}
|
||||
38
cockatrice/src/client/sound_engine.h
Normal file
38
cockatrice/src/client/sound_engine.h
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
#ifndef SOUNDENGINE_H
|
||||
#define SOUNDENGINE_H
|
||||
|
||||
#include <QMap>
|
||||
#include <QMediaPlayer>
|
||||
#include <QObject>
|
||||
#include <QString>
|
||||
|
||||
class QAudioOutput;
|
||||
class QBuffer;
|
||||
|
||||
typedef QMap<QString, QString> QStringMap;
|
||||
|
||||
class SoundEngine : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit SoundEngine(QObject *parent = nullptr);
|
||||
~SoundEngine() override;
|
||||
void playSound(const QString &fileName);
|
||||
QStringMap &getAvailableThemes();
|
||||
|
||||
private:
|
||||
QStringMap availableThemes;
|
||||
QMap<QString, QString> audioData;
|
||||
QMediaPlayer *player;
|
||||
|
||||
protected:
|
||||
void ensureThemeDirectoryExists();
|
||||
private slots:
|
||||
void soundEnabledChanged();
|
||||
void themeChangedSlot();
|
||||
public slots:
|
||||
void testSound();
|
||||
};
|
||||
|
||||
extern SoundEngine *soundEngine;
|
||||
#endif
|
||||
41
cockatrice/src/client/tabs/tab.cpp
Normal file
41
cockatrice/src/client/tabs/tab.cpp
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
#include "tab.h"
|
||||
|
||||
#include "../../game/cards/card_info_widget.h"
|
||||
|
||||
#include <QApplication>
|
||||
#include <QDebug>
|
||||
#include <QScreen>
|
||||
|
||||
Tab::Tab(TabSupervisor *_tabSupervisor, QWidget *parent)
|
||||
: QMainWindow(parent), tabSupervisor(_tabSupervisor), contentsChanged(false), infoPopup(0)
|
||||
{
|
||||
setAttribute(Qt::WA_DeleteOnClose);
|
||||
}
|
||||
|
||||
void Tab::showCardInfoPopup(const QPoint &pos, const QString &cardName)
|
||||
{
|
||||
if (infoPopup) {
|
||||
infoPopup->deleteLater();
|
||||
}
|
||||
currentCardName = cardName;
|
||||
infoPopup = new CardInfoWidget(
|
||||
cardName, 0, Qt::Widget | Qt::FramelessWindowHint | Qt::X11BypassWindowManagerHint | Qt::WindowStaysOnTopHint);
|
||||
infoPopup->setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||
|
||||
auto screenRect = qApp->primaryScreen()->geometry();
|
||||
infoPopup->move(qMax(screenRect.left(), qMin(pos.x() - infoPopup->width() / 2,
|
||||
screenRect.left() + screenRect.width() - infoPopup->width())),
|
||||
qMax(screenRect.top(), qMin(pos.y() - infoPopup->height() / 2,
|
||||
screenRect.top() + screenRect.height() - infoPopup->height())));
|
||||
infoPopup->show();
|
||||
}
|
||||
|
||||
void Tab::deleteCardInfoPopup(const QString &cardName)
|
||||
{
|
||||
if (infoPopup) {
|
||||
if ((currentCardName == cardName) || (cardName == "_")) {
|
||||
infoPopup->deleteLater();
|
||||
infoPopup = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
61
cockatrice/src/client/tabs/tab.h
Normal file
61
cockatrice/src/client/tabs/tab.h
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
#ifndef TAB_H
|
||||
#define TAB_H
|
||||
|
||||
#include <QMainWindow>
|
||||
|
||||
class QMenu;
|
||||
class TabSupervisor;
|
||||
class CardInfoWidget;
|
||||
|
||||
class Tab : public QMainWindow
|
||||
{
|
||||
Q_OBJECT
|
||||
signals:
|
||||
void userEvent(bool globalEvent = true);
|
||||
void tabTextChanged(Tab *tab, const QString &newTabText);
|
||||
|
||||
protected:
|
||||
TabSupervisor *tabSupervisor;
|
||||
void addTabMenu(QMenu *menu)
|
||||
{
|
||||
tabMenus.append(menu);
|
||||
}
|
||||
protected slots:
|
||||
void showCardInfoPopup(const QPoint &pos, const QString &cardName);
|
||||
void deleteCardInfoPopup(const QString &cardName);
|
||||
|
||||
private:
|
||||
QString currentCardName;
|
||||
bool contentsChanged;
|
||||
CardInfoWidget *infoPopup;
|
||||
QList<QMenu *> tabMenus;
|
||||
|
||||
public:
|
||||
Tab(TabSupervisor *_tabSupervisor, QWidget *parent = nullptr);
|
||||
const QList<QMenu *> &getTabMenus() const
|
||||
{
|
||||
return tabMenus;
|
||||
}
|
||||
TabSupervisor *getTabSupervisor() const
|
||||
{
|
||||
return tabSupervisor;
|
||||
}
|
||||
bool getContentsChanged() const
|
||||
{
|
||||
return contentsChanged;
|
||||
}
|
||||
void setContentsChanged(bool _contentsChanged)
|
||||
{
|
||||
contentsChanged = _contentsChanged;
|
||||
}
|
||||
virtual QString getTabText() const = 0;
|
||||
virtual void retranslateUi() = 0;
|
||||
virtual void closeRequest()
|
||||
{
|
||||
}
|
||||
virtual void tabActivated()
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
||||
238
cockatrice/src/client/tabs/tab_account.cpp
Normal file
238
cockatrice/src/client/tabs/tab_account.cpp
Normal file
|
|
@ -0,0 +1,238 @@
|
|||
#include "tab_account.h"
|
||||
|
||||
#include "../../deck/custom_line_edit.h"
|
||||
#include "../../server/pending_command.h"
|
||||
#include "../../server/user/user_info_box.h"
|
||||
#include "../../server/user/user_list.h"
|
||||
#include "../game_logic/abstract_client.h"
|
||||
#include "../sound_engine.h"
|
||||
#include "pb/event_add_to_list.pb.h"
|
||||
#include "pb/event_remove_from_list.pb.h"
|
||||
#include "pb/event_user_joined.pb.h"
|
||||
#include "pb/event_user_left.pb.h"
|
||||
#include "pb/response_list_users.pb.h"
|
||||
#include "pb/session_commands.pb.h"
|
||||
#include "trice_limits.h"
|
||||
|
||||
#include <QHBoxLayout>
|
||||
#include <QPushButton>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
TabUserLists::TabUserLists(TabSupervisor *_tabSupervisor,
|
||||
AbstractClient *_client,
|
||||
const ServerInfo_User &userInfo,
|
||||
QWidget *parent)
|
||||
: Tab(_tabSupervisor, parent), client(_client)
|
||||
{
|
||||
allUsersList = new UserList(_tabSupervisor, client, UserList::AllUsersList);
|
||||
buddyList = new UserList(_tabSupervisor, client, UserList::BuddyList);
|
||||
ignoreList = new UserList(_tabSupervisor, client, UserList::IgnoreList);
|
||||
userInfoBox = new UserInfoBox(client, true);
|
||||
userInfoBox->updateInfo(userInfo);
|
||||
|
||||
connect(allUsersList, SIGNAL(openMessageDialog(const QString &, bool)), this,
|
||||
SIGNAL(openMessageDialog(const QString &, bool)));
|
||||
connect(buddyList, SIGNAL(openMessageDialog(const QString &, bool)), this,
|
||||
SIGNAL(openMessageDialog(const QString &, bool)));
|
||||
connect(ignoreList, SIGNAL(openMessageDialog(const QString &, bool)), this,
|
||||
SIGNAL(openMessageDialog(const QString &, bool)));
|
||||
|
||||
connect(client, SIGNAL(userJoinedEventReceived(const Event_UserJoined &)), this,
|
||||
SLOT(processUserJoinedEvent(const Event_UserJoined &)));
|
||||
connect(client, SIGNAL(userLeftEventReceived(const Event_UserLeft &)), this,
|
||||
SLOT(processUserLeftEvent(const Event_UserLeft &)));
|
||||
connect(client, SIGNAL(buddyListReceived(const QList<ServerInfo_User> &)), this,
|
||||
SLOT(buddyListReceived(const QList<ServerInfo_User> &)));
|
||||
connect(client, SIGNAL(ignoreListReceived(const QList<ServerInfo_User> &)), this,
|
||||
SLOT(ignoreListReceived(const QList<ServerInfo_User> &)));
|
||||
connect(client, SIGNAL(addToListEventReceived(const Event_AddToList &)), this,
|
||||
SLOT(processAddToListEvent(const Event_AddToList &)));
|
||||
connect(client, SIGNAL(removeFromListEventReceived(const Event_RemoveFromList &)), this,
|
||||
SLOT(processRemoveFromListEvent(const Event_RemoveFromList &)));
|
||||
|
||||
PendingCommand *pend = client->prepareSessionCommand(Command_ListUsers());
|
||||
connect(pend, SIGNAL(finished(Response, CommandContainer, QVariant)), this,
|
||||
SLOT(processListUsersResponse(const Response &)));
|
||||
client->sendCommand(pend);
|
||||
|
||||
QVBoxLayout *vbox = new QVBoxLayout;
|
||||
vbox->addWidget(userInfoBox);
|
||||
vbox->addWidget(allUsersList);
|
||||
|
||||
QHBoxLayout *addToBuddyList = new QHBoxLayout;
|
||||
addBuddyEdit = new LineEditUnfocusable;
|
||||
addBuddyEdit->setMaxLength(MAX_NAME_LENGTH);
|
||||
addBuddyEdit->setPlaceholderText(tr("Add to Buddy List"));
|
||||
connect(addBuddyEdit, SIGNAL(returnPressed()), this, SLOT(addToBuddyList()));
|
||||
QPushButton *addBuddyButton = new QPushButton("Add");
|
||||
connect(addBuddyButton, SIGNAL(clicked()), this, SLOT(addToBuddyList()));
|
||||
addToBuddyList->addWidget(addBuddyEdit);
|
||||
addToBuddyList->addWidget(addBuddyButton);
|
||||
|
||||
QHBoxLayout *addToIgnoreList = new QHBoxLayout;
|
||||
addIgnoreEdit = new LineEditUnfocusable;
|
||||
addIgnoreEdit->setMaxLength(MAX_NAME_LENGTH);
|
||||
addIgnoreEdit->setPlaceholderText(tr("Add to Ignore List"));
|
||||
connect(addIgnoreEdit, SIGNAL(returnPressed()), this, SLOT(addToIgnoreList()));
|
||||
QPushButton *addIgnoreButton = new QPushButton("Add");
|
||||
connect(addIgnoreButton, SIGNAL(clicked()), this, SLOT(addToIgnoreList()));
|
||||
addToIgnoreList->addWidget(addIgnoreEdit);
|
||||
addToIgnoreList->addWidget(addIgnoreButton);
|
||||
|
||||
QVBoxLayout *buddyPanel = new QVBoxLayout;
|
||||
buddyPanel->addWidget(buddyList);
|
||||
buddyPanel->addLayout(addToBuddyList);
|
||||
|
||||
QVBoxLayout *ignorePanel = new QVBoxLayout;
|
||||
ignorePanel->addWidget(ignoreList);
|
||||
ignorePanel->addLayout(addToIgnoreList);
|
||||
|
||||
QHBoxLayout *mainLayout = new QHBoxLayout;
|
||||
mainLayout->addLayout(buddyPanel);
|
||||
mainLayout->addLayout(ignorePanel);
|
||||
mainLayout->addLayout(vbox);
|
||||
|
||||
retranslateUi();
|
||||
|
||||
QWidget *mainWidget = new QWidget(this);
|
||||
mainWidget->setLayout(mainLayout);
|
||||
setCentralWidget(mainWidget);
|
||||
}
|
||||
|
||||
void TabUserLists::addToBuddyList()
|
||||
{
|
||||
QString userName = addBuddyEdit->text();
|
||||
if (userName.length() < 1)
|
||||
return;
|
||||
|
||||
std::string listName = "buddy";
|
||||
addToList(listName, userName);
|
||||
addBuddyEdit->clear();
|
||||
}
|
||||
|
||||
void TabUserLists::addToIgnoreList()
|
||||
{
|
||||
QString userName = addIgnoreEdit->text();
|
||||
if (userName.length() < 1)
|
||||
return;
|
||||
|
||||
std::string listName = "ignore";
|
||||
addToList(listName, userName);
|
||||
addIgnoreEdit->clear();
|
||||
}
|
||||
|
||||
void TabUserLists::addToList(const std::string &listName, const QString &userName)
|
||||
{
|
||||
Command_AddToList cmd;
|
||||
cmd.set_list(listName);
|
||||
cmd.set_user_name(userName.toStdString());
|
||||
|
||||
client->sendCommand(client->prepareSessionCommand(cmd));
|
||||
}
|
||||
|
||||
void TabUserLists::retranslateUi()
|
||||
{
|
||||
allUsersList->retranslateUi();
|
||||
buddyList->retranslateUi();
|
||||
ignoreList->retranslateUi();
|
||||
userInfoBox->retranslateUi();
|
||||
}
|
||||
|
||||
void TabUserLists::processListUsersResponse(const Response &response)
|
||||
{
|
||||
const Response_ListUsers &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());
|
||||
allUsersList->processUserInfo(info, true);
|
||||
ignoreList->setUserOnline(userName, true);
|
||||
buddyList->setUserOnline(userName, true);
|
||||
}
|
||||
|
||||
allUsersList->sortItems();
|
||||
ignoreList->sortItems();
|
||||
buddyList->sortItems();
|
||||
}
|
||||
|
||||
void TabUserLists::processUserJoinedEvent(const Event_UserJoined &event)
|
||||
{
|
||||
const ServerInfo_User &info = event.user_info();
|
||||
const QString userName = QString::fromStdString(info.name());
|
||||
|
||||
allUsersList->processUserInfo(info, true);
|
||||
ignoreList->setUserOnline(userName, true);
|
||||
buddyList->setUserOnline(userName, true);
|
||||
|
||||
allUsersList->sortItems();
|
||||
ignoreList->sortItems();
|
||||
buddyList->sortItems();
|
||||
|
||||
if (buddyList->getUsers().keys().contains(userName))
|
||||
soundEngine->playSound("buddy_join");
|
||||
|
||||
emit userJoined(info);
|
||||
}
|
||||
|
||||
void TabUserLists::processUserLeftEvent(const Event_UserLeft &event)
|
||||
{
|
||||
QString userName = QString::fromStdString(event.name());
|
||||
|
||||
if (buddyList->getUsers().keys().contains(userName))
|
||||
soundEngine->playSound("buddy_leave");
|
||||
|
||||
if (allUsersList->deleteUser(userName)) {
|
||||
ignoreList->setUserOnline(userName, false);
|
||||
buddyList->setUserOnline(userName, false);
|
||||
ignoreList->sortItems();
|
||||
buddyList->sortItems();
|
||||
|
||||
emit userLeft(userName);
|
||||
}
|
||||
}
|
||||
|
||||
void TabUserLists::buddyListReceived(const QList<ServerInfo_User> &_buddyList)
|
||||
{
|
||||
for (int i = 0; i < _buddyList.size(); ++i)
|
||||
buddyList->processUserInfo(_buddyList[i], false);
|
||||
buddyList->sortItems();
|
||||
}
|
||||
|
||||
void TabUserLists::ignoreListReceived(const QList<ServerInfo_User> &_ignoreList)
|
||||
{
|
||||
for (int i = 0; i < _ignoreList.size(); ++i)
|
||||
ignoreList->processUserInfo(_ignoreList[i], false);
|
||||
ignoreList->sortItems();
|
||||
}
|
||||
|
||||
void TabUserLists::processAddToListEvent(const Event_AddToList &event)
|
||||
{
|
||||
const ServerInfo_User &info = event.user_info();
|
||||
bool online = allUsersList->getUsers().contains(QString::fromStdString(info.name()));
|
||||
QString list = QString::fromStdString(event.list_name());
|
||||
UserList *userList = 0;
|
||||
if (list == "buddy")
|
||||
userList = buddyList;
|
||||
else if (list == "ignore")
|
||||
userList = ignoreList;
|
||||
if (!userList)
|
||||
return;
|
||||
|
||||
userList->processUserInfo(info, online);
|
||||
userList->sortItems();
|
||||
}
|
||||
|
||||
void TabUserLists::processRemoveFromListEvent(const Event_RemoveFromList &event)
|
||||
{
|
||||
QString list = QString::fromStdString(event.list_name());
|
||||
QString user = QString::fromStdString(event.user_name());
|
||||
UserList *userList = 0;
|
||||
if (list == "buddy")
|
||||
userList = buddyList;
|
||||
else if (list == "ignore")
|
||||
userList = ignoreList;
|
||||
if (!userList)
|
||||
return;
|
||||
userList->deleteUser(user);
|
||||
}
|
||||
72
cockatrice/src/client/tabs/tab_account.h
Normal file
72
cockatrice/src/client/tabs/tab_account.h
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
#ifndef TAB_ACCOUNT_H
|
||||
#define TAB_ACCOUNT_H
|
||||
|
||||
#include "pb/serverinfo_user.pb.h"
|
||||
#include "tab.h"
|
||||
|
||||
class AbstractClient;
|
||||
class UserList;
|
||||
class UserInfoBox;
|
||||
class LineEditUnfocusable;
|
||||
|
||||
class Event_ListRooms;
|
||||
class Event_UserJoined;
|
||||
class Event_UserLeft;
|
||||
class Response;
|
||||
class ServerInfo_User;
|
||||
class Event_AddToList;
|
||||
class Event_RemoveFromList;
|
||||
|
||||
class TabUserLists : public Tab
|
||||
{
|
||||
Q_OBJECT
|
||||
signals:
|
||||
void openMessageDialog(const QString &userName, bool focus);
|
||||
void userLeft(const QString &userName);
|
||||
void userJoined(const ServerInfo_User &userInfo);
|
||||
private slots:
|
||||
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);
|
||||
void addToIgnoreList();
|
||||
void addToBuddyList();
|
||||
|
||||
private:
|
||||
AbstractClient *client;
|
||||
UserList *allUsersList;
|
||||
UserList *buddyList;
|
||||
UserList *ignoreList;
|
||||
UserInfoBox *userInfoBox;
|
||||
LineEditUnfocusable *addBuddyEdit;
|
||||
LineEditUnfocusable *addIgnoreEdit;
|
||||
void addToList(const std::string &listName, const QString &userName);
|
||||
|
||||
public:
|
||||
TabUserLists(TabSupervisor *_tabSupervisor,
|
||||
AbstractClient *_client,
|
||||
const ServerInfo_User &userInfo,
|
||||
QWidget *parent = nullptr);
|
||||
void retranslateUi();
|
||||
QString getTabText() const
|
||||
{
|
||||
return tr("Account");
|
||||
}
|
||||
const UserList *getAllUsersList() const
|
||||
{
|
||||
return allUsersList;
|
||||
}
|
||||
const UserList *getBuddyList() const
|
||||
{
|
||||
return buddyList;
|
||||
}
|
||||
const UserList *getIgnoreList() const
|
||||
{
|
||||
return ignoreList;
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
||||
148
cockatrice/src/client/tabs/tab_admin.cpp
Normal file
148
cockatrice/src/client/tabs/tab_admin.cpp
Normal file
|
|
@ -0,0 +1,148 @@
|
|||
#include "tab_admin.h"
|
||||
|
||||
#include "../game_logic/abstract_client.h"
|
||||
#include "pb/admin_commands.pb.h"
|
||||
#include "trice_limits.h"
|
||||
|
||||
#include <QDialogButtonBox>
|
||||
#include <QGridLayout>
|
||||
#include <QGroupBox>
|
||||
#include <QHBoxLayout>
|
||||
#include <QLabel>
|
||||
#include <QLineEdit>
|
||||
#include <QMessageBox>
|
||||
#include <QPushButton>
|
||||
#include <QSpinBox>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
ShutdownDialog::ShutdownDialog(QWidget *parent) : QDialog(parent)
|
||||
{
|
||||
QLabel *reasonLabel = new QLabel(tr("&Reason for shutdown:"));
|
||||
reasonEdit = new QLineEdit;
|
||||
reasonEdit->setMaxLength(MAX_TEXT_LENGTH);
|
||||
reasonLabel->setBuddy(reasonEdit);
|
||||
QLabel *minutesLabel = new QLabel(tr("&Time until shutdown (minutes):"));
|
||||
minutesEdit = new QSpinBox;
|
||||
minutesLabel->setBuddy(minutesEdit);
|
||||
minutesEdit->setMinimum(0);
|
||||
minutesEdit->setValue(5);
|
||||
minutesEdit->setMaximum(999);
|
||||
|
||||
QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
|
||||
connect(buttonBox, SIGNAL(accepted()), this, SLOT(accept()));
|
||||
connect(buttonBox, SIGNAL(rejected()), this, SLOT(reject()));
|
||||
|
||||
QGridLayout *mainLayout = new QGridLayout;
|
||||
mainLayout->addWidget(reasonLabel, 0, 0);
|
||||
mainLayout->addWidget(reasonEdit, 0, 1);
|
||||
mainLayout->addWidget(minutesLabel, 1, 0);
|
||||
mainLayout->addWidget(minutesEdit, 1, 1);
|
||||
mainLayout->addWidget(buttonBox, 2, 0, 1, 2);
|
||||
|
||||
setLayout(mainLayout);
|
||||
setWindowTitle(tr("Shut down server"));
|
||||
}
|
||||
|
||||
QString ShutdownDialog::getReason() const
|
||||
{
|
||||
return reasonEdit->text();
|
||||
}
|
||||
|
||||
int ShutdownDialog::getMinutes() const
|
||||
{
|
||||
return minutesEdit->value();
|
||||
}
|
||||
|
||||
TabAdmin::TabAdmin(TabSupervisor *_tabSupervisor, AbstractClient *_client, bool _fullAdmin, QWidget *parent)
|
||||
: Tab(_tabSupervisor, parent), locked(true), client(_client), fullAdmin(_fullAdmin)
|
||||
{
|
||||
updateServerMessageButton = new QPushButton;
|
||||
connect(updateServerMessageButton, SIGNAL(clicked()), this, SLOT(actUpdateServerMessage()));
|
||||
shutdownServerButton = new QPushButton;
|
||||
connect(shutdownServerButton, SIGNAL(clicked()), this, SLOT(actShutdownServer()));
|
||||
reloadConfigButton = new QPushButton;
|
||||
connect(reloadConfigButton, SIGNAL(clicked()), this, SLOT(actReloadConfig()));
|
||||
|
||||
QVBoxLayout *vbox = new QVBoxLayout;
|
||||
vbox->addWidget(updateServerMessageButton);
|
||||
vbox->addWidget(shutdownServerButton);
|
||||
vbox->addWidget(reloadConfigButton);
|
||||
vbox->addStretch();
|
||||
|
||||
adminGroupBox = new QGroupBox;
|
||||
adminGroupBox->setLayout(vbox);
|
||||
adminGroupBox->setEnabled(false);
|
||||
|
||||
unlockButton = new QPushButton;
|
||||
connect(unlockButton, SIGNAL(clicked()), this, SLOT(actUnlock()));
|
||||
lockButton = new QPushButton;
|
||||
lockButton->setEnabled(false);
|
||||
connect(lockButton, SIGNAL(clicked()), this, SLOT(actLock()));
|
||||
|
||||
QVBoxLayout *mainLayout = new QVBoxLayout;
|
||||
mainLayout->addWidget(adminGroupBox);
|
||||
mainLayout->addWidget(unlockButton);
|
||||
mainLayout->addWidget(lockButton);
|
||||
|
||||
retranslateUi();
|
||||
|
||||
QWidget *mainWidget = new QWidget(this);
|
||||
mainWidget->setLayout(mainLayout);
|
||||
setCentralWidget(mainWidget);
|
||||
|
||||
actUnlock();
|
||||
}
|
||||
|
||||
void TabAdmin::retranslateUi()
|
||||
{
|
||||
updateServerMessageButton->setText(tr("Update server &message"));
|
||||
shutdownServerButton->setText(tr("&Shut down server"));
|
||||
reloadConfigButton->setText(tr("&Reload configuration"));
|
||||
adminGroupBox->setTitle(tr("Server administration functions"));
|
||||
|
||||
unlockButton->setText(tr("&Unlock functions"));
|
||||
lockButton->setText(tr("&Lock functions"));
|
||||
}
|
||||
|
||||
void TabAdmin::actUpdateServerMessage()
|
||||
{
|
||||
client->sendCommand(client->prepareAdminCommand(Command_UpdateServerMessage()));
|
||||
}
|
||||
|
||||
void TabAdmin::actShutdownServer()
|
||||
{
|
||||
ShutdownDialog dlg;
|
||||
if (dlg.exec()) {
|
||||
Command_ShutdownServer cmd;
|
||||
cmd.set_reason(dlg.getReason().toStdString());
|
||||
cmd.set_minutes(dlg.getMinutes());
|
||||
|
||||
client->sendCommand(client->prepareAdminCommand(cmd));
|
||||
}
|
||||
}
|
||||
|
||||
void TabAdmin::actReloadConfig()
|
||||
{
|
||||
Command_ReloadConfig cmd;
|
||||
client->sendCommand(client->prepareAdminCommand(cmd));
|
||||
}
|
||||
|
||||
void TabAdmin::actUnlock()
|
||||
{
|
||||
if (fullAdmin)
|
||||
adminGroupBox->setEnabled(true);
|
||||
lockButton->setEnabled(true);
|
||||
unlockButton->setEnabled(false);
|
||||
locked = false;
|
||||
emit adminLockChanged(false);
|
||||
}
|
||||
|
||||
void TabAdmin::actLock()
|
||||
{
|
||||
if (fullAdmin)
|
||||
adminGroupBox->setEnabled(false);
|
||||
lockButton->setEnabled(false);
|
||||
unlockButton->setEnabled(true);
|
||||
locked = true;
|
||||
emit adminLockChanged(true);
|
||||
}
|
||||
61
cockatrice/src/client/tabs/tab_admin.h
Normal file
61
cockatrice/src/client/tabs/tab_admin.h
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
#ifndef TAB_ADMIN_H
|
||||
#define TAB_ADMIN_H
|
||||
|
||||
#include "tab.h"
|
||||
|
||||
#include <QDialog>
|
||||
|
||||
class AbstractClient;
|
||||
|
||||
class QGroupBox;
|
||||
class QPushButton;
|
||||
class QSpinBox;
|
||||
class QLineEdit;
|
||||
|
||||
class ShutdownDialog : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
private:
|
||||
QLineEdit *reasonEdit;
|
||||
QSpinBox *minutesEdit;
|
||||
|
||||
public:
|
||||
ShutdownDialog(QWidget *parent = nullptr);
|
||||
QString getReason() const;
|
||||
int getMinutes() const;
|
||||
};
|
||||
|
||||
class TabAdmin : public Tab
|
||||
{
|
||||
Q_OBJECT
|
||||
private:
|
||||
bool locked;
|
||||
AbstractClient *client;
|
||||
bool fullAdmin;
|
||||
QPushButton *updateServerMessageButton, *shutdownServerButton, *reloadConfigButton;
|
||||
QGroupBox *adminGroupBox;
|
||||
QPushButton *unlockButton, *lockButton;
|
||||
signals:
|
||||
void adminLockChanged(bool lock);
|
||||
private slots:
|
||||
void actUpdateServerMessage();
|
||||
void actShutdownServer();
|
||||
void actReloadConfig();
|
||||
|
||||
void actUnlock();
|
||||
void actLock();
|
||||
|
||||
public:
|
||||
TabAdmin(TabSupervisor *_tabSupervisor, AbstractClient *_client, bool _fullAdmin, QWidget *parent = nullptr);
|
||||
void retranslateUi();
|
||||
QString getTabText() const
|
||||
{
|
||||
return tr("Administration");
|
||||
}
|
||||
bool getLocked() const
|
||||
{
|
||||
return locked;
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
||||
1295
cockatrice/src/client/tabs/tab_deck_editor.cpp
Normal file
1295
cockatrice/src/client/tabs/tab_deck_editor.cpp
Normal file
File diff suppressed because it is too large
Load diff
170
cockatrice/src/client/tabs/tab_deck_editor.h
Normal file
170
cockatrice/src/client/tabs/tab_deck_editor.h
Normal file
|
|
@ -0,0 +1,170 @@
|
|||
#ifndef WINDOW_DECKEDITOR_H
|
||||
#define WINDOW_DECKEDITOR_H
|
||||
|
||||
#include "../../deck/custom_line_edit.h"
|
||||
#include "../../game/cards/card_database.h"
|
||||
#include "../game_logic/key_signals.h"
|
||||
#include "tab.h"
|
||||
|
||||
#include <QAbstractItemModel>
|
||||
#include <QDir>
|
||||
|
||||
class CardDatabaseModel;
|
||||
class CardDatabaseDisplayModel;
|
||||
class DeckListModel;
|
||||
class QTreeView;
|
||||
|
||||
class CardFrame;
|
||||
class QTextEdit;
|
||||
class QLabel;
|
||||
class DeckLoader;
|
||||
class Response;
|
||||
class FilterTreeModel;
|
||||
class FilterBuilder;
|
||||
class QGroupBox;
|
||||
class QHBoxLayout;
|
||||
class QVBoxLayout;
|
||||
class QPushButton;
|
||||
class QDockWidget;
|
||||
|
||||
class SearchLineEdit : public LineEditUnfocusable
|
||||
{
|
||||
private:
|
||||
QTreeView *treeView;
|
||||
|
||||
protected:
|
||||
void keyPressEvent(QKeyEvent *event) override;
|
||||
|
||||
public:
|
||||
SearchLineEdit() : LineEditUnfocusable(), treeView(nullptr)
|
||||
{
|
||||
}
|
||||
void setTreeView(QTreeView *_treeView)
|
||||
{
|
||||
treeView = _treeView;
|
||||
}
|
||||
};
|
||||
|
||||
class TabDeckEditor : public Tab
|
||||
{
|
||||
Q_OBJECT
|
||||
private slots:
|
||||
void updateName(const QString &name);
|
||||
void updateComments();
|
||||
void updateHash();
|
||||
void updateCardInfoLeft(const QModelIndex ¤t, const QModelIndex &previous);
|
||||
void updateCardInfoRight(const QModelIndex ¤t, const QModelIndex &previous);
|
||||
void updateSearch(const QString &search);
|
||||
void databaseCustomMenu(QPoint point);
|
||||
|
||||
void actNewDeck();
|
||||
void actLoadDeck();
|
||||
bool actSaveDeck();
|
||||
bool actSaveDeckAs();
|
||||
void actLoadDeckFromClipboard();
|
||||
void actSaveDeckToClipboard();
|
||||
void actSaveDeckToClipboardRaw();
|
||||
void actPrintDeck();
|
||||
void actExportDeckDecklist();
|
||||
void actAnalyzeDeckDeckstats();
|
||||
void actAnalyzeDeckTappedout();
|
||||
|
||||
void actClearFilterAll();
|
||||
void actClearFilterOne();
|
||||
|
||||
void actSwapCard();
|
||||
void actAddCard();
|
||||
void actAddCardToSideboard();
|
||||
void actRemoveCard();
|
||||
void actIncrement();
|
||||
void actDecrement();
|
||||
void actDecrementCard();
|
||||
void actDecrementCardFromSideboard();
|
||||
void copyDatabaseCellContents();
|
||||
|
||||
void saveDeckRemoteFinished(const Response &r);
|
||||
void filterViewCustomContextMenu(const QPoint &point);
|
||||
void filterRemove(QAction *action);
|
||||
|
||||
void loadLayout();
|
||||
void restartLayout();
|
||||
void freeDocksSize();
|
||||
void refreshShortcuts();
|
||||
|
||||
bool eventFilter(QObject *o, QEvent *e) override;
|
||||
void dockVisibleTriggered();
|
||||
void dockFloatingTriggered();
|
||||
void dockTopLevelChanged(bool topLevel);
|
||||
void saveDbHeaderState();
|
||||
void setSaveStatus(bool newStatus);
|
||||
void showSearchSyntaxHelp();
|
||||
|
||||
private:
|
||||
CardInfoPtr currentCardInfo() const;
|
||||
void addCardHelper(QString zoneName);
|
||||
void offsetCountAtIndex(const QModelIndex &idx, int offset);
|
||||
void decrementCardHelper(QString zoneName);
|
||||
void recursiveExpand(const QModelIndex &index);
|
||||
|
||||
CardDatabaseModel *databaseModel;
|
||||
CardDatabaseDisplayModel *databaseDisplayModel;
|
||||
DeckListModel *deckModel;
|
||||
QTreeView *databaseView;
|
||||
|
||||
QTreeView *deckView;
|
||||
KeySignals deckViewKeySignals;
|
||||
CardFrame *cardInfo;
|
||||
SearchLineEdit *searchEdit;
|
||||
KeySignals searchKeySignals;
|
||||
|
||||
QLabel *nameLabel;
|
||||
LineEditUnfocusable *nameEdit;
|
||||
QLabel *commentsLabel;
|
||||
QTextEdit *commentsEdit;
|
||||
QLabel *hashLabel1;
|
||||
LineEditUnfocusable *hashLabel;
|
||||
FilterTreeModel *filterModel;
|
||||
QTreeView *filterView;
|
||||
KeySignals filterViewKeySignals;
|
||||
QWidget *filterBox;
|
||||
|
||||
QMenu *deckMenu, *viewMenu, *cardInfoDockMenu, *deckDockMenu, *filterDockMenu, *analyzeDeckMenu,
|
||||
*saveDeckToClipboardMenu;
|
||||
QAction *aNewDeck, *aLoadDeck, *aSaveDeck, *aSaveDeckAs, *aLoadDeckFromClipboard, *aSaveDeckToClipboard,
|
||||
*aSaveDeckToClipboardRaw, *aPrintDeck, *aExportDeckDecklist, *aAnalyzeDeckDeckstats, *aAnalyzeDeckTappedout,
|
||||
*aClose;
|
||||
QAction *aClearFilterAll, *aClearFilterOne;
|
||||
QAction *aAddCard, *aAddCardToSideboard, *aRemoveCard, *aIncrement, *aDecrement;
|
||||
QAction *aResetLayout;
|
||||
QAction *aCardInfoDockVisible, *aCardInfoDockFloating, *aDeckDockVisible, *aDeckDockFloating, *aFilterDockVisible,
|
||||
*aFilterDockFloating;
|
||||
|
||||
bool modified;
|
||||
QVBoxLayout *centralFrame;
|
||||
QHBoxLayout *searchLayout;
|
||||
QDockWidget *cardInfoDock;
|
||||
QDockWidget *deckDock;
|
||||
QDockWidget *filterDock;
|
||||
QWidget *centralWidget;
|
||||
|
||||
public:
|
||||
explicit TabDeckEditor(TabSupervisor *_tabSupervisor, QWidget *parent = nullptr);
|
||||
~TabDeckEditor() override;
|
||||
void retranslateUi() override;
|
||||
QString getTabText() const override;
|
||||
void setDeck(DeckLoader *_deckLoader);
|
||||
void setModified(bool _windowModified);
|
||||
bool confirmClose();
|
||||
void createDeckDock();
|
||||
void createCardInfoDock();
|
||||
void createFiltersDock();
|
||||
void createMenus();
|
||||
void createCentralFrame();
|
||||
|
||||
public slots:
|
||||
void closeRequest() override;
|
||||
signals:
|
||||
void deckEditorClosing(TabDeckEditor *tab);
|
||||
};
|
||||
|
||||
#endif
|
||||
414
cockatrice/src/client/tabs/tab_deck_storage.cpp
Normal file
414
cockatrice/src/client/tabs/tab_deck_storage.cpp
Normal file
|
|
@ -0,0 +1,414 @@
|
|||
#include "tab_deck_storage.h"
|
||||
|
||||
#include "../../deck/deck_loader.h"
|
||||
#include "../../server/pending_command.h"
|
||||
#include "../../server/remote/remote_decklist_tree_widget.h"
|
||||
#include "../../settings/cache_settings.h"
|
||||
#include "../game_logic/abstract_client.h"
|
||||
#include "../get_text_with_max.h"
|
||||
#include "decklist.h"
|
||||
#include "pb/command_deck_del.pb.h"
|
||||
#include "pb/command_deck_del_dir.pb.h"
|
||||
#include "pb/command_deck_download.pb.h"
|
||||
#include "pb/command_deck_new_dir.pb.h"
|
||||
#include "pb/command_deck_upload.pb.h"
|
||||
#include "pb/response.pb.h"
|
||||
#include "pb/response_deck_download.pb.h"
|
||||
#include "pb/response_deck_upload.pb.h"
|
||||
|
||||
#include <QAction>
|
||||
#include <QApplication>
|
||||
#include <QDebug>
|
||||
#include <QFileSystemModel>
|
||||
#include <QGroupBox>
|
||||
#include <QHBoxLayout>
|
||||
#include <QHeaderView>
|
||||
#include <QInputDialog>
|
||||
#include <QMessageBox>
|
||||
#include <QToolBar>
|
||||
#include <QTreeView>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
TabDeckStorage::TabDeckStorage(TabSupervisor *_tabSupervisor, AbstractClient *_client)
|
||||
: Tab(_tabSupervisor), client(_client)
|
||||
{
|
||||
localDirModel = new QFileSystemModel(this);
|
||||
localDirModel->setRootPath(SettingsCache::instance().getDeckPath());
|
||||
localDirModel->sort(0, Qt::AscendingOrder);
|
||||
|
||||
localDirView = new QTreeView;
|
||||
localDirView->setModel(localDirModel);
|
||||
localDirView->setColumnHidden(1, true);
|
||||
localDirView->setRootIndex(localDirModel->index(localDirModel->rootPath(), 0));
|
||||
localDirView->setSortingEnabled(true);
|
||||
localDirView->header()->setSectionResizeMode(QHeaderView::ResizeToContents);
|
||||
localDirView->header()->setSortIndicator(0, Qt::AscendingOrder);
|
||||
|
||||
leftToolBar = new QToolBar;
|
||||
leftToolBar->setOrientation(Qt::Horizontal);
|
||||
leftToolBar->setIconSize(QSize(32, 32));
|
||||
QHBoxLayout *leftToolBarLayout = new QHBoxLayout;
|
||||
leftToolBarLayout->addStretch();
|
||||
leftToolBarLayout->addWidget(leftToolBar);
|
||||
leftToolBarLayout->addStretch();
|
||||
|
||||
QVBoxLayout *leftVbox = new QVBoxLayout;
|
||||
leftVbox->addWidget(localDirView);
|
||||
leftVbox->addLayout(leftToolBarLayout);
|
||||
leftGroupBox = new QGroupBox;
|
||||
leftGroupBox->setLayout(leftVbox);
|
||||
|
||||
rightToolBar = new QToolBar;
|
||||
rightToolBar->setOrientation(Qt::Horizontal);
|
||||
rightToolBar->setIconSize(QSize(32, 32));
|
||||
QHBoxLayout *rightToolBarLayout = new QHBoxLayout;
|
||||
rightToolBarLayout->addStretch();
|
||||
rightToolBarLayout->addWidget(rightToolBar);
|
||||
rightToolBarLayout->addStretch();
|
||||
|
||||
serverDirView = new RemoteDeckList_TreeWidget(client);
|
||||
|
||||
QVBoxLayout *rightVbox = new QVBoxLayout;
|
||||
rightVbox->addWidget(serverDirView);
|
||||
rightVbox->addLayout(rightToolBarLayout);
|
||||
rightGroupBox = new QGroupBox;
|
||||
rightGroupBox->setLayout(rightVbox);
|
||||
|
||||
QHBoxLayout *hbox = new QHBoxLayout;
|
||||
hbox->addWidget(leftGroupBox);
|
||||
hbox->addWidget(rightGroupBox);
|
||||
|
||||
aOpenLocalDeck = new QAction(this);
|
||||
aOpenLocalDeck->setIcon(QPixmap("theme:icons/pencil"));
|
||||
connect(aOpenLocalDeck, SIGNAL(triggered()), this, SLOT(actOpenLocalDeck()));
|
||||
aUpload = new QAction(this);
|
||||
aUpload->setIcon(QPixmap("theme:icons/arrow_right_green"));
|
||||
connect(aUpload, SIGNAL(triggered()), this, SLOT(actUpload()));
|
||||
aDeleteLocalDeck = new QAction(this);
|
||||
aDeleteLocalDeck->setIcon(QPixmap("theme:icons/remove_row"));
|
||||
connect(aDeleteLocalDeck, SIGNAL(triggered()), this, SLOT(actDeleteLocalDeck()));
|
||||
aOpenRemoteDeck = new QAction(this);
|
||||
aOpenRemoteDeck->setIcon(QPixmap("theme:icons/pencil"));
|
||||
connect(aOpenRemoteDeck, SIGNAL(triggered()), this, SLOT(actOpenRemoteDeck()));
|
||||
aDownload = new QAction(this);
|
||||
aDownload->setIcon(QPixmap("theme:icons/arrow_left_green"));
|
||||
connect(aDownload, SIGNAL(triggered()), this, SLOT(actDownload()));
|
||||
aNewFolder = new QAction(this);
|
||||
aNewFolder->setIcon(qApp->style()->standardIcon(QStyle::SP_FileDialogNewFolder));
|
||||
connect(aNewFolder, SIGNAL(triggered()), this, SLOT(actNewFolder()));
|
||||
aDeleteRemoteDeck = new QAction(this);
|
||||
aDeleteRemoteDeck->setIcon(QPixmap("theme:icons/remove_row"));
|
||||
connect(aDeleteRemoteDeck, SIGNAL(triggered()), this, SLOT(actDeleteRemoteDeck()));
|
||||
|
||||
leftToolBar->addAction(aOpenLocalDeck);
|
||||
leftToolBar->addAction(aUpload);
|
||||
leftToolBar->addAction(aDeleteLocalDeck);
|
||||
rightToolBar->addAction(aOpenRemoteDeck);
|
||||
rightToolBar->addAction(aDownload);
|
||||
rightToolBar->addAction(aNewFolder);
|
||||
rightToolBar->addAction(aDeleteRemoteDeck);
|
||||
|
||||
retranslateUi();
|
||||
|
||||
QWidget *mainWidget = new QWidget(this);
|
||||
mainWidget->setLayout(hbox);
|
||||
setCentralWidget(mainWidget);
|
||||
}
|
||||
|
||||
void TabDeckStorage::retranslateUi()
|
||||
{
|
||||
leftGroupBox->setTitle(tr("Local file system"));
|
||||
rightGroupBox->setTitle(tr("Server deck storage"));
|
||||
|
||||
aOpenLocalDeck->setText(tr("Open in deck editor"));
|
||||
aUpload->setText(tr("Upload deck"));
|
||||
aOpenRemoteDeck->setText(tr("Open in deck editor"));
|
||||
aDownload->setText(tr("Download deck"));
|
||||
aNewFolder->setText(tr("New folder"));
|
||||
aDeleteLocalDeck->setText(tr("Delete"));
|
||||
aDeleteRemoteDeck->setText(tr("Delete"));
|
||||
}
|
||||
|
||||
QString TabDeckStorage::getTargetPath() const
|
||||
{
|
||||
RemoteDeckList_TreeModel::Node *curRight = serverDirView->getCurrentItem();
|
||||
if (curRight == nullptr)
|
||||
return {};
|
||||
auto *dir = dynamic_cast<RemoteDeckList_TreeModel::DirectoryNode *>(curRight);
|
||||
if (dir == nullptr) {
|
||||
dir = dynamic_cast<RemoteDeckList_TreeModel::DirectoryNode *>(curRight->getParent());
|
||||
if (dir != nullptr) {
|
||||
return dir->getPath();
|
||||
} else {
|
||||
return {};
|
||||
}
|
||||
} else {
|
||||
return dir->getPath();
|
||||
}
|
||||
}
|
||||
|
||||
void TabDeckStorage::actOpenLocalDeck()
|
||||
{
|
||||
QModelIndex curLeft = localDirView->selectionModel()->currentIndex();
|
||||
if (localDirModel->isDir(curLeft))
|
||||
return;
|
||||
QString filePath = localDirModel->filePath(curLeft);
|
||||
|
||||
DeckLoader deckLoader;
|
||||
if (!deckLoader.loadFromFile(filePath, DeckLoader::CockatriceFormat))
|
||||
return;
|
||||
|
||||
emit openDeckEditor(&deckLoader);
|
||||
}
|
||||
|
||||
void TabDeckStorage::actUpload()
|
||||
{
|
||||
QModelIndex curLeft = localDirView->selectionModel()->currentIndex();
|
||||
if (localDirModel->isDir(curLeft))
|
||||
return;
|
||||
QString targetPath = getTargetPath();
|
||||
if (targetPath.length() > MAX_NAME_LENGTH) {
|
||||
qCritical() << "target path to upload to is too long" << targetPath;
|
||||
return;
|
||||
}
|
||||
|
||||
QString filePath = localDirModel->filePath(curLeft);
|
||||
QFile deckFile(filePath);
|
||||
QFileInfo deckFileInfo(deckFile);
|
||||
|
||||
QString deckString;
|
||||
DeckLoader deck;
|
||||
bool error = !deck.loadFromFile(filePath, DeckLoader::CockatriceFormat);
|
||||
if (!error) {
|
||||
deckString = deck.writeToString_Native();
|
||||
error = deckString.length() > MAX_FILE_LENGTH;
|
||||
}
|
||||
if (error) {
|
||||
QMessageBox::critical(this, tr("Error"), tr("Invalid deck file"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (deck.getName().isEmpty()) {
|
||||
bool ok;
|
||||
QString deckName =
|
||||
getTextWithMax(this, tr("Enter deck name"), tr("This decklist does not have a name.\nPlease enter a name:"),
|
||||
QLineEdit::Normal, deckFileInfo.completeBaseName(), &ok);
|
||||
if (!ok)
|
||||
return;
|
||||
if (deckName.isEmpty())
|
||||
deckName = tr("Unnamed deck");
|
||||
deck.setName(deckName);
|
||||
} else {
|
||||
deck.setName(deck.getName().left(MAX_NAME_LENGTH));
|
||||
}
|
||||
|
||||
Command_DeckUpload cmd;
|
||||
cmd.set_path(targetPath.toStdString());
|
||||
cmd.set_deck_list(deckString.toStdString());
|
||||
|
||||
PendingCommand *pend = client->prepareSessionCommand(cmd);
|
||||
connect(pend, SIGNAL(finished(Response, CommandContainer, QVariant)), this,
|
||||
SLOT(uploadFinished(Response, CommandContainer)));
|
||||
client->sendCommand(pend);
|
||||
}
|
||||
|
||||
void TabDeckStorage::uploadFinished(const Response &r, const CommandContainer &commandContainer)
|
||||
{
|
||||
if (r.response_code() != Response::RespOk) {
|
||||
qCritical() << "failed to upload deck:" << r.response_code();
|
||||
QMessageBox::critical(this, tr("Error"), tr("Failed to upload deck to server"));
|
||||
return;
|
||||
}
|
||||
|
||||
const Response_DeckUpload &resp = r.GetExtension(Response_DeckUpload::ext);
|
||||
const Command_DeckUpload &cmd = commandContainer.session_command(0).GetExtension(Command_DeckUpload::ext);
|
||||
|
||||
serverDirView->addFileToTree(resp.new_file(), serverDirView->getNodeByPath(QString::fromStdString(cmd.path())));
|
||||
}
|
||||
|
||||
void TabDeckStorage::actDeleteLocalDeck()
|
||||
{
|
||||
QModelIndex curLeft = localDirView->selectionModel()->currentIndex();
|
||||
if (localDirModel->isDir(curLeft))
|
||||
return;
|
||||
|
||||
if (QMessageBox::warning(this, tr("Delete local file"),
|
||||
tr("Are you sure you want to delete \"%1\"?").arg(localDirModel->fileName(curLeft)),
|
||||
QMessageBox::Yes | QMessageBox::No) != QMessageBox::Yes)
|
||||
return;
|
||||
|
||||
localDirModel->remove(curLeft);
|
||||
}
|
||||
|
||||
void TabDeckStorage::actOpenRemoteDeck()
|
||||
{
|
||||
RemoteDeckList_TreeModel::FileNode *curRight =
|
||||
dynamic_cast<RemoteDeckList_TreeModel::FileNode *>(serverDirView->getCurrentItem());
|
||||
if (!curRight)
|
||||
return;
|
||||
|
||||
Command_DeckDownload cmd;
|
||||
cmd.set_deck_id(curRight->getId());
|
||||
|
||||
PendingCommand *pend = client->prepareSessionCommand(cmd);
|
||||
connect(pend, SIGNAL(finished(Response, CommandContainer, QVariant)), this,
|
||||
SLOT(openRemoteDeckFinished(Response, CommandContainer)));
|
||||
client->sendCommand(pend);
|
||||
}
|
||||
|
||||
void TabDeckStorage::openRemoteDeckFinished(const Response &r, const CommandContainer &commandContainer)
|
||||
{
|
||||
if (r.response_code() != Response::RespOk)
|
||||
return;
|
||||
|
||||
const Response_DeckDownload &resp = r.GetExtension(Response_DeckDownload::ext);
|
||||
const Command_DeckDownload &cmd = commandContainer.session_command(0).GetExtension(Command_DeckDownload::ext);
|
||||
|
||||
DeckLoader loader;
|
||||
if (!loader.loadFromRemote(QString::fromStdString(resp.deck()), cmd.deck_id()))
|
||||
return;
|
||||
|
||||
emit openDeckEditor(&loader);
|
||||
}
|
||||
|
||||
void TabDeckStorage::actDownload()
|
||||
{
|
||||
QString filePath;
|
||||
QModelIndex curLeft = localDirView->selectionModel()->currentIndex();
|
||||
if (!curLeft.isValid())
|
||||
filePath = localDirModel->rootPath();
|
||||
else {
|
||||
while (!localDirModel->isDir(curLeft))
|
||||
curLeft = curLeft.parent();
|
||||
filePath = localDirModel->filePath(curLeft);
|
||||
}
|
||||
|
||||
RemoteDeckList_TreeModel::FileNode *curRight =
|
||||
dynamic_cast<RemoteDeckList_TreeModel::FileNode *>(serverDirView->getCurrentItem());
|
||||
if (!curRight)
|
||||
return;
|
||||
filePath += QString("/deck_%1.cod").arg(curRight->getId());
|
||||
|
||||
Command_DeckDownload cmd;
|
||||
cmd.set_deck_id(curRight->getId());
|
||||
|
||||
PendingCommand *pend = client->prepareSessionCommand(cmd);
|
||||
pend->setExtraData(filePath);
|
||||
connect(pend, SIGNAL(finished(Response, CommandContainer, QVariant)), this,
|
||||
SLOT(downloadFinished(Response, CommandContainer, QVariant)));
|
||||
client->sendCommand(pend);
|
||||
}
|
||||
|
||||
void TabDeckStorage::downloadFinished(const Response &r,
|
||||
const CommandContainer & /*commandContainer*/,
|
||||
const QVariant &extraData)
|
||||
{
|
||||
if (r.response_code() != Response::RespOk)
|
||||
return;
|
||||
|
||||
const Response_DeckDownload &resp = r.GetExtension(Response_DeckDownload::ext);
|
||||
QString filePath = extraData.toString();
|
||||
|
||||
DeckLoader deck(QString::fromStdString(resp.deck()));
|
||||
deck.saveToFile(filePath, DeckLoader::CockatriceFormat);
|
||||
}
|
||||
|
||||
void TabDeckStorage::actNewFolder()
|
||||
{
|
||||
QString targetPath = getTargetPath();
|
||||
int max_length = MAX_NAME_LENGTH - targetPath.length() - 1; // generated length would be path + / + name
|
||||
|
||||
if (max_length < 1) // can't create path that's short enough
|
||||
return;
|
||||
|
||||
QString folderName = getTextWithMax(this, tr("New folder"), tr("Name of new folder:"), max_length);
|
||||
if (folderName.isEmpty())
|
||||
return;
|
||||
|
||||
// '/' isn't a valid filename character on *nix so we're choosing to replace it with a different arbitrary
|
||||
// character.
|
||||
std::string folder = folderName.toStdString();
|
||||
std::replace(folder.begin(), folder.end(), '/', '-');
|
||||
|
||||
Command_DeckNewDir cmd;
|
||||
cmd.set_path(targetPath.toStdString());
|
||||
cmd.set_dir_name(folder);
|
||||
|
||||
PendingCommand *pend = client->prepareSessionCommand(cmd);
|
||||
connect(pend, SIGNAL(finished(Response, CommandContainer, QVariant)), this,
|
||||
SLOT(newFolderFinished(Response, CommandContainer)));
|
||||
client->sendCommand(pend);
|
||||
}
|
||||
|
||||
void TabDeckStorage::newFolderFinished(const Response &response, const CommandContainer &commandContainer)
|
||||
{
|
||||
if (response.response_code() != Response::RespOk)
|
||||
return;
|
||||
|
||||
const Command_DeckNewDir &cmd = commandContainer.session_command(0).GetExtension(Command_DeckNewDir::ext);
|
||||
serverDirView->addFolderToTree(QString::fromStdString(cmd.dir_name()),
|
||||
serverDirView->getNodeByPath(QString::fromStdString(cmd.path())));
|
||||
}
|
||||
|
||||
void TabDeckStorage::actDeleteRemoteDeck()
|
||||
{
|
||||
PendingCommand *pend;
|
||||
RemoteDeckList_TreeModel::Node *curRight = serverDirView->getCurrentItem();
|
||||
if (!curRight)
|
||||
return;
|
||||
RemoteDeckList_TreeModel::DirectoryNode *dir = dynamic_cast<RemoteDeckList_TreeModel::DirectoryNode *>(curRight);
|
||||
if (dir) {
|
||||
QString targetPath = dir->getPath();
|
||||
if (targetPath.isEmpty())
|
||||
return;
|
||||
if (targetPath.length() > MAX_NAME_LENGTH) {
|
||||
qCritical() << "target path to delete is too long" << targetPath;
|
||||
return;
|
||||
}
|
||||
if (QMessageBox::warning(this, tr("Delete remote folder"),
|
||||
tr("Are you sure you want to delete \"%1\"?").arg(targetPath),
|
||||
QMessageBox::Yes | QMessageBox::No) != QMessageBox::Yes)
|
||||
return;
|
||||
Command_DeckDelDir cmd;
|
||||
cmd.set_path(targetPath.toStdString());
|
||||
pend = client->prepareSessionCommand(cmd);
|
||||
connect(pend, SIGNAL(finished(Response, CommandContainer, QVariant)), this,
|
||||
SLOT(deleteFolderFinished(Response, CommandContainer)));
|
||||
} else {
|
||||
RemoteDeckList_TreeModel::FileNode *deckNode = dynamic_cast<RemoteDeckList_TreeModel::FileNode *>(curRight);
|
||||
if (QMessageBox::warning(this, tr("Delete remote deck"),
|
||||
tr("Are you sure you want to delete \"%1\"?").arg(deckNode->getName()),
|
||||
QMessageBox::Yes | QMessageBox::No) != QMessageBox::Yes)
|
||||
return;
|
||||
|
||||
Command_DeckDel cmd;
|
||||
cmd.set_deck_id(deckNode->getId());
|
||||
pend = client->prepareSessionCommand(cmd);
|
||||
connect(pend, SIGNAL(finished(Response, CommandContainer, QVariant)), this,
|
||||
SLOT(deleteDeckFinished(Response, CommandContainer)));
|
||||
}
|
||||
|
||||
client->sendCommand(pend);
|
||||
}
|
||||
|
||||
void TabDeckStorage::deleteDeckFinished(const Response &response, const CommandContainer &commandContainer)
|
||||
{
|
||||
if (response.response_code() != Response::RespOk)
|
||||
return;
|
||||
|
||||
const Command_DeckDel &cmd = commandContainer.session_command(0).GetExtension(Command_DeckDel::ext);
|
||||
RemoteDeckList_TreeModel::Node *toDelete = serverDirView->getNodeById(cmd.deck_id());
|
||||
if (toDelete)
|
||||
serverDirView->removeNode(toDelete);
|
||||
}
|
||||
|
||||
void TabDeckStorage::deleteFolderFinished(const Response &response, const CommandContainer &commandContainer)
|
||||
{
|
||||
if (response.response_code() != Response::RespOk)
|
||||
return;
|
||||
|
||||
const Command_DeckDelDir &cmd = commandContainer.session_command(0).GetExtension(Command_DeckDelDir::ext);
|
||||
RemoteDeckList_TreeModel::Node *toDelete = serverDirView->getNodeByPath(QString::fromStdString(cmd.path()));
|
||||
if (toDelete)
|
||||
serverDirView->removeNode(toDelete);
|
||||
}
|
||||
63
cockatrice/src/client/tabs/tab_deck_storage.h
Normal file
63
cockatrice/src/client/tabs/tab_deck_storage.h
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
#ifndef TAB_DECK_STORAGE_H
|
||||
#define TAB_DECK_STORAGE_H
|
||||
|
||||
#include "tab.h"
|
||||
|
||||
class AbstractClient;
|
||||
class QTreeView;
|
||||
class QFileSystemModel;
|
||||
class QToolBar;
|
||||
class QTreeWidget;
|
||||
class QTreeWidgetItem;
|
||||
class QGroupBox;
|
||||
class RemoteDeckList_TreeWidget;
|
||||
class CommandContainer;
|
||||
class Response;
|
||||
class DeckLoader;
|
||||
|
||||
class TabDeckStorage : public Tab
|
||||
{
|
||||
Q_OBJECT
|
||||
private:
|
||||
AbstractClient *client;
|
||||
QTreeView *localDirView;
|
||||
QFileSystemModel *localDirModel;
|
||||
QToolBar *leftToolBar, *rightToolBar;
|
||||
RemoteDeckList_TreeWidget *serverDirView;
|
||||
QGroupBox *leftGroupBox, *rightGroupBox;
|
||||
|
||||
QAction *aOpenLocalDeck, *aUpload, *aDeleteLocalDeck, *aOpenRemoteDeck, *aDownload, *aNewFolder, *aDeleteRemoteDeck;
|
||||
QString getTargetPath() const;
|
||||
private slots:
|
||||
void actOpenLocalDeck();
|
||||
|
||||
void actUpload();
|
||||
void uploadFinished(const Response &r, const CommandContainer &commandContainer);
|
||||
|
||||
void actDeleteLocalDeck();
|
||||
|
||||
void actOpenRemoteDeck();
|
||||
void openRemoteDeckFinished(const Response &r, const CommandContainer &commandContainer);
|
||||
|
||||
void actDownload();
|
||||
void downloadFinished(const Response &r, const CommandContainer &commandContainer, const QVariant &extraData);
|
||||
|
||||
void actNewFolder();
|
||||
void newFolderFinished(const Response &response, const CommandContainer &commandContainer);
|
||||
|
||||
void actDeleteRemoteDeck();
|
||||
void deleteFolderFinished(const Response &response, const CommandContainer &commandContainer);
|
||||
void deleteDeckFinished(const Response &response, const CommandContainer &commandContainer);
|
||||
|
||||
public:
|
||||
TabDeckStorage(TabSupervisor *_tabSupervisor, AbstractClient *_client);
|
||||
void retranslateUi();
|
||||
QString getTabText() const
|
||||
{
|
||||
return tr("Deck storage");
|
||||
}
|
||||
signals:
|
||||
void openDeckEditor(const DeckLoader *deckLoader);
|
||||
};
|
||||
|
||||
#endif
|
||||
1989
cockatrice/src/client/tabs/tab_game.cpp
Normal file
1989
cockatrice/src/client/tabs/tab_game.cpp
Normal file
File diff suppressed because it is too large
Load diff
315
cockatrice/src/client/tabs/tab_game.h
Normal file
315
cockatrice/src/client/tabs/tab_game.h
Normal file
|
|
@ -0,0 +1,315 @@
|
|||
#ifndef TAB_GAME_H
|
||||
#define TAB_GAME_H
|
||||
|
||||
#include "../../client/tearoff_menu.h"
|
||||
#include "pb/event_leave.pb.h"
|
||||
#include "pb/serverinfo_game.pb.h"
|
||||
#include "tab.h"
|
||||
|
||||
#include <QCompleter>
|
||||
#include <QMap>
|
||||
#include <QPushButton>
|
||||
|
||||
class AbstractClient;
|
||||
class CardDatabase;
|
||||
class GameView;
|
||||
class DeckView;
|
||||
class GameScene;
|
||||
class CardFrame;
|
||||
class MessageLogWidget;
|
||||
class QTimer;
|
||||
class QSplitter;
|
||||
class QLabel;
|
||||
class QPushButton;
|
||||
class QToolButton;
|
||||
class QMenu;
|
||||
class ZoneViewLayout;
|
||||
class ZoneViewWidget;
|
||||
class PhasesToolbar;
|
||||
class PlayerListWidget;
|
||||
class ReplayTimelineWidget;
|
||||
class Response;
|
||||
class GameEventContainer;
|
||||
class GameEventContext;
|
||||
class GameCommand;
|
||||
class CommandContainer;
|
||||
class Event_GameJoined;
|
||||
class Event_GameStateChanged;
|
||||
class Event_PlayerPropertiesChanged;
|
||||
class Event_Join;
|
||||
class Event_Leave;
|
||||
class Event_GameHostChanged;
|
||||
class Event_GameClosed;
|
||||
class Event_GameStart;
|
||||
class Event_SetActivePlayer;
|
||||
class Event_SetActivePhase;
|
||||
class Event_Ping;
|
||||
class Event_GameSay;
|
||||
class Event_Kicked;
|
||||
class Event_ReverseTurn;
|
||||
class Player;
|
||||
class CardZone;
|
||||
class AbstractCardItem;
|
||||
class CardItem;
|
||||
class TabGame;
|
||||
class DeckLoader;
|
||||
class QVBoxLayout;
|
||||
class QHBoxLayout;
|
||||
class GameReplay;
|
||||
class ServerInfo_User;
|
||||
class PendingCommand;
|
||||
class LineEditCompleter;
|
||||
class QDockWidget;
|
||||
class QStackedWidget;
|
||||
|
||||
class ToggleButton : public QPushButton
|
||||
{
|
||||
Q_OBJECT
|
||||
private:
|
||||
bool state;
|
||||
signals:
|
||||
void stateChanged();
|
||||
|
||||
public:
|
||||
ToggleButton(QWidget *parent = nullptr);
|
||||
bool getState() const
|
||||
{
|
||||
return state;
|
||||
}
|
||||
void setState(bool _state);
|
||||
|
||||
protected:
|
||||
void paintEvent(QPaintEvent *event);
|
||||
};
|
||||
|
||||
class DeckViewContainer : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
private:
|
||||
QPushButton *loadLocalButton, *loadRemoteButton;
|
||||
ToggleButton *readyStartButton, *sideboardLockButton;
|
||||
DeckView *deckView;
|
||||
TabGame *parentGame;
|
||||
int playerId;
|
||||
private slots:
|
||||
void loadLocalDeck();
|
||||
void loadRemoteDeck();
|
||||
void readyStart();
|
||||
void deckSelectFinished(const Response &r);
|
||||
void sideboardPlanChanged();
|
||||
void sideboardLockButtonClicked();
|
||||
void updateSideboardLockButtonText();
|
||||
void refreshShortcuts();
|
||||
signals:
|
||||
void newCardAdded(AbstractCardItem *card);
|
||||
void notIdle();
|
||||
|
||||
public:
|
||||
DeckViewContainer(int _playerId, TabGame *parent);
|
||||
void retranslateUi();
|
||||
void setButtonsVisible(bool _visible);
|
||||
void setReadyStart(bool ready);
|
||||
void setSideboardLocked(bool locked);
|
||||
void setDeck(const DeckLoader &deck);
|
||||
};
|
||||
|
||||
class TabGame : public Tab
|
||||
{
|
||||
Q_OBJECT
|
||||
private:
|
||||
QTimer *gameTimer;
|
||||
int secondsElapsed;
|
||||
QList<AbstractClient *> clients;
|
||||
ServerInfo_Game gameInfo;
|
||||
QMap<int, QString> roomGameTypes;
|
||||
int hostId;
|
||||
int localPlayerId;
|
||||
const bool isLocalGame;
|
||||
bool spectator;
|
||||
bool judge;
|
||||
QMap<int, Player *> players;
|
||||
QMap<int, ServerInfo_User> spectators;
|
||||
bool gameStateKnown;
|
||||
bool resuming;
|
||||
QStringList phasesList;
|
||||
int currentPhase;
|
||||
int activePlayer;
|
||||
CardItem *activeCard;
|
||||
bool gameClosed;
|
||||
QStringList gameTypes;
|
||||
QCompleter *completer;
|
||||
QStringList autocompleteUserList;
|
||||
QStackedWidget *mainWidget;
|
||||
|
||||
// Replay related members
|
||||
GameReplay *replay;
|
||||
int currentReplayStep;
|
||||
QList<int> replayTimeline;
|
||||
ReplayTimelineWidget *timelineWidget;
|
||||
QToolButton *replayPlayButton, *replayFastForwardButton;
|
||||
|
||||
CardFrame *cardInfo;
|
||||
PlayerListWidget *playerListWidget;
|
||||
QLabel *timeElapsedLabel;
|
||||
MessageLogWidget *messageLog;
|
||||
QLabel *sayLabel;
|
||||
LineEditCompleter *sayEdit;
|
||||
PhasesToolbar *phasesToolbar;
|
||||
GameScene *scene;
|
||||
GameView *gameView;
|
||||
QMap<int, DeckViewContainer *> deckViewContainers;
|
||||
QVBoxLayout *cardVInfoLayout, *messageLogLayout, *gamePlayAreaVBox, *deckViewContainerLayout;
|
||||
QHBoxLayout *cardHInfoLayout, *sayHLayout, *mainHLayout, *replayControlLayout;
|
||||
QWidget *cardBoxLayoutWidget, *messageLogLayoutWidget, *gamePlayAreaWidget, *deckViewContainerWidget,
|
||||
*replayControlWidget;
|
||||
QDockWidget *cardInfoDock, *messageLayoutDock, *playerListDock, *replayDock;
|
||||
QAction *playersSeparator;
|
||||
QMenu *gameMenu, *viewMenu, *cardInfoDockMenu, *messageLayoutDockMenu, *playerListDockMenu, *replayDockMenu;
|
||||
TearOffMenu *phasesMenu;
|
||||
QAction *aGameInfo, *aConcede, *aLeaveGame, *aCloseReplay, *aNextPhase, *aNextPhaseAction, *aNextTurn,
|
||||
*aReverseTurn, *aRemoveLocalArrows, *aRotateViewCW, *aRotateViewCCW, *aResetLayout, *aResetReplayLayout;
|
||||
QAction *aCardInfoDockVisible, *aCardInfoDockFloating, *aMessageLayoutDockVisible, *aMessageLayoutDockFloating,
|
||||
*aPlayerListDockVisible, *aPlayerListDockFloating, *aReplayDockVisible, *aReplayDockFloating;
|
||||
QAction *aFocusChat;
|
||||
QList<QAction *> phaseActions;
|
||||
|
||||
Player *addPlayer(int playerId, const ServerInfo_User &info);
|
||||
|
||||
void startGame(bool resuming);
|
||||
void stopGame();
|
||||
void closeGame();
|
||||
|
||||
void eventSpectatorSay(const Event_GameSay &event, int eventPlayerId, const GameEventContext &context);
|
||||
void eventSpectatorLeave(const Event_Leave &event, int eventPlayerId, const GameEventContext &context);
|
||||
|
||||
void eventGameStateChanged(const Event_GameStateChanged &event, int eventPlayerId, const GameEventContext &context);
|
||||
void eventPlayerPropertiesChanged(const Event_PlayerPropertiesChanged &event,
|
||||
int eventPlayerId,
|
||||
const GameEventContext &context);
|
||||
void eventJoin(const Event_Join &event, int eventPlayerId, const GameEventContext &context);
|
||||
void eventLeave(const Event_Leave &event, int eventPlayerId, const GameEventContext &context);
|
||||
void eventKicked(const Event_Kicked &event, int eventPlayerId, const GameEventContext &context);
|
||||
void eventGameHostChanged(const Event_GameHostChanged &event, int eventPlayerId, const GameEventContext &context);
|
||||
void eventGameClosed(const Event_GameClosed &event, int eventPlayerId, const GameEventContext &context);
|
||||
Player *setActivePlayer(int id);
|
||||
void eventSetActivePlayer(const Event_SetActivePlayer &event, int eventPlayerId, const GameEventContext &context);
|
||||
void setActivePhase(int phase);
|
||||
void eventSetActivePhase(const Event_SetActivePhase &event, int eventPlayerId, const GameEventContext &context);
|
||||
void eventPing(const Event_Ping &event, int eventPlayerId, const GameEventContext &context);
|
||||
void eventReverseTurn(const Event_ReverseTurn &event, int eventPlayerId, const GameEventContext & /*context*/);
|
||||
void emitUserEvent();
|
||||
void createMenuItems();
|
||||
void createReplayMenuItems();
|
||||
void createViewMenuItems();
|
||||
void createCardInfoDock(bool bReplay = false);
|
||||
void createPlayerListDock(bool bReplay = false);
|
||||
void createMessageDock(bool bReplay = false);
|
||||
void createPlayAreaWidget(bool bReplay = false);
|
||||
void createDeckViewContainerWidget(bool bReplay = false);
|
||||
void createReplayDock();
|
||||
QString getLeaveReason(Event_Leave::LeaveReason reason);
|
||||
signals:
|
||||
void gameClosing(TabGame *tab);
|
||||
void playerAdded(Player *player);
|
||||
void playerRemoved(Player *player);
|
||||
void containerProcessingStarted(const GameEventContext &context);
|
||||
void containerProcessingDone();
|
||||
void openMessageDialog(const QString &userName, bool focus);
|
||||
void openDeckEditor(const DeckLoader *deck);
|
||||
void notIdle();
|
||||
private slots:
|
||||
void replayNextEvent();
|
||||
void replayFinished();
|
||||
void replayPlayButtonToggled(bool checked);
|
||||
void replayFastForwardButtonToggled(bool checked);
|
||||
|
||||
void incrementGameTime();
|
||||
void adminLockChanged(bool lock);
|
||||
void newCardAdded(AbstractCardItem *card);
|
||||
void updateCardMenu(AbstractCardItem *card);
|
||||
|
||||
void actGameInfo();
|
||||
void actConcede();
|
||||
void actLeaveGame();
|
||||
void actRemoveLocalArrows();
|
||||
void actRotateViewCW();
|
||||
void actRotateViewCCW();
|
||||
void actSay();
|
||||
void actPhaseAction();
|
||||
void actNextPhase();
|
||||
void actNextPhaseAction();
|
||||
void actNextTurn();
|
||||
void actReverseTurn();
|
||||
|
||||
void addMentionTag(const QString &value);
|
||||
void linkCardToChat(const QString &cardName);
|
||||
void commandFinished(const Response &response);
|
||||
|
||||
void refreshShortcuts();
|
||||
|
||||
void loadLayout();
|
||||
void actCompleterChanged();
|
||||
void actResetLayout();
|
||||
void freeDocksSize();
|
||||
|
||||
bool eventFilter(QObject *o, QEvent *e) override;
|
||||
void dockVisibleTriggered();
|
||||
void dockFloatingTriggered();
|
||||
void dockTopLevelChanged(bool topLevel);
|
||||
|
||||
public:
|
||||
TabGame(TabSupervisor *_tabSupervisor,
|
||||
QList<AbstractClient *> &_clients,
|
||||
const Event_GameJoined &event,
|
||||
const QMap<int, QString> &_roomGameTypes);
|
||||
TabGame(TabSupervisor *_tabSupervisor, GameReplay *replay);
|
||||
~TabGame() override;
|
||||
void retranslateUi() override;
|
||||
void updatePlayerListDockTitle();
|
||||
void closeRequest() override;
|
||||
const QMap<int, Player *> &getPlayers() const
|
||||
{
|
||||
return players;
|
||||
}
|
||||
CardItem *getCard(int playerId, const QString &zoneName, int cardId) const;
|
||||
bool isHost() const
|
||||
{
|
||||
return hostId == localPlayerId;
|
||||
}
|
||||
bool getIsLocalGame() const
|
||||
{
|
||||
return isLocalGame;
|
||||
}
|
||||
int getGameId() const
|
||||
{
|
||||
return gameInfo.game_id();
|
||||
}
|
||||
QString getTabText() const override;
|
||||
bool getSpectator() const
|
||||
{
|
||||
return spectator;
|
||||
}
|
||||
bool getSpectatorsSeeEverything() const
|
||||
{
|
||||
return gameInfo.spectators_omniscient();
|
||||
}
|
||||
bool isSpectator();
|
||||
Player *getActiveLocalPlayer() const;
|
||||
AbstractClient *getClientForPlayer(int playerId) const;
|
||||
|
||||
void setActiveCard(CardItem *card);
|
||||
CardItem *getActiveCard() const
|
||||
{
|
||||
return activeCard;
|
||||
}
|
||||
|
||||
void processGameEventContainer(const GameEventContainer &cont, AbstractClient *client);
|
||||
PendingCommand *prepareGameCommand(const ::google::protobuf::Message &cmd);
|
||||
PendingCommand *prepareGameCommand(const QList<const ::google::protobuf::Message *> &cmdList);
|
||||
public slots:
|
||||
void sendGameCommand(PendingCommand *pend, int playerId = -1);
|
||||
void sendGameCommand(const ::google::protobuf::Message &command, int playerId = -1);
|
||||
void viewCardInfo(const QString &cardName);
|
||||
};
|
||||
|
||||
#endif
|
||||
350
cockatrice/src/client/tabs/tab_logs.cpp
Normal file
350
cockatrice/src/client/tabs/tab_logs.cpp
Normal file
|
|
@ -0,0 +1,350 @@
|
|||
#include "tab_logs.h"
|
||||
|
||||
#include "../../deck/custom_line_edit.h"
|
||||
#include "../../dialogs/dlg_manage_sets.h"
|
||||
#include "../../server/pending_command.h"
|
||||
#include "../game_logic/abstract_client.h"
|
||||
#include "pb/moderator_commands.pb.h"
|
||||
#include "pb/response_viewlog_history.pb.h"
|
||||
#include "trice_limits.h"
|
||||
|
||||
#include <QCheckBox>
|
||||
#include <QDialogButtonBox>
|
||||
#include <QGroupBox>
|
||||
#include <QHBoxLayout>
|
||||
#include <QLabel>
|
||||
#include <QMessageBox>
|
||||
#include <QPushButton>
|
||||
#include <QRadioButton>
|
||||
#include <QSpinBox>
|
||||
#include <QTabWidget>
|
||||
#include <QtGui>
|
||||
#include <QtWidgets>
|
||||
|
||||
TabLog::TabLog(TabSupervisor *_tabSupervisor, AbstractClient *_client, QWidget *parent)
|
||||
: Tab(_tabSupervisor, parent), client(_client)
|
||||
{
|
||||
roomTable = new QTableWidget();
|
||||
roomTable->setColumnCount(6);
|
||||
roomTable->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
|
||||
roomTable->setHorizontalHeaderLabels(
|
||||
QString(tr("Time;SenderName;SenderIP;Message;TargetID;TargetName")).split(";"));
|
||||
|
||||
gameTable = new QTableWidget();
|
||||
gameTable->setColumnCount(6);
|
||||
gameTable->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
|
||||
gameTable->setHorizontalHeaderLabels(
|
||||
QString(tr("Time;SenderName;SenderIP;Message;TargetID;TargetName")).split(";"));
|
||||
|
||||
chatTable = new QTableWidget();
|
||||
chatTable->setColumnCount(6);
|
||||
chatTable->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
|
||||
chatTable->setHorizontalHeaderLabels(
|
||||
QString(tr("Time;SenderName;SenderIP;Message;TargetID;TargetName")).split(";"));
|
||||
|
||||
QTabWidget *tabManager = new QTabWidget();
|
||||
tabManager->addTab(roomTable, tr("Room Logs"));
|
||||
tabManager->addTab(gameTable, tr("Game Logs"));
|
||||
tabManager->addTab(chatTable, tr("Chat Logs"));
|
||||
setCentralWidget(tabManager);
|
||||
|
||||
createDock();
|
||||
restartLayout();
|
||||
clearClicked();
|
||||
retranslateUi();
|
||||
}
|
||||
|
||||
TabLog::~TabLog()
|
||||
{
|
||||
}
|
||||
|
||||
void TabLog::retranslateUi()
|
||||
{
|
||||
}
|
||||
|
||||
void TabLog::getClicked()
|
||||
{
|
||||
if (findUsername->text().isEmpty() && findIPAddress->text().isEmpty() && findGameName->text().isEmpty() &&
|
||||
findGameID->text().isEmpty() && findMessage->text().isEmpty()) {
|
||||
QMessageBox::critical(this, tr("Error"), tr("You must select at least one filter."));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!lastHour->isChecked() && !today->isChecked() && !pastDays->isChecked()) {
|
||||
pastDays->setChecked(true);
|
||||
pastXDays->setValue(20);
|
||||
}
|
||||
|
||||
if (pastDays->isChecked() && pastXDays->value() == 0) {
|
||||
QMessageBox::critical(this, tr("Error"), tr("You have to select a valid number of days to locate."));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!mainRoom->isChecked() && !gameRoom->isChecked() && !privateChat->isChecked()) {
|
||||
mainRoom->setChecked(true);
|
||||
gameRoom->setChecked(true);
|
||||
privateChat->setChecked(true);
|
||||
}
|
||||
|
||||
if (maximumResults->value() == 0)
|
||||
maximumResults->setValue(1000);
|
||||
|
||||
int dateRange = 0;
|
||||
if (lastHour->isChecked())
|
||||
dateRange = 1;
|
||||
|
||||
if (today->isChecked())
|
||||
dateRange = 24;
|
||||
|
||||
if (pastDays->isChecked())
|
||||
dateRange = pastXDays->value() * 24;
|
||||
|
||||
Command_ViewLogHistory cmd;
|
||||
cmd.set_user_name(findUsername->text().toStdString());
|
||||
cmd.set_ip_address(findIPAddress->text().toStdString());
|
||||
cmd.set_game_name(findGameName->text().toStdString());
|
||||
cmd.set_game_id(findGameID->text().toStdString());
|
||||
cmd.set_message(findMessage->text().toStdString());
|
||||
if (mainRoom->isChecked()) {
|
||||
cmd.add_log_location("room");
|
||||
};
|
||||
if (gameRoom->isChecked()) {
|
||||
cmd.add_log_location("game");
|
||||
};
|
||||
if (privateChat->isChecked()) {
|
||||
cmd.add_log_location("chat");
|
||||
};
|
||||
cmd.set_date_range(dateRange);
|
||||
cmd.set_maximum_results(maximumResults->value());
|
||||
PendingCommand *pend = client->prepareModeratorCommand(cmd);
|
||||
connect(pend, SIGNAL(finished(Response, CommandContainer, QVariant)), this,
|
||||
SLOT(viewLogHistory_processResponse(Response)));
|
||||
client->sendCommand(pend);
|
||||
}
|
||||
|
||||
void TabLog::clearClicked()
|
||||
{
|
||||
findUsername->clear();
|
||||
findIPAddress->clear();
|
||||
findGameName->clear();
|
||||
findGameID->clear();
|
||||
findMessage->clear();
|
||||
pastXDays->clear();
|
||||
maximumResults->clear();
|
||||
mainRoom->setChecked(false);
|
||||
gameRoom->setChecked(false);
|
||||
privateChat->setChecked(false);
|
||||
pastDays->setAutoExclusive(false);
|
||||
pastDays->setChecked(false);
|
||||
today->setAutoExclusive(false);
|
||||
today->setChecked(false);
|
||||
lastHour->setAutoExclusive(false);
|
||||
lastHour->setChecked(false);
|
||||
pastDays->setAutoExclusive(true);
|
||||
today->setAutoExclusive(true);
|
||||
lastHour->setAutoExclusive(true);
|
||||
}
|
||||
|
||||
void TabLog::createDock()
|
||||
{
|
||||
labelFindUserName = new QLabel(tr("Username: "));
|
||||
findUsername = new LineEditUnfocusable("");
|
||||
findUsername->setMaxLength(MAX_NAME_LENGTH);
|
||||
findUsername->setAlignment(Qt::AlignCenter);
|
||||
labelFindIPAddress = new QLabel(tr("IP Address: "));
|
||||
findIPAddress = new LineEditUnfocusable("");
|
||||
findIPAddress->setMaxLength(MAX_NAME_LENGTH);
|
||||
findIPAddress->setAlignment(Qt::AlignCenter);
|
||||
labelFindGameName = new QLabel(tr("Game Name: "));
|
||||
findGameName = new LineEditUnfocusable("");
|
||||
findGameName->setMaxLength(MAX_NAME_LENGTH);
|
||||
findGameName->setAlignment(Qt::AlignCenter);
|
||||
labelFindGameID = new QLabel(tr("GameID: "));
|
||||
findGameID = new LineEditUnfocusable("");
|
||||
findGameID->setMaxLength(MAX_NAME_LENGTH);
|
||||
findGameID->setAlignment(Qt::AlignCenter);
|
||||
labelMessage = new QLabel(tr("Message: "));
|
||||
findMessage = new LineEditUnfocusable("");
|
||||
findMessage->setMaxLength(MAX_TEXT_LENGTH);
|
||||
findMessage->setAlignment(Qt::AlignCenter);
|
||||
|
||||
mainRoom = new QCheckBox(tr("Main Room"));
|
||||
gameRoom = new QCheckBox(tr("Game Room"));
|
||||
privateChat = new QCheckBox(tr("Private Chat"));
|
||||
|
||||
pastDays = new QRadioButton(tr("Past X Days: "));
|
||||
today = new QRadioButton(tr("Today"));
|
||||
lastHour = new QRadioButton(tr("Last Hour"));
|
||||
pastXDays = new QSpinBox;
|
||||
pastXDays->setMaximum(20);
|
||||
|
||||
labelMaximum = new QLabel(tr("Maximum Results: "));
|
||||
maximumResults = new QSpinBox;
|
||||
maximumResults->setMaximum(1000);
|
||||
|
||||
labelDescription = new QLabel(tr(
|
||||
"At least one filter is required.\nThe more information you put in, the more specific your results will be."));
|
||||
|
||||
getButton = new QPushButton(tr("Get User Logs"));
|
||||
getButton->setAutoDefault(true);
|
||||
connect(getButton, SIGNAL(clicked()), this, SLOT(getClicked()));
|
||||
|
||||
clearButton = new QPushButton(tr("Clear Filters"));
|
||||
clearButton->setAutoDefault(true);
|
||||
connect(clearButton, SIGNAL(clicked()), this, SLOT(clearClicked()));
|
||||
|
||||
criteriaGrid = new QGridLayout;
|
||||
criteriaGrid->addWidget(labelFindUserName, 0, 0);
|
||||
criteriaGrid->addWidget(findUsername, 0, 1);
|
||||
criteriaGrid->addWidget(labelFindIPAddress, 1, 0);
|
||||
criteriaGrid->addWidget(findIPAddress, 1, 1);
|
||||
criteriaGrid->addWidget(labelFindGameName, 2, 0);
|
||||
criteriaGrid->addWidget(findGameName, 2, 1);
|
||||
criteriaGrid->addWidget(labelFindGameID, 3, 0);
|
||||
criteriaGrid->addWidget(findGameID, 3, 1);
|
||||
criteriaGrid->addWidget(labelMessage, 4, 0);
|
||||
criteriaGrid->addWidget(findMessage, 4, 1);
|
||||
|
||||
criteriaGroupBox = new QGroupBox(tr("Filters"));
|
||||
criteriaGroupBox->setLayout(criteriaGrid);
|
||||
criteriaGroupBox->setMaximumSize(500, 300);
|
||||
criteriaGroupBox->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
|
||||
|
||||
locationGrid = new QGridLayout;
|
||||
locationGrid->addWidget(mainRoom, 0, 0);
|
||||
locationGrid->addWidget(gameRoom, 0, 1);
|
||||
locationGrid->addWidget(privateChat, 0, 2);
|
||||
|
||||
locationGroupBox = new QGroupBox(tr("Log Locations"));
|
||||
locationGroupBox->setLayout(locationGrid);
|
||||
|
||||
rangeGrid = new QGridLayout;
|
||||
rangeGrid->addWidget(pastDays, 0, 0);
|
||||
rangeGrid->addWidget(pastXDays, 0, 1);
|
||||
rangeGrid->addWidget(today, 0, 2);
|
||||
rangeGrid->addWidget(lastHour, 0, 3);
|
||||
|
||||
rangeGroupBox = new QGroupBox(tr("Date Range"));
|
||||
rangeGroupBox->setLayout(rangeGrid);
|
||||
|
||||
maxResultsGrid = new QGridLayout;
|
||||
maxResultsGrid->addWidget(labelMaximum, 0, 0);
|
||||
maxResultsGrid->addWidget(maximumResults, 0, 1);
|
||||
|
||||
maxResultsGroupBox = new QGroupBox(tr("Maximum Results"));
|
||||
maxResultsGroupBox->setLayout(maxResultsGrid);
|
||||
|
||||
descriptionGrid = new QGridLayout;
|
||||
descriptionGrid->addWidget(labelDescription, 0, 0);
|
||||
|
||||
descriptionGroupBox = new QGroupBox(tr(""));
|
||||
descriptionGroupBox->setLayout(descriptionGrid);
|
||||
|
||||
buttonGrid = new QGridLayout;
|
||||
buttonGrid->addWidget(getButton, 0, 0);
|
||||
buttonGrid->addWidget(clearButton, 0, 1);
|
||||
|
||||
buttonGroupBox = new QGroupBox(tr(""));
|
||||
buttonGroupBox->setLayout(buttonGrid);
|
||||
|
||||
mainLayout = new QVBoxLayout(this);
|
||||
mainLayout->addWidget(criteriaGroupBox);
|
||||
mainLayout->addWidget(locationGroupBox);
|
||||
mainLayout->addWidget(rangeGroupBox);
|
||||
mainLayout->addWidget(maxResultsGroupBox);
|
||||
mainLayout->addWidget(descriptionGroupBox);
|
||||
mainLayout->addWidget(buttonGroupBox);
|
||||
mainLayout->setAlignment(Qt::AlignCenter);
|
||||
|
||||
searchDockContents = new QWidget(this);
|
||||
searchDockContents->setLayout(mainLayout);
|
||||
|
||||
searchDock = new QDockWidget(this);
|
||||
searchDock->setFeatures(QDockWidget::DockWidgetFloatable | QDockWidget::DockWidgetMovable);
|
||||
searchDock->setWidget(searchDockContents);
|
||||
}
|
||||
|
||||
void TabLog::viewLogHistory_processResponse(const Response &resp)
|
||||
{
|
||||
const Response_ViewLogHistory &response = resp.GetExtension(Response_ViewLogHistory::ext);
|
||||
if (resp.response_code() != Response::RespOk) {
|
||||
QMessageBox::critical(static_cast<QWidget *>(parent()), tr("Message History"),
|
||||
tr("Failed to collect message history information."));
|
||||
return;
|
||||
}
|
||||
|
||||
if (response.log_message_size() == 0) {
|
||||
QMessageBox::information(static_cast<QWidget *>(parent()), tr("Message History"),
|
||||
tr("There are no messages for the selected filters."));
|
||||
return;
|
||||
}
|
||||
|
||||
int roomCounter = 0, gameCounter = 0, chatCounter = 0;
|
||||
roomTable->setRowCount(roomCounter);
|
||||
gameTable->setRowCount(gameCounter);
|
||||
chatTable->setRowCount(chatCounter);
|
||||
|
||||
for (int i = 0; i < response.log_message_size(); ++i) {
|
||||
ServerInfo_ChatMessage message = response.log_message(i);
|
||||
if (QString::fromStdString(message.target_type()) == "room") {
|
||||
roomTable->insertRow(roomCounter);
|
||||
roomTable->setItem(roomCounter, 0, new QTableWidgetItem(QString::fromStdString(message.time())));
|
||||
roomTable->setItem(roomCounter, 1, new QTableWidgetItem(QString::fromStdString(message.sender_name())));
|
||||
roomTable->setItem(roomCounter, 2, new QTableWidgetItem(QString::fromStdString(message.sender_ip())));
|
||||
roomTable->setItem(roomCounter, 3, new QTableWidgetItem(QString::fromStdString(message.message())));
|
||||
roomTable->setItem(roomCounter, 4, new QTableWidgetItem(QString::fromStdString(message.target_id())));
|
||||
roomTable->setItem(roomCounter, 5, new QTableWidgetItem(QString::fromStdString(message.target_name())));
|
||||
++roomCounter;
|
||||
}
|
||||
|
||||
if (QString::fromStdString(message.target_type()) == "game") {
|
||||
gameTable->insertRow(gameCounter);
|
||||
gameTable->setItem(gameCounter, 0, new QTableWidgetItem(QString::fromStdString(message.time())));
|
||||
gameTable->setItem(gameCounter, 1, new QTableWidgetItem(QString::fromStdString(message.sender_name())));
|
||||
gameTable->setItem(gameCounter, 2, new QTableWidgetItem(QString::fromStdString(message.sender_ip())));
|
||||
gameTable->setItem(gameCounter, 3, new QTableWidgetItem(QString::fromStdString(message.message())));
|
||||
gameTable->setItem(gameCounter, 4, new QTableWidgetItem(QString::fromStdString(message.target_id())));
|
||||
gameTable->setItem(gameCounter, 5, new QTableWidgetItem(QString::fromStdString(message.target_name())));
|
||||
++gameCounter;
|
||||
}
|
||||
|
||||
if (QString::fromStdString(message.target_type()) == "chat") {
|
||||
chatTable->insertRow(chatCounter);
|
||||
chatTable->setItem(chatCounter, 0, new QTableWidgetItem(QString::fromStdString(message.time())));
|
||||
chatTable->setItem(chatCounter, 1, new QTableWidgetItem(QString::fromStdString(message.sender_name())));
|
||||
chatTable->setItem(chatCounter, 2, new QTableWidgetItem(QString::fromStdString(message.sender_ip())));
|
||||
chatTable->setItem(chatCounter, 3, new QTableWidgetItem(QString::fromStdString(message.message())));
|
||||
chatTable->setItem(chatCounter, 4, new QTableWidgetItem(QString::fromStdString(message.target_id())));
|
||||
chatTable->setItem(chatCounter, 5, new QTableWidgetItem(QString::fromStdString(message.target_name())));
|
||||
++chatCounter;
|
||||
}
|
||||
}
|
||||
|
||||
if (roomCounter) {
|
||||
roomTable->show();
|
||||
roomTable->resizeColumnsToContents();
|
||||
} else {
|
||||
roomTable->hide();
|
||||
}
|
||||
|
||||
if (gameCounter) {
|
||||
gameTable->resizeColumnsToContents();
|
||||
gameTable->show();
|
||||
} else {
|
||||
gameTable->hide();
|
||||
}
|
||||
|
||||
if (chatCounter) {
|
||||
chatTable->resizeColumnsToContents();
|
||||
chatTable->show();
|
||||
} else {
|
||||
chatTable->hide();
|
||||
}
|
||||
}
|
||||
|
||||
void TabLog::restartLayout()
|
||||
{
|
||||
searchDock->setFloating(false);
|
||||
addDockWidget(Qt::LeftDockWidgetArea, searchDock);
|
||||
searchDock->setVisible(true);
|
||||
}
|
||||
65
cockatrice/src/client/tabs/tab_logs.h
Normal file
65
cockatrice/src/client/tabs/tab_logs.h
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
#ifndef TAB_LOG_H
|
||||
#define TAB_LOG_H
|
||||
|
||||
#include "tab.h"
|
||||
|
||||
#include <QDialog>
|
||||
|
||||
class AbstractClient;
|
||||
class LineEditUnfocusable;
|
||||
|
||||
class QGroupBox;
|
||||
class QPushButton;
|
||||
class QSpinBox;
|
||||
class QCheckBox;
|
||||
class QRadioButton;
|
||||
class QLabel;
|
||||
class QDockWidget;
|
||||
class QWidget;
|
||||
class QGridLayout;
|
||||
class QVBoxLayout;
|
||||
class QTableWidget;
|
||||
class CommandContainer;
|
||||
class Response;
|
||||
class AbstractClient;
|
||||
|
||||
class TabLog : public Tab
|
||||
{
|
||||
Q_OBJECT
|
||||
private:
|
||||
AbstractClient *client;
|
||||
QLabel *labelFindUserName, *labelFindIPAddress, *labelFindGameName, *labelFindGameID, *labelMessage, *labelMaximum,
|
||||
*labelDescription;
|
||||
LineEditUnfocusable *findUsername, *findIPAddress, *findGameName, *findGameID, *findMessage;
|
||||
QCheckBox *mainRoom, *gameRoom, *privateChat;
|
||||
QRadioButton *pastDays, *today, *lastHour;
|
||||
QSpinBox *maximumResults, *pastXDays;
|
||||
QDockWidget *searchDock;
|
||||
QWidget *searchDockContents;
|
||||
QPushButton *getButton, *clearButton;
|
||||
QGridLayout *criteriaGrid, *locationGrid, *rangeGrid, *maxResultsGrid, *descriptionGrid, *buttonGrid;
|
||||
QGroupBox *criteriaGroupBox, *locationGroupBox, *rangeGroupBox, *maxResultsGroupBox, *descriptionGroupBox,
|
||||
*buttonGroupBox;
|
||||
QVBoxLayout *mainLayout;
|
||||
QTableWidget *roomTable, *gameTable, *chatTable;
|
||||
|
||||
void createDock();
|
||||
signals:
|
||||
|
||||
private slots:
|
||||
void getClicked();
|
||||
void clearClicked();
|
||||
void viewLogHistory_processResponse(const Response &resp);
|
||||
void restartLayout();
|
||||
|
||||
public:
|
||||
TabLog(TabSupervisor *_tabSupervisor, AbstractClient *_client, QWidget *parent = nullptr);
|
||||
~TabLog();
|
||||
void retranslateUi();
|
||||
QString getTabText() const
|
||||
{
|
||||
return tr("Logs");
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
||||
177
cockatrice/src/client/tabs/tab_message.cpp
Normal file
177
cockatrice/src/client/tabs/tab_message.cpp
Normal file
|
|
@ -0,0 +1,177 @@
|
|||
#include "tab_message.h"
|
||||
|
||||
#include "../../deck/custom_line_edit.h"
|
||||
#include "../../main.h"
|
||||
#include "../../server/chat_view/chat_view.h"
|
||||
#include "../../server/pending_command.h"
|
||||
#include "../../settings/cache_settings.h"
|
||||
#include "../game_logic/abstract_client.h"
|
||||
#include "../sound_engine.h"
|
||||
#include "pb/event_user_message.pb.h"
|
||||
#include "pb/serverinfo_user.pb.h"
|
||||
#include "pb/session_commands.pb.h"
|
||||
#include "trice_limits.h"
|
||||
|
||||
#include <QApplication>
|
||||
#include <QDebug>
|
||||
#include <QMenu>
|
||||
#include <QSystemTrayIcon>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
TabMessage::TabMessage(TabSupervisor *_tabSupervisor,
|
||||
AbstractClient *_client,
|
||||
const ServerInfo_User &_ownUserInfo,
|
||||
const ServerInfo_User &_otherUserInfo)
|
||||
: Tab(_tabSupervisor), client(_client), ownUserInfo(new ServerInfo_User(_ownUserInfo)),
|
||||
otherUserInfo(new ServerInfo_User(_otherUserInfo)), userOnline(true)
|
||||
{
|
||||
chatView = new ChatView(tabSupervisor, tabSupervisor, 0, true);
|
||||
connect(chatView, SIGNAL(showCardInfoPopup(QPoint, QString)), this, SLOT(showCardInfoPopup(QPoint, QString)));
|
||||
connect(chatView, SIGNAL(deleteCardInfoPopup(QString)), this, SLOT(deleteCardInfoPopup(QString)));
|
||||
connect(chatView, SIGNAL(addMentionTag(QString)), this, SLOT(addMentionTag(QString)));
|
||||
sayEdit = new LineEditUnfocusable;
|
||||
sayEdit->setMaxLength(MAX_TEXT_LENGTH);
|
||||
connect(sayEdit, SIGNAL(returnPressed()), this, SLOT(sendMessage()));
|
||||
|
||||
QVBoxLayout *vbox = new QVBoxLayout;
|
||||
vbox->addWidget(chatView);
|
||||
vbox->addWidget(sayEdit);
|
||||
|
||||
aLeave = new QAction(this);
|
||||
connect(aLeave, SIGNAL(triggered()), this, SLOT(actLeave()));
|
||||
|
||||
messageMenu = new QMenu(this);
|
||||
messageMenu->addAction(aLeave);
|
||||
addTabMenu(messageMenu);
|
||||
|
||||
retranslateUi();
|
||||
|
||||
QWidget *mainWidget = new QWidget(this);
|
||||
mainWidget->setLayout(vbox);
|
||||
setCentralWidget(mainWidget);
|
||||
}
|
||||
|
||||
TabMessage::~TabMessage()
|
||||
{
|
||||
emit talkClosing(this);
|
||||
delete ownUserInfo;
|
||||
delete otherUserInfo;
|
||||
}
|
||||
|
||||
void TabMessage::addMentionTag(QString mentionTag)
|
||||
{
|
||||
sayEdit->insert(mentionTag + " ");
|
||||
sayEdit->setFocus();
|
||||
}
|
||||
|
||||
void TabMessage::retranslateUi()
|
||||
{
|
||||
messageMenu->setTitle(tr("Private &chat"));
|
||||
aLeave->setText(tr("&Leave"));
|
||||
}
|
||||
|
||||
void TabMessage::tabActivated()
|
||||
{
|
||||
if (!sayEdit->hasFocus())
|
||||
sayEdit->setFocus();
|
||||
}
|
||||
|
||||
QString TabMessage::getUserName() const
|
||||
{
|
||||
return QString::fromStdString(otherUserInfo->name());
|
||||
}
|
||||
|
||||
QString TabMessage::getTabText() const
|
||||
{
|
||||
return tr("%1 - Private chat").arg(QString::fromStdString(otherUserInfo->name()));
|
||||
}
|
||||
|
||||
void TabMessage::closeRequest()
|
||||
{
|
||||
actLeave();
|
||||
}
|
||||
|
||||
void TabMessage::sendMessage()
|
||||
{
|
||||
if (sayEdit->text().isEmpty() || !userOnline)
|
||||
return;
|
||||
|
||||
Command_Message cmd;
|
||||
cmd.set_user_name(otherUserInfo->name());
|
||||
cmd.set_message(sayEdit->text().toStdString());
|
||||
|
||||
PendingCommand *pend = client->prepareSessionCommand(cmd);
|
||||
connect(pend, SIGNAL(finished(Response, CommandContainer, QVariant)), this, SLOT(messageSent(const Response &)));
|
||||
client->sendCommand(pend);
|
||||
|
||||
sayEdit->clear();
|
||||
}
|
||||
|
||||
void TabMessage::messageSent(const Response &response)
|
||||
{
|
||||
if (response.response_code() == Response::RespInIgnoreList)
|
||||
chatView->appendMessage(tr(
|
||||
"This user is ignoring you, they cannot see your messages in main chat and you cannot join their games."));
|
||||
}
|
||||
|
||||
void TabMessage::actLeave()
|
||||
{
|
||||
deleteLater();
|
||||
}
|
||||
|
||||
void TabMessage::processUserMessageEvent(const Event_UserMessage &event)
|
||||
{
|
||||
auto userInfo = event.sender_name() == otherUserInfo->name() ? otherUserInfo : ownUserInfo;
|
||||
const UserLevelFlags userLevel(userInfo->user_level());
|
||||
const QString userPriv = QString::fromStdString(userInfo->privlevel());
|
||||
|
||||
chatView->appendMessage(QString::fromStdString(event.message()), {}, QString::fromStdString(event.sender_name()),
|
||||
userLevel, userPriv, true);
|
||||
if (tabSupervisor->currentIndex() != tabSupervisor->indexOf(this))
|
||||
soundEngine->playSound("private_message");
|
||||
if (SettingsCache::instance().getShowMessagePopup() && shouldShowSystemPopup(event))
|
||||
showSystemPopup(event);
|
||||
if (QString::fromStdString(event.sender_name()).toLower().simplified() == "servatrice")
|
||||
sayEdit->setDisabled(true);
|
||||
|
||||
emit userEvent();
|
||||
}
|
||||
|
||||
bool TabMessage::shouldShowSystemPopup(const Event_UserMessage &event)
|
||||
{
|
||||
return (QApplication::activeWindow() == 0 || QApplication::focusWidget() == 0 ||
|
||||
(event.sender_name() == otherUserInfo->name() &&
|
||||
tabSupervisor->currentIndex() != tabSupervisor->indexOf(this)));
|
||||
}
|
||||
|
||||
void TabMessage::showSystemPopup(const Event_UserMessage &event)
|
||||
{
|
||||
if (trayIcon) {
|
||||
disconnect(trayIcon, SIGNAL(messageClicked()), 0, 0);
|
||||
trayIcon->showMessage(tr("Private message from") + " " + otherUserInfo->name().c_str(),
|
||||
event.message().c_str());
|
||||
connect(trayIcon, SIGNAL(messageClicked()), this, SLOT(messageClicked()));
|
||||
} else {
|
||||
qDebug() << "Error: trayIcon is NULL. TabMessage::showSystemPopup failed";
|
||||
}
|
||||
}
|
||||
|
||||
void TabMessage::messageClicked()
|
||||
{
|
||||
tabSupervisor->setCurrentIndex(tabSupervisor->indexOf(this));
|
||||
activateWindow();
|
||||
emit maximizeClient();
|
||||
}
|
||||
|
||||
void TabMessage::processUserLeft()
|
||||
{
|
||||
chatView->appendMessage(tr("%1 has left the server.").arg(QString::fromStdString(otherUserInfo->name())));
|
||||
userOnline = false;
|
||||
}
|
||||
|
||||
void TabMessage::processUserJoined(const ServerInfo_User &_userInfo)
|
||||
{
|
||||
chatView->appendMessage(tr("%1 has joined the server.").arg(QString::fromStdString(otherUserInfo->name())));
|
||||
userOnline = true;
|
||||
*otherUserInfo = _userInfo;
|
||||
}
|
||||
59
cockatrice/src/client/tabs/tab_message.h
Normal file
59
cockatrice/src/client/tabs/tab_message.h
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
#ifndef TAB_MESSAGE_H
|
||||
#define TAB_MESSAGE_H
|
||||
|
||||
#include "tab.h"
|
||||
|
||||
class AbstractClient;
|
||||
class ChatView;
|
||||
class LineEditUnfocusable;
|
||||
class Event_UserMessage;
|
||||
class Response;
|
||||
class ServerInfo_User;
|
||||
|
||||
class TabMessage : public Tab
|
||||
{
|
||||
Q_OBJECT
|
||||
private:
|
||||
AbstractClient *client;
|
||||
QMenu *messageMenu;
|
||||
ServerInfo_User *ownUserInfo;
|
||||
ServerInfo_User *otherUserInfo;
|
||||
bool userOnline;
|
||||
|
||||
ChatView *chatView;
|
||||
LineEditUnfocusable *sayEdit;
|
||||
|
||||
QAction *aLeave;
|
||||
signals:
|
||||
void talkClosing(TabMessage *tab);
|
||||
void maximizeClient();
|
||||
private slots:
|
||||
void sendMessage();
|
||||
void actLeave();
|
||||
void messageSent(const Response &response);
|
||||
void addMentionTag(QString mentionTag);
|
||||
void messageClicked();
|
||||
|
||||
public:
|
||||
TabMessage(TabSupervisor *_tabSupervisor,
|
||||
AbstractClient *_client,
|
||||
const ServerInfo_User &_ownUserInfo,
|
||||
const ServerInfo_User &_otherUserInfo);
|
||||
~TabMessage();
|
||||
void retranslateUi();
|
||||
void closeRequest();
|
||||
void tabActivated();
|
||||
QString getUserName() const;
|
||||
QString getTabText() const;
|
||||
|
||||
void processUserMessageEvent(const Event_UserMessage &event);
|
||||
|
||||
void processUserLeft();
|
||||
void processUserJoined(const ServerInfo_User &_userInfo);
|
||||
|
||||
private:
|
||||
bool shouldShowSystemPopup(const Event_UserMessage &event);
|
||||
void showSystemPopup(const Event_UserMessage &event);
|
||||
};
|
||||
|
||||
#endif
|
||||
298
cockatrice/src/client/tabs/tab_replays.cpp
Normal file
298
cockatrice/src/client/tabs/tab_replays.cpp
Normal file
|
|
@ -0,0 +1,298 @@
|
|||
#include "tab_replays.h"
|
||||
|
||||
#include "../../server/pending_command.h"
|
||||
#include "../../server/remote/remote_replay_list_tree_widget.h"
|
||||
#include "../../settings/cache_settings.h"
|
||||
#include "../game_logic/abstract_client.h"
|
||||
#include "pb/command_replay_delete_match.pb.h"
|
||||
#include "pb/command_replay_download.pb.h"
|
||||
#include "pb/command_replay_modify_match.pb.h"
|
||||
#include "pb/event_replay_added.pb.h"
|
||||
#include "pb/game_replay.pb.h"
|
||||
#include "pb/response.pb.h"
|
||||
#include "pb/response_replay_download.pb.h"
|
||||
#include "tab_game.h"
|
||||
|
||||
#include <QAction>
|
||||
#include <QApplication>
|
||||
#include <QFileSystemModel>
|
||||
#include <QGroupBox>
|
||||
#include <QHBoxLayout>
|
||||
#include <QHeaderView>
|
||||
#include <QInputDialog>
|
||||
#include <QMessageBox>
|
||||
#include <QToolBar>
|
||||
#include <QTreeView>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
TabReplays::TabReplays(TabSupervisor *_tabSupervisor, AbstractClient *_client) : Tab(_tabSupervisor), client(_client)
|
||||
{
|
||||
localDirModel = new QFileSystemModel(this);
|
||||
localDirModel->setRootPath(SettingsCache::instance().getReplaysPath());
|
||||
localDirModel->sort(0, Qt::AscendingOrder);
|
||||
|
||||
localDirView = new QTreeView;
|
||||
localDirView->setModel(localDirModel);
|
||||
localDirView->setColumnHidden(1, true);
|
||||
localDirView->setRootIndex(localDirModel->index(localDirModel->rootPath(), 0));
|
||||
localDirView->setSortingEnabled(true);
|
||||
localDirView->header()->setSectionResizeMode(QHeaderView::ResizeToContents);
|
||||
localDirView->header()->setSortIndicator(0, Qt::AscendingOrder);
|
||||
|
||||
leftToolBar = new QToolBar;
|
||||
leftToolBar->setOrientation(Qt::Horizontal);
|
||||
leftToolBar->setIconSize(QSize(32, 32));
|
||||
QHBoxLayout *leftToolBarLayout = new QHBoxLayout;
|
||||
leftToolBarLayout->addStretch();
|
||||
leftToolBarLayout->addWidget(leftToolBar);
|
||||
leftToolBarLayout->addStretch();
|
||||
|
||||
QVBoxLayout *leftVbox = new QVBoxLayout;
|
||||
leftVbox->addWidget(localDirView);
|
||||
leftVbox->addLayout(leftToolBarLayout);
|
||||
leftGroupBox = new QGroupBox;
|
||||
leftGroupBox->setLayout(leftVbox);
|
||||
|
||||
rightToolBar = new QToolBar;
|
||||
rightToolBar->setOrientation(Qt::Horizontal);
|
||||
rightToolBar->setIconSize(QSize(32, 32));
|
||||
QHBoxLayout *rightToolBarLayout = new QHBoxLayout;
|
||||
rightToolBarLayout->addStretch();
|
||||
rightToolBarLayout->addWidget(rightToolBar);
|
||||
rightToolBarLayout->addStretch();
|
||||
|
||||
serverDirView = new RemoteReplayList_TreeWidget(client);
|
||||
|
||||
QVBoxLayout *rightVbox = new QVBoxLayout;
|
||||
rightVbox->addWidget(serverDirView);
|
||||
rightVbox->addLayout(rightToolBarLayout);
|
||||
rightGroupBox = new QGroupBox;
|
||||
rightGroupBox->setLayout(rightVbox);
|
||||
|
||||
QHBoxLayout *hbox = new QHBoxLayout;
|
||||
hbox->addWidget(leftGroupBox);
|
||||
hbox->addWidget(rightGroupBox);
|
||||
|
||||
aOpenLocalReplay = new QAction(this);
|
||||
aOpenLocalReplay->setIcon(QPixmap("theme:icons/view"));
|
||||
connect(aOpenLocalReplay, SIGNAL(triggered()), this, SLOT(actOpenLocalReplay()));
|
||||
connect(localDirView, SIGNAL(doubleClicked(const QModelIndex &)), this, SLOT(actOpenLocalReplay()));
|
||||
aDeleteLocalReplay = new QAction(this);
|
||||
aDeleteLocalReplay->setIcon(QPixmap("theme:icons/remove_row"));
|
||||
connect(aDeleteLocalReplay, SIGNAL(triggered()), this, SLOT(actDeleteLocalReplay()));
|
||||
aOpenRemoteReplay = new QAction(this);
|
||||
aOpenRemoteReplay->setIcon(QPixmap("theme:icons/view"));
|
||||
connect(aOpenRemoteReplay, SIGNAL(triggered()), this, SLOT(actOpenRemoteReplay()));
|
||||
connect(serverDirView, SIGNAL(doubleClicked(const QModelIndex &)), this, SLOT(actOpenRemoteReplay()));
|
||||
aDownload = new QAction(this);
|
||||
aDownload->setIcon(QPixmap("theme:icons/arrow_left_green"));
|
||||
connect(aDownload, SIGNAL(triggered()), this, SLOT(actDownload()));
|
||||
aKeep = new QAction(this);
|
||||
aKeep->setIcon(QPixmap("theme:icons/lock"));
|
||||
connect(aKeep, SIGNAL(triggered()), this, SLOT(actKeepRemoteReplay()));
|
||||
aDeleteRemoteReplay = new QAction(this);
|
||||
aDeleteRemoteReplay->setIcon(QPixmap("theme:icons/remove_row"));
|
||||
connect(aDeleteRemoteReplay, SIGNAL(triggered()), this, SLOT(actDeleteRemoteReplay()));
|
||||
|
||||
leftToolBar->addAction(aOpenLocalReplay);
|
||||
leftToolBar->addAction(aDeleteLocalReplay);
|
||||
rightToolBar->addAction(aOpenRemoteReplay);
|
||||
rightToolBar->addAction(aDownload);
|
||||
rightToolBar->addAction(aKeep);
|
||||
rightToolBar->addAction(aDeleteRemoteReplay);
|
||||
|
||||
retranslateUi();
|
||||
|
||||
QWidget *mainWidget = new QWidget(this);
|
||||
mainWidget->setLayout(hbox);
|
||||
setCentralWidget(mainWidget);
|
||||
|
||||
connect(client, SIGNAL(replayAddedEventReceived(const Event_ReplayAdded &)), this,
|
||||
SLOT(replayAddedEventReceived(const Event_ReplayAdded &)));
|
||||
}
|
||||
|
||||
void TabReplays::retranslateUi()
|
||||
{
|
||||
leftGroupBox->setTitle(tr("Local file system"));
|
||||
rightGroupBox->setTitle(tr("Server replay storage"));
|
||||
|
||||
aOpenLocalReplay->setText(tr("Watch replay"));
|
||||
aDeleteLocalReplay->setText(tr("Delete"));
|
||||
aOpenRemoteReplay->setText(tr("Watch replay"));
|
||||
aDownload->setText(tr("Download replay"));
|
||||
aKeep->setText(tr("Toggle expiration lock"));
|
||||
aDeleteRemoteReplay->setText(tr("Delete"));
|
||||
}
|
||||
|
||||
void TabReplays::actOpenLocalReplay()
|
||||
{
|
||||
QModelIndex curLeft = localDirView->selectionModel()->currentIndex();
|
||||
if (localDirModel->isDir(curLeft))
|
||||
return;
|
||||
QString filePath = localDirModel->filePath(curLeft);
|
||||
|
||||
QFile f(filePath);
|
||||
if (!f.open(QIODevice::ReadOnly))
|
||||
return;
|
||||
QByteArray _data = f.readAll();
|
||||
f.close();
|
||||
|
||||
GameReplay *replay = new GameReplay;
|
||||
replay->ParseFromArray(_data.data(), _data.size());
|
||||
|
||||
emit openReplay(replay);
|
||||
}
|
||||
|
||||
void TabReplays::actDeleteLocalReplay()
|
||||
{
|
||||
QModelIndex curLeft = localDirView->selectionModel()->currentIndex();
|
||||
if (!curLeft.isValid())
|
||||
return;
|
||||
|
||||
if (QMessageBox::warning(this, tr("Delete local file"),
|
||||
tr("Are you sure you want to delete \"%1\"?").arg(localDirModel->fileName(curLeft)),
|
||||
QMessageBox::Yes | QMessageBox::No) != QMessageBox::Yes)
|
||||
return;
|
||||
|
||||
localDirModel->remove(curLeft);
|
||||
}
|
||||
|
||||
void TabReplays::actOpenRemoteReplay()
|
||||
{
|
||||
ServerInfo_Replay const *curRight = serverDirView->getCurrentReplay();
|
||||
if (!curRight)
|
||||
return;
|
||||
|
||||
Command_ReplayDownload cmd;
|
||||
cmd.set_replay_id(curRight->replay_id());
|
||||
|
||||
PendingCommand *pend = client->prepareSessionCommand(cmd);
|
||||
connect(pend, SIGNAL(finished(Response, CommandContainer, QVariant)), this,
|
||||
SLOT(openRemoteReplayFinished(const Response &)));
|
||||
client->sendCommand(pend);
|
||||
}
|
||||
|
||||
void TabReplays::openRemoteReplayFinished(const Response &r)
|
||||
{
|
||||
if (r.response_code() != Response::RespOk)
|
||||
return;
|
||||
|
||||
const Response_ReplayDownload &resp = r.GetExtension(Response_ReplayDownload::ext);
|
||||
GameReplay *replay = new GameReplay;
|
||||
replay->ParseFromString(resp.replay_data());
|
||||
|
||||
emit openReplay(replay);
|
||||
}
|
||||
|
||||
void TabReplays::actDownload()
|
||||
{
|
||||
QString filePath;
|
||||
QModelIndex curLeft = localDirView->selectionModel()->currentIndex();
|
||||
if (!curLeft.isValid())
|
||||
filePath = localDirModel->rootPath();
|
||||
else {
|
||||
while (!localDirModel->isDir(curLeft))
|
||||
curLeft = curLeft.parent();
|
||||
filePath = localDirModel->filePath(curLeft);
|
||||
}
|
||||
|
||||
ServerInfo_Replay const *curRight = serverDirView->getCurrentReplay();
|
||||
|
||||
if (!curRight) {
|
||||
QMessageBox::information(this, tr("Downloading Replays"),
|
||||
tr("Folder download is not yet supported. Please download replays individually."));
|
||||
return;
|
||||
}
|
||||
|
||||
filePath += QString("/replay_%1.cor").arg(curRight->replay_id());
|
||||
|
||||
Command_ReplayDownload cmd;
|
||||
cmd.set_replay_id(curRight->replay_id());
|
||||
|
||||
PendingCommand *pend = client->prepareSessionCommand(cmd);
|
||||
pend->setExtraData(filePath);
|
||||
connect(pend, SIGNAL(finished(Response, CommandContainer, QVariant)), this,
|
||||
SLOT(downloadFinished(Response, CommandContainer, QVariant)));
|
||||
client->sendCommand(pend);
|
||||
}
|
||||
|
||||
void TabReplays::downloadFinished(const Response &r,
|
||||
const CommandContainer & /* commandContainer */,
|
||||
const QVariant &extraData)
|
||||
{
|
||||
if (r.response_code() != Response::RespOk)
|
||||
return;
|
||||
|
||||
const Response_ReplayDownload &resp = r.GetExtension(Response_ReplayDownload::ext);
|
||||
QString filePath = extraData.toString();
|
||||
|
||||
const std::string &_data = resp.replay_data();
|
||||
QFile f(filePath);
|
||||
f.open(QIODevice::WriteOnly);
|
||||
f.write((const char *)_data.data(), _data.size());
|
||||
f.close();
|
||||
}
|
||||
|
||||
void TabReplays::actKeepRemoteReplay()
|
||||
{
|
||||
ServerInfo_ReplayMatch const *curRight = serverDirView->getCurrentReplayMatch();
|
||||
if (!curRight)
|
||||
return;
|
||||
|
||||
Command_ReplayModifyMatch cmd;
|
||||
cmd.set_game_id(curRight->game_id());
|
||||
cmd.set_do_not_hide(!curRight->do_not_hide());
|
||||
|
||||
PendingCommand *pend = client->prepareSessionCommand(cmd);
|
||||
connect(pend, SIGNAL(finished(Response, CommandContainer, QVariant)), this,
|
||||
SLOT(keepRemoteReplayFinished(Response, CommandContainer)));
|
||||
client->sendCommand(pend);
|
||||
}
|
||||
|
||||
void TabReplays::keepRemoteReplayFinished(const Response &r, const CommandContainer &commandContainer)
|
||||
{
|
||||
if (r.response_code() != Response::RespOk)
|
||||
return;
|
||||
|
||||
const Command_ReplayModifyMatch &cmd =
|
||||
commandContainer.session_command(0).GetExtension(Command_ReplayModifyMatch::ext);
|
||||
|
||||
ServerInfo_ReplayMatch temp;
|
||||
temp.set_do_not_hide(cmd.do_not_hide());
|
||||
|
||||
serverDirView->updateMatchInfo(cmd.game_id(), temp);
|
||||
}
|
||||
|
||||
void TabReplays::actDeleteRemoteReplay()
|
||||
{
|
||||
ServerInfo_ReplayMatch const *curRight = serverDirView->getCurrentReplayMatch();
|
||||
if (!curRight)
|
||||
return;
|
||||
if (QMessageBox::warning(this, tr("Delete remote replay"),
|
||||
tr("Are you sure you want to delete the replay of game %1?").arg(curRight->game_id()),
|
||||
QMessageBox::Yes | QMessageBox::No) != QMessageBox::Yes)
|
||||
return;
|
||||
|
||||
Command_ReplayDeleteMatch cmd;
|
||||
cmd.set_game_id(curRight->game_id());
|
||||
|
||||
PendingCommand *pend = client->prepareSessionCommand(cmd);
|
||||
connect(pend, SIGNAL(finished(Response, CommandContainer, QVariant)), this,
|
||||
SLOT(deleteRemoteReplayFinished(Response, CommandContainer)));
|
||||
client->sendCommand(pend);
|
||||
}
|
||||
|
||||
void TabReplays::deleteRemoteReplayFinished(const Response &r, const CommandContainer &commandContainer)
|
||||
{
|
||||
if (r.response_code() != Response::RespOk)
|
||||
return;
|
||||
|
||||
const Command_ReplayDeleteMatch &cmd =
|
||||
commandContainer.session_command(0).GetExtension(Command_ReplayDeleteMatch::ext);
|
||||
serverDirView->removeMatchInfo(cmd.game_id());
|
||||
}
|
||||
|
||||
void TabReplays::replayAddedEventReceived(const Event_ReplayAdded &event)
|
||||
{
|
||||
serverDirView->addMatchInfo(event.match_info());
|
||||
}
|
||||
59
cockatrice/src/client/tabs/tab_replays.h
Normal file
59
cockatrice/src/client/tabs/tab_replays.h
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
#ifndef TAB_REPLAYS_H
|
||||
#define TAB_REPLAYS_H
|
||||
|
||||
#include "tab.h"
|
||||
|
||||
class Response;
|
||||
class AbstractClient;
|
||||
class QTreeView;
|
||||
class QFileSystemModel;
|
||||
class QToolBar;
|
||||
class QGroupBox;
|
||||
class RemoteReplayList_TreeWidget;
|
||||
class GameReplay;
|
||||
class Event_ReplayAdded;
|
||||
class CommandContainer;
|
||||
|
||||
class TabReplays : public Tab
|
||||
{
|
||||
Q_OBJECT
|
||||
private:
|
||||
AbstractClient *client;
|
||||
QTreeView *localDirView;
|
||||
QFileSystemModel *localDirModel;
|
||||
QToolBar *leftToolBar, *rightToolBar;
|
||||
RemoteReplayList_TreeWidget *serverDirView;
|
||||
QGroupBox *leftGroupBox, *rightGroupBox;
|
||||
|
||||
QAction *aOpenLocalReplay, *aDeleteLocalReplay, *aOpenRemoteReplay, *aDownload, *aKeep, *aDeleteRemoteReplay;
|
||||
private slots:
|
||||
void actOpenLocalReplay();
|
||||
|
||||
void actDeleteLocalReplay();
|
||||
|
||||
void actOpenRemoteReplay();
|
||||
void openRemoteReplayFinished(const Response &r);
|
||||
|
||||
void actDownload();
|
||||
void downloadFinished(const Response &r, const CommandContainer &commandContainer, const QVariant &extraData);
|
||||
|
||||
void actKeepRemoteReplay();
|
||||
void keepRemoteReplayFinished(const Response &r, const CommandContainer &commandContainer);
|
||||
|
||||
void actDeleteRemoteReplay();
|
||||
void deleteRemoteReplayFinished(const Response &r, const CommandContainer &commandContainer);
|
||||
|
||||
void replayAddedEventReceived(const Event_ReplayAdded &event);
|
||||
signals:
|
||||
void openReplay(GameReplay *replay);
|
||||
|
||||
public:
|
||||
TabReplays(TabSupervisor *_tabSupervisor, AbstractClient *_client);
|
||||
void retranslateUi();
|
||||
QString getTabText() const
|
||||
{
|
||||
return tr("Game replays");
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
||||
347
cockatrice/src/client/tabs/tab_room.cpp
Normal file
347
cockatrice/src/client/tabs/tab_room.cpp
Normal file
|
|
@ -0,0 +1,347 @@
|
|||
#include "tab_room.h"
|
||||
|
||||
#include "../../client/game_logic/abstract_client.h"
|
||||
#include "../../dialogs/dlg_settings.h"
|
||||
#include "../../game/game_selector.h"
|
||||
#include "../../main.h"
|
||||
#include "../../server/chat_view/chat_view.h"
|
||||
#include "../../server/pending_command.h"
|
||||
#include "../../server/user/user_list.h"
|
||||
#include "../../settings/cache_settings.h"
|
||||
#include "get_pb_extension.h"
|
||||
#include "pb/event_join_room.pb.h"
|
||||
#include "pb/event_leave_room.pb.h"
|
||||
#include "pb/event_list_games.pb.h"
|
||||
#include "pb/event_remove_messages.pb.h"
|
||||
#include "pb/event_room_say.pb.h"
|
||||
#include "pb/room_commands.pb.h"
|
||||
#include "pb/serverinfo_room.pb.h"
|
||||
#include "tab_account.h"
|
||||
#include "tab_supervisor.h"
|
||||
#include "trice_limits.h"
|
||||
|
||||
#include <QApplication>
|
||||
#include <QCompleter>
|
||||
#include <QLabel>
|
||||
#include <QMenu>
|
||||
#include <QMessageBox>
|
||||
#include <QPushButton>
|
||||
#include <QSplitter>
|
||||
#include <QSystemTrayIcon>
|
||||
#include <QToolButton>
|
||||
#include <QVBoxLayout>
|
||||
#include <QtCore/qdatetime.h>
|
||||
|
||||
TabRoom::TabRoom(TabSupervisor *_tabSupervisor,
|
||||
AbstractClient *_client,
|
||||
ServerInfo_User *_ownUser,
|
||||
const ServerInfo_Room &info)
|
||||
: Tab(_tabSupervisor), client(_client), roomId(info.room_id()), roomName(QString::fromStdString(info.name())),
|
||||
ownUser(_ownUser)
|
||||
{
|
||||
const int gameTypeListSize = info.gametype_list_size();
|
||||
for (int i = 0; i < gameTypeListSize; ++i)
|
||||
gameTypes.insert(info.gametype_list(i).game_type_id(),
|
||||
QString::fromStdString(info.gametype_list(i).description()));
|
||||
|
||||
QMap<int, GameTypeMap> tempMap;
|
||||
tempMap.insert(info.room_id(), gameTypes);
|
||||
gameSelector = new GameSelector(client, tabSupervisor, this, QMap<int, QString>(), tempMap, true, true);
|
||||
userList = new UserList(tabSupervisor, client, UserList::RoomList);
|
||||
connect(userList, SIGNAL(openMessageDialog(const QString &, bool)), this,
|
||||
SIGNAL(openMessageDialog(const QString &, bool)));
|
||||
|
||||
chatView = new ChatView(tabSupervisor, tabSupervisor, nullptr, true, this);
|
||||
connect(chatView, SIGNAL(showMentionPopup(const QString &)), this, SLOT(actShowMentionPopup(const QString &)));
|
||||
connect(chatView, SIGNAL(messageClickedSignal()), this, SLOT(focusTab()));
|
||||
connect(chatView, SIGNAL(openMessageDialog(QString, bool)), this, SIGNAL(openMessageDialog(QString, bool)));
|
||||
connect(chatView, SIGNAL(showCardInfoPopup(QPoint, QString)), this, SLOT(showCardInfoPopup(QPoint, QString)));
|
||||
connect(chatView, SIGNAL(deleteCardInfoPopup(QString)), this, SLOT(deleteCardInfoPopup(QString)));
|
||||
connect(chatView, SIGNAL(addMentionTag(QString)), this, SLOT(addMentionTag(QString)));
|
||||
connect(&SettingsCache::instance(), SIGNAL(chatMentionCompleterChanged()), this, SLOT(actCompleterChanged()));
|
||||
sayLabel = new QLabel;
|
||||
sayEdit = new LineEditCompleter;
|
||||
sayEdit->setMaxLength(MAX_TEXT_LENGTH);
|
||||
sayLabel->setBuddy(sayEdit);
|
||||
connect(sayEdit, SIGNAL(returnPressed()), this, SLOT(sendMessage()));
|
||||
|
||||
QMenu *chatSettingsMenu = new QMenu(this);
|
||||
|
||||
aClearChat = chatSettingsMenu->addAction(QString());
|
||||
connect(aClearChat, SIGNAL(triggered()), this, SLOT(actClearChat()));
|
||||
|
||||
chatSettingsMenu->addSeparator();
|
||||
|
||||
aOpenChatSettings = chatSettingsMenu->addAction(QString());
|
||||
connect(aOpenChatSettings, SIGNAL(triggered()), this, SLOT(actOpenChatSettings()));
|
||||
|
||||
QToolButton *chatSettingsButton = new QToolButton;
|
||||
chatSettingsButton->setIcon(QPixmap("theme:icons/settings"));
|
||||
chatSettingsButton->setMenu(chatSettingsMenu);
|
||||
chatSettingsButton->setPopupMode(QToolButton::InstantPopup);
|
||||
|
||||
QHBoxLayout *sayHbox = new QHBoxLayout;
|
||||
sayHbox->addWidget(sayLabel);
|
||||
sayHbox->addWidget(sayEdit);
|
||||
sayHbox->addWidget(chatSettingsButton);
|
||||
|
||||
QVBoxLayout *chatVbox = new QVBoxLayout;
|
||||
chatVbox->addWidget(chatView);
|
||||
chatVbox->addLayout(sayHbox);
|
||||
|
||||
chatGroupBox = new QGroupBox;
|
||||
chatGroupBox->setLayout(chatVbox);
|
||||
|
||||
QSplitter *splitter = new QSplitter(Qt::Vertical);
|
||||
splitter->addWidget(gameSelector);
|
||||
splitter->addWidget(chatGroupBox);
|
||||
|
||||
QHBoxLayout *hbox = new QHBoxLayout;
|
||||
hbox->addWidget(splitter, 3);
|
||||
hbox->addWidget(userList, 1);
|
||||
|
||||
aLeaveRoom = new QAction(this);
|
||||
connect(aLeaveRoom, SIGNAL(triggered()), this, SLOT(actLeaveRoom()));
|
||||
|
||||
roomMenu = new QMenu(this);
|
||||
roomMenu->addAction(aLeaveRoom);
|
||||
addTabMenu(roomMenu);
|
||||
|
||||
const int userListSize = info.user_list_size();
|
||||
for (int i = 0; i < userListSize; ++i) {
|
||||
userList->processUserInfo(info.user_list(i), true);
|
||||
autocompleteUserList.append("@" + QString::fromStdString(info.user_list(i).name()));
|
||||
}
|
||||
userList->sortItems();
|
||||
|
||||
const int gameListSize = info.game_list_size();
|
||||
for (int i = 0; i < gameListSize; ++i)
|
||||
gameSelector->processGameInfo(info.game_list(i));
|
||||
|
||||
completer = new QCompleter(autocompleteUserList, sayEdit);
|
||||
completer->setCaseSensitivity(Qt::CaseInsensitive);
|
||||
completer->setMaxVisibleItems(5);
|
||||
completer->setFilterMode(Qt::MatchStartsWith);
|
||||
|
||||
sayEdit->setCompleter(completer);
|
||||
actCompleterChanged();
|
||||
connect(&SettingsCache::instance().shortcuts(), SIGNAL(shortCutChanged()), this, SLOT(refreshShortcuts()));
|
||||
refreshShortcuts();
|
||||
|
||||
retranslateUi();
|
||||
|
||||
QWidget *mainWidget = new QWidget(this);
|
||||
mainWidget->setLayout(hbox);
|
||||
setCentralWidget(mainWidget);
|
||||
}
|
||||
|
||||
TabRoom::~TabRoom()
|
||||
{
|
||||
emit roomClosing(this);
|
||||
}
|
||||
|
||||
void TabRoom::retranslateUi()
|
||||
{
|
||||
gameSelector->retranslateUi();
|
||||
chatView->retranslateUi();
|
||||
userList->retranslateUi();
|
||||
sayLabel->setText(tr("&Say:"));
|
||||
chatGroupBox->setTitle(tr("Chat"));
|
||||
roomMenu->setTitle(tr("&Room"));
|
||||
aLeaveRoom->setText(tr("&Leave room"));
|
||||
aClearChat->setText(tr("&Clear chat"));
|
||||
aOpenChatSettings->setText(tr("Chat Settings..."));
|
||||
}
|
||||
|
||||
void TabRoom::focusTab()
|
||||
{
|
||||
activateWindow();
|
||||
tabSupervisor->setCurrentIndex(tabSupervisor->indexOf(this));
|
||||
emit maximizeClient();
|
||||
}
|
||||
|
||||
void TabRoom::actShowMentionPopup(const QString &sender)
|
||||
{
|
||||
this->actShowPopup(sender + tr(" mentioned you."));
|
||||
}
|
||||
|
||||
void TabRoom::actShowPopup(const QString &message)
|
||||
{
|
||||
if (trayIcon && (tabSupervisor->currentIndex() != tabSupervisor->indexOf(this) ||
|
||||
QApplication::activeWindow() == nullptr || QApplication::focusWidget() == nullptr)) {
|
||||
disconnect(trayIcon, SIGNAL(messageClicked()), nullptr, nullptr);
|
||||
trayIcon->showMessage(message, tr("Click to view"));
|
||||
connect(trayIcon, SIGNAL(messageClicked()), chatView, SLOT(actMessageClicked()));
|
||||
}
|
||||
}
|
||||
|
||||
void TabRoom::closeRequest()
|
||||
{
|
||||
actLeaveRoom();
|
||||
}
|
||||
|
||||
void TabRoom::tabActivated()
|
||||
{
|
||||
if (!sayEdit->hasFocus())
|
||||
sayEdit->setFocus();
|
||||
}
|
||||
|
||||
QString TabRoom::sanitizeHtml(QString dirty) const
|
||||
{
|
||||
return dirty.replace("&", "&").replace("<", "<").replace(">", ">");
|
||||
}
|
||||
|
||||
void TabRoom::sendMessage()
|
||||
{
|
||||
if (sayEdit->text().isEmpty()) {
|
||||
return;
|
||||
} else if (completer->popup()->isVisible()) {
|
||||
completer->popup()->hide();
|
||||
return;
|
||||
} else {
|
||||
Command_RoomSay cmd;
|
||||
cmd.set_message(sayEdit->text().toStdString());
|
||||
|
||||
PendingCommand *pend = prepareRoomCommand(cmd);
|
||||
connect(pend, SIGNAL(finished(Response, CommandContainer, QVariant)), this,
|
||||
SLOT(sayFinished(const Response &)));
|
||||
sendRoomCommand(pend);
|
||||
sayEdit->clear();
|
||||
}
|
||||
}
|
||||
|
||||
void TabRoom::sayFinished(const Response &response)
|
||||
{
|
||||
if (response.response_code() == Response::RespChatFlood)
|
||||
chatView->appendMessage(tr("You are flooding the chat. Please wait a couple of seconds."));
|
||||
}
|
||||
|
||||
void TabRoom::actLeaveRoom()
|
||||
{
|
||||
sendRoomCommand(prepareRoomCommand(Command_LeaveRoom()));
|
||||
deleteLater();
|
||||
}
|
||||
|
||||
void TabRoom::actClearChat()
|
||||
{
|
||||
chatView->clearChat();
|
||||
}
|
||||
|
||||
void TabRoom::actOpenChatSettings()
|
||||
{
|
||||
DlgSettings settings(this);
|
||||
settings.setTab(4);
|
||||
settings.exec();
|
||||
}
|
||||
|
||||
void TabRoom::actCompleterChanged()
|
||||
{
|
||||
SettingsCache::instance().getChatMentionCompleter() ? completer->setCompletionRole(2)
|
||||
: completer->setCompletionRole(1);
|
||||
}
|
||||
|
||||
void TabRoom::processRoomEvent(const RoomEvent &event)
|
||||
{
|
||||
switch (static_cast<RoomEvent::RoomEventType>(getPbExtension(event))) {
|
||||
case RoomEvent::LIST_GAMES:
|
||||
processListGamesEvent(event.GetExtension(Event_ListGames::ext));
|
||||
break;
|
||||
case RoomEvent::JOIN_ROOM:
|
||||
processJoinRoomEvent(event.GetExtension(Event_JoinRoom::ext));
|
||||
break;
|
||||
case RoomEvent::LEAVE_ROOM:
|
||||
processLeaveRoomEvent(event.GetExtension(Event_LeaveRoom::ext));
|
||||
break;
|
||||
case RoomEvent::ROOM_SAY:
|
||||
processRoomSayEvent(event.GetExtension(Event_RoomSay::ext));
|
||||
break;
|
||||
case RoomEvent::REMOVE_MESSAGES:
|
||||
processRemoveMessagesEvent(event.GetExtension(Event_RemoveMessages::ext));
|
||||
break;
|
||||
default:;
|
||||
}
|
||||
}
|
||||
|
||||
void TabRoom::processListGamesEvent(const Event_ListGames &event)
|
||||
{
|
||||
const int gameListSize = event.game_list_size();
|
||||
for (int i = 0; i < gameListSize; ++i)
|
||||
gameSelector->processGameInfo(event.game_list(i));
|
||||
}
|
||||
|
||||
void TabRoom::processJoinRoomEvent(const Event_JoinRoom &event)
|
||||
{
|
||||
userList->processUserInfo(event.user_info(), true);
|
||||
userList->sortItems();
|
||||
if (!autocompleteUserList.contains("@" + QString::fromStdString(event.user_info().name()))) {
|
||||
autocompleteUserList << "@" + QString::fromStdString(event.user_info().name());
|
||||
sayEdit->setCompletionList(autocompleteUserList);
|
||||
}
|
||||
}
|
||||
|
||||
void TabRoom::processLeaveRoomEvent(const Event_LeaveRoom &event)
|
||||
{
|
||||
userList->deleteUser(QString::fromStdString(event.name()));
|
||||
autocompleteUserList.removeOne("@" + QString::fromStdString(event.name()));
|
||||
sayEdit->setCompletionList(autocompleteUserList);
|
||||
}
|
||||
|
||||
void TabRoom::processRoomSayEvent(const Event_RoomSay &event)
|
||||
{
|
||||
QString senderName = QString::fromStdString(event.name());
|
||||
QString message = QString::fromStdString(event.message());
|
||||
|
||||
if (tabSupervisor->getUserListsTab()->getIgnoreList()->getUsers().contains(senderName))
|
||||
return;
|
||||
|
||||
UserListTWI *twi = userList->getUsers().value(senderName);
|
||||
UserLevelFlags userLevel;
|
||||
QString userPrivLevel;
|
||||
if (twi) {
|
||||
userLevel = UserLevelFlags(twi->getUserInfo().user_level());
|
||||
userPrivLevel = QString::fromStdString(twi->getUserInfo().privlevel());
|
||||
if (SettingsCache::instance().getIgnoreUnregisteredUsers() &&
|
||||
!userLevel.testFlag(ServerInfo_User::IsRegistered))
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.message_type() == Event_RoomSay::ChatHistory && !SettingsCache::instance().getRoomHistory())
|
||||
return;
|
||||
|
||||
if (event.message_type() == Event_RoomSay::ChatHistory)
|
||||
message =
|
||||
"[" +
|
||||
QString(QDateTime::fromMSecsSinceEpoch(event.time_of()).toLocalTime().toString("d MMM yyyy HH:mm:ss")) +
|
||||
"] " + message;
|
||||
|
||||
chatView->appendMessage(message, event.message_type(), senderName, userLevel, userPrivLevel, true);
|
||||
emit userEvent(false);
|
||||
}
|
||||
|
||||
void TabRoom::processRemoveMessagesEvent(const Event_RemoveMessages &event)
|
||||
{
|
||||
QString userName = QString::fromStdString(event.name());
|
||||
int amount = event.amount();
|
||||
chatView->redactMessages(userName, amount);
|
||||
}
|
||||
|
||||
void TabRoom::refreshShortcuts()
|
||||
{
|
||||
aClearChat->setShortcuts(SettingsCache::instance().shortcuts().getShortcut("tab_room/aClearChat"));
|
||||
}
|
||||
|
||||
void TabRoom::addMentionTag(QString mentionTag)
|
||||
{
|
||||
sayEdit->insert(mentionTag + " ");
|
||||
sayEdit->setFocus();
|
||||
}
|
||||
|
||||
PendingCommand *TabRoom::prepareRoomCommand(const ::google::protobuf::Message &cmd)
|
||||
{
|
||||
return client->prepareRoomCommand(cmd, roomId);
|
||||
}
|
||||
|
||||
void TabRoom::sendRoomCommand(PendingCommand *pend)
|
||||
{
|
||||
client->sendCommand(pend);
|
||||
}
|
||||
124
cockatrice/src/client/tabs/tab_room.h
Normal file
124
cockatrice/src/client/tabs/tab_room.h
Normal file
|
|
@ -0,0 +1,124 @@
|
|||
#ifndef TAB_ROOM_H
|
||||
#define TAB_ROOM_H
|
||||
|
||||
#include "../ui/line_edit_completer.h"
|
||||
#include "tab.h"
|
||||
|
||||
#include <QFocusEvent>
|
||||
#include <QGroupBox>
|
||||
#include <QKeyEvent>
|
||||
#include <QMap>
|
||||
|
||||
namespace google
|
||||
{
|
||||
namespace protobuf
|
||||
{
|
||||
class Message;
|
||||
}
|
||||
} // namespace google
|
||||
class AbstractClient;
|
||||
class UserList;
|
||||
class QLabel;
|
||||
class ChatView;
|
||||
class QPushButton;
|
||||
class QTextTable;
|
||||
class QCompleter;
|
||||
class RoomEvent;
|
||||
class ServerInfo_Room;
|
||||
class ServerInfo_Game;
|
||||
class Event_ListGames;
|
||||
class Event_JoinRoom;
|
||||
class Event_LeaveRoom;
|
||||
class Event_RoomSay;
|
||||
class Event_RemoveMessages;
|
||||
class GameSelector;
|
||||
class Response;
|
||||
class PendingCommand;
|
||||
class ServerInfo_User;
|
||||
class LineEditCompleter;
|
||||
|
||||
class TabRoom : public Tab
|
||||
{
|
||||
Q_OBJECT
|
||||
private:
|
||||
AbstractClient *client;
|
||||
int roomId;
|
||||
QString roomName;
|
||||
ServerInfo_User *ownUser;
|
||||
QMap<int, QString> gameTypes;
|
||||
|
||||
GameSelector *gameSelector;
|
||||
UserList *userList;
|
||||
ChatView *chatView;
|
||||
QLabel *sayLabel;
|
||||
LineEditCompleter *sayEdit;
|
||||
QGroupBox *chatGroupBox;
|
||||
|
||||
QMenu *roomMenu;
|
||||
QAction *aLeaveRoom;
|
||||
QAction *aOpenChatSettings;
|
||||
QAction *aClearChat;
|
||||
QString sanitizeHtml(QString dirty) const;
|
||||
|
||||
QStringList autocompleteUserList;
|
||||
QCompleter *completer;
|
||||
signals:
|
||||
void roomClosing(TabRoom *tab);
|
||||
void openMessageDialog(const QString &userName, bool focus);
|
||||
void maximizeClient();
|
||||
void notIdle();
|
||||
private slots:
|
||||
void sendMessage();
|
||||
void sayFinished(const Response &response);
|
||||
void actLeaveRoom();
|
||||
void actClearChat();
|
||||
void actOpenChatSettings();
|
||||
void addMentionTag(QString mentionTag);
|
||||
void focusTab();
|
||||
void actShowMentionPopup(const QString &sender);
|
||||
void actShowPopup(const QString &message);
|
||||
void actCompleterChanged();
|
||||
|
||||
void processListGamesEvent(const Event_ListGames &event);
|
||||
void processJoinRoomEvent(const Event_JoinRoom &event);
|
||||
void processLeaveRoomEvent(const Event_LeaveRoom &event);
|
||||
void processRoomSayEvent(const Event_RoomSay &event);
|
||||
void processRemoveMessagesEvent(const Event_RemoveMessages &event);
|
||||
void refreshShortcuts();
|
||||
|
||||
public:
|
||||
TabRoom(TabSupervisor *_tabSupervisor,
|
||||
AbstractClient *_client,
|
||||
ServerInfo_User *_ownUser,
|
||||
const ServerInfo_Room &info);
|
||||
~TabRoom();
|
||||
void retranslateUi();
|
||||
void closeRequest();
|
||||
void tabActivated();
|
||||
void processRoomEvent(const RoomEvent &event);
|
||||
int getRoomId() const
|
||||
{
|
||||
return roomId;
|
||||
}
|
||||
const QMap<int, QString> &getGameTypes() const
|
||||
{
|
||||
return gameTypes;
|
||||
}
|
||||
QString getChannelName() const
|
||||
{
|
||||
return roomName;
|
||||
}
|
||||
QString getTabText() const
|
||||
{
|
||||
return roomName;
|
||||
}
|
||||
const ServerInfo_User *getUserInfo() const
|
||||
{
|
||||
return ownUser;
|
||||
}
|
||||
|
||||
PendingCommand *prepareRoomCommand(const ::google::protobuf::Message &cmd);
|
||||
void sendRoomCommand(PendingCommand *pend);
|
||||
};
|
||||
|
||||
#endif
|
||||
231
cockatrice/src/client/tabs/tab_server.cpp
Normal file
231
cockatrice/src/client/tabs/tab_server.cpp
Normal file
|
|
@ -0,0 +1,231 @@
|
|||
#include "tab_server.h"
|
||||
|
||||
#include "../../client/game_logic/abstract_client.h"
|
||||
#include "../../server/pending_command.h"
|
||||
#include "../../server/user/user_list.h"
|
||||
#include "pb/event_list_rooms.pb.h"
|
||||
#include "pb/event_server_message.pb.h"
|
||||
#include "pb/response_join_room.pb.h"
|
||||
#include "pb/session_commands.pb.h"
|
||||
#include "tab_supervisor.h"
|
||||
|
||||
#include <QCheckBox>
|
||||
#include <QDebug>
|
||||
#include <QHBoxLayout>
|
||||
#include <QHeaderView>
|
||||
#include <QInputDialog>
|
||||
#include <QLabel>
|
||||
#include <QMessageBox>
|
||||
#include <QPushButton>
|
||||
#include <QTextEdit>
|
||||
#include <QTreeView>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
RoomSelector::RoomSelector(AbstractClient *_client, QWidget *parent) : QGroupBox(parent), client(_client)
|
||||
{
|
||||
roomList = new QTreeWidget;
|
||||
roomList->setRootIsDecorated(false);
|
||||
roomList->setColumnCount(5);
|
||||
roomList->header()->setStretchLastSection(false);
|
||||
roomList->header()->setSectionResizeMode(0, QHeaderView::ResizeToContents);
|
||||
roomList->header()->setSectionResizeMode(1, QHeaderView::Stretch);
|
||||
roomList->header()->setSectionResizeMode(2, QHeaderView::ResizeToContents);
|
||||
roomList->header()->setSectionResizeMode(3, QHeaderView::ResizeToContents);
|
||||
|
||||
joinButton = new QPushButton;
|
||||
connect(joinButton, SIGNAL(clicked()), this, SLOT(joinClicked()));
|
||||
QHBoxLayout *buttonLayout = new QHBoxLayout;
|
||||
buttonLayout->addStretch();
|
||||
buttonLayout->addWidget(joinButton);
|
||||
QVBoxLayout *vbox = new QVBoxLayout;
|
||||
vbox->addWidget(roomList);
|
||||
vbox->addLayout(buttonLayout);
|
||||
|
||||
retranslateUi();
|
||||
setLayout(vbox);
|
||||
|
||||
connect(client, SIGNAL(listRoomsEventReceived(const Event_ListRooms &)), this,
|
||||
SLOT(processListRoomsEvent(const Event_ListRooms &)));
|
||||
connect(roomList, SIGNAL(activated(const QModelIndex &)), this, SLOT(joinClicked()));
|
||||
client->sendCommand(client->prepareSessionCommand(Command_ListRooms()));
|
||||
}
|
||||
|
||||
void RoomSelector::retranslateUi()
|
||||
{
|
||||
setTitle(tr("Rooms"));
|
||||
joinButton->setText(tr("Joi&n"));
|
||||
|
||||
QTreeWidgetItem *header = roomList->headerItem();
|
||||
header->setText(0, tr("Room"));
|
||||
header->setText(1, tr("Description"));
|
||||
header->setText(2, tr("Permissions"));
|
||||
header->setText(3, tr("Players"));
|
||||
header->setText(4, tr("Games"));
|
||||
header->setTextAlignment(2, Qt::AlignRight);
|
||||
header->setTextAlignment(3, Qt::AlignRight);
|
||||
header->setTextAlignment(4, Qt::AlignRight);
|
||||
}
|
||||
|
||||
void RoomSelector::processListRoomsEvent(const Event_ListRooms &event)
|
||||
{
|
||||
const int roomListSize = event.room_list_size();
|
||||
for (int i = 0; i < roomListSize; ++i) {
|
||||
const ServerInfo_Room &room = event.room_list(i);
|
||||
|
||||
for (int j = 0; j < roomList->topLevelItemCount(); ++j) {
|
||||
QTreeWidgetItem *twi = roomList->topLevelItem(j);
|
||||
if (twi->data(0, Qt::UserRole).toInt() == room.room_id()) {
|
||||
if (room.has_name())
|
||||
twi->setData(0, Qt::DisplayRole, QString::fromStdString(room.name()));
|
||||
if (room.has_description())
|
||||
twi->setData(1, Qt::DisplayRole, QString::fromStdString(room.description()));
|
||||
if (room.has_permissionlevel())
|
||||
twi->setData(2, Qt::DisplayRole, getRoomPermissionDisplay(room));
|
||||
if (room.has_player_count())
|
||||
twi->setData(3, Qt::DisplayRole, room.player_count());
|
||||
if (room.has_game_count())
|
||||
twi->setData(4, Qt::DisplayRole, room.game_count());
|
||||
return;
|
||||
}
|
||||
}
|
||||
QTreeWidgetItem *twi = new QTreeWidgetItem;
|
||||
twi->setData(0, Qt::UserRole, room.room_id());
|
||||
if (room.has_name())
|
||||
twi->setData(0, Qt::DisplayRole, QString::fromStdString(room.name()));
|
||||
if (room.has_description())
|
||||
twi->setData(1, Qt::DisplayRole, QString::fromStdString(room.description()));
|
||||
if (room.has_permissionlevel())
|
||||
twi->setData(2, Qt::DisplayRole, getRoomPermissionDisplay(room));
|
||||
twi->setData(3, Qt::DisplayRole, room.player_count());
|
||||
twi->setData(4, Qt::DisplayRole, room.game_count());
|
||||
twi->setTextAlignment(2, Qt::AlignRight);
|
||||
twi->setTextAlignment(3, Qt::AlignRight);
|
||||
twi->setTextAlignment(4, Qt::AlignRight);
|
||||
|
||||
roomList->addTopLevelItem(twi);
|
||||
if (room.has_auto_join())
|
||||
if (room.auto_join())
|
||||
emit joinRoomRequest(room.room_id(), false);
|
||||
}
|
||||
}
|
||||
|
||||
QString RoomSelector::getRoomPermissionDisplay(const ServerInfo_Room &room)
|
||||
{
|
||||
/*
|
||||
* A server room can have a permission level and a privilege level. How ever we want to display only the necessary
|
||||
* information on the server tab needed to inform users of required permissions to enter a room. If the room has a
|
||||
* privilege level the server tab will display the privilege level in the "permissions" column in the row however if
|
||||
* the room contains a permissions level for the room the permissions level defined for the room will be displayed.
|
||||
*/
|
||||
|
||||
QString roomPermissionDisplay = QString::fromStdString(room.privilegelevel()).toLower();
|
||||
if (QString::fromStdString(room.permissionlevel()).toLower() != "none")
|
||||
roomPermissionDisplay = QString::fromStdString(room.permissionlevel()).toLower();
|
||||
if (roomPermissionDisplay == "") // catch all for misconfigured .ini room definitions
|
||||
roomPermissionDisplay = "none";
|
||||
|
||||
return roomPermissionDisplay;
|
||||
}
|
||||
|
||||
void RoomSelector::joinClicked()
|
||||
{
|
||||
QTreeWidgetItem *twi = roomList->currentItem();
|
||||
if (!twi)
|
||||
return;
|
||||
|
||||
int id = twi->data(0, Qt::UserRole).toInt();
|
||||
|
||||
emit joinRoomRequest(id, true);
|
||||
}
|
||||
|
||||
TabServer::TabServer(TabSupervisor *_tabSupervisor, AbstractClient *_client, QWidget *parent)
|
||||
: Tab(_tabSupervisor, parent), client(_client)
|
||||
{
|
||||
roomSelector = new RoomSelector(client);
|
||||
serverInfoBox = new QTextBrowser;
|
||||
serverInfoBox->setOpenExternalLinks(true);
|
||||
|
||||
connect(roomSelector, SIGNAL(joinRoomRequest(int, bool)), this, SLOT(joinRoom(int, bool)));
|
||||
|
||||
connect(client, SIGNAL(serverMessageEventReceived(const Event_ServerMessage &)), this,
|
||||
SLOT(processServerMessageEvent(const Event_ServerMessage &)));
|
||||
|
||||
QVBoxLayout *vbox = new QVBoxLayout;
|
||||
vbox->addWidget(roomSelector);
|
||||
vbox->addWidget(serverInfoBox);
|
||||
|
||||
retranslateUi();
|
||||
|
||||
QWidget *mainWidget = new QWidget(this);
|
||||
mainWidget->setLayout(vbox);
|
||||
setCentralWidget(mainWidget);
|
||||
}
|
||||
|
||||
void TabServer::retranslateUi()
|
||||
{
|
||||
roomSelector->retranslateUi();
|
||||
}
|
||||
|
||||
void TabServer::processServerMessageEvent(const Event_ServerMessage &event)
|
||||
{
|
||||
serverInfoBox->setHtml(QString::fromStdString(event.message()));
|
||||
if (shouldEmitUpdate) {
|
||||
// prevent the initial server message from taking attention from ping icon
|
||||
emit userEvent();
|
||||
} else {
|
||||
shouldEmitUpdate = true;
|
||||
}
|
||||
}
|
||||
|
||||
void TabServer::joinRoom(int id, bool setCurrent)
|
||||
{
|
||||
TabRoom *room = tabSupervisor->getRoomTabs().value(id);
|
||||
if (!room) {
|
||||
Command_JoinRoom cmd;
|
||||
cmd.set_room_id(id);
|
||||
|
||||
PendingCommand *pend = client->prepareSessionCommand(cmd);
|
||||
pend->setExtraData(setCurrent);
|
||||
connect(pend, SIGNAL(finished(Response, CommandContainer, QVariant)), this,
|
||||
SLOT(joinRoomFinished(Response, CommandContainer, QVariant)));
|
||||
|
||||
client->sendCommand(pend);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (setCurrent)
|
||||
tabSupervisor->setCurrentWidget((QWidget *)room);
|
||||
}
|
||||
|
||||
void TabServer::joinRoomFinished(const Response &r,
|
||||
const CommandContainer & /*commandContainer*/,
|
||||
const QVariant &extraData)
|
||||
{
|
||||
switch (r.response_code()) {
|
||||
case Response::RespOk:
|
||||
break;
|
||||
case Response::RespNameNotFound:
|
||||
QMessageBox::critical(this, tr("Error"),
|
||||
tr("Failed to join the server room: it doesn't exist on the server."));
|
||||
return;
|
||||
case Response::RespContextError:
|
||||
QMessageBox::critical(
|
||||
this, tr("Error"),
|
||||
tr("The server thinks you are in the server room but your client is unable to display it. "
|
||||
"Try restarting your client."));
|
||||
return;
|
||||
case Response::RespUserLevelTooLow:
|
||||
QMessageBox::critical(this, tr("Error"),
|
||||
tr("You do not have the required permission to join this server room."));
|
||||
return;
|
||||
default:
|
||||
QMessageBox::critical(
|
||||
this, tr("Error"),
|
||||
tr("Failed to join the server room due to an unknown error: %1.").arg(r.response_code()));
|
||||
return;
|
||||
}
|
||||
|
||||
const Response_JoinRoom &resp = r.GetExtension(Response_JoinRoom::ext);
|
||||
emit roomJoined(resp.room_info(), extraData.toBool());
|
||||
}
|
||||
66
cockatrice/src/client/tabs/tab_server.h
Normal file
66
cockatrice/src/client/tabs/tab_server.h
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
#ifndef TAB_SERVER_H
|
||||
#define TAB_SERVER_H
|
||||
|
||||
#include "tab.h"
|
||||
|
||||
#include <QGroupBox>
|
||||
#include <QTextBrowser>
|
||||
#include <QTreeWidget>
|
||||
|
||||
class AbstractClient;
|
||||
class QTextEdit;
|
||||
class QLabel;
|
||||
class UserList;
|
||||
class QPushButton;
|
||||
|
||||
class Event_ListRooms;
|
||||
class Event_ServerMessage;
|
||||
class Response;
|
||||
class ServerInfo_Room;
|
||||
class CommandContainer;
|
||||
|
||||
class RoomSelector : public QGroupBox
|
||||
{
|
||||
Q_OBJECT
|
||||
private:
|
||||
QTreeWidget *roomList;
|
||||
QPushButton *joinButton;
|
||||
AbstractClient *client;
|
||||
QString getRoomPermissionDisplay(const ServerInfo_Room &room);
|
||||
private slots:
|
||||
void processListRoomsEvent(const Event_ListRooms &event);
|
||||
void joinClicked();
|
||||
signals:
|
||||
void joinRoomRequest(int, bool setCurrent);
|
||||
|
||||
public:
|
||||
RoomSelector(AbstractClient *_client, QWidget *parent = nullptr);
|
||||
void retranslateUi();
|
||||
};
|
||||
|
||||
class TabServer : public Tab
|
||||
{
|
||||
Q_OBJECT
|
||||
signals:
|
||||
void roomJoined(const ServerInfo_Room &info, bool setCurrent);
|
||||
private slots:
|
||||
void processServerMessageEvent(const Event_ServerMessage &event);
|
||||
void joinRoom(int id, bool setCurrent);
|
||||
void joinRoomFinished(const Response &resp, const CommandContainer &commandContainer, const QVariant &extraData);
|
||||
|
||||
private:
|
||||
AbstractClient *client;
|
||||
RoomSelector *roomSelector;
|
||||
QTextBrowser *serverInfoBox;
|
||||
bool shouldEmitUpdate = false;
|
||||
|
||||
public:
|
||||
TabServer(TabSupervisor *_tabSupervisor, AbstractClient *_client, QWidget *parent = nullptr);
|
||||
void retranslateUi();
|
||||
QString getTabText() const
|
||||
{
|
||||
return tr("Server");
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
||||
750
cockatrice/src/client/tabs/tab_supervisor.cpp
Normal file
750
cockatrice/src/client/tabs/tab_supervisor.cpp
Normal file
|
|
@ -0,0 +1,750 @@
|
|||
#include "tab_supervisor.h"
|
||||
|
||||
#include "../../client/game_logic/abstract_client.h"
|
||||
#include "../../main.h"
|
||||
#include "../../server/user/user_list.h"
|
||||
#include "../../settings/cache_settings.h"
|
||||
#include "../ui/pixel_map_generator.h"
|
||||
#include "pb/event_game_joined.pb.h"
|
||||
#include "pb/event_notify_user.pb.h"
|
||||
#include "pb/event_user_message.pb.h"
|
||||
#include "pb/game_event_container.pb.h"
|
||||
#include "pb/moderator_commands.pb.h"
|
||||
#include "pb/room_commands.pb.h"
|
||||
#include "pb/room_event.pb.h"
|
||||
#include "pb/serverinfo_room.pb.h"
|
||||
#include "pb/serverinfo_user.pb.h"
|
||||
#include "tab_account.h"
|
||||
#include "tab_admin.h"
|
||||
#include "tab_deck_editor.h"
|
||||
#include "tab_deck_storage.h"
|
||||
#include "tab_game.h"
|
||||
#include "tab_logs.h"
|
||||
#include "tab_message.h"
|
||||
#include "tab_replays.h"
|
||||
#include "tab_room.h"
|
||||
#include "tab_server.h"
|
||||
|
||||
#include <QApplication>
|
||||
#include <QDebug>
|
||||
#include <QMessageBox>
|
||||
#include <QPainter>
|
||||
#include <QSystemTrayIcon>
|
||||
|
||||
QRect MacOSTabFixStyle::subElementRect(SubElement element, const QStyleOption *option, const QWidget *widget) const
|
||||
{
|
||||
if (element != SE_TabBarTabText) {
|
||||
return QProxyStyle::subElementRect(element, option, widget);
|
||||
}
|
||||
|
||||
// Skip over QProxyStyle handling subElementRect,
|
||||
// This fixes an issue with Qt 5.10 on OSX where the labels for tabs with a button and an icon
|
||||
// get cut-off too early
|
||||
return QCommonStyle::subElementRect(element, option, widget);
|
||||
}
|
||||
|
||||
CloseButton::CloseButton(QWidget *parent) : QAbstractButton(parent)
|
||||
{
|
||||
setFocusPolicy(Qt::NoFocus);
|
||||
setCursor(Qt::ArrowCursor);
|
||||
resize(sizeHint());
|
||||
}
|
||||
|
||||
QSize CloseButton::sizeHint() const
|
||||
{
|
||||
ensurePolished();
|
||||
int width = style()->pixelMetric(QStyle::PM_TabCloseIndicatorWidth, 0, this);
|
||||
int height = style()->pixelMetric(QStyle::PM_TabCloseIndicatorHeight, 0, this);
|
||||
return QSize(width, height);
|
||||
}
|
||||
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
|
||||
void CloseButton::enterEvent(QEnterEvent *event)
|
||||
#else
|
||||
void CloseButton::enterEvent(QEvent *event)
|
||||
#endif
|
||||
{
|
||||
update();
|
||||
QAbstractButton::enterEvent(event);
|
||||
}
|
||||
|
||||
void CloseButton::leaveEvent(QEvent *event)
|
||||
{
|
||||
update();
|
||||
QAbstractButton::leaveEvent(event);
|
||||
}
|
||||
|
||||
void CloseButton::paintEvent(QPaintEvent * /*event*/)
|
||||
{
|
||||
QPainter p(this);
|
||||
QStyleOption opt;
|
||||
opt.initFrom(this);
|
||||
opt.state |= QStyle::State_AutoRaise;
|
||||
if (isEnabled() && underMouse() && !isChecked() && !isDown())
|
||||
opt.state |= QStyle::State_Raised;
|
||||
if (isChecked())
|
||||
opt.state |= QStyle::State_On;
|
||||
if (isDown())
|
||||
opt.state |= QStyle::State_Sunken;
|
||||
|
||||
if (const QTabBar *tb = qobject_cast<const QTabBar *>(parent())) {
|
||||
int index = tb->currentIndex();
|
||||
QTabBar::ButtonPosition position =
|
||||
(QTabBar::ButtonPosition)style()->styleHint(QStyle::SH_TabBar_CloseButtonPosition, 0, tb);
|
||||
if (tb->tabButton(index, position) == this)
|
||||
opt.state |= QStyle::State_Selected;
|
||||
}
|
||||
|
||||
style()->drawPrimitive(QStyle::PE_IndicatorTabClose, &opt, &p, this);
|
||||
}
|
||||
|
||||
TabSupervisor::TabSupervisor(AbstractClient *_client, QWidget *parent)
|
||||
: QTabWidget(parent), userInfo(0), client(_client), tabServer(0), tabUserLists(0), tabDeckStorage(0), tabReplays(0),
|
||||
tabAdmin(0), tabLog(0)
|
||||
{
|
||||
setElideMode(Qt::ElideRight);
|
||||
setMovable(true);
|
||||
setIconSize(QSize(15, 15));
|
||||
|
||||
#if defined(Q_OS_MAC)
|
||||
// This is necessary to fix an issue on macOS with qt5.10,
|
||||
// where tabs with icons and buttons get drawn incorrectly
|
||||
tabBar()->setStyle(new MacOSTabFixStyle);
|
||||
#endif
|
||||
|
||||
connect(this, SIGNAL(currentChanged(int)), this, SLOT(updateCurrent(int)));
|
||||
|
||||
connect(client, SIGNAL(roomEventReceived(const RoomEvent &)), this, SLOT(processRoomEvent(const RoomEvent &)));
|
||||
connect(client, SIGNAL(gameEventContainerReceived(const GameEventContainer &)), this,
|
||||
SLOT(processGameEventContainer(const GameEventContainer &)));
|
||||
connect(client, SIGNAL(gameJoinedEventReceived(const Event_GameJoined &)), this,
|
||||
SLOT(gameJoined(const Event_GameJoined &)));
|
||||
connect(client, SIGNAL(userMessageEventReceived(const Event_UserMessage &)), this,
|
||||
SLOT(processUserMessageEvent(const Event_UserMessage &)));
|
||||
connect(client, SIGNAL(maxPingTime(int, int)), this, SLOT(updatePingTime(int, int)));
|
||||
connect(client, SIGNAL(notifyUserEventReceived(const Event_NotifyUser &)), this,
|
||||
SLOT(processNotifyUserEvent(const Event_NotifyUser &)));
|
||||
|
||||
retranslateUi();
|
||||
}
|
||||
|
||||
TabSupervisor::~TabSupervisor()
|
||||
{
|
||||
stop();
|
||||
}
|
||||
|
||||
void TabSupervisor::retranslateUi()
|
||||
{
|
||||
QList<Tab *> tabs;
|
||||
tabs.append(tabServer);
|
||||
tabs.append(tabReplays);
|
||||
tabs.append(tabDeckStorage);
|
||||
tabs.append(tabAdmin);
|
||||
tabs.append(tabUserLists);
|
||||
tabs.append(tabLog);
|
||||
QMapIterator<int, TabRoom *> roomIterator(roomTabs);
|
||||
while (roomIterator.hasNext())
|
||||
tabs.append(roomIterator.next().value());
|
||||
QMapIterator<int, TabGame *> gameIterator(gameTabs);
|
||||
while (gameIterator.hasNext())
|
||||
tabs.append(gameIterator.next().value());
|
||||
QListIterator<TabGame *> replayIterator(replayTabs);
|
||||
while (replayIterator.hasNext())
|
||||
tabs.append(replayIterator.next());
|
||||
QListIterator<TabDeckEditor *> deckEditorIterator(deckEditorTabs);
|
||||
while (deckEditorIterator.hasNext())
|
||||
tabs.append(deckEditorIterator.next());
|
||||
QMapIterator<QString, TabMessage *> messageIterator(messageTabs);
|
||||
while (messageIterator.hasNext())
|
||||
tabs.append(messageIterator.next().value());
|
||||
|
||||
for (int i = 0; i < tabs.size(); ++i)
|
||||
if (tabs[i]) {
|
||||
int idx = indexOf(tabs[i]);
|
||||
QString tabText = tabs[i]->getTabText();
|
||||
setTabText(idx, sanitizeTabName(tabText));
|
||||
setTabToolTip(idx, sanitizeHtml(tabText));
|
||||
tabs[i]->retranslateUi();
|
||||
}
|
||||
}
|
||||
|
||||
bool TabSupervisor::closeRequest()
|
||||
{
|
||||
if (getGameCount()) {
|
||||
if (QMessageBox::question(this, tr("Are you sure?"),
|
||||
tr("There are still open games. Are you sure you want to quit?"),
|
||||
QMessageBox::Yes | QMessageBox::No, QMessageBox::No) == QMessageBox::No) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (TabDeckEditor *tab, deckEditorTabs) {
|
||||
if (!tab->confirmClose())
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
AbstractClient *TabSupervisor::getClient() const
|
||||
{
|
||||
return localClients.isEmpty() ? client : localClients.first();
|
||||
}
|
||||
|
||||
QString TabSupervisor::sanitizeTabName(QString dirty) const
|
||||
{
|
||||
return dirty.replace("&", "&&");
|
||||
}
|
||||
|
||||
QString TabSupervisor::sanitizeHtml(QString dirty) const
|
||||
{
|
||||
return dirty.replace("&", "&").replace("<", "<").replace(">", ">").replace("\"", """);
|
||||
}
|
||||
|
||||
int TabSupervisor::myAddTab(Tab *tab)
|
||||
{
|
||||
connect(tab, SIGNAL(userEvent(bool)), this, SLOT(tabUserEvent(bool)));
|
||||
connect(tab, SIGNAL(tabTextChanged(Tab *, QString)), this, SLOT(updateTabText(Tab *, QString)));
|
||||
|
||||
QString tabText = tab->getTabText();
|
||||
int idx = addTab(tab, sanitizeTabName(tabText));
|
||||
setTabToolTip(idx, sanitizeHtml(tabText));
|
||||
|
||||
return idx;
|
||||
}
|
||||
|
||||
void TabSupervisor::start(const ServerInfo_User &_userInfo)
|
||||
{
|
||||
isLocalGame = false;
|
||||
userInfo = new ServerInfo_User(_userInfo);
|
||||
|
||||
tabServer = new TabServer(this, client);
|
||||
connect(tabServer, SIGNAL(roomJoined(const ServerInfo_Room &, bool)), this,
|
||||
SLOT(addRoomTab(const ServerInfo_Room &, bool)));
|
||||
myAddTab(tabServer);
|
||||
|
||||
tabUserLists = new TabUserLists(this, client, *userInfo);
|
||||
connect(tabUserLists, SIGNAL(openMessageDialog(const QString &, bool)), this,
|
||||
SLOT(addMessageTab(const QString &, bool)));
|
||||
connect(tabUserLists, SIGNAL(userJoined(ServerInfo_User)), this, SLOT(processUserJoined(ServerInfo_User)));
|
||||
connect(tabUserLists, SIGNAL(userLeft(const QString &)), this, SLOT(processUserLeft(const QString &)));
|
||||
myAddTab(tabUserLists);
|
||||
|
||||
updatePingTime(0, -1);
|
||||
|
||||
if (userInfo->user_level() & ServerInfo_User::IsRegistered) {
|
||||
tabDeckStorage = new TabDeckStorage(this, client);
|
||||
connect(tabDeckStorage, SIGNAL(openDeckEditor(const DeckLoader *)), this,
|
||||
SLOT(addDeckEditorTab(const DeckLoader *)));
|
||||
myAddTab(tabDeckStorage);
|
||||
|
||||
tabReplays = new TabReplays(this, client);
|
||||
connect(tabReplays, SIGNAL(openReplay(GameReplay *)), this, SLOT(openReplay(GameReplay *)));
|
||||
myAddTab(tabReplays);
|
||||
} else {
|
||||
tabDeckStorage = 0;
|
||||
tabReplays = 0;
|
||||
}
|
||||
|
||||
if (userInfo->user_level() & ServerInfo_User::IsModerator) {
|
||||
tabAdmin = new TabAdmin(this, client, (userInfo->user_level() & ServerInfo_User::IsAdmin));
|
||||
connect(tabAdmin, SIGNAL(adminLockChanged(bool)), this, SIGNAL(adminLockChanged(bool)));
|
||||
myAddTab(tabAdmin);
|
||||
|
||||
tabLog = new TabLog(this, client);
|
||||
myAddTab(tabLog);
|
||||
} else {
|
||||
tabAdmin = 0;
|
||||
tabLog = 0;
|
||||
}
|
||||
|
||||
retranslateUi();
|
||||
}
|
||||
|
||||
void TabSupervisor::startLocal(const QList<AbstractClient *> &_clients)
|
||||
{
|
||||
tabUserLists = 0;
|
||||
tabDeckStorage = 0;
|
||||
tabReplays = 0;
|
||||
tabAdmin = 0;
|
||||
tabLog = 0;
|
||||
isLocalGame = true;
|
||||
userInfo = new ServerInfo_User;
|
||||
localClients = _clients;
|
||||
for (int i = 0; i < localClients.size(); ++i)
|
||||
connect(localClients[i], SIGNAL(gameEventContainerReceived(const GameEventContainer &)), this,
|
||||
SLOT(processGameEventContainer(const GameEventContainer &)));
|
||||
connect(localClients.first(), SIGNAL(gameJoinedEventReceived(const Event_GameJoined &)), this,
|
||||
SLOT(localGameJoined(const Event_GameJoined &)));
|
||||
}
|
||||
|
||||
void TabSupervisor::stop()
|
||||
{
|
||||
if ((!client) && localClients.isEmpty())
|
||||
return;
|
||||
|
||||
if (!localClients.isEmpty()) {
|
||||
for (int i = 0; i < localClients.size(); ++i)
|
||||
localClients[i]->deleteLater();
|
||||
localClients.clear();
|
||||
|
||||
emit localGameEnded();
|
||||
} else {
|
||||
if (tabUserLists)
|
||||
tabUserLists->deleteLater();
|
||||
if (tabServer)
|
||||
tabServer->deleteLater();
|
||||
if (tabDeckStorage)
|
||||
tabDeckStorage->deleteLater();
|
||||
if (tabReplays)
|
||||
tabReplays->deleteLater();
|
||||
if (tabAdmin)
|
||||
tabAdmin->deleteLater();
|
||||
if (tabLog)
|
||||
tabLog->deleteLater();
|
||||
}
|
||||
tabUserLists = 0;
|
||||
tabServer = 0;
|
||||
tabDeckStorage = 0;
|
||||
tabReplays = 0;
|
||||
tabAdmin = 0;
|
||||
tabLog = 0;
|
||||
|
||||
QMapIterator<int, TabRoom *> roomIterator(roomTabs);
|
||||
while (roomIterator.hasNext())
|
||||
roomIterator.next().value()->deleteLater();
|
||||
roomTabs.clear();
|
||||
|
||||
QMapIterator<int, TabGame *> gameIterator(gameTabs);
|
||||
while (gameIterator.hasNext())
|
||||
gameIterator.next().value()->deleteLater();
|
||||
gameTabs.clear();
|
||||
|
||||
QListIterator<TabGame *> replayIterator(replayTabs);
|
||||
while (replayIterator.hasNext())
|
||||
replayIterator.next()->deleteLater();
|
||||
replayTabs.clear();
|
||||
|
||||
delete userInfo;
|
||||
userInfo = 0;
|
||||
}
|
||||
|
||||
void TabSupervisor::updatePingTime(int value, int max)
|
||||
{
|
||||
if (!tabServer)
|
||||
return;
|
||||
if (tabServer->getContentsChanged())
|
||||
return;
|
||||
|
||||
setTabIcon(indexOf(tabServer), QIcon(PingPixmapGenerator::generatePixmap(15, value, max)));
|
||||
}
|
||||
|
||||
void TabSupervisor::closeButtonPressed()
|
||||
{
|
||||
Tab *tab = static_cast<Tab *>(static_cast<CloseButton *>(sender())->property("tab").value<QObject *>());
|
||||
tab->closeRequest();
|
||||
}
|
||||
|
||||
void TabSupervisor::addCloseButtonToTab(Tab *tab, int tabIndex)
|
||||
{
|
||||
QTabBar::ButtonPosition closeSide =
|
||||
(QTabBar::ButtonPosition)tabBar()->style()->styleHint(QStyle::SH_TabBar_CloseButtonPosition, 0, tabBar());
|
||||
CloseButton *closeButton = new CloseButton;
|
||||
connect(closeButton, SIGNAL(clicked()), this, SLOT(closeButtonPressed()));
|
||||
closeButton->setProperty("tab", QVariant::fromValue((QObject *)tab));
|
||||
tabBar()->setTabButton(tabIndex, closeSide, closeButton);
|
||||
}
|
||||
|
||||
void TabSupervisor::gameJoined(const Event_GameJoined &event)
|
||||
{
|
||||
QMap<int, QString> roomGameTypes;
|
||||
TabRoom *room = roomTabs.value(event.game_info().room_id());
|
||||
if (room)
|
||||
roomGameTypes = room->getGameTypes();
|
||||
else
|
||||
for (int i = 0; i < event.game_types_size(); ++i)
|
||||
roomGameTypes.insert(event.game_types(i).game_type_id(),
|
||||
QString::fromStdString(event.game_types(i).description()));
|
||||
|
||||
TabGame *tab = new TabGame(this, QList<AbstractClient *>() << client, event, roomGameTypes);
|
||||
connect(tab, SIGNAL(gameClosing(TabGame *)), this, SLOT(gameLeft(TabGame *)));
|
||||
connect(tab, SIGNAL(openMessageDialog(const QString &, bool)), this, SLOT(addMessageTab(const QString &, bool)));
|
||||
connect(tab, SIGNAL(openDeckEditor(const DeckLoader *)), this, SLOT(addDeckEditorTab(const DeckLoader *)));
|
||||
int tabIndex = myAddTab(tab);
|
||||
addCloseButtonToTab(tab, tabIndex);
|
||||
gameTabs.insert(event.game_info().game_id(), tab);
|
||||
setCurrentWidget(tab);
|
||||
}
|
||||
|
||||
void TabSupervisor::localGameJoined(const Event_GameJoined &event)
|
||||
{
|
||||
TabGame *tab = new TabGame(this, localClients, event, QMap<int, QString>());
|
||||
connect(tab, SIGNAL(gameClosing(TabGame *)), this, SLOT(gameLeft(TabGame *)));
|
||||
connect(tab, SIGNAL(openDeckEditor(const DeckLoader *)), this, SLOT(addDeckEditorTab(const DeckLoader *)));
|
||||
int tabIndex = myAddTab(tab);
|
||||
addCloseButtonToTab(tab, tabIndex);
|
||||
gameTabs.insert(event.game_info().game_id(), tab);
|
||||
setCurrentWidget(tab);
|
||||
|
||||
for (int i = 1; i < localClients.size(); ++i) {
|
||||
Command_JoinGame cmd;
|
||||
cmd.set_game_id(event.game_info().game_id());
|
||||
localClients[i]->sendCommand(localClients[i]->prepareRoomCommand(cmd, 0));
|
||||
}
|
||||
}
|
||||
|
||||
void TabSupervisor::gameLeft(TabGame *tab)
|
||||
{
|
||||
if (tab == currentWidget())
|
||||
emit setMenu();
|
||||
|
||||
gameTabs.remove(tab->getGameId());
|
||||
removeTab(indexOf(tab));
|
||||
|
||||
if (!localClients.isEmpty())
|
||||
stop();
|
||||
}
|
||||
|
||||
void TabSupervisor::addRoomTab(const ServerInfo_Room &info, bool setCurrent)
|
||||
{
|
||||
TabRoom *tab = new TabRoom(this, client, userInfo, info);
|
||||
connect(tab, SIGNAL(maximizeClient()), this, SLOT(maximizeMainWindow()));
|
||||
connect(tab, SIGNAL(roomClosing(TabRoom *)), this, SLOT(roomLeft(TabRoom *)));
|
||||
connect(tab, SIGNAL(openMessageDialog(const QString &, bool)), this, SLOT(addMessageTab(const QString &, bool)));
|
||||
int tabIndex = myAddTab(tab);
|
||||
addCloseButtonToTab(tab, tabIndex);
|
||||
roomTabs.insert(info.room_id(), tab);
|
||||
if (setCurrent)
|
||||
setCurrentWidget(tab);
|
||||
}
|
||||
|
||||
void TabSupervisor::roomLeft(TabRoom *tab)
|
||||
{
|
||||
if (tab == currentWidget())
|
||||
emit setMenu();
|
||||
|
||||
roomTabs.remove(tab->getRoomId());
|
||||
removeTab(indexOf(tab));
|
||||
}
|
||||
|
||||
void TabSupervisor::openReplay(GameReplay *replay)
|
||||
{
|
||||
TabGame *replayTab = new TabGame(this, replay);
|
||||
connect(replayTab, SIGNAL(gameClosing(TabGame *)), this, SLOT(replayLeft(TabGame *)));
|
||||
int tabIndex = myAddTab(replayTab);
|
||||
addCloseButtonToTab(replayTab, tabIndex);
|
||||
replayTabs.append(replayTab);
|
||||
setCurrentWidget(replayTab);
|
||||
}
|
||||
|
||||
void TabSupervisor::replayLeft(TabGame *tab)
|
||||
{
|
||||
if (tab == currentWidget())
|
||||
emit setMenu();
|
||||
|
||||
replayTabs.removeOne(tab);
|
||||
}
|
||||
|
||||
TabMessage *TabSupervisor::addMessageTab(const QString &receiverName, bool focus)
|
||||
{
|
||||
if (receiverName == QString::fromStdString(userInfo->name()))
|
||||
return nullptr;
|
||||
|
||||
ServerInfo_User otherUser;
|
||||
UserListTWI *twi = tabUserLists->getAllUsersList()->getUsers().value(receiverName);
|
||||
if (twi)
|
||||
otherUser = twi->getUserInfo();
|
||||
else
|
||||
otherUser.set_name(receiverName.toStdString());
|
||||
|
||||
TabMessage *tab;
|
||||
tab = messageTabs.value(QString::fromStdString(otherUser.name()));
|
||||
if (tab) {
|
||||
if (focus)
|
||||
setCurrentWidget(tab);
|
||||
return tab;
|
||||
}
|
||||
|
||||
tab = new TabMessage(this, client, *userInfo, otherUser);
|
||||
connect(tab, SIGNAL(talkClosing(TabMessage *)), this, SLOT(talkLeft(TabMessage *)));
|
||||
connect(tab, SIGNAL(maximizeClient()), this, SLOT(maximizeMainWindow()));
|
||||
int tabIndex = myAddTab(tab);
|
||||
addCloseButtonToTab(tab, tabIndex);
|
||||
messageTabs.insert(receiverName, tab);
|
||||
if (focus)
|
||||
setCurrentWidget(tab);
|
||||
return tab;
|
||||
}
|
||||
|
||||
void TabSupervisor::maximizeMainWindow()
|
||||
{
|
||||
emit showWindowIfHidden();
|
||||
}
|
||||
|
||||
void TabSupervisor::talkLeft(TabMessage *tab)
|
||||
{
|
||||
if (tab == currentWidget())
|
||||
emit setMenu();
|
||||
|
||||
messageTabs.remove(tab->getUserName());
|
||||
removeTab(indexOf(tab));
|
||||
}
|
||||
|
||||
TabDeckEditor *TabSupervisor::addDeckEditorTab(const DeckLoader *deckToOpen)
|
||||
{
|
||||
TabDeckEditor *tab = new TabDeckEditor(this);
|
||||
if (deckToOpen)
|
||||
tab->setDeck(new DeckLoader(*deckToOpen));
|
||||
connect(tab, SIGNAL(deckEditorClosing(TabDeckEditor *)), this, SLOT(deckEditorClosed(TabDeckEditor *)));
|
||||
int tabIndex = myAddTab(tab);
|
||||
addCloseButtonToTab(tab, tabIndex);
|
||||
deckEditorTabs.append(tab);
|
||||
setCurrentWidget(tab);
|
||||
return tab;
|
||||
}
|
||||
|
||||
void TabSupervisor::deckEditorClosed(TabDeckEditor *tab)
|
||||
{
|
||||
if (tab == currentWidget())
|
||||
emit setMenu();
|
||||
|
||||
deckEditorTabs.removeOne(tab);
|
||||
removeTab(indexOf(tab));
|
||||
}
|
||||
|
||||
void TabSupervisor::tabUserEvent(bool globalEvent)
|
||||
{
|
||||
Tab *tab = static_cast<Tab *>(sender());
|
||||
if (tab != currentWidget()) {
|
||||
tab->setContentsChanged(true);
|
||||
setTabIcon(indexOf(tab), QPixmap("theme:icons/tab_changed"));
|
||||
}
|
||||
if (globalEvent && SettingsCache::instance().getNotificationsEnabled())
|
||||
QApplication::alert(this);
|
||||
}
|
||||
|
||||
void TabSupervisor::updateTabText(Tab *tab, const QString &newTabText)
|
||||
{
|
||||
int idx = indexOf(tab);
|
||||
setTabText(idx, sanitizeTabName(newTabText));
|
||||
setTabToolTip(idx, sanitizeHtml(newTabText));
|
||||
}
|
||||
|
||||
void TabSupervisor::processRoomEvent(const RoomEvent &event)
|
||||
{
|
||||
TabRoom *tab = roomTabs.value(event.room_id(), 0);
|
||||
if (tab)
|
||||
tab->processRoomEvent(event);
|
||||
}
|
||||
|
||||
void TabSupervisor::processGameEventContainer(const GameEventContainer &cont)
|
||||
{
|
||||
TabGame *tab = gameTabs.value(cont.game_id());
|
||||
if (tab)
|
||||
tab->processGameEventContainer(cont, qobject_cast<AbstractClient *>(sender()));
|
||||
else
|
||||
qDebug() << "gameEvent: invalid gameId";
|
||||
}
|
||||
|
||||
void TabSupervisor::processUserMessageEvent(const Event_UserMessage &event)
|
||||
{
|
||||
QString senderName = QString::fromStdString(event.sender_name());
|
||||
TabMessage *tab = messageTabs.value(senderName);
|
||||
if (!tab)
|
||||
tab = messageTabs.value(QString::fromStdString(event.receiver_name()));
|
||||
if (!tab) {
|
||||
UserListTWI *twi = tabUserLists->getAllUsersList()->getUsers().value(senderName);
|
||||
if (twi) {
|
||||
UserLevelFlags userLevel = UserLevelFlags(twi->getUserInfo().user_level());
|
||||
if (SettingsCache::instance().getIgnoreUnregisteredUserMessages() &&
|
||||
!userLevel.testFlag(ServerInfo_User::IsRegistered))
|
||||
// Flags are additive, so reg/mod/admin are all IsRegistered
|
||||
return;
|
||||
}
|
||||
tab = addMessageTab(QString::fromStdString(event.sender_name()), false);
|
||||
}
|
||||
if (!tab)
|
||||
return;
|
||||
tab->processUserMessageEvent(event);
|
||||
}
|
||||
|
||||
void TabSupervisor::actShowPopup(const QString &message)
|
||||
{
|
||||
qDebug() << "ACT SHOW POPUP";
|
||||
if (trayIcon && (QApplication::activeWindow() == nullptr || QApplication::focusWidget() == nullptr)) {
|
||||
qDebug() << "LAUNCHING POPUP";
|
||||
// disconnect(trayIcon, SIGNAL(messageClicked()), nullptr, nullptr);
|
||||
trayIcon->showMessage(message, tr("Click to view"));
|
||||
// connect(trayIcon, SIGNAL(messageClicked()), chatView, SLOT(actMessageClicked()));
|
||||
}
|
||||
}
|
||||
|
||||
void TabSupervisor::processUserLeft(const QString &userName)
|
||||
{
|
||||
TabMessage *tab = messageTabs.value(userName);
|
||||
if (tab)
|
||||
tab->processUserLeft();
|
||||
}
|
||||
|
||||
void TabSupervisor::processUserJoined(const ServerInfo_User &userInfoJoined)
|
||||
{
|
||||
QString userName = QString::fromStdString(userInfoJoined.name());
|
||||
if (isUserBuddy(userName)) {
|
||||
Tab *tab = static_cast<Tab *>(getUserListsTab());
|
||||
|
||||
if (tab != currentWidget()) {
|
||||
tab->setContentsChanged(true);
|
||||
QPixmap avatarPixmap =
|
||||
UserLevelPixmapGenerator::generatePixmap(13, (UserLevelFlags)userInfoJoined.user_level(), true,
|
||||
QString::fromStdString(userInfoJoined.privlevel()));
|
||||
setTabIcon(indexOf(tab), QPixmap(avatarPixmap));
|
||||
}
|
||||
|
||||
if (SettingsCache::instance().getBuddyConnectNotificationsEnabled()) {
|
||||
QApplication::alert(this);
|
||||
this->actShowPopup(tr("Your buddy %1 has signed on!").arg(userName));
|
||||
}
|
||||
}
|
||||
|
||||
TabMessage *tab = messageTabs.value(userName);
|
||||
if (tab)
|
||||
tab->processUserJoined(userInfoJoined);
|
||||
}
|
||||
|
||||
void TabSupervisor::updateCurrent(int index)
|
||||
{
|
||||
if (index != -1) {
|
||||
Tab *tab = static_cast<Tab *>(widget(index));
|
||||
if (tab->getContentsChanged()) {
|
||||
setTabIcon(index, QIcon());
|
||||
tab->setContentsChanged(false);
|
||||
}
|
||||
emit setMenu(static_cast<Tab *>(widget(index))->getTabMenus());
|
||||
tab->tabActivated();
|
||||
} else
|
||||
emit setMenu();
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if a user is a moderator/administrator
|
||||
* By seeing if they have the admin tab open & unlocked
|
||||
* @return if the admin tab is open & unlocked
|
||||
*/
|
||||
bool TabSupervisor::getAdminLocked() const
|
||||
{
|
||||
if (!tabAdmin)
|
||||
return true;
|
||||
return tabAdmin->getLocked();
|
||||
}
|
||||
|
||||
void TabSupervisor::processNotifyUserEvent(const Event_NotifyUser &event)
|
||||
{
|
||||
|
||||
switch ((Event_NotifyUser::NotificationType)event.type()) {
|
||||
case Event_NotifyUser::UNKNOWN:
|
||||
QMessageBox::information(
|
||||
this, tr("Unknown Event"),
|
||||
tr("The server has sent you a message that your client does not understand.\nThis message might mean "
|
||||
"there is a new version of Cockatrice available or this server is running a custom or pre-release "
|
||||
"version.\n\nTo update your client, go to Help -> Check for Updates."));
|
||||
break;
|
||||
case Event_NotifyUser::IDLEWARNING:
|
||||
QMessageBox::information(this, tr("Idle Timeout"), tr("You are about to be logged out due to inactivity."));
|
||||
break;
|
||||
case Event_NotifyUser::PROMOTED:
|
||||
QMessageBox::information(
|
||||
this, tr("Promotion"),
|
||||
tr("You have been promoted. Please log out and back in for changes to take effect."));
|
||||
break;
|
||||
case Event_NotifyUser::WARNING: {
|
||||
if (!QString::fromStdString(event.warning_reason()).simplified().isEmpty())
|
||||
QMessageBox::warning(this, tr("Warned"),
|
||||
tr("You have received a warning due to %1.\nPlease refrain from engaging in this "
|
||||
"activity or further actions may be taken against you. If you have any "
|
||||
"questions, please private message a moderator.")
|
||||
.arg(QString::fromStdString(event.warning_reason()).simplified()));
|
||||
break;
|
||||
}
|
||||
case Event_NotifyUser::CUSTOM: {
|
||||
if (!QString::fromStdString(event.custom_title()).simplified().isEmpty() &&
|
||||
!QString::fromStdString(event.custom_content()).simplified().isEmpty()) {
|
||||
QMessageBox msgBox;
|
||||
msgBox.setParent(this);
|
||||
msgBox.setWindowFlags(Qt::Dialog);
|
||||
msgBox.setIcon(QMessageBox::Information);
|
||||
msgBox.setWindowTitle(QString::fromStdString(event.custom_title()).simplified());
|
||||
msgBox.setText(tr("You have received the following message from the server.\n(custom messages like "
|
||||
"these could be untranslated)"));
|
||||
msgBox.setDetailedText(QString::fromStdString(event.custom_content()).simplified());
|
||||
msgBox.setMinimumWidth(200);
|
||||
msgBox.exec();
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:;
|
||||
}
|
||||
}
|
||||
|
||||
bool TabSupervisor::isOwnUserRegistered() const
|
||||
{
|
||||
return userInfo != nullptr && (userInfo->user_level() & ServerInfo_User::IsRegistered) != 0;
|
||||
}
|
||||
|
||||
QString TabSupervisor::getOwnUsername() const
|
||||
{
|
||||
return userInfo != nullptr ? QString::fromStdString(userInfo->name()) : QString();
|
||||
}
|
||||
|
||||
bool TabSupervisor::isUserBuddy(const QString &userName) const
|
||||
{
|
||||
if (!getUserListsTab())
|
||||
return false;
|
||||
if (!getUserListsTab()->getBuddyList())
|
||||
return false;
|
||||
QMap<QString, UserListTWI *> buddyList = getUserListsTab()->getBuddyList()->getUsers();
|
||||
bool senderIsBuddy = buddyList.contains(userName);
|
||||
return senderIsBuddy;
|
||||
}
|
||||
|
||||
bool TabSupervisor::isUserIgnored(const QString &userName) const
|
||||
{
|
||||
if (!getUserListsTab())
|
||||
return false;
|
||||
if (!getUserListsTab()->getIgnoreList())
|
||||
return false;
|
||||
QMap<QString, UserListTWI *> buddyList = getUserListsTab()->getIgnoreList()->getUsers();
|
||||
bool senderIsBuddy = buddyList.contains(userName);
|
||||
return senderIsBuddy;
|
||||
}
|
||||
|
||||
const ServerInfo_User *TabSupervisor::getOnlineUser(const QString &userName) const
|
||||
{
|
||||
if (!getUserListsTab())
|
||||
return nullptr;
|
||||
if (!getUserListsTab()->getAllUsersList())
|
||||
return nullptr;
|
||||
QMap<QString, UserListTWI *> userList = getUserListsTab()->getAllUsersList()->getUsers();
|
||||
const QString &userNameToMatchLower = userName.toLower();
|
||||
QMap<QString, UserListTWI *>::iterator i;
|
||||
|
||||
for (i = userList.begin(); i != userList.end(); ++i)
|
||||
if (i.key().toLower() == userNameToMatchLower) {
|
||||
const ServerInfo_User &_userInfo = i.value()->getUserInfo();
|
||||
return &_userInfo;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
};
|
||||
|
||||
bool TabSupervisor::switchToGameTabIfAlreadyExists(const int gameId)
|
||||
{
|
||||
bool isGameTabExists = false;
|
||||
if (gameTabs.contains(gameId)) {
|
||||
isGameTabExists = true;
|
||||
TabGame *tabGame = gameTabs[gameId];
|
||||
const int gameTabIndex = indexOf(tabGame);
|
||||
setCurrentIndex(gameTabIndex);
|
||||
}
|
||||
|
||||
return isGameTabExists;
|
||||
}
|
||||
158
cockatrice/src/client/tabs/tab_supervisor.h
Normal file
158
cockatrice/src/client/tabs/tab_supervisor.h
Normal file
|
|
@ -0,0 +1,158 @@
|
|||
#ifndef TAB_SUPERVISOR_H
|
||||
#define TAB_SUPERVISOR_H
|
||||
|
||||
#include "../../deck/deck_loader.h"
|
||||
#include "../../server/chat_view/user_list_proxy.h"
|
||||
|
||||
#include <QAbstractButton>
|
||||
#include <QCommonStyle>
|
||||
#include <QMap>
|
||||
#include <QProxyStyle>
|
||||
#include <QTabWidget>
|
||||
|
||||
class QMenu;
|
||||
class AbstractClient;
|
||||
class Tab;
|
||||
class TabServer;
|
||||
class TabRoom;
|
||||
class TabGame;
|
||||
class TabDeckStorage;
|
||||
class TabReplays;
|
||||
class TabAdmin;
|
||||
class TabMessage;
|
||||
class TabUserLists;
|
||||
class TabDeckEditor;
|
||||
class TabLog;
|
||||
class RoomEvent;
|
||||
class GameEventContainer;
|
||||
class Event_GameJoined;
|
||||
class Event_UserMessage;
|
||||
class Event_NotifyUser;
|
||||
class ServerInfo_Room;
|
||||
class ServerInfo_User;
|
||||
class GameReplay;
|
||||
class DeckList;
|
||||
|
||||
class MacOSTabFixStyle : public QProxyStyle
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
QRect subElementRect(SubElement, const QStyleOption *, const QWidget *) const;
|
||||
};
|
||||
|
||||
class CloseButton : public QAbstractButton
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
CloseButton(QWidget *parent = nullptr);
|
||||
QSize sizeHint() const;
|
||||
inline QSize minimumSizeHint() const
|
||||
{
|
||||
return sizeHint();
|
||||
}
|
||||
|
||||
protected:
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
|
||||
void enterEvent(QEnterEvent *event);
|
||||
#else
|
||||
void enterEvent(QEvent *event);
|
||||
#endif
|
||||
void leaveEvent(QEvent *event);
|
||||
void paintEvent(QPaintEvent *event);
|
||||
};
|
||||
|
||||
class TabSupervisor : public QTabWidget, public UserlistProxy
|
||||
{
|
||||
Q_OBJECT
|
||||
private:
|
||||
ServerInfo_User *userInfo;
|
||||
AbstractClient *client;
|
||||
QList<AbstractClient *> localClients;
|
||||
TabServer *tabServer;
|
||||
TabUserLists *tabUserLists;
|
||||
TabDeckStorage *tabDeckStorage;
|
||||
TabReplays *tabReplays;
|
||||
TabAdmin *tabAdmin;
|
||||
TabLog *tabLog;
|
||||
QMap<int, TabRoom *> roomTabs;
|
||||
QMap<int, TabGame *> gameTabs;
|
||||
QList<TabGame *> replayTabs;
|
||||
QMap<QString, TabMessage *> messageTabs;
|
||||
QList<TabDeckEditor *> deckEditorTabs;
|
||||
int myAddTab(Tab *tab);
|
||||
void addCloseButtonToTab(Tab *tab, int tabIndex);
|
||||
QString sanitizeTabName(QString dirty) const;
|
||||
QString sanitizeHtml(QString dirty) const;
|
||||
bool isLocalGame;
|
||||
|
||||
public:
|
||||
TabSupervisor(AbstractClient *_client, QWidget *parent = nullptr);
|
||||
~TabSupervisor();
|
||||
void retranslateUi();
|
||||
void start(const ServerInfo_User &userInfo);
|
||||
void startLocal(const QList<AbstractClient *> &_clients);
|
||||
void stop();
|
||||
bool getIsLocalGame() const
|
||||
{
|
||||
return isLocalGame;
|
||||
}
|
||||
int getGameCount() const
|
||||
{
|
||||
return gameTabs.size();
|
||||
}
|
||||
TabUserLists *getUserListsTab() const
|
||||
{
|
||||
return tabUserLists;
|
||||
}
|
||||
ServerInfo_User *getUserInfo() const
|
||||
{
|
||||
return userInfo;
|
||||
}
|
||||
AbstractClient *getClient() const;
|
||||
const QMap<int, TabRoom *> &getRoomTabs() const
|
||||
{
|
||||
return roomTabs;
|
||||
}
|
||||
bool getAdminLocked() const;
|
||||
bool closeRequest();
|
||||
bool isOwnUserRegistered() const;
|
||||
QString getOwnUsername() const;
|
||||
bool isUserBuddy(const QString &userName) const;
|
||||
bool isUserIgnored(const QString &userName) const;
|
||||
const ServerInfo_User *getOnlineUser(const QString &userName) const;
|
||||
bool switchToGameTabIfAlreadyExists(const int gameId);
|
||||
void actShowPopup(const QString &message);
|
||||
signals:
|
||||
void setMenu(const QList<QMenu *> &newMenuList = QList<QMenu *>());
|
||||
void localGameEnded();
|
||||
void adminLockChanged(bool lock);
|
||||
void showWindowIfHidden();
|
||||
|
||||
public slots:
|
||||
TabDeckEditor *addDeckEditorTab(const DeckLoader *deckToOpen);
|
||||
void openReplay(GameReplay *replay);
|
||||
void maximizeMainWindow();
|
||||
private slots:
|
||||
void closeButtonPressed();
|
||||
void updateCurrent(int index);
|
||||
void updatePingTime(int value, int max);
|
||||
void gameJoined(const Event_GameJoined &event);
|
||||
void localGameJoined(const Event_GameJoined &event);
|
||||
void gameLeft(TabGame *tab);
|
||||
void addRoomTab(const ServerInfo_Room &info, bool setCurrent);
|
||||
void roomLeft(TabRoom *tab);
|
||||
TabMessage *addMessageTab(const QString &userName, bool focus);
|
||||
void replayLeft(TabGame *tab);
|
||||
void processUserLeft(const QString &userName);
|
||||
void processUserJoined(const ServerInfo_User &userInfo);
|
||||
void talkLeft(TabMessage *tab);
|
||||
void deckEditorClosed(TabDeckEditor *tab);
|
||||
void tabUserEvent(bool globalEvent);
|
||||
void updateTabText(Tab *tab, const QString &newTabText);
|
||||
void processRoomEvent(const RoomEvent &event);
|
||||
void processGameEventContainer(const GameEventContainer &cont);
|
||||
void processUserMessageEvent(const Event_UserMessage &event);
|
||||
void processNotifyUserEvent(const Event_NotifyUser &event);
|
||||
};
|
||||
|
||||
#endif
|
||||
122
cockatrice/src/client/tapped_out_interface.cpp
Normal file
122
cockatrice/src/client/tapped_out_interface.cpp
Normal file
|
|
@ -0,0 +1,122 @@
|
|||
#include "tapped_out_interface.h"
|
||||
|
||||
#include "decklist.h"
|
||||
|
||||
#include <QDesktopServices>
|
||||
#include <QMessageBox>
|
||||
#include <QNetworkAccessManager>
|
||||
#include <QNetworkReply>
|
||||
#include <QNetworkRequest>
|
||||
#include <QRegularExpression>
|
||||
#include <QUrlQuery>
|
||||
|
||||
TappedOutInterface::TappedOutInterface(CardDatabase &_cardDatabase, QObject *parent)
|
||||
: QObject(parent), cardDatabase(_cardDatabase)
|
||||
{
|
||||
manager = new QNetworkAccessManager(this);
|
||||
connect(manager, SIGNAL(finished(QNetworkReply *)), this, SLOT(queryFinished(QNetworkReply *)));
|
||||
}
|
||||
|
||||
void TappedOutInterface::queryFinished(QNetworkReply *reply)
|
||||
{
|
||||
if (reply->error() != QNetworkReply::NoError) {
|
||||
QMessageBox::critical(nullptr, tr("Error"), reply->errorString());
|
||||
reply->deleteLater();
|
||||
deleteLater();
|
||||
return;
|
||||
}
|
||||
|
||||
int httpStatus = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
||||
if (reply->hasRawHeader("Location")) {
|
||||
/*
|
||||
* If the reply contains a "Location" header, a relative URL to the deck on TappedOut
|
||||
* can be extracted from the header. The http status is a 302 "redirect".
|
||||
*/
|
||||
QString deckUrl = reply->rawHeader("Location");
|
||||
qDebug() << "Tappedout: good reply, http status" << httpStatus << "location" << deckUrl;
|
||||
QDesktopServices::openUrl("https://tappedout.net" + deckUrl);
|
||||
} else {
|
||||
/*
|
||||
* Otherwise, the deck has not been parsed correctly. Error messages can be extracted
|
||||
* from the html. Css pseudo selector for errors: $("div.alert-danger > ul > li")
|
||||
*/
|
||||
QString data(reply->readAll());
|
||||
QStringList errorMessageList = {tr("Unable to analyze the deck.")};
|
||||
|
||||
static const QRegularExpression rx("<div class=\"alert alert-danger.*?<ul>(.*?)</ul>");
|
||||
auto match = rx.match(data);
|
||||
if (match.hasMatch()) {
|
||||
QString errors = match.captured(1);
|
||||
static const QRegularExpression rx2("<li>(.*?)</li>");
|
||||
static const QRegularExpression rxremove("<[^>]*>");
|
||||
auto matchIterator = rx2.globalMatch(errors);
|
||||
while (matchIterator.hasNext()) {
|
||||
auto match2 = matchIterator.next();
|
||||
errorMessageList.append(match2.captured(1).remove(rxremove).simplified());
|
||||
}
|
||||
}
|
||||
|
||||
QString errorMessage = errorMessageList.join("\n");
|
||||
qDebug() << "Tappedout: bad reply, http status" << httpStatus << "size" << data.size() << "message"
|
||||
<< errorMessage;
|
||||
|
||||
QMessageBox::critical(nullptr, tr("Error"), errorMessage);
|
||||
}
|
||||
|
||||
reply->deleteLater();
|
||||
deleteLater();
|
||||
}
|
||||
|
||||
void TappedOutInterface::getAnalyzeRequestData(DeckList *deck, QByteArray *data)
|
||||
{
|
||||
DeckList mainboard, sideboard;
|
||||
copyDeckSplitMainAndSide(*deck, mainboard, sideboard);
|
||||
|
||||
QUrl params;
|
||||
QUrlQuery urlQuery;
|
||||
urlQuery.addQueryItem("name", deck->getName());
|
||||
urlQuery.addQueryItem("mainboard", mainboard.writeToString_Plain(false, true));
|
||||
urlQuery.addQueryItem("sideboard", sideboard.writeToString_Plain(false, true));
|
||||
params.setQuery(urlQuery);
|
||||
data->append(params.query(QUrl::EncodeReserved).toUtf8());
|
||||
}
|
||||
|
||||
void TappedOutInterface::analyzeDeck(DeckList *deck)
|
||||
{
|
||||
QByteArray data;
|
||||
getAnalyzeRequestData(deck, &data);
|
||||
|
||||
QNetworkRequest request(QUrl("https://tappedout.net/mtg-decks/paste/"));
|
||||
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");
|
||||
|
||||
manager->post(request, data);
|
||||
}
|
||||
|
||||
struct CopyMainOrSide
|
||||
{
|
||||
CardDatabase &cardDatabase;
|
||||
DeckList &mainboard, &sideboard;
|
||||
|
||||
CopyMainOrSide(CardDatabase &_cardDatabase, DeckList &_mainboard, DeckList &_sideboard)
|
||||
: cardDatabase(_cardDatabase), mainboard(_mainboard), sideboard(_sideboard){};
|
||||
|
||||
void operator()(const InnerDecklistNode *node, const DecklistCardNode *card) const
|
||||
{
|
||||
CardInfoPtr dbCard = cardDatabase.getCard(card->getName());
|
||||
if (!dbCard || dbCard->getIsToken())
|
||||
return;
|
||||
|
||||
DecklistCardNode *addedCard;
|
||||
if (node->getName() == DECK_ZONE_SIDE)
|
||||
addedCard = sideboard.addCard(card->getName(), node->getName());
|
||||
else
|
||||
addedCard = mainboard.addCard(card->getName(), node->getName());
|
||||
addedCard->setNumber(card->getNumber());
|
||||
}
|
||||
};
|
||||
|
||||
void TappedOutInterface::copyDeckSplitMainAndSide(const DeckList &source, DeckList &mainboard, DeckList &sideboard)
|
||||
{
|
||||
CopyMainOrSide copyMainOrSide(cardDatabase, mainboard, sideboard);
|
||||
source.forEachCard(copyMainOrSide);
|
||||
}
|
||||
37
cockatrice/src/client/tapped_out_interface.h
Normal file
37
cockatrice/src/client/tapped_out_interface.h
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
#ifndef TAPPEDOUT_INTERFACE_H
|
||||
#define TAPPEDOUT_INTERFACE_H
|
||||
|
||||
#include "../game/cards/card_database.h"
|
||||
#include "decklist.h"
|
||||
|
||||
#include <QObject>
|
||||
|
||||
class QByteArray;
|
||||
class QNetworkAccessManager;
|
||||
class QNetworkReply;
|
||||
class DeckList;
|
||||
|
||||
/**
|
||||
* TappedOutInterface exists in order to support the "Analyze on TappedOut" feature.
|
||||
* An http POST request is sent and the result is retrieved from the reply. Parsing
|
||||
* logic is implemented in TappedOutInterface::queryFinished().
|
||||
*/
|
||||
|
||||
class TappedOutInterface : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
private:
|
||||
QNetworkAccessManager *manager;
|
||||
|
||||
CardDatabase &cardDatabase;
|
||||
void copyDeckSplitMainAndSide(const DeckList &source, DeckList &mainboard, DeckList &sideboard);
|
||||
private slots:
|
||||
void queryFinished(QNetworkReply *reply);
|
||||
void getAnalyzeRequestData(DeckList *deck, QByteArray *data);
|
||||
|
||||
public:
|
||||
TappedOutInterface(CardDatabase &_cardDatabase, QObject *parent = nullptr);
|
||||
void analyzeDeck(DeckList *deck);
|
||||
};
|
||||
|
||||
#endif
|
||||
30
cockatrice/src/client/tearoff_menu.h
Normal file
30
cockatrice/src/client/tearoff_menu.h
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
#pragma once
|
||||
|
||||
#include "../settings/cache_settings.h"
|
||||
|
||||
#include <QMenu>
|
||||
|
||||
class TearOffMenu : public QMenu
|
||||
{
|
||||
public:
|
||||
TearOffMenu(const QString &title, QWidget *parent = nullptr) : QMenu(title, parent)
|
||||
{
|
||||
connect(&SettingsCache::instance(), &SettingsCache::useTearOffMenusChanged, this,
|
||||
[=](bool state) { setTearOffEnabled(state); });
|
||||
setTearOffEnabled(SettingsCache::instance().getUseTearOffMenus());
|
||||
}
|
||||
|
||||
TearOffMenu(QWidget *parent = nullptr) : QMenu(parent)
|
||||
{
|
||||
connect(&SettingsCache::instance(), &SettingsCache::useTearOffMenusChanged, this,
|
||||
[=](bool state) { setTearOffEnabled(state); });
|
||||
setTearOffEnabled(SettingsCache::instance().getUseTearOffMenus());
|
||||
}
|
||||
|
||||
TearOffMenu *addTearOffMenu(const QString &title)
|
||||
{
|
||||
TearOffMenu *menu = new TearOffMenu(title, this);
|
||||
addMenu(menu);
|
||||
return menu;
|
||||
}
|
||||
};
|
||||
11
cockatrice/src/client/translate_counter_name.cpp
Normal file
11
cockatrice/src/client/translate_counter_name.cpp
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
#include "translate_counter_name.h"
|
||||
|
||||
const QMap<QString, QString> TranslateCounterName::translated = {
|
||||
{"life", QT_TRANSLATE_NOOP("TranslateCounterName", "Life")},
|
||||
{"w", QT_TRANSLATE_NOOP("TranslateCounterName", "White")},
|
||||
{"u", QT_TRANSLATE_NOOP("TranslateCounterName", "Blue")},
|
||||
{"b", QT_TRANSLATE_NOOP("TranslateCounterName", "Black")},
|
||||
{"r", QT_TRANSLATE_NOOP("TranslateCounterName", "Red")},
|
||||
{"g", QT_TRANSLATE_NOOP("TranslateCounterName", "Green")},
|
||||
{"x", QT_TRANSLATE_NOOP("TranslateCounterName", "Colorless")},
|
||||
{"storm", QT_TRANSLATE_NOOP("TranslateCounterName", "Other")}};
|
||||
24
cockatrice/src/client/translate_counter_name.h
Normal file
24
cockatrice/src/client/translate_counter_name.h
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
#ifndef TRANSLATECOUNTERNAME_H
|
||||
#define TRANSLATECOUNTERNAME_H
|
||||
|
||||
#include <QString>
|
||||
#include <QtCore>
|
||||
|
||||
class TranslateCounterName
|
||||
{
|
||||
Q_DECLARE_TR_FUNCTIONS(TranslateCounterName)
|
||||
|
||||
static const QMap<QString, QString> translated;
|
||||
|
||||
public:
|
||||
static QString getDisplayName(const QString &name)
|
||||
{
|
||||
if (translated.contains(name)) {
|
||||
return tr(translated[name].toLatin1());
|
||||
} else {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
#endif // TRANSLATECOUNTERNAME_H
|
||||
13
cockatrice/src/client/translation.h
Normal file
13
cockatrice/src/client/translation.h
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
#ifndef TRANSLATION_H
|
||||
#define TRANSLATION_H
|
||||
|
||||
enum GrammaticalCase
|
||||
{
|
||||
CaseNominative,
|
||||
CaseLookAtZone,
|
||||
CaseTopCardsOfZone,
|
||||
CaseRevealZone,
|
||||
CaseShuffleZone
|
||||
};
|
||||
|
||||
#endif
|
||||
133
cockatrice/src/client/ui/line_edit_completer.cpp
Normal file
133
cockatrice/src/client/ui/line_edit_completer.cpp
Normal file
|
|
@ -0,0 +1,133 @@
|
|||
#include "line_edit_completer.h"
|
||||
|
||||
#include <QAbstractItemView>
|
||||
#include <QCompleter>
|
||||
#include <QFocusEvent>
|
||||
#include <QKeyEvent>
|
||||
#include <QScrollBar>
|
||||
#include <QStringListModel>
|
||||
#include <QTextCursor>
|
||||
#include <QWidget>
|
||||
|
||||
LineEditCompleter::LineEditCompleter(QWidget *parent) : LineEditUnfocusable(parent), c(nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
void LineEditCompleter::focusOutEvent(QFocusEvent *e)
|
||||
{
|
||||
LineEditUnfocusable::focusOutEvent(e);
|
||||
if (c->popup()->isVisible()) {
|
||||
// Remove Popup
|
||||
c->popup()->hide();
|
||||
// Truncate the line to last space or whole string
|
||||
QString textValue = text();
|
||||
int lastIndex = textValue.length();
|
||||
int lastWordStartIndex = textValue.lastIndexOf(" ") + 1;
|
||||
int leftShift = qMin(lastIndex, lastWordStartIndex);
|
||||
setText(textValue.left(leftShift));
|
||||
// Insert highlighted line from popup
|
||||
insert(c->completionModel()->index(c->popup()->currentIndex().row(), 0).data().toString() + " ");
|
||||
// Set focus back to the textbox since tab was pressed
|
||||
setFocus();
|
||||
}
|
||||
}
|
||||
|
||||
void LineEditCompleter::keyPressEvent(QKeyEvent *event)
|
||||
{
|
||||
switch (event->key()) {
|
||||
case Qt::Key_Return:
|
||||
case Qt::Key_Enter:
|
||||
case Qt::Key_Escape:
|
||||
if (c->popup()->isVisible()) {
|
||||
event->ignore();
|
||||
// Remove Popup
|
||||
c->popup()->hide();
|
||||
// Truncate the line to last space or whole string
|
||||
QString textValue = text();
|
||||
int lastIndexof = qMax(0, textValue.lastIndexOf(" "));
|
||||
QString finalString = textValue.left(lastIndexof);
|
||||
// Add a space if there's a word
|
||||
if (finalString != "")
|
||||
finalString += " ";
|
||||
setText(finalString);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case Qt::Key_Space:
|
||||
if (c->popup()->isVisible()) {
|
||||
event->ignore();
|
||||
// Remove Popup
|
||||
c->popup()->hide();
|
||||
// Truncate the line to last space or whole string
|
||||
QString textValue = text();
|
||||
int lastIndex = textValue.length();
|
||||
int lastWordStartIndex = textValue.lastIndexOf(" ") + 1;
|
||||
int leftShift = qMin(lastIndex, lastWordStartIndex);
|
||||
setText(textValue.left(leftShift));
|
||||
// Insert highlighted line from popup
|
||||
insert(c->completionModel()->index(c->popup()->currentIndex().row(), 0).data().toString() + " ");
|
||||
return;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
LineEditUnfocusable::keyPressEvent(event);
|
||||
// return if the completer is null or if the most recently typed char was '@'.
|
||||
// Only want the popup AFTER typing the first char of the mention.
|
||||
if (!c || text().right(1).contains("@")) {
|
||||
c->popup()->hide();
|
||||
return;
|
||||
}
|
||||
|
||||
// Set new completion prefix
|
||||
c->setCompletionPrefix(cursorWord(text()));
|
||||
if (c->completionPrefix().length() < 1) {
|
||||
c->popup()->hide();
|
||||
return;
|
||||
}
|
||||
|
||||
// Draw completion box
|
||||
QRect cr = cursorRect();
|
||||
cr.setWidth(c->popup()->sizeHintForColumn(0) + c->popup()->verticalScrollBar()->sizeHint().width());
|
||||
c->complete(cr);
|
||||
|
||||
// Select first item in the completion popup
|
||||
QItemSelectionModel *sm = new QItemSelectionModel(c->completionModel());
|
||||
c->popup()->setSelectionModel(sm);
|
||||
sm->select(c->completionModel()->index(0, 0), QItemSelectionModel::ClearAndSelect);
|
||||
sm->setCurrentIndex(c->completionModel()->index(0, 0), QItemSelectionModel::NoUpdate);
|
||||
}
|
||||
|
||||
QString LineEditCompleter::cursorWord(const QString &line) const
|
||||
{
|
||||
return line.mid(line.left(cursorPosition()).lastIndexOf(" ") + 1,
|
||||
cursorPosition() - line.left(cursorPosition()).lastIndexOf(" ") - 1);
|
||||
}
|
||||
|
||||
void LineEditCompleter::insertCompletion(QString arg)
|
||||
{
|
||||
QString s_arg = arg + " ";
|
||||
setText(text().replace(text().left(cursorPosition()).lastIndexOf(" ") + 1,
|
||||
cursorPosition() - text().left(cursorPosition()).lastIndexOf(" ") - 1, s_arg));
|
||||
}
|
||||
|
||||
void LineEditCompleter::setCompleter(QCompleter *completer)
|
||||
{
|
||||
c = completer;
|
||||
c->setWidget(this);
|
||||
connect(c, SIGNAL(activated(QString)), this, SLOT(insertCompletion(QString)));
|
||||
}
|
||||
|
||||
void LineEditCompleter::setCompletionList(QStringList completionList)
|
||||
{
|
||||
if (!c || c->popup()->isVisible())
|
||||
return;
|
||||
|
||||
QStringListModel *model;
|
||||
model = (QStringListModel *)(c->model());
|
||||
if (model == NULL)
|
||||
model = new QStringListModel();
|
||||
model->setStringList(completionList);
|
||||
}
|
||||
29
cockatrice/src/client/ui/line_edit_completer.h
Normal file
29
cockatrice/src/client/ui/line_edit_completer.h
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
#ifndef LINEEDITCOMPLETER_H
|
||||
#define LINEEDITCOMPLETER_H
|
||||
|
||||
#include "../../deck/custom_line_edit.h"
|
||||
|
||||
#include <QFocusEvent>
|
||||
#include <QKeyEvent>
|
||||
#include <QStringList>
|
||||
|
||||
class LineEditCompleter : public LineEditUnfocusable
|
||||
{
|
||||
Q_OBJECT
|
||||
private:
|
||||
QString cursorWord(const QString &line) const;
|
||||
QCompleter *c;
|
||||
private slots:
|
||||
void insertCompletion(QString);
|
||||
|
||||
protected:
|
||||
void keyPressEvent(QKeyEvent *event);
|
||||
void focusOutEvent(QFocusEvent *e);
|
||||
|
||||
public:
|
||||
explicit LineEditCompleter(QWidget *parent = nullptr);
|
||||
void setCompleter(QCompleter *);
|
||||
void setCompletionList(QStringList);
|
||||
};
|
||||
|
||||
#endif
|
||||
273
cockatrice/src/client/ui/phases_toolbar.cpp
Normal file
273
cockatrice/src/client/ui/phases_toolbar.cpp
Normal file
|
|
@ -0,0 +1,273 @@
|
|||
#include "phases_toolbar.h"
|
||||
|
||||
#include "pb/command_draw_cards.pb.h"
|
||||
#include "pb/command_next_turn.pb.h"
|
||||
#include "pb/command_set_active_phase.pb.h"
|
||||
#include "pb/command_set_card_attr.pb.h"
|
||||
#include "pixel_map_generator.h"
|
||||
|
||||
#include <QAction>
|
||||
#include <QDebug>
|
||||
#include <QPainter>
|
||||
#include <QPen>
|
||||
#include <QTimer>
|
||||
|
||||
PhaseButton::PhaseButton(const QString &_name, QGraphicsItem *parent, QAction *_doubleClickAction, bool _highlightable)
|
||||
: QObject(), QGraphicsItem(parent), name(_name), active(false), highlightable(_highlightable),
|
||||
activeAnimationCounter(0), doubleClickAction(_doubleClickAction), width(50)
|
||||
{
|
||||
if (highlightable) {
|
||||
activeAnimationTimer = new QTimer(this);
|
||||
connect(activeAnimationTimer, SIGNAL(timeout()), this, SLOT(updateAnimation()));
|
||||
activeAnimationTimer->setSingleShot(false);
|
||||
} else
|
||||
activeAnimationCounter = 9;
|
||||
|
||||
setCacheMode(DeviceCoordinateCache);
|
||||
}
|
||||
|
||||
QRectF PhaseButton::boundingRect() const
|
||||
{
|
||||
return {0, 0, width, width};
|
||||
}
|
||||
|
||||
void PhaseButton::paint(QPainter *painter, const QStyleOptionGraphicsItem * /*option*/, QWidget * /*widget*/)
|
||||
{
|
||||
QRectF iconRect = boundingRect().adjusted(3, 3, -3, -3);
|
||||
QRectF translatedIconRect = painter->combinedTransform().mapRect(iconRect);
|
||||
qreal scaleFactor = translatedIconRect.width() / iconRect.width();
|
||||
QPixmap iconPixmap = PhasePixmapGenerator::generatePixmap(qRound(translatedIconRect.height()), name);
|
||||
|
||||
painter->setBrush(QColor(static_cast<int>(220 * (activeAnimationCounter / 10.0)),
|
||||
static_cast<int>(220 * (activeAnimationCounter / 10.0)),
|
||||
static_cast<int>(220 * (activeAnimationCounter / 10.0))));
|
||||
painter->setPen(Qt::gray);
|
||||
painter->drawRect(0, 0, static_cast<int>(width - 1), static_cast<int>(width - 1));
|
||||
painter->save();
|
||||
resetPainterTransform(painter);
|
||||
painter->drawPixmap(iconPixmap.rect().translated(qRound(3 * scaleFactor), qRound(3 * scaleFactor)), iconPixmap,
|
||||
iconPixmap.rect());
|
||||
painter->restore();
|
||||
|
||||
painter->setBrush(QColor(0, 0, 0, static_cast<int>(255 * ((10 - activeAnimationCounter) / 15.0))));
|
||||
painter->setPen(Qt::gray);
|
||||
painter->drawRect(0, 0, static_cast<int>(width - 1), static_cast<int>(width - 1));
|
||||
}
|
||||
|
||||
void PhaseButton::setWidth(double _width)
|
||||
{
|
||||
prepareGeometryChange();
|
||||
width = _width;
|
||||
}
|
||||
|
||||
void PhaseButton::setActive(bool _active)
|
||||
{
|
||||
if ((active == _active) || !highlightable)
|
||||
return;
|
||||
|
||||
active = _active;
|
||||
activeAnimationTimer->start(25);
|
||||
}
|
||||
|
||||
void PhaseButton::updateAnimation()
|
||||
{
|
||||
if (!highlightable)
|
||||
return;
|
||||
|
||||
if (active) {
|
||||
if (++activeAnimationCounter >= 10)
|
||||
activeAnimationTimer->stop();
|
||||
} else {
|
||||
if (--activeAnimationCounter <= 0)
|
||||
activeAnimationTimer->stop();
|
||||
}
|
||||
update();
|
||||
}
|
||||
|
||||
void PhaseButton::mousePressEvent(QGraphicsSceneMouseEvent * /*event*/)
|
||||
{
|
||||
emit clicked();
|
||||
}
|
||||
|
||||
void PhaseButton::mouseDoubleClickEvent(QGraphicsSceneMouseEvent * /*event*/)
|
||||
{
|
||||
triggerDoubleClickAction();
|
||||
}
|
||||
|
||||
void PhaseButton::triggerDoubleClickAction()
|
||||
{
|
||||
if (doubleClickAction)
|
||||
doubleClickAction->trigger();
|
||||
}
|
||||
|
||||
PhasesToolbar::PhasesToolbar(QGraphicsItem *parent)
|
||||
: QGraphicsItem(parent), width(100), height(100), ySpacing(1), symbolSize(8)
|
||||
{
|
||||
auto *aUntapAll = new QAction(this);
|
||||
connect(aUntapAll, SIGNAL(triggered()), this, SLOT(actUntapAll()));
|
||||
auto *aDrawCard = new QAction(this);
|
||||
connect(aDrawCard, SIGNAL(triggered()), this, SLOT(actDrawCard()));
|
||||
|
||||
PhaseButton *untapButton = new PhaseButton("untap", this, aUntapAll);
|
||||
PhaseButton *upkeepButton = new PhaseButton("upkeep", this);
|
||||
PhaseButton *drawButton = new PhaseButton("draw", this, aDrawCard);
|
||||
PhaseButton *main1Button = new PhaseButton("main1", this);
|
||||
PhaseButton *combatStartButton = new PhaseButton("combat_start", this);
|
||||
PhaseButton *combatAttackersButton = new PhaseButton("combat_attackers", this);
|
||||
PhaseButton *combatBlockersButton = new PhaseButton("combat_blockers", this);
|
||||
PhaseButton *combatDamageButton = new PhaseButton("combat_damage", this);
|
||||
PhaseButton *combatEndButton = new PhaseButton("combat_end", this);
|
||||
PhaseButton *main2Button = new PhaseButton("main2", this);
|
||||
PhaseButton *cleanupButton = new PhaseButton("cleanup", this);
|
||||
|
||||
buttonList << untapButton << upkeepButton << drawButton << main1Button << combatStartButton << combatAttackersButton
|
||||
<< combatBlockersButton << combatDamageButton << combatEndButton << main2Button << cleanupButton;
|
||||
|
||||
for (auto &i : buttonList)
|
||||
connect(i, SIGNAL(clicked()), this, SLOT(phaseButtonClicked()));
|
||||
|
||||
nextTurnButton = new PhaseButton("nextturn", this, nullptr, false);
|
||||
connect(nextTurnButton, SIGNAL(clicked()), this, SLOT(actNextTurn()));
|
||||
|
||||
rearrangeButtons();
|
||||
|
||||
retranslateUi();
|
||||
}
|
||||
|
||||
QRectF PhasesToolbar::boundingRect() const
|
||||
{
|
||||
return {0, 0, width, height};
|
||||
}
|
||||
|
||||
void PhasesToolbar::retranslateUi()
|
||||
{
|
||||
for (int i = 0; i < buttonList.size(); ++i)
|
||||
buttonList[i]->setToolTip(getLongPhaseName(i));
|
||||
}
|
||||
|
||||
QString PhasesToolbar::getLongPhaseName(int phase) const
|
||||
{
|
||||
switch (phase) {
|
||||
case 0:
|
||||
return tr("Untap step");
|
||||
case 1:
|
||||
return tr("Upkeep step");
|
||||
case 2:
|
||||
return tr("Draw step");
|
||||
case 3:
|
||||
return tr("First main phase");
|
||||
case 4:
|
||||
return tr("Beginning of combat step");
|
||||
case 5:
|
||||
return tr("Declare attackers step");
|
||||
case 6:
|
||||
return tr("Declare blockers step");
|
||||
case 7:
|
||||
return tr("Combat damage step");
|
||||
case 8:
|
||||
return tr("End of combat step");
|
||||
case 9:
|
||||
return tr("Second main phase");
|
||||
case 10:
|
||||
return tr("End of turn step");
|
||||
default:
|
||||
return QString();
|
||||
}
|
||||
}
|
||||
|
||||
void PhasesToolbar::paint(QPainter *painter, const QStyleOptionGraphicsItem * /*option*/, QWidget * /*widget*/)
|
||||
{
|
||||
painter->fillRect(boundingRect(), QColor(50, 50, 50));
|
||||
}
|
||||
|
||||
const double PhasesToolbar::marginSize = 3;
|
||||
|
||||
void PhasesToolbar::rearrangeButtons()
|
||||
{
|
||||
for (auto &i : buttonList)
|
||||
i->setWidth(symbolSize);
|
||||
nextTurnButton->setWidth(symbolSize);
|
||||
|
||||
double y = marginSize;
|
||||
buttonList[0]->setPos(marginSize, y);
|
||||
buttonList[1]->setPos(marginSize, y += symbolSize);
|
||||
buttonList[2]->setPos(marginSize, y += symbolSize);
|
||||
y += ySpacing;
|
||||
buttonList[3]->setPos(marginSize, y += symbolSize);
|
||||
y += ySpacing;
|
||||
buttonList[4]->setPos(marginSize, y += symbolSize);
|
||||
buttonList[5]->setPos(marginSize, y += symbolSize);
|
||||
buttonList[6]->setPos(marginSize, y += symbolSize);
|
||||
buttonList[7]->setPos(marginSize, y += symbolSize);
|
||||
buttonList[8]->setPos(marginSize, y += symbolSize);
|
||||
y += ySpacing;
|
||||
buttonList[9]->setPos(marginSize, y += symbolSize);
|
||||
y += ySpacing;
|
||||
buttonList[10]->setPos(marginSize, y += symbolSize);
|
||||
y += ySpacing;
|
||||
y += ySpacing;
|
||||
nextTurnButton->setPos(marginSize, y + symbolSize);
|
||||
}
|
||||
|
||||
void PhasesToolbar::setHeight(double _height)
|
||||
{
|
||||
prepareGeometryChange();
|
||||
|
||||
height = _height;
|
||||
ySpacing = (height - 2 * marginSize) / (buttonCount * 5 + spaceCount);
|
||||
symbolSize = ySpacing * 5;
|
||||
width = symbolSize + 2 * marginSize;
|
||||
|
||||
rearrangeButtons();
|
||||
}
|
||||
|
||||
void PhasesToolbar::setActivePhase(int phase)
|
||||
{
|
||||
if (phase >= buttonList.size())
|
||||
return;
|
||||
|
||||
for (int i = 0; i < buttonList.size(); ++i)
|
||||
buttonList[i]->setActive(i == phase);
|
||||
}
|
||||
|
||||
void PhasesToolbar::triggerPhaseAction(int phase)
|
||||
{
|
||||
if (0 <= phase && phase < buttonList.size()) {
|
||||
buttonList[phase]->triggerDoubleClickAction();
|
||||
}
|
||||
}
|
||||
|
||||
void PhasesToolbar::phaseButtonClicked()
|
||||
{
|
||||
auto *button = qobject_cast<PhaseButton *>(sender());
|
||||
if (button->getActive())
|
||||
button->triggerDoubleClickAction();
|
||||
|
||||
Command_SetActivePhase cmd;
|
||||
cmd.set_phase(static_cast<google::protobuf::uint32>(buttonList.indexOf(button)));
|
||||
|
||||
emit sendGameCommand(cmd, -1);
|
||||
}
|
||||
|
||||
void PhasesToolbar::actNextTurn()
|
||||
{
|
||||
emit sendGameCommand(Command_NextTurn(), -1);
|
||||
}
|
||||
|
||||
void PhasesToolbar::actUntapAll()
|
||||
{
|
||||
Command_SetCardAttr cmd;
|
||||
cmd.set_zone("table");
|
||||
cmd.set_attribute(AttrTapped);
|
||||
cmd.set_attr_value("0");
|
||||
|
||||
emit sendGameCommand(cmd, -1);
|
||||
}
|
||||
|
||||
void PhasesToolbar::actDrawCard()
|
||||
{
|
||||
Command_DrawCards cmd;
|
||||
cmd.set_number(1);
|
||||
|
||||
emit sendGameCommand(cmd, -1);
|
||||
}
|
||||
100
cockatrice/src/client/ui/phases_toolbar.h
Normal file
100
cockatrice/src/client/ui/phases_toolbar.h
Normal file
|
|
@ -0,0 +1,100 @@
|
|||
#ifndef PHASESTOOLBAR_H
|
||||
#define PHASESTOOLBAR_H
|
||||
|
||||
#include "../../game/board/abstract_graphics_item.h"
|
||||
|
||||
#include <QFrame>
|
||||
#include <QGraphicsObject>
|
||||
#include <QList>
|
||||
|
||||
namespace google
|
||||
{
|
||||
namespace protobuf
|
||||
{
|
||||
class Message;
|
||||
}
|
||||
} // namespace google
|
||||
class Player;
|
||||
class GameCommand;
|
||||
|
||||
class PhaseButton : public QObject, public QGraphicsItem
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_INTERFACES(QGraphicsItem)
|
||||
private:
|
||||
QString name;
|
||||
bool active, highlightable;
|
||||
int activeAnimationCounter;
|
||||
QTimer *activeAnimationTimer;
|
||||
QAction *doubleClickAction;
|
||||
double width;
|
||||
|
||||
// void updatePixmap(QPixmap &pixmap);
|
||||
private slots:
|
||||
void updateAnimation();
|
||||
|
||||
public:
|
||||
explicit PhaseButton(const QString &_name,
|
||||
QGraphicsItem *parent = nullptr,
|
||||
QAction *_doubleClickAction = nullptr,
|
||||
bool _highlightable = true);
|
||||
QRectF boundingRect() const override;
|
||||
void setWidth(double _width);
|
||||
void setActive(bool _active);
|
||||
bool getActive() const
|
||||
{
|
||||
return active;
|
||||
}
|
||||
void triggerDoubleClickAction();
|
||||
signals:
|
||||
void clicked();
|
||||
|
||||
protected:
|
||||
void paint(QPainter *painter, const QStyleOptionGraphicsItem * /*option*/, QWidget * /*widget*/) override;
|
||||
void mousePressEvent(QGraphicsSceneMouseEvent *event) override;
|
||||
void mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event) override;
|
||||
};
|
||||
|
||||
class PhasesToolbar : public QObject, public QGraphicsItem
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_INTERFACES(QGraphicsItem)
|
||||
private:
|
||||
QList<PhaseButton *> buttonList;
|
||||
PhaseButton *nextTurnButton;
|
||||
double width, height, ySpacing, symbolSize;
|
||||
static const int buttonCount = 12;
|
||||
static const int spaceCount = 6;
|
||||
static const double marginSize;
|
||||
void rearrangeButtons();
|
||||
|
||||
public:
|
||||
explicit PhasesToolbar(QGraphicsItem *parent = nullptr);
|
||||
QRectF boundingRect() const override;
|
||||
void retranslateUi();
|
||||
void setHeight(double _height);
|
||||
double getWidth() const
|
||||
{
|
||||
return width;
|
||||
}
|
||||
int phaseCount() const
|
||||
{
|
||||
return buttonList.size();
|
||||
}
|
||||
QString getLongPhaseName(int phase) const;
|
||||
public slots:
|
||||
void setActivePhase(int phase);
|
||||
void triggerPhaseAction(int phase);
|
||||
private slots:
|
||||
void phaseButtonClicked();
|
||||
void actNextTurn();
|
||||
void actUntapAll();
|
||||
void actDrawCard();
|
||||
signals:
|
||||
void sendGameCommand(const ::google::protobuf::Message &command, int playerId);
|
||||
|
||||
protected:
|
||||
void paint(QPainter *painter, const QStyleOptionGraphicsItem * /*option*/, QWidget * /*widget*/) override;
|
||||
};
|
||||
|
||||
#endif
|
||||
697
cockatrice/src/client/ui/picture_loader.cpp
Normal file
697
cockatrice/src/client/ui/picture_loader.cpp
Normal file
|
|
@ -0,0 +1,697 @@
|
|||
#include "picture_loader.h"
|
||||
|
||||
#include "../../game/cards/card_database.h"
|
||||
#include "../../main.h"
|
||||
#include "../../settings/cache_settings.h"
|
||||
#include "theme_manager.h"
|
||||
|
||||
#include <QApplication>
|
||||
#include <QBuffer>
|
||||
#include <QCryptographicHash>
|
||||
#include <QDebug>
|
||||
#include <QDir>
|
||||
#include <QDirIterator>
|
||||
#include <QFile>
|
||||
#include <QImageReader>
|
||||
#include <QMovie>
|
||||
#include <QNetworkAccessManager>
|
||||
#include <QNetworkDiskCache>
|
||||
#include <QNetworkReply>
|
||||
#include <QNetworkRequest>
|
||||
#include <QPainter>
|
||||
#include <QPixmapCache>
|
||||
#include <QRegularExpression>
|
||||
#include <QScreen>
|
||||
#include <QSet>
|
||||
#include <QSvgRenderer>
|
||||
#include <QThread>
|
||||
#include <QUrl>
|
||||
#include <algorithm>
|
||||
#include <utility>
|
||||
|
||||
// never cache more than 300 cards at once for a single deck
|
||||
#define CACHED_CARD_PER_DECK_MAX 300
|
||||
|
||||
PictureToLoad::PictureToLoad(CardInfoPtr _card)
|
||||
: card(std::move(_card)), urlTemplates(SettingsCache::instance().downloads().getAllURLs())
|
||||
{
|
||||
if (card) {
|
||||
for (const auto &set : card->getSets()) {
|
||||
sortedSets << set.getPtr();
|
||||
}
|
||||
if (sortedSets.empty()) {
|
||||
sortedSets << CardSet::newInstance("", "", "", QDate());
|
||||
}
|
||||
std::sort(sortedSets.begin(), sortedSets.end(), SetDownloadPriorityComparator());
|
||||
// The first time called, nextSet will also populate the Urls for the first set.
|
||||
nextSet();
|
||||
}
|
||||
}
|
||||
|
||||
void PictureToLoad::populateSetUrls()
|
||||
{
|
||||
/* currentSetUrls is a list, populated each time a new set is requested for a particular card
|
||||
and Urls are removed from it as a download is attempted from each one. Custom Urls for
|
||||
a set are given higher priority, so should be placed first in the list. */
|
||||
currentSetUrls.clear();
|
||||
|
||||
if (card && currentSet) {
|
||||
QString setCustomURL = card->getCustomPicURL(currentSet->getShortName());
|
||||
|
||||
if (!setCustomURL.isEmpty()) {
|
||||
currentSetUrls.append(setCustomURL);
|
||||
}
|
||||
}
|
||||
|
||||
for (const QString &urlTemplate : urlTemplates) {
|
||||
QString transformedUrl = transformUrl(urlTemplate);
|
||||
|
||||
if (!transformedUrl.isEmpty()) {
|
||||
currentSetUrls.append(transformedUrl);
|
||||
}
|
||||
}
|
||||
|
||||
/* Call nextUrl to make sure currentUrl is up-to-date
|
||||
but we don't need the result here. */
|
||||
(void)nextUrl();
|
||||
}
|
||||
|
||||
bool PictureToLoad::nextSet()
|
||||
{
|
||||
if (!sortedSets.isEmpty()) {
|
||||
currentSet = sortedSets.takeFirst();
|
||||
populateSetUrls();
|
||||
return true;
|
||||
}
|
||||
currentSet = {};
|
||||
return false;
|
||||
}
|
||||
|
||||
bool PictureToLoad::nextUrl()
|
||||
{
|
||||
if (!currentSetUrls.isEmpty()) {
|
||||
currentUrl = currentSetUrls.takeFirst();
|
||||
return true;
|
||||
}
|
||||
currentUrl = QString();
|
||||
return false;
|
||||
}
|
||||
|
||||
QString PictureToLoad::getSetName() const
|
||||
{
|
||||
if (currentSet) {
|
||||
return currentSet->getCorrectedShortName();
|
||||
} else {
|
||||
return QString();
|
||||
}
|
||||
}
|
||||
|
||||
// Card back returned by gatherer when card is not found
|
||||
QStringList PictureLoaderWorker::md5Blacklist = QStringList() << "db0c48db407a907c16ade38de048a441";
|
||||
|
||||
PictureLoaderWorker::PictureLoaderWorker()
|
||||
: QObject(nullptr), picsPath(SettingsCache::instance().getPicsPath()),
|
||||
customPicsPath(SettingsCache::instance().getCustomPicsPath()),
|
||||
picDownload(SettingsCache::instance().getPicDownload()), downloadRunning(false), loadQueueRunning(false)
|
||||
{
|
||||
connect(this, SIGNAL(startLoadQueue()), this, SLOT(processLoadQueue()), Qt::QueuedConnection);
|
||||
connect(&SettingsCache::instance(), SIGNAL(picsPathChanged()), this, SLOT(picsPathChanged()));
|
||||
connect(&SettingsCache::instance(), SIGNAL(picDownloadChanged()), this, SLOT(picDownloadChanged()));
|
||||
|
||||
networkManager = new QNetworkAccessManager(this);
|
||||
// We need a timeout to ensure requests don't hang indefinitely in case of
|
||||
// cache corruption, see related Qt bug: https://bugreports.qt.io/browse/QTBUG-111397
|
||||
// Use Qt's default timeout (30s, as of 2023-02-22)
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(5, 15, 0))
|
||||
networkManager->setTransferTimeout();
|
||||
#endif
|
||||
auto cache = new QNetworkDiskCache(this);
|
||||
cache->setCacheDirectory(SettingsCache::instance().getNetworkCachePath());
|
||||
// Note: the settings is in MB, but QNetworkDiskCache uses bytes
|
||||
connect(&SettingsCache::instance(), &SettingsCache::networkCacheSizeChanged, cache,
|
||||
[cache](int newSizeInMB) { cache->setMaximumCacheSize(1024L * 1024L * static_cast<qint64>(newSizeInMB)); });
|
||||
networkManager->setCache(cache);
|
||||
// Use a ManualRedirectPolicy since we keep track of redirects in picDownloadFinished
|
||||
// We can't use NoLessSafeRedirectPolicy because it is not applied with AlwaysCache
|
||||
networkManager->setRedirectPolicy(QNetworkRequest::ManualRedirectPolicy);
|
||||
connect(networkManager, SIGNAL(finished(QNetworkReply *)), this, SLOT(picDownloadFinished(QNetworkReply *)));
|
||||
|
||||
pictureLoaderThread = new QThread;
|
||||
pictureLoaderThread->start(QThread::LowPriority);
|
||||
moveToThread(pictureLoaderThread);
|
||||
}
|
||||
|
||||
PictureLoaderWorker::~PictureLoaderWorker()
|
||||
{
|
||||
pictureLoaderThread->deleteLater();
|
||||
}
|
||||
|
||||
void PictureLoaderWorker::processLoadQueue()
|
||||
{
|
||||
if (loadQueueRunning) {
|
||||
return;
|
||||
}
|
||||
|
||||
loadQueueRunning = true;
|
||||
while (true) {
|
||||
mutex.lock();
|
||||
if (loadQueue.isEmpty()) {
|
||||
mutex.unlock();
|
||||
loadQueueRunning = false;
|
||||
return;
|
||||
}
|
||||
cardBeingLoaded = loadQueue.takeFirst();
|
||||
mutex.unlock();
|
||||
|
||||
QString setName = cardBeingLoaded.getSetName();
|
||||
QString cardName = cardBeingLoaded.getCard()->getName();
|
||||
QString correctedCardName = cardBeingLoaded.getCard()->getCorrectedName();
|
||||
|
||||
qDebug().nospace() << "PictureLoader: [card: " << cardName << " set: " << setName
|
||||
<< "]: Trying to load picture";
|
||||
|
||||
if (cardImageExistsOnDisk(setName, correctedCardName)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
qDebug().nospace() << "PictureLoader: [card: " << cardName << " set: " << setName
|
||||
<< "]: No custom picture, trying to download";
|
||||
cardsToDownload.append(cardBeingLoaded);
|
||||
cardBeingLoaded.clear();
|
||||
if (!downloadRunning) {
|
||||
startNextPicDownload();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool PictureLoaderWorker::cardImageExistsOnDisk(QString &setName, QString &correctedCardname)
|
||||
{
|
||||
QImage image;
|
||||
QImageReader imgReader;
|
||||
imgReader.setDecideFormatFromContent(true);
|
||||
QList<QString> picsPaths = QList<QString>();
|
||||
QDirIterator it(customPicsPath, QDirIterator::Subdirectories | QDirIterator::FollowSymlinks);
|
||||
|
||||
// Recursively check all subdirectories of the CUSTOM folder
|
||||
while (it.hasNext()) {
|
||||
QString thisPath(it.next());
|
||||
QFileInfo thisFileInfo(thisPath);
|
||||
|
||||
if (thisFileInfo.isFile() &&
|
||||
(thisFileInfo.fileName() == correctedCardname || thisFileInfo.completeBaseName() == correctedCardname ||
|
||||
thisFileInfo.baseName() == correctedCardname)) {
|
||||
picsPaths << thisPath; // Card found in the CUSTOM directory, somewhere
|
||||
}
|
||||
}
|
||||
|
||||
if (!setName.isEmpty()) {
|
||||
picsPaths << picsPath + "/" + setName + "/" + correctedCardname
|
||||
// We no longer store downloaded images there, but don't just ignore
|
||||
// stuff that old versions have put there.
|
||||
<< picsPath + "/downloadedPics/" + setName + "/" + correctedCardname;
|
||||
}
|
||||
|
||||
// Iterates through the list of paths, searching for images with the desired
|
||||
// name with any QImageReader-supported
|
||||
// extension
|
||||
for (const auto &_picsPath : picsPaths) {
|
||||
imgReader.setFileName(_picsPath);
|
||||
if (imgReader.read(&image)) {
|
||||
qDebug().nospace() << "PictureLoader: [card: " << correctedCardname << " set: " << setName
|
||||
<< "]: Picture found on disk.";
|
||||
imageLoaded(cardBeingLoaded.getCard(), image);
|
||||
return true;
|
||||
}
|
||||
imgReader.setFileName(_picsPath + ".full");
|
||||
if (imgReader.read(&image)) {
|
||||
qDebug().nospace() << "PictureLoader: [card: " << correctedCardname << " set: " << setName
|
||||
<< "]: Picture.full found on disk.";
|
||||
imageLoaded(cardBeingLoaded.getCard(), image);
|
||||
return true;
|
||||
}
|
||||
imgReader.setFileName(_picsPath + ".xlhq");
|
||||
if (imgReader.read(&image)) {
|
||||
qDebug().nospace() << "PictureLoader: [card: " << correctedCardname << " set: " << setName
|
||||
<< "]: Picture.xlhq found on disk.";
|
||||
imageLoaded(cardBeingLoaded.getCard(), image);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static int parse(const QString &urlTemplate,
|
||||
const QString &propType,
|
||||
const QString &cardName,
|
||||
const QString &setName,
|
||||
std::function<QString(const QString &)> getProperty,
|
||||
QMap<QString, QString> &transformMap)
|
||||
{
|
||||
static const QRegularExpression rxFillWith("^(.+)_fill_with_(.+)$");
|
||||
static const QRegularExpression rxSubStr("^(.+)_substr_(\\d+)_(\\d+)$");
|
||||
|
||||
const QRegularExpression rxCardProp("!" + propType + ":([^!]+)!");
|
||||
|
||||
auto matches = rxCardProp.globalMatch(urlTemplate);
|
||||
while (matches.hasNext()) {
|
||||
auto match = matches.next();
|
||||
QString templatePropertyName = match.captured(1);
|
||||
auto fillMatch = rxFillWith.match(templatePropertyName);
|
||||
QString cardPropertyName;
|
||||
QString fillWith;
|
||||
int subStrPos = 0;
|
||||
int subStrLen = -1;
|
||||
if (fillMatch.hasMatch()) {
|
||||
cardPropertyName = fillMatch.captured(1);
|
||||
fillWith = fillMatch.captured(2);
|
||||
} else {
|
||||
fillWith = QString();
|
||||
auto subStrMatch = rxSubStr.match(templatePropertyName);
|
||||
if (subStrMatch.hasMatch()) {
|
||||
cardPropertyName = subStrMatch.captured(1);
|
||||
subStrPos = subStrMatch.captured(2).toInt();
|
||||
subStrLen = subStrMatch.captured(3).toInt();
|
||||
} else {
|
||||
cardPropertyName = templatePropertyName;
|
||||
}
|
||||
}
|
||||
QString propertyValue = getProperty(cardPropertyName);
|
||||
if (propertyValue.isEmpty()) {
|
||||
qDebug().nospace() << "PictureLoader: [card: " << cardName << " set: " << setName << "]: Requested "
|
||||
<< propType << "property (" << cardPropertyName << ") for Url template (" << urlTemplate
|
||||
<< ") is not available";
|
||||
return 1;
|
||||
} else {
|
||||
int propLength = propertyValue.length();
|
||||
if (subStrLen > 0) {
|
||||
if (subStrPos + subStrLen > propLength) {
|
||||
qDebug().nospace() << "PictureLoader: [card: " << cardName << " set: " << setName << "]: Requested "
|
||||
<< propType << " property (" << cardPropertyName << ") for Url template ("
|
||||
<< urlTemplate << ") is smaller than substr specification (" << subStrPos
|
||||
<< " + " << subStrLen << " > " << propLength << ")";
|
||||
return 1;
|
||||
} else {
|
||||
propertyValue = propertyValue.mid(subStrPos, subStrLen);
|
||||
propLength = subStrLen;
|
||||
}
|
||||
}
|
||||
|
||||
if (!fillWith.isEmpty()) {
|
||||
int fillLength = fillWith.length();
|
||||
if (fillLength < propLength) {
|
||||
qDebug().nospace() << "PictureLoader: [card: " << cardName << " set: " << setName << "]: Requested "
|
||||
<< propType << " property (" << cardPropertyName << ") for Url template ("
|
||||
<< urlTemplate << ") is longer than fill specification (" << fillWith << ")";
|
||||
return 1;
|
||||
} else {
|
||||
|
||||
propertyValue = fillWith.left(fillLength - propLength) + propertyValue;
|
||||
}
|
||||
}
|
||||
|
||||
transformMap["!" + propType + ":" + templatePropertyName + "!"] = propertyValue;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
QString PictureToLoad::transformUrl(const QString &urlTemplate) const
|
||||
{
|
||||
/* This function takes Url templates and substitutes actual card details
|
||||
into the url. This is used for making Urls with follow a predictable format
|
||||
for downloading images. If information is requested by the template that is
|
||||
not populated for this specific card/set combination, an empty string is returned.*/
|
||||
|
||||
CardSetPtr set = getCurrentSet();
|
||||
|
||||
QMap<QString, QString> transformMap = QMap<QString, QString>();
|
||||
QString setName = getSetName();
|
||||
|
||||
// name
|
||||
QString cardName = card->getName();
|
||||
transformMap["!name!"] = cardName;
|
||||
transformMap["!name_lower!"] = card->getName().toLower();
|
||||
transformMap["!corrected_name!"] = card->getCorrectedName();
|
||||
transformMap["!corrected_name_lower!"] = card->getCorrectedName().toLower();
|
||||
|
||||
// card properties
|
||||
if (parse(
|
||||
urlTemplate, "prop", cardName, setName, [&](const QString &name) { return card->getProperty(name); },
|
||||
transformMap)) {
|
||||
return QString();
|
||||
}
|
||||
|
||||
if (set) {
|
||||
transformMap["!setcode!"] = set->getShortName();
|
||||
transformMap["!setcode_lower!"] = set->getShortName().toLower();
|
||||
transformMap["!setname!"] = set->getLongName();
|
||||
transformMap["!setname_lower!"] = set->getLongName().toLower();
|
||||
|
||||
if (parse(
|
||||
urlTemplate, "set", cardName, setName,
|
||||
[&](const QString &name) { return card->getSetProperty(set->getShortName(), name); }, transformMap)) {
|
||||
return QString();
|
||||
}
|
||||
}
|
||||
|
||||
// language setting
|
||||
transformMap["!sflang!"] = QString(QCoreApplication::translate(
|
||||
"PictureLoader", "en", "code for scryfall's language property, not available for all languages"));
|
||||
|
||||
QString transformedUrl = urlTemplate;
|
||||
for (const QString &prop : transformMap.keys()) {
|
||||
if (transformedUrl.contains(prop)) {
|
||||
if (!transformMap[prop].isEmpty()) {
|
||||
transformedUrl.replace(prop, QUrl::toPercentEncoding(transformMap[prop]));
|
||||
} else {
|
||||
/* This means the template is requesting information that is not
|
||||
* populated in this card, so it should return an empty string,
|
||||
* indicating an invalid Url.
|
||||
*/
|
||||
qDebug().nospace() << "PictureLoader: [card: " << cardName << " set: " << setName
|
||||
<< "]: Requested information (" << prop << ") for Url template (" << urlTemplate
|
||||
<< ") is not available";
|
||||
return QString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return transformedUrl;
|
||||
}
|
||||
|
||||
void PictureLoaderWorker::startNextPicDownload()
|
||||
{
|
||||
if (cardsToDownload.isEmpty()) {
|
||||
cardBeingDownloaded.clear();
|
||||
downloadRunning = false;
|
||||
return;
|
||||
}
|
||||
|
||||
downloadRunning = true;
|
||||
|
||||
cardBeingDownloaded = cardsToDownload.takeFirst();
|
||||
|
||||
QString picUrl = cardBeingDownloaded.getCurrentUrl();
|
||||
|
||||
if (picUrl.isEmpty()) {
|
||||
downloadRunning = false;
|
||||
picDownloadFailed();
|
||||
} else {
|
||||
QUrl url(picUrl);
|
||||
qDebug().nospace() << "PictureLoader: [card: " << cardBeingDownloaded.getCard()->getCorrectedName()
|
||||
<< " set: " << cardBeingDownloaded.getSetName() << "]: Trying to fetch picture from url "
|
||||
<< url.toDisplayString();
|
||||
makeRequest(url);
|
||||
}
|
||||
}
|
||||
|
||||
void PictureLoaderWorker::picDownloadFailed()
|
||||
{
|
||||
/* Take advantage of short circuiting here to call the nextUrl until one
|
||||
is not available. Only once nextUrl evaluates to false will this move
|
||||
on to nextSet. If the Urls for a particular card are empty, this will
|
||||
effectively go through the sets for that card. */
|
||||
if (cardBeingDownloaded.nextUrl() || cardBeingDownloaded.nextSet()) {
|
||||
mutex.lock();
|
||||
loadQueue.prepend(cardBeingDownloaded);
|
||||
mutex.unlock();
|
||||
} else {
|
||||
qDebug().nospace() << "PictureLoader: [card: " << cardBeingDownloaded.getCard()->getCorrectedName()
|
||||
<< " set: " << cardBeingDownloaded.getSetName() << "]: Picture NOT found, "
|
||||
<< (picDownload ? "download failed" : "downloads disabled")
|
||||
<< ", no more url combinations to try: BAILING OUT";
|
||||
imageLoaded(cardBeingDownloaded.getCard(), QImage());
|
||||
cardBeingDownloaded.clear();
|
||||
}
|
||||
emit startLoadQueue();
|
||||
}
|
||||
|
||||
bool PictureLoaderWorker::imageIsBlackListed(const QByteArray &picData)
|
||||
{
|
||||
QString md5sum = QCryptographicHash::hash(picData, QCryptographicHash::Md5).toHex();
|
||||
return md5Blacklist.contains(md5sum);
|
||||
}
|
||||
|
||||
QNetworkReply *PictureLoaderWorker::makeRequest(const QUrl &url)
|
||||
{
|
||||
QNetworkRequest req(url);
|
||||
if (!picDownload) {
|
||||
req.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::AlwaysCache);
|
||||
}
|
||||
return networkManager->get(req);
|
||||
}
|
||||
|
||||
void PictureLoaderWorker::picDownloadFinished(QNetworkReply *reply)
|
||||
{
|
||||
bool isFromCache = reply->attribute(QNetworkRequest::SourceIsFromCacheAttribute).toBool();
|
||||
|
||||
if (reply->error()) {
|
||||
if (isFromCache) {
|
||||
qDebug().nospace() << "PictureLoader: [card: " << cardBeingDownloaded.getCard()->getName()
|
||||
<< " set: " << cardBeingDownloaded.getSetName()
|
||||
<< "]: Removing corrupted cache file for url " << reply->url().toDisplayString()
|
||||
<< " and retrying (" << reply->errorString() << ")";
|
||||
|
||||
networkManager->cache()->remove(reply->url());
|
||||
|
||||
makeRequest(reply->url());
|
||||
} else {
|
||||
qDebug().nospace() << "PictureLoader: [card: " << cardBeingDownloaded.getCard()->getName()
|
||||
<< " set: " << cardBeingDownloaded.getSetName()
|
||||
<< "]: " << (picDownload ? "Download" : "Cache search") << " failed for url "
|
||||
<< reply->url().toDisplayString() << " (" << reply->errorString() << ")";
|
||||
|
||||
picDownloadFailed();
|
||||
startNextPicDownload();
|
||||
}
|
||||
|
||||
reply->deleteLater();
|
||||
return;
|
||||
}
|
||||
|
||||
// List of status codes from https://doc.qt.io/qt-6/qnetworkreply.html#redirected
|
||||
int statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
||||
if (statusCode == 301 || statusCode == 302 || statusCode == 303 || statusCode == 305 || statusCode == 307 ||
|
||||
statusCode == 308) {
|
||||
QUrl redirectUrl = reply->header(QNetworkRequest::LocationHeader).toUrl();
|
||||
qDebug().nospace() << "PictureLoader: [card: " << cardBeingDownloaded.getCard()->getName()
|
||||
<< " set: " << cardBeingDownloaded.getSetName() << "]: following "
|
||||
<< (isFromCache ? "cached redirect" : "redirect") << " to " << redirectUrl.toDisplayString();
|
||||
makeRequest(redirectUrl);
|
||||
reply->deleteLater();
|
||||
return;
|
||||
}
|
||||
|
||||
// peek is used to keep the data in the buffer for use by QImageReader
|
||||
const QByteArray &picData = reply->peek(reply->size());
|
||||
|
||||
if (imageIsBlackListed(picData)) {
|
||||
qDebug().nospace() << "PictureLoader: [card: " << cardBeingDownloaded.getCard()->getName()
|
||||
<< " set: " << cardBeingDownloaded.getSetName()
|
||||
<< "]: Picture found, but blacklisted, will consider it as not found";
|
||||
|
||||
picDownloadFailed();
|
||||
reply->deleteLater();
|
||||
startNextPicDownload();
|
||||
return;
|
||||
}
|
||||
|
||||
QImage testImage;
|
||||
|
||||
QImageReader imgReader;
|
||||
imgReader.setDecideFormatFromContent(true);
|
||||
imgReader.setDevice(reply);
|
||||
|
||||
bool logSuccessMessage = false;
|
||||
|
||||
static const int riffHeaderSize = 12; // RIFF_HEADER_SIZE from webp/format_constants.h
|
||||
auto replyHeader = reply->peek(riffHeaderSize);
|
||||
|
||||
if (replyHeader.startsWith("RIFF") && replyHeader.endsWith("WEBP")) {
|
||||
auto imgBuf = QBuffer(this);
|
||||
imgBuf.setData(reply->readAll());
|
||||
|
||||
auto movie = QMovie(&imgBuf);
|
||||
movie.start();
|
||||
movie.stop();
|
||||
|
||||
imageLoaded(cardBeingDownloaded.getCard(), movie.currentImage());
|
||||
logSuccessMessage = true;
|
||||
} else if (imgReader.read(&testImage)) {
|
||||
imageLoaded(cardBeingDownloaded.getCard(), testImage);
|
||||
logSuccessMessage = true;
|
||||
} else {
|
||||
qDebug().nospace() << "PictureLoader: [card: " << cardBeingDownloaded.getCard()->getName()
|
||||
<< " set: " << cardBeingDownloaded.getSetName() << "]: Possible "
|
||||
<< (isFromCache ? "cached" : "downloaded") << " picture at "
|
||||
<< reply->url().toDisplayString() << " could not be loaded: " << reply->errorString();
|
||||
|
||||
picDownloadFailed();
|
||||
}
|
||||
|
||||
if (logSuccessMessage) {
|
||||
qDebug().nospace() << "PictureLoader: [card: " << cardBeingDownloaded.getCard()->getName()
|
||||
<< " set: " << cardBeingDownloaded.getSetName() << "]: Image successfully "
|
||||
<< (isFromCache ? "loaded from cached" : "downloaded from") << " url "
|
||||
<< reply->url().toDisplayString();
|
||||
}
|
||||
|
||||
reply->deleteLater();
|
||||
startNextPicDownload();
|
||||
}
|
||||
|
||||
void PictureLoaderWorker::enqueueImageLoad(CardInfoPtr card)
|
||||
{
|
||||
QMutexLocker locker(&mutex);
|
||||
|
||||
// avoid queueing the same card more than once
|
||||
if (!card || card == cardBeingLoaded.getCard() || card == cardBeingDownloaded.getCard()) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const PictureToLoad &pic : loadQueue) {
|
||||
if (pic.getCard() == card)
|
||||
return;
|
||||
}
|
||||
|
||||
for (const PictureToLoad &pic : cardsToDownload) {
|
||||
if (pic.getCard() == card)
|
||||
return;
|
||||
}
|
||||
|
||||
loadQueue.append(PictureToLoad(card));
|
||||
emit startLoadQueue();
|
||||
}
|
||||
|
||||
void PictureLoaderWorker::picDownloadChanged()
|
||||
{
|
||||
QMutexLocker locker(&mutex);
|
||||
picDownload = SettingsCache::instance().getPicDownload();
|
||||
}
|
||||
|
||||
void PictureLoaderWorker::picsPathChanged()
|
||||
{
|
||||
QMutexLocker locker(&mutex);
|
||||
picsPath = SettingsCache::instance().getPicsPath();
|
||||
customPicsPath = SettingsCache::instance().getCustomPicsPath();
|
||||
}
|
||||
|
||||
void PictureLoaderWorker::clearNetworkCache()
|
||||
{
|
||||
networkManager->cache()->clear();
|
||||
}
|
||||
|
||||
PictureLoader::PictureLoader() : QObject(nullptr)
|
||||
{
|
||||
worker = new PictureLoaderWorker;
|
||||
connect(&SettingsCache::instance(), SIGNAL(picsPathChanged()), this, SLOT(picsPathChanged()));
|
||||
connect(&SettingsCache::instance(), SIGNAL(picDownloadChanged()), this, SLOT(picDownloadChanged()));
|
||||
|
||||
connect(worker, SIGNAL(imageLoaded(CardInfoPtr, const QImage &)), this,
|
||||
SLOT(imageLoaded(CardInfoPtr, const QImage &)));
|
||||
}
|
||||
|
||||
PictureLoader::~PictureLoader()
|
||||
{
|
||||
worker->deleteLater();
|
||||
}
|
||||
|
||||
void PictureLoader::getCardBackPixmap(QPixmap &pixmap, QSize size)
|
||||
{
|
||||
QString backCacheKey = "_trice_card_back_" + QString::number(size.width()) + QString::number(size.height());
|
||||
if (!QPixmapCache::find(backCacheKey, &pixmap)) {
|
||||
qDebug() << "PictureLoader: cache fail for" << backCacheKey;
|
||||
pixmap = QPixmap("theme:cardback").scaled(size, Qt::KeepAspectRatio, Qt::SmoothTransformation);
|
||||
QPixmapCache::insert(backCacheKey, pixmap);
|
||||
}
|
||||
}
|
||||
|
||||
void PictureLoader::getPixmap(QPixmap &pixmap, CardInfoPtr card, QSize size)
|
||||
{
|
||||
if (card == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
// search for an exact size copy of the picture in cache
|
||||
QString key = card->getPixmapCacheKey();
|
||||
QString sizeKey = key + QLatin1Char('_') + QString::number(size.width()) + QString::number(size.height());
|
||||
if (QPixmapCache::find(sizeKey, &pixmap))
|
||||
return;
|
||||
|
||||
// load the image and create a copy of the correct size
|
||||
QPixmap bigPixmap;
|
||||
if (QPixmapCache::find(key, &bigPixmap)) {
|
||||
QScreen *screen = qApp->primaryScreen();
|
||||
qreal dpr = screen->devicePixelRatio();
|
||||
pixmap = bigPixmap.scaled(size * dpr, Qt::KeepAspectRatio, Qt::SmoothTransformation);
|
||||
pixmap.setDevicePixelRatio(dpr);
|
||||
QPixmapCache::insert(sizeKey, pixmap);
|
||||
return;
|
||||
}
|
||||
|
||||
// add the card to the load queue
|
||||
getInstance().worker->enqueueImageLoad(card);
|
||||
}
|
||||
|
||||
void PictureLoader::imageLoaded(CardInfoPtr card, const QImage &image)
|
||||
{
|
||||
if (image.isNull()) {
|
||||
QPixmapCache::insert(card->getPixmapCacheKey(), QPixmap());
|
||||
} else {
|
||||
if (card->getUpsideDownArt()) {
|
||||
QImage mirrorImage = image.mirrored(true, true);
|
||||
QPixmapCache::insert(card->getPixmapCacheKey(), QPixmap::fromImage(mirrorImage));
|
||||
} else {
|
||||
QPixmapCache::insert(card->getPixmapCacheKey(), QPixmap::fromImage(image));
|
||||
}
|
||||
}
|
||||
|
||||
card->emitPixmapUpdated();
|
||||
}
|
||||
|
||||
void PictureLoader::clearPixmapCache(CardInfoPtr card)
|
||||
{
|
||||
if (card) {
|
||||
QPixmapCache::remove(card->getPixmapCacheKey());
|
||||
}
|
||||
}
|
||||
|
||||
void PictureLoader::clearPixmapCache()
|
||||
{
|
||||
QPixmapCache::clear();
|
||||
}
|
||||
|
||||
void PictureLoader::clearNetworkCache()
|
||||
{
|
||||
getInstance().worker->clearNetworkCache();
|
||||
}
|
||||
|
||||
void PictureLoader::cacheCardPixmaps(QList<CardInfoPtr> cards)
|
||||
{
|
||||
QPixmap tmp;
|
||||
int max = qMin(cards.size(), CACHED_CARD_PER_DECK_MAX);
|
||||
for (int i = 0; i < max; ++i) {
|
||||
const CardInfoPtr &card = cards.at(i);
|
||||
if (!card) {
|
||||
continue;
|
||||
}
|
||||
|
||||
QString key = card->getPixmapCacheKey();
|
||||
if (QPixmapCache::find(key, &tmp)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
getInstance().worker->enqueueImageLoad(card);
|
||||
}
|
||||
}
|
||||
|
||||
void PictureLoader::picDownloadChanged()
|
||||
{
|
||||
QPixmapCache::clear();
|
||||
}
|
||||
|
||||
void PictureLoader::picsPathChanged()
|
||||
{
|
||||
QPixmapCache::clear();
|
||||
}
|
||||
144
cockatrice/src/client/ui/picture_loader.h
Normal file
144
cockatrice/src/client/ui/picture_loader.h
Normal file
|
|
@ -0,0 +1,144 @@
|
|||
#ifndef PICTURELOADER_H
|
||||
#define PICTURELOADER_H
|
||||
|
||||
#include "../../game/cards/card_database.h"
|
||||
|
||||
#include <QList>
|
||||
#include <QMap>
|
||||
#include <QMutex>
|
||||
#include <QNetworkRequest>
|
||||
class QNetworkAccessManager;
|
||||
class QNetworkReply;
|
||||
class QThread;
|
||||
|
||||
class PictureToLoad
|
||||
{
|
||||
private:
|
||||
class SetDownloadPriorityComparator
|
||||
{
|
||||
public:
|
||||
/*
|
||||
* Returns true if a has higher download priority than b
|
||||
* Enabled sets have priority over disabled sets
|
||||
* Both groups follows the user-defined order
|
||||
*/
|
||||
inline bool operator()(const CardSetPtr &a, const CardSetPtr &b) const
|
||||
{
|
||||
if (a->getEnabled()) {
|
||||
return !b->getEnabled() || a->getSortKey() < b->getSortKey();
|
||||
} else {
|
||||
return !b->getEnabled() && a->getSortKey() < b->getSortKey();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
CardInfoPtr card;
|
||||
QList<CardSetPtr> sortedSets;
|
||||
QList<QString> urlTemplates;
|
||||
QList<QString> currentSetUrls;
|
||||
QString currentUrl;
|
||||
CardSetPtr currentSet;
|
||||
|
||||
public:
|
||||
explicit PictureToLoad(CardInfoPtr _card = CardInfoPtr());
|
||||
|
||||
CardInfoPtr getCard() const
|
||||
{
|
||||
return card;
|
||||
}
|
||||
void clear()
|
||||
{
|
||||
card.clear();
|
||||
}
|
||||
QString getCurrentUrl() const
|
||||
{
|
||||
return currentUrl;
|
||||
}
|
||||
CardSetPtr getCurrentSet() const
|
||||
{
|
||||
return currentSet;
|
||||
}
|
||||
QString getSetName() const;
|
||||
QString transformUrl(const QString &urlTemplate) const;
|
||||
bool nextSet();
|
||||
bool nextUrl();
|
||||
void populateSetUrls();
|
||||
};
|
||||
|
||||
class PictureLoaderWorker : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit PictureLoaderWorker();
|
||||
~PictureLoaderWorker() override;
|
||||
|
||||
void enqueueImageLoad(CardInfoPtr card);
|
||||
void clearNetworkCache();
|
||||
|
||||
private:
|
||||
static QStringList md5Blacklist;
|
||||
|
||||
QThread *pictureLoaderThread;
|
||||
QString picsPath, customPicsPath;
|
||||
QList<PictureToLoad> loadQueue;
|
||||
QMutex mutex;
|
||||
QNetworkAccessManager *networkManager;
|
||||
QList<PictureToLoad> cardsToDownload;
|
||||
PictureToLoad cardBeingLoaded;
|
||||
PictureToLoad cardBeingDownloaded;
|
||||
bool picDownload, downloadRunning, loadQueueRunning;
|
||||
void startNextPicDownload();
|
||||
bool cardImageExistsOnDisk(QString &setName, QString &correctedCardName);
|
||||
bool imageIsBlackListed(const QByteArray &);
|
||||
QNetworkReply *makeRequest(const QUrl &url);
|
||||
private slots:
|
||||
void picDownloadFinished(QNetworkReply *reply);
|
||||
void picDownloadFailed();
|
||||
|
||||
void picDownloadChanged();
|
||||
void picsPathChanged();
|
||||
public slots:
|
||||
void processLoadQueue();
|
||||
|
||||
signals:
|
||||
void startLoadQueue();
|
||||
void imageLoaded(CardInfoPtr card, const QImage &image);
|
||||
};
|
||||
|
||||
class PictureLoader : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
static PictureLoader &getInstance()
|
||||
{
|
||||
static PictureLoader instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
private:
|
||||
explicit PictureLoader();
|
||||
~PictureLoader() override;
|
||||
// Singleton - Don't implement copy constructor and assign operator
|
||||
PictureLoader(PictureLoader const &);
|
||||
void operator=(PictureLoader const &);
|
||||
|
||||
PictureLoaderWorker *worker;
|
||||
|
||||
public:
|
||||
static void getPixmap(QPixmap &pixmap, CardInfoPtr card, QSize size);
|
||||
static void getCardBackPixmap(QPixmap &pixmap, QSize size);
|
||||
static void clearPixmapCache(CardInfoPtr card);
|
||||
static void clearPixmapCache();
|
||||
static void cacheCardPixmaps(QList<CardInfoPtr> cards);
|
||||
|
||||
public slots:
|
||||
static void clearNetworkCache();
|
||||
|
||||
private slots:
|
||||
void picDownloadChanged();
|
||||
void picsPathChanged();
|
||||
|
||||
public slots:
|
||||
void imageLoaded(CardInfoPtr card, const QImage &image);
|
||||
};
|
||||
#endif
|
||||
159
cockatrice/src/client/ui/pixel_map_generator.cpp
Normal file
159
cockatrice/src/client/ui/pixel_map_generator.cpp
Normal file
|
|
@ -0,0 +1,159 @@
|
|||
#include "pixel_map_generator.h"
|
||||
|
||||
#include "pb/serverinfo_user.pb.h"
|
||||
|
||||
#include <QApplication>
|
||||
#include <QDebug>
|
||||
#include <QPainter>
|
||||
#include <QPalette>
|
||||
|
||||
QMap<QString, QPixmap> PhasePixmapGenerator::pmCache;
|
||||
|
||||
QPixmap PhasePixmapGenerator::generatePixmap(int height, QString name)
|
||||
{
|
||||
QString key = name + QString::number(height);
|
||||
if (pmCache.contains(key))
|
||||
return pmCache.value(key);
|
||||
|
||||
QPixmap pixmap =
|
||||
QPixmap("theme:phases/" + name).scaled(height, height, Qt::KeepAspectRatio, Qt::SmoothTransformation);
|
||||
|
||||
pmCache.insert(key, pixmap);
|
||||
return pixmap;
|
||||
}
|
||||
|
||||
QMap<QString, QPixmap> CounterPixmapGenerator::pmCache;
|
||||
|
||||
QPixmap CounterPixmapGenerator::generatePixmap(int height, QString name, bool highlight)
|
||||
{
|
||||
if (highlight)
|
||||
name.append("_highlight");
|
||||
QString key = name + QString::number(height);
|
||||
if (pmCache.contains(key))
|
||||
return pmCache.value(key);
|
||||
|
||||
QPixmap pixmap =
|
||||
QPixmap("theme:counters/" + name).scaled(height, height, Qt::KeepAspectRatio, Qt::SmoothTransformation);
|
||||
if (pixmap.isNull()) {
|
||||
name = "general";
|
||||
if (highlight)
|
||||
name.append("_highlight");
|
||||
pixmap =
|
||||
QPixmap("theme:counters/" + name).scaled(height, height, Qt::KeepAspectRatio, Qt::SmoothTransformation);
|
||||
}
|
||||
|
||||
pmCache.insert(key, pixmap);
|
||||
return pixmap;
|
||||
}
|
||||
|
||||
QPixmap PingPixmapGenerator::generatePixmap(int size, int value, int max)
|
||||
{
|
||||
int key = size * 1000000 + max * 1000 + value;
|
||||
if (pmCache.contains(key))
|
||||
return pmCache.value(key);
|
||||
|
||||
QPixmap pixmap(size, size);
|
||||
pixmap.fill(Qt::transparent);
|
||||
QPainter painter(&pixmap);
|
||||
QColor color;
|
||||
if ((max == -1) || (value == -1))
|
||||
color = Qt::black;
|
||||
else
|
||||
color.setHsv(120 * (1.0 - ((double)value / max)), 255, 255);
|
||||
|
||||
QRadialGradient g(QPointF((double)pixmap.width() / 2, (double)pixmap.height() / 2),
|
||||
qMin(pixmap.width(), pixmap.height()) / 2.0);
|
||||
g.setColorAt(0, color);
|
||||
g.setColorAt(1, Qt::transparent);
|
||||
painter.fillRect(0, 0, pixmap.width(), pixmap.height(), QBrush(g));
|
||||
|
||||
pmCache.insert(key, pixmap);
|
||||
|
||||
return pixmap;
|
||||
}
|
||||
|
||||
QMap<int, QPixmap> PingPixmapGenerator::pmCache;
|
||||
|
||||
QPixmap CountryPixmapGenerator::generatePixmap(int height, const QString &countryCode)
|
||||
{
|
||||
if (countryCode.size() != 2)
|
||||
return QPixmap();
|
||||
QString key = countryCode + QString::number(height);
|
||||
if (pmCache.contains(key))
|
||||
return pmCache.value(key);
|
||||
|
||||
int width = height * 2;
|
||||
QPixmap pixmap = QPixmap("theme:countries/" + countryCode.toLower())
|
||||
.scaled(width, height, Qt::KeepAspectRatio, Qt::SmoothTransformation);
|
||||
|
||||
QPainter painter(&pixmap);
|
||||
painter.setPen(Qt::black);
|
||||
painter.drawRect(0, 0, pixmap.width() - 1, pixmap.height() - 1);
|
||||
|
||||
pmCache.insert(key, pixmap);
|
||||
return pixmap;
|
||||
}
|
||||
|
||||
QMap<QString, QPixmap> CountryPixmapGenerator::pmCache;
|
||||
|
||||
QPixmap UserLevelPixmapGenerator::generatePixmap(int height, UserLevelFlags userLevel, bool isBuddy, QString privLevel)
|
||||
{
|
||||
|
||||
QString key = QString::number(height * 10000) + ":" + (short)userLevel + ":" + (short)isBuddy + ":" + privLevel;
|
||||
if (pmCache.contains(key))
|
||||
return pmCache.value(key);
|
||||
|
||||
QString levelString;
|
||||
if (userLevel.testFlag(ServerInfo_User::IsAdmin)) {
|
||||
levelString = "admin";
|
||||
if (privLevel.toLower() == "vip")
|
||||
levelString.append("_" + privLevel.toLower());
|
||||
} else if (userLevel.testFlag(ServerInfo_User::IsModerator)) {
|
||||
levelString = "moderator";
|
||||
if (privLevel.toLower() == "vip")
|
||||
levelString.append("_" + privLevel.toLower());
|
||||
} else if (userLevel.testFlag(ServerInfo_User::IsRegistered)) {
|
||||
levelString = "registered";
|
||||
if (privLevel.toLower() != "none")
|
||||
levelString.append("_" + privLevel.toLower());
|
||||
} else
|
||||
levelString = "normal";
|
||||
|
||||
if (isBuddy)
|
||||
levelString.append("_buddy");
|
||||
|
||||
QPixmap pixmap = QPixmap("theme:userlevels/" + levelString)
|
||||
.scaled(height, height, Qt::KeepAspectRatio, Qt::SmoothTransformation);
|
||||
|
||||
pmCache.insert(key, pixmap);
|
||||
return pixmap;
|
||||
}
|
||||
|
||||
QMap<QString, QPixmap> UserLevelPixmapGenerator::pmCache;
|
||||
|
||||
QPixmap LockPixmapGenerator::generatePixmap(int height)
|
||||
{
|
||||
|
||||
int key = height;
|
||||
if (pmCache.contains(key))
|
||||
return pmCache.value(key);
|
||||
|
||||
QPixmap pixmap = QPixmap("theme:icons/lock").scaled(height, height, Qt::KeepAspectRatio, Qt::SmoothTransformation);
|
||||
pmCache.insert(key, pixmap);
|
||||
return pixmap;
|
||||
}
|
||||
|
||||
QMap<int, QPixmap> LockPixmapGenerator::pmCache;
|
||||
|
||||
const QPixmap loadColorAdjustedPixmap(QString name)
|
||||
{
|
||||
if (qApp->palette().windowText().color().lightness() > 200) {
|
||||
QImage img(name);
|
||||
img.invertPixels();
|
||||
QPixmap result;
|
||||
result.convertFromImage(img);
|
||||
return result;
|
||||
} else {
|
||||
return QPixmap(name);
|
||||
}
|
||||
}
|
||||
89
cockatrice/src/client/ui/pixel_map_generator.h
Normal file
89
cockatrice/src/client/ui/pixel_map_generator.h
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
#ifndef PIXMAPGENERATOR_H
|
||||
#define PIXMAPGENERATOR_H
|
||||
|
||||
#include "user_level.h"
|
||||
|
||||
#include <QMap>
|
||||
#include <QPixmap>
|
||||
|
||||
class PhasePixmapGenerator
|
||||
{
|
||||
private:
|
||||
static QMap<QString, QPixmap> pmCache;
|
||||
|
||||
public:
|
||||
static QPixmap generatePixmap(int size, QString name);
|
||||
static void clear()
|
||||
{
|
||||
pmCache.clear();
|
||||
}
|
||||
};
|
||||
|
||||
class CounterPixmapGenerator
|
||||
{
|
||||
private:
|
||||
static QMap<QString, QPixmap> pmCache;
|
||||
|
||||
public:
|
||||
static QPixmap generatePixmap(int size, QString name, bool highlight);
|
||||
static void clear()
|
||||
{
|
||||
pmCache.clear();
|
||||
}
|
||||
};
|
||||
|
||||
class PingPixmapGenerator
|
||||
{
|
||||
private:
|
||||
static QMap<int, QPixmap> pmCache;
|
||||
|
||||
public:
|
||||
static QPixmap generatePixmap(int size, int value, int max);
|
||||
static void clear()
|
||||
{
|
||||
pmCache.clear();
|
||||
}
|
||||
};
|
||||
|
||||
class CountryPixmapGenerator
|
||||
{
|
||||
private:
|
||||
static QMap<QString, QPixmap> pmCache;
|
||||
|
||||
public:
|
||||
static QPixmap generatePixmap(int height, const QString &countryCode);
|
||||
static void clear()
|
||||
{
|
||||
pmCache.clear();
|
||||
}
|
||||
};
|
||||
|
||||
class UserLevelPixmapGenerator
|
||||
{
|
||||
private:
|
||||
static QMap<QString, QPixmap> pmCache;
|
||||
|
||||
public:
|
||||
static QPixmap generatePixmap(int height, UserLevelFlags userLevel, bool isBuddy, QString privLevel = "NONE");
|
||||
static void clear()
|
||||
{
|
||||
pmCache.clear();
|
||||
}
|
||||
};
|
||||
|
||||
class LockPixmapGenerator
|
||||
{
|
||||
private:
|
||||
static QMap<int, QPixmap> pmCache;
|
||||
|
||||
public:
|
||||
static QPixmap generatePixmap(int height);
|
||||
static void clear()
|
||||
{
|
||||
pmCache.clear();
|
||||
}
|
||||
};
|
||||
|
||||
const QPixmap loadColorAdjustedPixmap(QString name);
|
||||
|
||||
#endif
|
||||
204
cockatrice/src/client/ui/theme_manager.cpp
Normal file
204
cockatrice/src/client/ui/theme_manager.cpp
Normal file
|
|
@ -0,0 +1,204 @@
|
|||
#include "theme_manager.h"
|
||||
|
||||
#include "../../settings/cache_settings.h"
|
||||
|
||||
#include <QApplication>
|
||||
#include <QColor>
|
||||
#include <QDebug>
|
||||
#include <QLibraryInfo>
|
||||
#include <QPixmapCache>
|
||||
#include <QStandardPaths>
|
||||
|
||||
#define NONE_THEME_NAME "Default"
|
||||
#define STYLE_CSS_NAME "style.css"
|
||||
#define HANDZONE_BG_NAME "handzone"
|
||||
#define PLAYERZONE_BG_NAME "playerzone"
|
||||
#define STACKZONE_BG_NAME "stackzone"
|
||||
#define TABLEZONE_BG_NAME "tablezone"
|
||||
static const QColor HANDZONE_BG_DEFAULT = QColor(80, 100, 50);
|
||||
static const QColor TABLEZONE_BG_DEFAULT = QColor(70, 50, 100);
|
||||
static const QColor PLAYERZONE_BG_DEFAULT = QColor(200, 200, 200);
|
||||
static const QColor STACKZONE_BG_DEFAULT = QColor(113, 43, 43);
|
||||
static const QStringList DEFAULT_RESOURCE_PATHS = {":/resources"};
|
||||
|
||||
ThemeManager::ThemeManager(QObject *parent) : QObject(parent)
|
||||
{
|
||||
ensureThemeDirectoryExists();
|
||||
connect(&SettingsCache::instance(), SIGNAL(themeChanged()), this, SLOT(themeChangedSlot()));
|
||||
themeChangedSlot();
|
||||
}
|
||||
|
||||
void ThemeManager::ensureThemeDirectoryExists()
|
||||
{
|
||||
if (SettingsCache::instance().getThemeName().isEmpty() ||
|
||||
!getAvailableThemes().contains(SettingsCache::instance().getThemeName())) {
|
||||
qDebug() << "Theme name not set, setting default value";
|
||||
SettingsCache::instance().setThemeName(NONE_THEME_NAME);
|
||||
}
|
||||
}
|
||||
|
||||
QStringMap &ThemeManager::getAvailableThemes()
|
||||
{
|
||||
QDir dir;
|
||||
availableThemes.clear();
|
||||
|
||||
// add default value
|
||||
availableThemes.insert(NONE_THEME_NAME, QString());
|
||||
|
||||
// load themes from user profile dir
|
||||
dir.setPath(SettingsCache::instance().getThemesPath());
|
||||
|
||||
for (QString themeName : dir.entryList(QDir::AllDirs | QDir::NoDotAndDotDot, QDir::Name)) {
|
||||
if (!availableThemes.contains(themeName)) {
|
||||
availableThemes.insert(themeName, dir.absoluteFilePath(themeName));
|
||||
}
|
||||
}
|
||||
|
||||
// load themes from cockatrice system dir
|
||||
dir.setPath(qApp->applicationDirPath() +
|
||||
#ifdef Q_OS_MAC
|
||||
"/../Resources/themes"
|
||||
#elif defined(Q_OS_WIN)
|
||||
"/themes"
|
||||
#else // linux
|
||||
"/../share/cockatrice/themes"
|
||||
#endif
|
||||
);
|
||||
|
||||
for (QString themeName : dir.entryList(QDir::AllDirs | QDir::NoDotAndDotDot, QDir::Name)) {
|
||||
if (!availableThemes.contains(themeName)) {
|
||||
availableThemes.insert(themeName, dir.absoluteFilePath(themeName));
|
||||
}
|
||||
}
|
||||
|
||||
return availableThemes;
|
||||
}
|
||||
|
||||
QBrush ThemeManager::loadBrush(QString fileName, QColor fallbackColor)
|
||||
{
|
||||
QBrush brush;
|
||||
QPixmap tmp = QPixmap("theme:zones/" + fileName);
|
||||
if (tmp.isNull()) {
|
||||
brush.setColor(fallbackColor);
|
||||
brush.setStyle(Qt::SolidPattern);
|
||||
} else {
|
||||
brush.setTexture(tmp);
|
||||
}
|
||||
|
||||
return brush;
|
||||
}
|
||||
|
||||
QBrush ThemeManager::loadExtraBrush(QString fileName, QBrush &fallbackBrush)
|
||||
{
|
||||
QBrush brush;
|
||||
QPixmap tmp = QPixmap("theme:zones/" + fileName);
|
||||
|
||||
if (tmp.isNull()) {
|
||||
brush = fallbackBrush;
|
||||
} else {
|
||||
brush.setTexture(tmp);
|
||||
}
|
||||
|
||||
return brush;
|
||||
}
|
||||
|
||||
void ThemeManager::themeChangedSlot()
|
||||
{
|
||||
QString themeName = SettingsCache::instance().getThemeName();
|
||||
qDebug() << "Theme changed:" << themeName;
|
||||
|
||||
QString dirPath = getAvailableThemes().value(themeName);
|
||||
QDir dir = dirPath;
|
||||
|
||||
// css
|
||||
if (!dirPath.isEmpty() && dir.exists(STYLE_CSS_NAME)) {
|
||||
qApp->setStyleSheet("file:///" + dir.absoluteFilePath(STYLE_CSS_NAME));
|
||||
} else {
|
||||
qApp->setStyleSheet("");
|
||||
}
|
||||
|
||||
if (dirPath.isEmpty()) {
|
||||
// set default values
|
||||
QDir::setSearchPaths("theme", DEFAULT_RESOURCE_PATHS);
|
||||
handBgBrush = HANDZONE_BG_DEFAULT;
|
||||
tableBgBrush = TABLEZONE_BG_DEFAULT;
|
||||
playerBgBrush = PLAYERZONE_BG_DEFAULT;
|
||||
stackBgBrush = STACKZONE_BG_DEFAULT;
|
||||
} else {
|
||||
// resources
|
||||
QStringList resources;
|
||||
resources << dir.absolutePath() << DEFAULT_RESOURCE_PATHS;
|
||||
QDir::setSearchPaths("theme", resources);
|
||||
|
||||
// zones bg
|
||||
dir.cd("zones");
|
||||
handBgBrush = loadBrush(HANDZONE_BG_NAME, HANDZONE_BG_DEFAULT);
|
||||
tableBgBrush = loadBrush(TABLEZONE_BG_NAME, TABLEZONE_BG_DEFAULT);
|
||||
playerBgBrush = loadBrush(PLAYERZONE_BG_NAME, PLAYERZONE_BG_DEFAULT);
|
||||
stackBgBrush = loadBrush(STACKZONE_BG_NAME, STACKZONE_BG_DEFAULT);
|
||||
}
|
||||
tableBgBrushesCache.clear();
|
||||
stackBgBrushesCache.clear();
|
||||
playerBgBrushesCache.clear();
|
||||
handBgBrushesCache.clear();
|
||||
|
||||
QPixmapCache::clear();
|
||||
|
||||
emit themeChanged();
|
||||
}
|
||||
|
||||
QBrush ThemeManager::getExtraTableBgBrush(QString extraNumber, QBrush &fallbackBrush)
|
||||
{
|
||||
QBrush returnBrush;
|
||||
|
||||
if (!tableBgBrushesCache.contains(extraNumber.toInt())) {
|
||||
returnBrush = loadExtraBrush(TABLEZONE_BG_NAME + extraNumber, fallbackBrush);
|
||||
tableBgBrushesCache.insert(extraNumber.toInt(), returnBrush);
|
||||
} else {
|
||||
returnBrush = tableBgBrushesCache.value(extraNumber.toInt());
|
||||
}
|
||||
|
||||
return returnBrush;
|
||||
}
|
||||
|
||||
QBrush ThemeManager::getExtraStackBgBrush(QString extraNumber, QBrush &fallbackBrush)
|
||||
{
|
||||
QBrush returnBrush;
|
||||
|
||||
if (!stackBgBrushesCache.contains(extraNumber.toInt())) {
|
||||
returnBrush = loadExtraBrush(STACKZONE_BG_NAME + extraNumber, fallbackBrush);
|
||||
stackBgBrushesCache.insert(extraNumber.toInt(), returnBrush);
|
||||
} else {
|
||||
returnBrush = stackBgBrushesCache.value(extraNumber.toInt());
|
||||
}
|
||||
|
||||
return returnBrush;
|
||||
}
|
||||
|
||||
QBrush ThemeManager::getExtraPlayerBgBrush(QString extraNumber, QBrush &fallbackBrush)
|
||||
{
|
||||
QBrush returnBrush;
|
||||
|
||||
if (!playerBgBrushesCache.contains(extraNumber.toInt())) {
|
||||
returnBrush = loadExtraBrush(PLAYERZONE_BG_NAME + extraNumber, fallbackBrush);
|
||||
playerBgBrushesCache.insert(extraNumber.toInt(), returnBrush);
|
||||
} else {
|
||||
returnBrush = playerBgBrushesCache.value(extraNumber.toInt());
|
||||
}
|
||||
|
||||
return returnBrush;
|
||||
}
|
||||
|
||||
QBrush ThemeManager::getExtraHandBgBrush(QString extraNumber, QBrush &fallbackBrush)
|
||||
{
|
||||
QBrush returnBrush;
|
||||
|
||||
if (!handBgBrushesCache.contains(extraNumber.toInt())) {
|
||||
returnBrush = loadExtraBrush(HANDZONE_BG_NAME + extraNumber, fallbackBrush);
|
||||
handBgBrushesCache.insert(extraNumber.toInt(), returnBrush);
|
||||
} else {
|
||||
returnBrush = handBgBrushesCache.value(extraNumber.toInt());
|
||||
}
|
||||
|
||||
return returnBrush;
|
||||
}
|
||||
65
cockatrice/src/client/ui/theme_manager.h
Normal file
65
cockatrice/src/client/ui/theme_manager.h
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
#ifndef THEMEMANAGER_H
|
||||
#define THEMEMANAGER_H
|
||||
|
||||
#include <QBrush>
|
||||
#include <QDir>
|
||||
#include <QMap>
|
||||
#include <QObject>
|
||||
#include <QPixmap>
|
||||
#include <QString>
|
||||
|
||||
typedef QMap<QString, QString> QStringMap;
|
||||
typedef QMap<int, QBrush> QBrushMap;
|
||||
|
||||
class QApplication;
|
||||
|
||||
class ThemeManager : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
ThemeManager(QObject *parent = nullptr);
|
||||
|
||||
private:
|
||||
QBrush handBgBrush, stackBgBrush, tableBgBrush, playerBgBrush;
|
||||
QStringMap availableThemes;
|
||||
/*
|
||||
Internal cache for multiple backgrounds
|
||||
*/
|
||||
QBrushMap tableBgBrushesCache, stackBgBrushesCache, playerBgBrushesCache, handBgBrushesCache;
|
||||
|
||||
protected:
|
||||
void ensureThemeDirectoryExists();
|
||||
QBrush loadBrush(QString fileName, QColor fallbackColor);
|
||||
QBrush loadExtraBrush(QString fileName, QBrush &fallbackBrush);
|
||||
|
||||
public:
|
||||
QBrush &getHandBgBrush()
|
||||
{
|
||||
return handBgBrush;
|
||||
}
|
||||
QBrush &getStackBgBrush()
|
||||
{
|
||||
return stackBgBrush;
|
||||
}
|
||||
QBrush &getTableBgBrush()
|
||||
{
|
||||
return tableBgBrush;
|
||||
}
|
||||
QBrush &getPlayerBgBrush()
|
||||
{
|
||||
return playerBgBrush;
|
||||
}
|
||||
QStringMap &getAvailableThemes();
|
||||
QBrush getExtraTableBgBrush(QString extraNumber, QBrush &fallbackBrush);
|
||||
QBrush getExtraStackBgBrush(QString extraNumber, QBrush &fallbackBrush);
|
||||
QBrush getExtraPlayerBgBrush(QString extraNumber, QBrush &fallbackBrush);
|
||||
QBrush getExtraHandBgBrush(QString extraNumber, QBrush &fallbackBrush);
|
||||
protected slots:
|
||||
void themeChangedSlot();
|
||||
signals:
|
||||
void themeChanged();
|
||||
};
|
||||
|
||||
extern ThemeManager *themeManager;
|
||||
|
||||
#endif
|
||||
103
cockatrice/src/client/ui/tip_of_the_day.cpp
Normal file
103
cockatrice/src/client/ui/tip_of_the_day.cpp
Normal file
|
|
@ -0,0 +1,103 @@
|
|||
#include "tip_of_the_day.h"
|
||||
|
||||
#include <QDate>
|
||||
#include <QFile>
|
||||
#include <QMessageBox>
|
||||
#include <QTextStream>
|
||||
#include <QXmlStreamReader>
|
||||
#include <utility>
|
||||
|
||||
#define TIPDDBMODEL_COLUMNS 3
|
||||
|
||||
TipOfTheDay::TipOfTheDay(QString _title, QString _content, QString _imagePath, QDate _date)
|
||||
: title(std::move(_title)), content(std::move(_content)), imagePath(std::move(_imagePath)), date(_date)
|
||||
{
|
||||
}
|
||||
|
||||
TipsOfTheDay::TipsOfTheDay(QString xmlPath, QObject *parent) : QAbstractListModel(parent)
|
||||
{
|
||||
tipList = new QList<TipOfTheDay>;
|
||||
|
||||
QFile xmlFile(xmlPath);
|
||||
|
||||
QTextStream errorStream(stderr);
|
||||
if (!QFile::exists(xmlPath)) {
|
||||
errorStream << tr("File does not exist.\n");
|
||||
return;
|
||||
} else if (!xmlFile.open(QIODevice::ReadOnly)) {
|
||||
errorStream << tr("Failed to open file.\n");
|
||||
return;
|
||||
}
|
||||
|
||||
QXmlStreamReader reader(&xmlFile);
|
||||
|
||||
while (!reader.atEnd()) {
|
||||
if (reader.readNext() == QXmlStreamReader::EndElement) {
|
||||
break;
|
||||
}
|
||||
|
||||
auto readerName = reader.name().toString();
|
||||
|
||||
if (readerName == "tip") {
|
||||
QString title, content, imagePath;
|
||||
QDate date;
|
||||
reader.readNext();
|
||||
while (!reader.atEnd()) {
|
||||
if (reader.readNext() == QXmlStreamReader::EndElement) {
|
||||
break;
|
||||
}
|
||||
|
||||
readerName = reader.name().toString();
|
||||
|
||||
if (readerName == "title") {
|
||||
title = reader.readElementText();
|
||||
} else if (readerName == "text") {
|
||||
content = reader.readElementText();
|
||||
} else if (readerName == "image") {
|
||||
imagePath = "theme:tips/images/" + reader.readElementText();
|
||||
} else if (readerName == "date") {
|
||||
date = QDate::fromString(reader.readElementText(), Qt::ISODate);
|
||||
} else {
|
||||
// unknown element, do nothing
|
||||
}
|
||||
}
|
||||
tipList->append(TipOfTheDay(title, content, imagePath, date));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TipsOfTheDay::~TipsOfTheDay()
|
||||
{
|
||||
delete tipList;
|
||||
}
|
||||
|
||||
QVariant TipsOfTheDay::data(const QModelIndex &index, int /*role*/) const
|
||||
{
|
||||
if (!index.isValid() || index.row() >= tipList->size() || index.column() >= TIPDDBMODEL_COLUMNS)
|
||||
return QVariant();
|
||||
|
||||
TipOfTheDay tip = tipList->at(index.row());
|
||||
switch (index.column()) {
|
||||
case TitleColumn:
|
||||
return tip.getTitle();
|
||||
case ContentColumn:
|
||||
return tip.getContent();
|
||||
case ImagePathColumn:
|
||||
return tip.getImagePath();
|
||||
case DateColumn:
|
||||
return tip.getDate();
|
||||
default:
|
||||
return QVariant();
|
||||
}
|
||||
}
|
||||
|
||||
TipOfTheDay TipsOfTheDay::getTip(int tipId)
|
||||
{
|
||||
return tipList->at(tipId);
|
||||
}
|
||||
|
||||
int TipsOfTheDay::rowCount(const QModelIndex &parent) const
|
||||
{
|
||||
Q_UNUSED(parent);
|
||||
return tipList->size();
|
||||
}
|
||||
55
cockatrice/src/client/ui/tip_of_the_day.h
Normal file
55
cockatrice/src/client/ui/tip_of_the_day.h
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
#ifndef TIP_OF_DAY_H
|
||||
#define TIP_OF_DAY_H
|
||||
|
||||
#include <QAbstractListModel>
|
||||
#include <QDate>
|
||||
|
||||
class TipOfTheDay
|
||||
{
|
||||
public:
|
||||
explicit TipOfTheDay(QString _title, QString _content, QString _imagePath, QDate _date);
|
||||
QString getTitle() const
|
||||
{
|
||||
return title;
|
||||
}
|
||||
QString getContent() const
|
||||
{
|
||||
return content;
|
||||
}
|
||||
QString getImagePath() const
|
||||
{
|
||||
return imagePath;
|
||||
}
|
||||
QDate getDate() const
|
||||
{
|
||||
return date;
|
||||
}
|
||||
|
||||
private:
|
||||
QString title, content, imagePath;
|
||||
QDate date;
|
||||
};
|
||||
|
||||
class TipsOfTheDay : public QAbstractListModel
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
enum Columns
|
||||
{
|
||||
TitleColumn,
|
||||
ContentColumn,
|
||||
ImagePathColumn,
|
||||
DateColumn,
|
||||
};
|
||||
|
||||
explicit TipsOfTheDay(QString xmlPath, QObject *parent = nullptr);
|
||||
~TipsOfTheDay() override;
|
||||
TipOfTheDay getTip(int tipId);
|
||||
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||
QVariant data(const QModelIndex &index, int role) const override;
|
||||
|
||||
private:
|
||||
QList<TipOfTheDay> *tipList;
|
||||
};
|
||||
|
||||
#endif
|
||||
1371
cockatrice/src/client/ui/window_main.cpp
Normal file
1371
cockatrice/src/client/ui/window_main.cpp
Normal file
File diff suppressed because it is too large
Load diff
157
cockatrice/src/client/ui/window_main.h
Normal file
157
cockatrice/src/client/ui/window_main.h
Normal file
|
|
@ -0,0 +1,157 @@
|
|||
/***************************************************************************
|
||||
* Copyright (C) 2008 by Max-Wilhelm Bruker *
|
||||
* brukie@gmx.net *
|
||||
* *
|
||||
* This program is free software; you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU General Public License as published by *
|
||||
* the Free Software Foundation; either version 2 of the License, or *
|
||||
* (at your option) any later version. *
|
||||
* *
|
||||
* This program is distributed in the hope that it will be useful, *
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
* GNU General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU General Public License *
|
||||
* along with this program; if not, write to the *
|
||||
* Free Software Foundation, Inc., *
|
||||
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
|
||||
***************************************************************************/
|
||||
#ifndef WINDOW_H
|
||||
#define WINDOW_H
|
||||
|
||||
#include "../game_logic/abstract_client.h"
|
||||
#include "pb/response.pb.h"
|
||||
|
||||
#include <QList>
|
||||
#include <QMainWindow>
|
||||
#include <QMessageBox>
|
||||
#include <QProcess>
|
||||
#include <QSystemTrayIcon>
|
||||
#include <QtNetwork>
|
||||
|
||||
class DlgConnect;
|
||||
class DlgViewLog;
|
||||
class GameReplay;
|
||||
class HandlePublicServers;
|
||||
class LocalClient;
|
||||
class LocalServer;
|
||||
class QThread;
|
||||
class RemoteClient;
|
||||
class ServerInfo_User;
|
||||
class TabSupervisor;
|
||||
class WndSets;
|
||||
class DlgTipOfTheDay;
|
||||
|
||||
class MainWindow : public QMainWindow
|
||||
{
|
||||
Q_OBJECT
|
||||
public slots:
|
||||
void actCheckCardUpdates();
|
||||
void actCheckServerUpdates();
|
||||
private slots:
|
||||
void updateTabMenu(const QList<QMenu *> &newMenuList);
|
||||
void statusChanged(ClientStatus _status);
|
||||
void processConnectionClosedEvent(const Event_ConnectionClosed &event);
|
||||
void processServerShutdownEvent(const Event_ServerShutdown &event);
|
||||
void serverTimeout();
|
||||
void loginError(Response::ResponseCode r, QString reasonStr, quint32 endTime, QList<QString> missingFeatures);
|
||||
void registerError(Response::ResponseCode r, QString reasonStr, quint32 endTime);
|
||||
void activateError();
|
||||
void socketError(const QString &errorStr);
|
||||
void protocolVersionMismatch(int localVersion, int remoteVersion);
|
||||
void userInfoReceived(const ServerInfo_User &userInfo);
|
||||
void registerAccepted();
|
||||
void registerAcceptedNeedsActivate();
|
||||
void activateAccepted();
|
||||
void localGameEnded();
|
||||
void pixmapCacheSizeChanged(int newSizeInMBs);
|
||||
void notifyUserAboutUpdate();
|
||||
void actConnect();
|
||||
void actDisconnect();
|
||||
void actSinglePlayer();
|
||||
void actWatchReplay();
|
||||
void actDeckEditor();
|
||||
void actFullScreen(bool checked);
|
||||
void actRegister();
|
||||
void actSettings();
|
||||
void actExit();
|
||||
void actForgotPasswordRequest();
|
||||
void actAbout();
|
||||
void actTips();
|
||||
void actUpdate();
|
||||
void actViewLog();
|
||||
void forgotPasswordSuccess();
|
||||
void forgotPasswordError();
|
||||
void promptForgotPasswordReset();
|
||||
void actShow();
|
||||
void promptForgotPasswordChallenge();
|
||||
void showWindowIfHidden();
|
||||
|
||||
void cardUpdateError(QProcess::ProcessError err);
|
||||
void cardUpdateFinished(int exitCode, QProcess::ExitStatus exitStatus);
|
||||
void refreshShortcuts();
|
||||
void cardDatabaseLoadingFailed();
|
||||
void cardDatabaseNewSetsFound(int numUnknownSets, QStringList unknownSetsNames);
|
||||
void cardDatabaseAllNewSetsEnabled();
|
||||
|
||||
void actOpenCustomFolder();
|
||||
void actOpenCustomsetsFolder();
|
||||
void actAddCustomSet();
|
||||
|
||||
void actManageSets();
|
||||
void actEditTokens();
|
||||
|
||||
void startupConfigCheck();
|
||||
void alertForcedOracleRun(const QString &version, bool isUpdate);
|
||||
|
||||
private:
|
||||
static const QString appName;
|
||||
static const QStringList fileNameFilters;
|
||||
void setClientStatusTitle();
|
||||
void retranslateUi();
|
||||
void createActions();
|
||||
void createMenus();
|
||||
|
||||
void createTrayIcon();
|
||||
int getNextCustomSetPrefix(QDir dataDir);
|
||||
inline QString getCardUpdaterBinaryName()
|
||||
{
|
||||
return "oracle";
|
||||
};
|
||||
void exitCardDatabaseUpdate();
|
||||
|
||||
QList<QMenu *> tabMenus;
|
||||
QMenu *cockatriceMenu, *dbMenu, *helpMenu, *trayIconMenu;
|
||||
QAction *aConnect, *aDisconnect, *aSinglePlayer, *aWatchReplay, *aDeckEditor, *aFullScreen, *aSettings, *aExit,
|
||||
*aAbout, *aTips, *aCheckCardUpdates, *aRegister, *aForgotPassword, *aUpdate, *aViewLog, *aManageSets,
|
||||
*aEditTokens, *aOpenCustomFolder, *aOpenCustomsetsFolder, *aAddCustomSet, *aShow;
|
||||
TabSupervisor *tabSupervisor;
|
||||
WndSets *wndSets;
|
||||
RemoteClient *client;
|
||||
QThread *clientThread;
|
||||
LocalServer *localServer;
|
||||
bool bHasActivated, askedForDbUpdater;
|
||||
QMessageBox serverShutdownMessageBox;
|
||||
QProcess *cardUpdateProcess;
|
||||
DlgViewLog *logviewDialog;
|
||||
DlgConnect *dlgConnect;
|
||||
GameReplay *replay;
|
||||
DlgTipOfTheDay *tip;
|
||||
QUrl connectTo;
|
||||
|
||||
public:
|
||||
explicit MainWindow(QWidget *parent = nullptr);
|
||||
void setConnectTo(QString url)
|
||||
{
|
||||
connectTo = QUrl(QString("cockatrice://%1").arg(url));
|
||||
}
|
||||
~MainWindow() override;
|
||||
|
||||
protected:
|
||||
void closeEvent(QCloseEvent *event) override;
|
||||
void changeEvent(QEvent *event) override;
|
||||
QString extractInvalidUsernameMessage(QString &in);
|
||||
};
|
||||
|
||||
#endif
|
||||
66
cockatrice/src/client/update_downloader.cpp
Normal file
66
cockatrice/src/client/update_downloader.cpp
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
#include "update_downloader.h"
|
||||
|
||||
#include <QDebug>
|
||||
#include <QUrl>
|
||||
|
||||
UpdateDownloader::UpdateDownloader(QObject *parent) : QObject(parent), response(nullptr)
|
||||
{
|
||||
netMan = new QNetworkAccessManager(this);
|
||||
}
|
||||
|
||||
void UpdateDownloader::beginDownload(QUrl downloadUrl)
|
||||
{
|
||||
// Save the original URL because we need it for the filename
|
||||
if (originalUrl.isEmpty())
|
||||
originalUrl = downloadUrl;
|
||||
|
||||
response = netMan->get(QNetworkRequest(downloadUrl));
|
||||
connect(response, SIGNAL(finished()), this, SLOT(fileFinished()));
|
||||
connect(response, SIGNAL(downloadProgress(qint64, qint64)), this, SLOT(downloadProgress(qint64, qint64)));
|
||||
connect(this, SIGNAL(stopDownload()), response, SLOT(abort()));
|
||||
}
|
||||
|
||||
void UpdateDownloader::downloadError(QNetworkReply::NetworkError)
|
||||
{
|
||||
if (response == nullptr)
|
||||
return;
|
||||
|
||||
emit error(response->errorString().toUtf8());
|
||||
}
|
||||
|
||||
void UpdateDownloader::fileFinished()
|
||||
{
|
||||
// If we finished but there's a redirect, follow it
|
||||
QVariant redirect = response->attribute(QNetworkRequest::RedirectionTargetAttribute);
|
||||
if (!redirect.isNull()) {
|
||||
beginDownload(redirect.toUrl());
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle any errors we had
|
||||
if (response->error()) {
|
||||
emit error(response->errorString());
|
||||
return;
|
||||
}
|
||||
|
||||
// Work out the file name of the download
|
||||
QString fileName = QDir::temp().path() + QDir::separator() + originalUrl.toString().section('/', -1);
|
||||
|
||||
// Save the build in a temporary directory
|
||||
QFile file(fileName);
|
||||
if (!file.open(QIODevice::WriteOnly)) {
|
||||
emit error(tr("Could not open the file for reading."));
|
||||
return;
|
||||
}
|
||||
|
||||
file.write(response->readAll());
|
||||
file.close();
|
||||
|
||||
// Emit the success signal with a URL to the download file
|
||||
emit downloadSuccessful(QUrl::fromLocalFile(fileName));
|
||||
}
|
||||
|
||||
void UpdateDownloader::downloadProgress(qint64 bytesRead, qint64 totalBytes)
|
||||
{
|
||||
emit progressMade(bytesRead, totalBytes);
|
||||
}
|
||||
35
cockatrice/src/client/update_downloader.h
Normal file
35
cockatrice/src/client/update_downloader.h
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
//
|
||||
// Created by miguel on 28/12/15.
|
||||
//
|
||||
|
||||
#ifndef COCKATRICE_UPDATEDOWNLOADER_H
|
||||
#define COCKATRICE_UPDATEDOWNLOADER_H
|
||||
|
||||
#include <QDate>
|
||||
#include <QObject>
|
||||
#include <QUrl>
|
||||
#include <QtNetwork>
|
||||
|
||||
class UpdateDownloader : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
UpdateDownloader(QObject *parent);
|
||||
void beginDownload(QUrl url);
|
||||
signals:
|
||||
void downloadSuccessful(QUrl filepath);
|
||||
void progressMade(qint64 bytesRead, qint64 totalBytes);
|
||||
void error(QString errorString);
|
||||
void stopDownload();
|
||||
|
||||
private:
|
||||
QUrl originalUrl;
|
||||
QNetworkAccessManager *netMan;
|
||||
QNetworkReply *response;
|
||||
private slots:
|
||||
void fileFinished();
|
||||
void downloadProgress(qint64 bytesRead, qint64 totalBytes);
|
||||
void downloadError(QNetworkReply::NetworkError);
|
||||
};
|
||||
|
||||
#endif // COCKATRICE_UPDATEDOWNLOADER_H
|
||||
Loading…
Add table
Add a link
Reference in a new issue