Compare commits

...

17 Commits

Author SHA1 Message Date
BruebachL
07ee271478 Refactor codebase to new Qt Slot/Signal syntax - Pt1 (#5202)
---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
Co-authored-by: ZeldaZach <zahalpern+github@gmail.com>
2024-12-22 02:01:17 +00:00
RickyRister
4823cce622 Show conflicting shortcut in error message (#5287) 2024-12-22 01:58:55 +00:00
Zach H
23099f7e8b Fix token name highlight on open (#5286) 2024-12-22 01:43:00 +00:00
RickyRister
5bdbd51fa8 implement search bar in shortcuts menu (#5285)
* implement search bar in shortcuts menu

* remove unneeded imports

* use expandAll
2024-12-22 00:21:53 +00:00
BruebachL
a0e5871c6e Fix the image shrinking due to repeated scaling and FP precision loss. (#5284)
* Fix the image shrinking due to repeated scaling and FP precision loss.

* Add a setting for auto-rotating sideways layout cards.

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2024-12-21 22:47:43 +00:00
RickyRister
3cf0904651 add action to select all cards in table row (#5280) 2024-12-21 18:52:19 +00:00
Zach H
2bd06ff0fd Add CrashDump support for Windows OS (#5282) 2024-12-21 18:52:07 +00:00
RickyRister
6ea333d0f1 move SearchLineEdit into custom_line_edit file (#5281) 2024-12-21 05:12:14 +00:00
Zach H
91d2485940 Update PegLib, Fix Database Searching CFG (#5244)
* Support C++20 Standard

* Update peglib.h

* Fix lambdas

* Move from for loops to std::any/all_of

* Support fixed CFG

* Fix Rarity Search to be more accurate
2024-12-21 03:37:08 +00:00
RickyRister
0d99b2bcf4 make unattach shortcut always active (#5278) 2024-12-20 05:56:48 +00:00
RickyRister
a54a424f84 add action to select all cards in column (#5277)
* add action to select all cards in column

* change default shortcut to Ctrl+Shift+C
2024-12-20 03:39:17 +00:00
RickyRister
3514699f5b check that target card is in play before attaching (#5275) 2024-12-19 23:55:04 +00:00
RickyRister
d196988cab allow attached cards to be moved to other zones (#5276) 2024-12-19 23:53:48 +00:00
BruebachL
03aff83135 Add the ability to define starting life total during game creation. (#5174)
* Have the server respect gameType info when setting up zones.

* ServerPlayer::setupZones is now passed the room->getGameTypes();
* ServerPlayer::setupZones now checks if the GameType String includes "Commander" and then sets the life total to 40 instead.

* Formatting.

* Remove debug logging imports.

* Move game option value declarations to dlg_create_game.

* Lint.

* Fix mocks.

* Add a default for backwards compatibility.

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2024-12-19 23:52:47 +00:00
RickyRister
17e6bfaca6 fix bug with multi-attach sometimes only attaching one card (#5272) 2024-12-19 13:38:57 +00:00
Zach H
90281262be Revert "Revert "Rotate split cards (#5264)" (#5269)" (#5273)
This reverts commit d41aa30e10.
2024-12-19 13:25:54 +00:00
RickyRister
5bbc118920 fix bug introduced in #5267 (#5270)
* fix bug introduced in #5267

* remove default args to prevent similar bugs in the future

* add newInstance overload with default properties
2024-12-19 13:17:09 +00:00
53 changed files with 4510 additions and 2457 deletions

View File

@@ -81,9 +81,9 @@ if(NOT DEFINED GIT_TAG_RELEASENAME)
set(GIT_TAG_RELEASENAME "Rings of the Wild")
endif()
# Use c++17 for all targets
# Use c++20 for all targets
set(CMAKE_CXX_STANDARD
17
20
CACHE STRING "C++ ISO Standard"
)
set(CMAKE_CXX_STANDARD_REQUIRED True)
@@ -140,12 +140,14 @@ endif()
# Define proper compilation flags
if(MSVC)
# Visual Studio: Disable Warning C4251, C++17 compatibility, Multi-threaded Builds, Warn Detection, Unwind Semantics
set(CMAKE_CXX_FLAGS "/wd4251 /Zc:__cplusplus /std:c++17 /permissive- /W4 /MP /EHsc")
# Visual Studio: Disable Warning C4251, C++20 compatibility, Multi-threaded Builds, Warn Detection, Unwind Semantics
set(CMAKE_CXX_FLAGS "/wd4251 /Zc:__cplusplus /std:c++20 /permissive- /W4 /MP /EHsc")
# Visual Studio: Maximum Optimization, Multi-threaded DLL
set(CMAKE_CXX_FLAGS_RELEASE "/Ox /MD")
# Visual Studio: No Optimization, Multi-threaded Debug DLL, Debug Symbols
set(CMAKE_CXX_FLAGS_DEBUG "/Od /MDd /Zi")
add_compile_definitions(_SILENCE_STDEXT_ARR_ITERS_DEPRECATION_WARNING)
elseif(CMAKE_COMPILER_IS_GNUCXX)
# linux/gcc, bsd/gcc, windows/mingw
include(CheckCXXCompilerFlag)
@@ -158,7 +160,7 @@ elseif(CMAKE_COMPILER_IS_GNUCXX)
endif()
if(APPLE)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=gnu++17")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=gnu++20")
endif()
set(ADDITIONAL_DEBUG_FLAGS

View File

@@ -235,6 +235,13 @@ ${If} $PortableMode = 0
WriteUninstaller "$INSTDIR\uninstall.exe"
${GetSize} "$INSTDIR" "/S=0K" $0 $1 $2
IntFmt $0 "0x%08X" $0
; Enable Windows User-Mode Dumps
; https://learn.microsoft.com/en-us/windows/win32/wer/collecting-user-mode-dumps
WriteRegStr HKLM "Software\Microsoft\Windows\Windows Error Reporting\LocalDumps\cockatrice.exe" "DumpFolder" "%LOCALAPPDATA%\CrashDumps\Cockatrice"
WriteRegDWORD HKLM "Software\Microsoft\Windows\Windows Error Reporting\LocalDumps\cockatrice.exe" "DumpCount" "5"
WriteRegDWORD HKLM "Software\Microsoft\Windows\Windows Error Reporting\LocalDumps\cockatrice.exe" "DumpType" "2"
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Cockatrice" "DisplayIcon" "$INSTDIR\cockatrice.exe"
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Cockatrice" "DisplayName" "Cockatrice"
WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\Cockatrice" "DisplayVersion" "@CPACK_PACKAGE_VERSION_MAJOR@.@CPACK_PACKAGE_VERSION_MINOR@.@CPACK_PACKAGE_VERSION_PATCH@"

View File

@@ -126,6 +126,7 @@ set(cockatrice_SOURCES
src/settings/settings_manager.cpp
src/settings/cache_settings.cpp
src/settings/shortcuts_settings.cpp
src/settings/shortcut_treeview.cpp
src/client/sound_engine.cpp
src/client/network/spoiler_background_updater.cpp
src/game/zones/stack_zone.cpp

View File

@@ -52,7 +52,7 @@ AbstractClient::AbstractClient(QObject *parent)
FeatureSet features;
features.initalizeFeatureList(clientFeatures);
connect(this, SIGNAL(sigQueuePendingCommand(PendingCommand *)), this, SLOT(queuePendingCommand(PendingCommand *)));
connect(this, &AbstractClient::sigQueuePendingCommand, this, &AbstractClient::queuePendingCommand);
}
AbstractClient::~AbstractClient()

View File

@@ -38,7 +38,7 @@ 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()));
connect(response, &QNetworkReply::finished, this, &ReleaseChannel::releaseListFinished);
}
// Different release channel checking functions for different operating systems
@@ -158,7 +158,7 @@ void StableReleaseChannel::releaseListFinished()
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()));
connect(response, &QNetworkReply::finished, this, &StableReleaseChannel::tagListFinished);
}
void StableReleaseChannel::tagListFinished()
@@ -260,7 +260,7 @@ void BetaReleaseChannel::releaseListFinished()
qDebug() << "Searching for a corresponding file on the beta channel: " << betaBuildDownloadUrl;
response = netMan->get(QNetworkRequest(betaBuildDownloadUrl));
connect(response, SIGNAL(finished()), this, SLOT(fileListFinished()));
connect(response, &QNetworkReply::finished, this, &BetaReleaseChannel::fileListFinished);
}
void BetaReleaseChannel::fileListFinished()

View File

@@ -10,7 +10,7 @@ ReplayTimelineWidget::ReplayTimelineWidget(QWidget *parent)
currentEvent(0)
{
replayTimer = new QTimer(this);
connect(replayTimer, SIGNAL(timeout()), this, SLOT(replayTimerTimeout()));
connect(replayTimer, &QTimer::timeout, this, &ReplayTimelineWidget::replayTimerTimeout);
rewindBufferingTimer = new QTimer(this);
rewindBufferingTimer->setSingleShot(true);

View File

@@ -45,10 +45,10 @@ void SpoilerBackgroundUpdater::downloadFromURL(QUrl url, bool saveResults)
if (saveResults) {
// This will write out to the file (used for spoiler.xml)
connect(reply, SIGNAL(finished()), this, SLOT(actDownloadFinishedSpoilersFile()));
connect(reply, &QNetworkReply::finished, this, &SpoilerBackgroundUpdater::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()));
connect(reply, &QNetworkReply::finished, this, &SpoilerBackgroundUpdater::actCheckIfSpoilerSeasonEnabled);
}
}

View File

@@ -15,8 +15,8 @@
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()));
connect(&SettingsCache::instance(), &SettingsCache::soundThemeChanged, this, &SoundEngine::themeChangedSlot);
connect(&SettingsCache::instance(), &SettingsCache::soundEnabledChanged, this, &SoundEngine::soundEnabledChanged);
soundEnabledChanged();
themeChangedSlot();

View File

@@ -30,29 +30,22 @@ TabUserLists::TabUserLists(TabSupervisor *_tabSupervisor,
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(allUsersList, &UserList::openMessageDialog, this, &TabUserLists::openMessageDialog);
connect(buddyList, &UserList::openMessageDialog, this, &TabUserLists::openMessageDialog);
connect(ignoreList, &UserList::openMessageDialog, this, &TabUserLists::openMessageDialog);
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 &)));
connect(client, &AbstractClient::userJoinedEventReceived, this, &TabUserLists::processUserJoinedEvent);
connect(client, &AbstractClient::userLeftEventReceived, this, &TabUserLists::processUserLeftEvent);
connect(client, &AbstractClient::buddyListReceived, this, &TabUserLists::buddyListReceived);
connect(client, &AbstractClient::ignoreListReceived, this, &TabUserLists::ignoreListReceived);
connect(client, &AbstractClient::addToListEventReceived, this, &TabUserLists::processAddToListEvent);
connect(client, &AbstractClient::removeFromListEventReceived, this, &TabUserLists::processRemoveFromListEvent);
PendingCommand *pend = client->prepareSessionCommand(Command_ListUsers());
connect(pend, SIGNAL(finished(Response, CommandContainer, QVariant)), this,
SLOT(processListUsersResponse(const Response &)));
connect(pend,
static_cast<void (PendingCommand::*)(const Response &, const CommandContainer &, const QVariant &)>(
&PendingCommand::finished),
this, &TabUserLists::processListUsersResponse);
client->sendCommand(pend);
QVBoxLayout *vbox = new QVBoxLayout;
@@ -63,9 +56,9 @@ TabUserLists::TabUserLists(TabSupervisor *_tabSupervisor,
addBuddyEdit = new LineEditUnfocusable;
addBuddyEdit->setMaxLength(MAX_NAME_LENGTH);
addBuddyEdit->setPlaceholderText(tr("Add to Buddy List"));
connect(addBuddyEdit, SIGNAL(returnPressed()), this, SLOT(addToBuddyList()));
connect(addBuddyEdit, &LineEditUnfocusable::returnPressed, this, &TabUserLists::addToBuddyList);
QPushButton *addBuddyButton = new QPushButton("Add");
connect(addBuddyButton, SIGNAL(clicked()), this, SLOT(addToBuddyList()));
connect(addBuddyButton, &QPushButton::clicked, this, &TabUserLists::addToBuddyList);
addToBuddyList->addWidget(addBuddyEdit);
addToBuddyList->addWidget(addBuddyButton);
@@ -73,9 +66,9 @@ TabUserLists::TabUserLists(TabSupervisor *_tabSupervisor,
addIgnoreEdit = new LineEditUnfocusable;
addIgnoreEdit->setMaxLength(MAX_NAME_LENGTH);
addIgnoreEdit->setPlaceholderText(tr("Add to Ignore List"));
connect(addIgnoreEdit, SIGNAL(returnPressed()), this, SLOT(addToIgnoreList()));
connect(addIgnoreEdit, &LineEditUnfocusable::returnPressed, this, &TabUserLists::addToIgnoreList);
QPushButton *addIgnoreButton = new QPushButton("Add");
connect(addIgnoreButton, SIGNAL(clicked()), this, SLOT(addToIgnoreList()));
connect(addIgnoreButton, &QPushButton::clicked, this, &TabUserLists::addToIgnoreList);
addToIgnoreList->addWidget(addIgnoreEdit);
addToIgnoreList->addWidget(addIgnoreButton);

View File

@@ -29,8 +29,8 @@ ShutdownDialog::ShutdownDialog(QWidget *parent) : QDialog(parent)
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()));
connect(buttonBox, &QDialogButtonBox::accepted, this, &ShutdownDialog::accept);
connect(buttonBox, &QDialogButtonBox::accepted, this, &ShutdownDialog::reject);
QGridLayout *mainLayout = new QGridLayout;
mainLayout->addWidget(reasonLabel, 0, 0);
@@ -57,11 +57,11 @@ TabAdmin::TabAdmin(TabSupervisor *_tabSupervisor, AbstractClient *_client, bool
: Tab(_tabSupervisor, parent), locked(true), client(_client), fullAdmin(_fullAdmin)
{
updateServerMessageButton = new QPushButton;
connect(updateServerMessageButton, SIGNAL(clicked()), this, SLOT(actUpdateServerMessage()));
connect(updateServerMessageButton, &QPushButton::clicked, this, &TabAdmin::actUpdateServerMessage);
shutdownServerButton = new QPushButton;
connect(shutdownServerButton, SIGNAL(clicked()), this, SLOT(actShutdownServer()));
connect(shutdownServerButton, &QPushButton::clicked, this, &TabAdmin::actShutdownServer);
reloadConfigButton = new QPushButton;
connect(reloadConfigButton, SIGNAL(clicked()), this, SLOT(actReloadConfig()));
connect(reloadConfigButton, &QPushButton::clicked, this, &TabAdmin::actReloadConfig);
QVBoxLayout *vbox = new QVBoxLayout;
vbox->addWidget(updateServerMessageButton);
@@ -74,10 +74,10 @@ TabAdmin::TabAdmin(TabSupervisor *_tabSupervisor, AbstractClient *_client, bool
adminGroupBox->setEnabled(false);
unlockButton = new QPushButton;
connect(unlockButton, SIGNAL(clicked()), this, SLOT(actUnlock()));
connect(unlockButton, &QPushButton::clicked, this, &TabAdmin::actUnlock);
lockButton = new QPushButton;
lockButton->setEnabled(false);
connect(lockButton, SIGNAL(clicked()), this, SLOT(actLock()));
connect(lockButton, &QPushButton::clicked, this, &TabAdmin::actLock);
QVBoxLayout *mainLayout = new QVBoxLayout;
mainLayout->addWidget(adminGroupBox);

View File

@@ -50,22 +50,6 @@
#include <QUrl>
#include <QVBoxLayout>
void SearchLineEdit::keyPressEvent(QKeyEvent *event)
{
// List of key events that must be handled by the card list instead of the search box
static const QVector<Qt::Key> forwardToTreeView = {Qt::Key_Up, Qt::Key_Down, Qt::Key_PageDown, Qt::Key_PageUp};
// forward only if the search text is empty
static const QVector<Qt::Key> forwardWhenEmpty = {Qt::Key_Home, Qt::Key_End};
Qt::Key key = static_cast<Qt::Key>(event->key());
if (treeView) {
if (forwardToTreeView.contains(key))
QCoreApplication::sendEvent(treeView, event);
if (text().isEmpty() && forwardWhenEmpty.contains(key))
QCoreApplication::sendEvent(treeView, event);
}
LineEditUnfocusable::keyPressEvent(event);
}
void TabDeckEditor::createDeckDock()
{
deckModel = new DeckListModel(this);
@@ -1535,6 +1519,6 @@ void TabDeckEditor::showSearchSyntaxHelp()
browser->document()->setDefaultStyleSheet(sheet);
browser->setHtml(text);
connect(browser, &QTextBrowser::anchorClicked, [=](const QUrl &link) { searchEdit->setText(link.fragment()); });
connect(browser, &QTextBrowser::anchorClicked, [this](const QUrl &link) { searchEdit->setText(link.fragment()); });
browser->show();
}

View File

@@ -29,24 +29,6 @@ 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

View File

@@ -1742,22 +1742,22 @@ void TabGame::createReplayDock()
aReplaySkipForward = new QAction(timelineWidget);
timelineWidget->addAction(aReplaySkipForward);
connect(aReplaySkipForward, &QAction::triggered, this,
[=]() { timelineWidget->skipByAmount(ReplayTimelineWidget::SMALL_SKIP_MS); });
[this] { timelineWidget->skipByAmount(ReplayTimelineWidget::SMALL_SKIP_MS); });
aReplaySkipBackward = new QAction(timelineWidget);
timelineWidget->addAction(aReplaySkipBackward);
connect(aReplaySkipBackward, &QAction::triggered, this,
[=]() { timelineWidget->skipByAmount(-ReplayTimelineWidget::SMALL_SKIP_MS); });
[this] { timelineWidget->skipByAmount(-ReplayTimelineWidget::SMALL_SKIP_MS); });
aReplaySkipForwardBig = new QAction(timelineWidget);
timelineWidget->addAction(aReplaySkipForwardBig);
connect(aReplaySkipForwardBig, &QAction::triggered, this,
[=]() { timelineWidget->skipByAmount(ReplayTimelineWidget::BIG_SKIP_MS); });
[this] { timelineWidget->skipByAmount(ReplayTimelineWidget::BIG_SKIP_MS); });
aReplaySkipBackwardBig = new QAction(timelineWidget);
timelineWidget->addAction(aReplaySkipBackwardBig);
connect(aReplaySkipBackwardBig, &QAction::triggered, this,
[=]() { timelineWidget->skipByAmount(-ReplayTimelineWidget::BIG_SKIP_MS); });
[this] { timelineWidget->skipByAmount(-ReplayTimelineWidget::BIG_SKIP_MS); });
// buttons
replayPlayButton = new QToolButton;

View File

@@ -14,7 +14,7 @@ TappedOutInterface::TappedOutInterface(CardDatabase &_cardDatabase, QObject *par
: QObject(parent), cardDatabase(_cardDatabase)
{
manager = new QNetworkAccessManager(this);
connect(manager, SIGNAL(finished(QNetworkReply *)), this, SLOT(queryFinished(QNetworkReply *)));
connect(manager, &QNetworkAccessManager::finished, this, &TappedOutInterface::queryFinished);
}
void TappedOutInterface::queryFinished(QNetworkReply *reply)

View File

@@ -7,23 +7,23 @@
class TearOffMenu : public QMenu
{
public:
TearOffMenu(const QString &title, QWidget *parent = nullptr) : QMenu(title, parent)
explicit TearOffMenu(const QString &title, QWidget *parent = nullptr) : QMenu(title, parent)
{
connect(&SettingsCache::instance(), &SettingsCache::useTearOffMenusChanged, this,
[=](bool state) { setTearOffEnabled(state); });
[this](const bool state) { setTearOffEnabled(state); });
setTearOffEnabled(SettingsCache::instance().getUseTearOffMenus());
}
TearOffMenu(QWidget *parent = nullptr) : QMenu(parent)
explicit TearOffMenu(QWidget *parent = nullptr) : QMenu(parent)
{
connect(&SettingsCache::instance(), &SettingsCache::useTearOffMenusChanged, this,
[=](bool state) { setTearOffEnabled(state); });
[this](const bool state) { setTearOffEnabled(state); });
setTearOffEnabled(SettingsCache::instance().getUseTearOffMenus());
}
TearOffMenu *addTearOffMenu(const QString &title)
{
TearOffMenu *menu = new TearOffMenu(title, this);
auto *menu = new TearOffMenu(title, this);
addMenu(menu);
return menu;
}

View File

@@ -1,6 +1,7 @@
#include "card_info_picture_widget.h"
#include "../../../../game/cards/card_item.h"
#include "../../../../settings/cache_settings.h"
#include "../../picture_loader.h"
#include <QMouseEvent>
@@ -141,6 +142,7 @@ void CardInfoPictureWidget::loadPixmap()
void CardInfoPictureWidget::paintEvent(QPaintEvent *event)
{
QWidget::paintEvent(event);
if (width() == 0 || height() == 0) {
return;
}
@@ -149,15 +151,31 @@ void CardInfoPictureWidget::paintEvent(QPaintEvent *event)
loadPixmap();
}
const QSize scaledSize = resizedPixmap.size().scaled(size(), Qt::KeepAspectRatio);
const QPoint topLeft{(width() - scaledSize.width()) / 2, (height() - scaledSize.height()) / 2};
QPixmap transformedPixmap = resizedPixmap; // Default pixmap
if (SettingsCache::instance().getAutoRotateSidewaysLayoutCards()) {
if (info && info->getLandscapeOrientation()) {
// Rotate pixmap 90 degrees to the left
QTransform transform;
transform.rotate(90);
transformedPixmap = resizedPixmap.transformed(transform, Qt::SmoothTransformation);
}
}
// Adjust scaling after rotation
const QSize availableSize = size(); // Size of the widget
const QSize scaledSize = transformedPixmap.size().scaled(availableSize, Qt::KeepAspectRatio);
const QRect targetRect{(availableSize.width() - scaledSize.width()) / 2,
(availableSize.height() - scaledSize.height()) / 2, scaledSize.width(), scaledSize.height()};
const qreal radius = 0.05 * scaledSize.width();
// Draw the pixmap with rounded corners
QStylePainter painter(this);
QPainterPath shape;
shape.addRoundedRect(QRect(topLeft, scaledSize), radius, radius);
shape.addRoundedRect(targetRect, radius, radius);
painter.setClipPath(shape);
painter.drawItemPixmap(QRect(topLeft, scaledSize), Qt::AlignCenter, resizedPixmap);
painter.drawPixmap(targetRect, transformedPixmap.scaled(scaledSize, Qt::KeepAspectRatio, Qt::SmoothTransformation));
}
/**

View File

@@ -175,7 +175,7 @@ void PrintingSelector::getAllSetsForCurrentCard()
// Defer widget creation
currentIndex = 0;
connect(widgetLoadingBufferTimer, &QTimer::timeout, this, [=]() mutable {
connect(widgetLoadingBufferTimer, &QTimer::timeout, this, [=, this]() mutable {
for (int i = 0; i < BATCH_SIZE && currentIndex < setsToUse.size(); ++i, ++currentIndex) {
auto *cardDisplayWidget = new PrintingSelectorCardDisplayWidget(this, deckEditor, deckModel, deckView,
cardSizeWidget->getSlider(), selectedCard,

View File

@@ -15,9 +15,9 @@ void UpdateDownloader::beginDownload(QUrl downloadUrl)
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()));
connect(response, &QNetworkReply::finished, this, &UpdateDownloader::fileFinished);
connect(response, &QNetworkReply::downloadProgress, this, &UpdateDownloader::downloadProgress);
connect(this, &UpdateDownloader::stopDownload, response, &QNetworkReply::abort);
}
void UpdateDownloader::downloadError(QNetworkReply::NetworkError)

View File

@@ -6,6 +6,7 @@
#include <QKeyEvent>
#include <QLineEdit>
#include <QObject>
#include <QTreeView>
#include <QWidget>
LineEditUnfocusable::LineEditUnfocusable(QWidget *parent) : QLineEdit(parent)
@@ -69,3 +70,19 @@ bool LineEditUnfocusable::eventFilter(QObject *watched, QEvent *event)
return QLineEdit::eventFilter(watched, event);
}
void SearchLineEdit::keyPressEvent(QKeyEvent *event)
{
// List of key events that must be handled by the card list instead of the search box
static const QVector<Qt::Key> forwardToTreeView = {Qt::Key_Up, Qt::Key_Down, Qt::Key_PageDown, Qt::Key_PageUp};
// forward only if the search text is empty
static const QVector<Qt::Key> forwardWhenEmpty = {Qt::Key_Home, Qt::Key_End};
Qt::Key key = static_cast<Qt::Key>(event->key());
if (treeView) {
if (forwardToTreeView.contains(key))
QCoreApplication::sendEvent(treeView, event);
if (text().isEmpty() && forwardWhenEmpty.contains(key))
QCoreApplication::sendEvent(treeView, event);
}
LineEditUnfocusable::keyPressEvent(event);
}

View File

@@ -4,6 +4,7 @@
#include <QLineEdit>
class QTreeView;
class QKeyEvent;
class QWidget;
class QString;
@@ -25,4 +26,22 @@ protected:
bool eventFilter(QObject *watched, QEvent *event) override;
};
class SearchLineEdit : public LineEditUnfocusable
{
private:
QTreeView *treeView;
protected:
void keyPressEvent(QKeyEvent *event) override;
public:
SearchLineEdit() : LineEditUnfocusable(), treeView(nullptr)
{
}
void setTreeView(QTreeView *_treeView)
{
treeView = _treeView;
}
};
#endif

View File

@@ -95,12 +95,26 @@ void DlgCreateGame::sharedCtor()
spectatorsGroupBox = new QGroupBox(tr("Spectators"));
spectatorsGroupBox->setLayout(spectatorsLayout);
startingLifeTotalLabel = new QLabel(tr("Starting life total:"));
startingLifeTotalEdit = new QSpinBox();
startingLifeTotalEdit->setMinimum(1);
startingLifeTotalEdit->setMaximum(99999); ///< Arbitrary but we can raise this when people start complaining.
startingLifeTotalEdit->setValue(20);
startingLifeTotalLabel->setBuddy(startingLifeTotalEdit);
QGridLayout *gameSetupOptionsLayout = new QGridLayout;
gameSetupOptionsLayout->addWidget(startingLifeTotalLabel, 0, 0);
gameSetupOptionsLayout->addWidget(startingLifeTotalEdit, 0, 1);
gameSetupOptionsGroupBox = new QGroupBox(tr("Game setup options"));
gameSetupOptionsGroupBox->setLayout(gameSetupOptionsLayout);
QGridLayout *grid = new QGridLayout;
grid->addWidget(generalGroupBox, 0, 0);
grid->addWidget(joinRestrictionsGroupBox, 0, 1);
grid->addWidget(gameTypeGroupBox, 1, 0);
grid->addWidget(spectatorsGroupBox, 1, 1, Qt::AlignTop);
grid->addWidget(rememberGameSettings, 2, 0);
grid->addWidget(gameSetupOptionsGroupBox, 2, 0);
grid->addWidget(rememberGameSettings, 3, 0);
buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok);
connect(buttonBox, SIGNAL(rejected()), this, SLOT(reject()));
@@ -134,6 +148,7 @@ DlgCreateGame::DlgCreateGame(TabRoom *_room, const QMap<int, QString> &_gameType
spectatorsCanTalkCheckBox->setChecked(SettingsCache::instance().getSpectatorsCanTalk());
spectatorsSeeEverythingCheckBox->setChecked(SettingsCache::instance().getSpectatorsCanSeeEverything());
createGameAsSpectatorCheckBox->setChecked(SettingsCache::instance().getCreateGameAsSpectator());
startingLifeTotalEdit->setValue(SettingsCache::instance().getDefaultStartingLifeTotal());
if (!rememberGameSettings->isChecked()) {
actReset();
@@ -208,6 +223,8 @@ void DlgCreateGame::actReset()
spectatorsSeeEverythingCheckBox->setChecked(false);
createGameAsSpectatorCheckBox->setChecked(false);
startingLifeTotalEdit->setValue(20);
QMapIterator<int, QRadioButton *> gameTypeCheckBoxIterator(gameTypeCheckBoxes);
while (gameTypeCheckBoxIterator.hasNext()) {
gameTypeCheckBoxIterator.next();
@@ -234,6 +251,7 @@ void DlgCreateGame::actOK()
cmd.set_spectators_see_everything(spectatorsSeeEverythingCheckBox->isChecked());
cmd.set_join_as_judge(QApplication::keyboardModifiers() & Qt::ShiftModifier);
cmd.set_join_as_spectator(createGameAsSpectatorCheckBox->isChecked());
cmd.set_starting_life_total(startingLifeTotalEdit->value());
QString _gameTypes = QString();
QMapIterator<int, QRadioButton *> gameTypeCheckBoxIterator(gameTypeCheckBoxes);
@@ -256,6 +274,7 @@ void DlgCreateGame::actOK()
SettingsCache::instance().setSpectatorsCanTalk(spectatorsCanTalkCheckBox->isChecked());
SettingsCache::instance().setSpectatorsCanSeeEverything(spectatorsSeeEverythingCheckBox->isChecked());
SettingsCache::instance().setCreateGameAsSpectator(createGameAsSpectatorCheckBox->isChecked());
SettingsCache::instance().setDefaultStartingLifeTotal(startingLifeTotalEdit->value());
SettingsCache::instance().setGameTypes(_gameTypes);
}
PendingCommand *pend = room->prepareRoomCommand(cmd);

View File

@@ -35,10 +35,10 @@ private:
QMap<int, QString> gameTypes;
QMap<int, QRadioButton *> gameTypeCheckBoxes;
QGroupBox *generalGroupBox, *spectatorsGroupBox;
QLabel *descriptionLabel, *passwordLabel, *maxPlayersLabel;
QGroupBox *generalGroupBox, *spectatorsGroupBox, *gameSetupOptionsGroupBox;
QLabel *descriptionLabel, *passwordLabel, *maxPlayersLabel, *startingLifeTotalLabel;
QLineEdit *descriptionEdit, *passwordEdit;
QSpinBox *maxPlayersEdit;
QSpinBox *maxPlayersEdit, *startingLifeTotalEdit;
QCheckBox *onlyBuddiesCheckBox, *onlyRegisteredCheckBox;
QCheckBox *spectatorsAllowedCheckBox, *spectatorsNeedPasswordCheckBox, *spectatorsCanTalkCheckBox,
*spectatorsSeeEverythingCheckBox, *createGameAsSpectatorCheckBox;

View File

@@ -31,7 +31,11 @@ DlgCreateToken::DlgCreateToken(const QStringList &_predefinedTokens, QWidget *pa
nameLabel = new QLabel(tr("&Name:"));
nameEdit = new QLineEdit(tr("Token"));
nameEdit->setMaxLength(MAX_NAME_LENGTH);
QTimer::singleShot(100, this, [=, this]() {
nameEdit->selectAll();
nameEdit->setFocus();
});
connect(nameEdit, SIGNAL(textChanged(const QString &)), this, SLOT(updateSearch(const QString &)));
nameLabel->setBuddy(nameEdit);

View File

@@ -6,10 +6,12 @@
#include "../client/sound_engine.h"
#include "../client/ui/picture_loader.h"
#include "../client/ui/theme_manager.h"
#include "../deck/custom_line_edit.h"
#include "../game/cards/card_database.h"
#include "../game/cards/card_database_manager.h"
#include "../main.h"
#include "../settings/cache_settings.h"
#include "../settings/shortcut_treeview.h"
#include "../utility/sequence_edit.h"
#include <QAction>
@@ -37,8 +39,6 @@
#include <QStackedWidget>
#include <QToolBar>
#include <QTranslator>
#include <QTreeWidget>
#include <QTreeWidgetItem>
#define WIKI_CUSTOM_PIC_URL "https://github.com/Cockatrice/Cockatrice/wiki/Custom-Picture-Download-URLs"
#define WIKI_CUSTOM_SHORTCUTS "https://github.com/Cockatrice/Cockatrice/wiki/Custom-Keyboard-Shortcuts"
@@ -338,6 +338,10 @@ AppearanceSettingsPage::AppearanceSettingsPage()
displayCardNamesCheckBox.setChecked(settings.getDisplayCardNames());
connect(&displayCardNamesCheckBox, &QCheckBox::QT_STATE_CHANGED, &settings, &SettingsCache::setDisplayCardNames);
autoRotateSidewaysLayoutCardsCheckBox.setChecked(settings.getAutoRotateSidewaysLayoutCards());
connect(&autoRotateSidewaysLayoutCardsCheckBox, &QCheckBox::QT_STATE_CHANGED, &settings,
&SettingsCache::setAutoRotateSidewaysLayoutCards);
overrideAllCardArtWithPersonalPreferenceCheckBox.setChecked(settings.getOverrideAllCardArtWithPersonalPreference());
connect(&overrideAllCardArtWithPersonalPreferenceCheckBox, &QCheckBox::QT_STATE_CHANGED, &settings,
&SettingsCache::setOverrideAllCardArtWithPersonalPreference);
@@ -361,13 +365,14 @@ AppearanceSettingsPage::AppearanceSettingsPage()
auto *cardsGrid = new QGridLayout;
cardsGrid->addWidget(&displayCardNamesCheckBox, 0, 0, 1, 2);
cardsGrid->addWidget(&cardScalingCheckBox, 1, 0, 1, 2);
cardsGrid->addWidget(&overrideAllCardArtWithPersonalPreferenceCheckBox, 2, 0, 1, 2);
cardsGrid->addWidget(&bumpSetsWithCardsInDeckToTopCheckBox, 3, 0, 1, 2);
cardsGrid->addWidget(&verticalCardOverlapPercentLabel, 4, 0, 1, 1);
cardsGrid->addWidget(&verticalCardOverlapPercentBox, 4, 1, 1, 1);
cardsGrid->addWidget(&cardViewInitialRowsMaxLabel, 5, 0);
cardsGrid->addWidget(&cardViewInitialRowsMaxBox, 5, 1);
cardsGrid->addWidget(&autoRotateSidewaysLayoutCardsCheckBox, 1, 0, 1, 2);
cardsGrid->addWidget(&cardScalingCheckBox, 2, 0, 1, 2);
cardsGrid->addWidget(&overrideAllCardArtWithPersonalPreferenceCheckBox, 3, 0, 1, 2);
cardsGrid->addWidget(&bumpSetsWithCardsInDeckToTopCheckBox, 4, 0, 1, 2);
cardsGrid->addWidget(&verticalCardOverlapPercentLabel, 5, 0, 1, 1);
cardsGrid->addWidget(&verticalCardOverlapPercentBox, 5, 1, 1, 1);
cardsGrid->addWidget(&cardViewInitialRowsMaxLabel, 6, 0);
cardsGrid->addWidget(&cardViewInitialRowsMaxBox, 6, 1);
cardsGroupBox = new QGroupBox;
cardsGroupBox->setLayout(cardsGrid);
@@ -462,6 +467,7 @@ void AppearanceSettingsPage::retranslateUi()
cardsGroupBox->setTitle(tr("Card rendering"));
displayCardNamesCheckBox.setText(tr("Display card names on cards having a picture"));
autoRotateSidewaysLayoutCardsCheckBox.setText(tr("Auto-Rotate cards with sideways layout"));
overrideAllCardArtWithPersonalPreferenceCheckBox.setText(
tr("Override all card art with personal set preference (Pre-ProviderID change behavior) [Requires Client "
"restart]"));
@@ -1251,13 +1257,24 @@ void SoundSettingsPage::retranslateUi()
ShortcutSettingsPage::ShortcutSettingsPage()
{
// search bar
searchEdit = new SearchLineEdit;
searchEdit->setObjectName("searchEdit");
searchEdit->setPlaceholderText(tr("Search by shortcut name"));
searchEdit->setClearButtonEnabled(true);
setFocusProxy(searchEdit);
setFocusPolicy(Qt::ClickFocus);
// table
shortcutsTable = new QTreeWidget();
shortcutsTable->setColumnCount(2);
shortcutsTable = new ShortcutTreeView(this);
shortcutsTable->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
shortcutsTable->setUniformRowHeights(true);
shortcutsTable->setAlternatingRowColors(true);
shortcutsTable->header()->resizeSection(0, shortcutsTable->width() / 3 * 2);
shortcutsTable->setEditTriggers(QAbstractItemView::NoEditTriggers);
shortcutsTable->setColumnWidth(0, width() / 3 * 2);
searchEdit->setTreeView(shortcutsTable);
connect(searchEdit, &SearchLineEdit::textChanged, shortcutsTable, &ShortcutTreeView::updateSearchString);
// edit widget
currentActionGroupLabel = new QLabel(this);
@@ -1297,6 +1314,7 @@ ShortcutSettingsPage::ShortcutSettingsPage()
_buttonsLayout->addWidget(btnClearAll);
auto *_mainLayout = new QVBoxLayout;
_mainLayout->addWidget(searchEdit);
_mainLayout->addWidget(shortcutsTable);
_mainLayout->addWidget(editShortcutGroupBox);
_mainLayout->addLayout(_buttonsLayout);
@@ -1305,21 +1323,17 @@ ShortcutSettingsPage::ShortcutSettingsPage()
connect(btnResetAll, SIGNAL(clicked()), this, SLOT(resetShortcuts()));
connect(btnClearAll, SIGNAL(clicked()), this, SLOT(clearShortcuts()));
connect(shortcutsTable, SIGNAL(currentItemChanged(QTreeWidgetItem *, QTreeWidgetItem *)), this,
SLOT(currentItemChanged(QTreeWidgetItem *, QTreeWidgetItem *)));
connect(&SettingsCache::instance().shortcuts(), SIGNAL(shortCutChanged()), this, SLOT(refreshShortcuts()));
createShortcuts();
connect(shortcutsTable, &ShortcutTreeView::currentItemChanged, this, &ShortcutSettingsPage::currentItemChanged);
}
void ShortcutSettingsPage::currentItemChanged(QTreeWidgetItem *current, QTreeWidgetItem * /*previous */)
void ShortcutSettingsPage::currentItemChanged(const QString &key)
{
if (current == nullptr) {
if (key.isEmpty()) {
currentActionGroupName->setText("");
currentActionName->setText("");
editTextBox->setShortcutName("");
} else {
QString key = current->data(2, Qt::DisplayRole).toString();
QString group = SettingsCache::instance().shortcuts().getShortcut(key).getGroupName();
QString action = SettingsCache::instance().shortcuts().getShortcut(key).getName();
currentActionGroupName->setText(group);
@@ -1336,59 +1350,6 @@ void ShortcutSettingsPage::resetShortcuts()
}
}
void ShortcutSettingsPage::createShortcuts()
{
QHash<QString, QTreeWidgetItem *> parentItems;
QTreeWidgetItem *curParent = nullptr;
for (const auto &key : SettingsCache::instance().shortcuts().getAllShortcutKeys()) {
QString name = SettingsCache::instance().shortcuts().getShortcut(key).getName();
QString group = SettingsCache::instance().shortcuts().getShortcut(key).getGroupName();
QString shortcut = SettingsCache::instance().shortcuts().getShortcutString(key);
if (parentItems.contains(group)) {
curParent = parentItems.value(group);
} else {
curParent = new QTreeWidgetItem((QTreeWidget *)nullptr, QStringList({group, "", ""}));
static QFont font = curParent->font(0);
font.setBold(true);
curParent->setFont(0, font);
parentItems.insert(group, curParent);
}
new QTreeWidgetItem(curParent, QStringList({name, shortcut, key}));
}
shortcutsTable->clear();
shortcutsTable->insertTopLevelItems(0, parentItems.values());
shortcutsTable->setCurrentItem(nullptr);
shortcutsTable->expandAll();
shortcutsTable->sortItems(0, Qt::AscendingOrder);
}
void ShortcutSettingsPage::refreshShortcuts()
{
QTreeWidgetItem *curParent = nullptr;
QTreeWidgetItem *curChild = nullptr;
for (int i = 0; i < shortcutsTable->topLevelItemCount(); ++i) {
curParent = shortcutsTable->topLevelItem(i);
for (int j = 0; j < curParent->childCount(); ++j) {
curChild = curParent->child(j);
QString key = curChild->data(2, Qt::DisplayRole).toString();
QString name = SettingsCache::instance().shortcuts().getShortcut(key).getName();
QString shortcut = SettingsCache::instance().shortcuts().getShortcutString(key);
curChild->setText(0, name);
curChild->setText(1, shortcut);
if (j == 0) {
// the first child also updates the parent's group name
QString group = SettingsCache::instance().shortcuts().getShortcut(key).getGroupName();
curParent->setText(0, group);
}
}
}
shortcutsTable->sortItems(0, Qt::AscendingOrder);
currentItemChanged(shortcutsTable->currentItem(), nullptr);
}
void ShortcutSettingsPage::clearShortcuts()
{
if (QMessageBox::question(this, tr("Clear all default shortcuts"),
@@ -1399,8 +1360,7 @@ void ShortcutSettingsPage::clearShortcuts()
void ShortcutSettingsPage::retranslateUi()
{
shortcutsTable->setHeaderLabels(QStringList() << tr("Action") << tr("Shortcut"));
refreshShortcuts();
shortcutsTable->retranslateUi();
currentActionGroupLabel->setText(tr("Section:"));
currentActionLabel->setText(tr("Action:"));

View File

@@ -11,6 +11,10 @@
#include <QPushButton>
#include <QSpinBox>
class ShortcutTreeView;
class SearchLineEdit;
class QTreeView;
class QStandardItemModel;
class CardDatabase;
class QCloseEvent;
class QGridLayout;
@@ -21,8 +25,6 @@ class QListWidgetItem;
class QRadioButton;
class QSlider;
class QStackedWidget;
class QTreeWidget;
class QTreeWidgetItem;
class QVBoxLayout;
class SequenceEdit;
@@ -93,6 +95,7 @@ private:
QLabel maxFontSizeForCardsLabel;
QCheckBox showShortcutsCheckBox;
QCheckBox displayCardNamesCheckBox;
QCheckBox autoRotateSidewaysLayoutCardsCheckBox;
QCheckBox overrideAllCardArtWithPersonalPreferenceCheckBox;
QCheckBox bumpSetsWithCardsInDeckToTopCheckBox;
QCheckBox cardScalingCheckBox;
@@ -267,7 +270,8 @@ public:
void retranslateUi() override;
private:
QTreeWidget *shortcutsTable;
SearchLineEdit *searchEdit;
ShortcutTreeView *shortcutsTable;
QVBoxLayout *mainLayout;
QHBoxLayout *buttonsLayout;
QGroupBox *editShortcutGroupBox;
@@ -284,10 +288,8 @@ private:
private slots:
void resetShortcuts();
void refreshShortcuts();
void createShortcuts();
void clearShortcuts();
void currentItemChanged(QTreeWidgetItem *current, QTreeWidgetItem *previous);
void currentItemChanged(const QString &key);
};
class DlgSettings : public QDialog

View File

@@ -19,8 +19,8 @@
#include <QtMath>
ArrowItem::ArrowItem(Player *_player, int _id, ArrowTarget *_startItem, ArrowTarget *_targetItem, const QColor &_color)
: QGraphicsItem(), player(_player), id(_id), startItem(_startItem), targetItem(_targetItem), color(_color),
fullColor(true)
: QGraphicsItem(), player(_player), id(_id), startItem(_startItem), targetItem(_targetItem), targetLocked(false),
color(_color), fullColor(true)
{
qDebug() << "ArrowItem constructor: startItem=" << static_cast<QGraphicsItem *>(startItem);
setZValue(2000000005);
@@ -167,7 +167,7 @@ void ArrowDragItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
{
// This ensures that if a mouse move event happens after a call to delArrow(),
// the event will be discarded as it would create some stray pointers.
if (!startItem)
if (targetLocked || !startItem)
return;
QPointF endPos = event->scenePos();
@@ -268,7 +268,7 @@ void ArrowAttachItem::addChildArrow(ArrowAttachItem *childArrow)
void ArrowAttachItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
{
if (!startItem)
if (targetLocked || !startItem)
return;
QPointF endPos = event->scenePos();
@@ -309,8 +309,8 @@ void ArrowAttachItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
void ArrowAttachItem::attachCards(CardItem *startCard, const CardItem *targetCard)
{
// do nothing if target is already attached to another card
if (targetCard->getAttachedTo()) {
// do nothing if target is already attached to another card or is not in play
if (targetCard->getAttachedTo() || targetCard->getZone()->getName() != "table") {
return;
}
@@ -337,6 +337,12 @@ void ArrowAttachItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
if (!startItem)
return;
// Attaching could move startItem under the current cursor position, causing all children to retarget to it right
// before they are processed. Prevent that.
for (ArrowAttachItem *child : childArrows) {
child->setTargetLocked(true);
}
if (targetItem && (targetItem != startItem)) {
auto startCard = qgraphicsitem_cast<CardItem *>(startItem);
auto targetCard = qgraphicsitem_cast<CardItem *>(targetItem);

View File

@@ -21,6 +21,7 @@ protected:
Player *player;
int id;
ArrowTarget *startItem, *targetItem;
bool targetLocked;
QColor color;
bool fullColor;
void mousePressEvent(QGraphicsSceneMouseEvent *event);
@@ -64,6 +65,10 @@ public:
{
return targetItem;
}
void setTargetLocked(bool _targetLocked)
{
targetLocked = _targetLocked;
}
void delArrow();
};

View File

@@ -252,6 +252,12 @@ CardInfo::~CardInfo()
PictureLoader::clearPixmapCache(smartThis);
}
CardInfoPtr CardInfo::newInstance(const QString &_name)
{
return newInstance(_name, QString(), false, QVariantHash(), QList<CardRelation *>(), QList<CardRelation *>(),
CardInfoPerSetMap(), false, false, 0, false);
}
CardInfoPtr CardInfo::newInstance(const QString &_name,
const QString &_text,
bool _isToken,

View File

@@ -202,17 +202,17 @@ private:
bool upsideDownArt;
public:
explicit CardInfo(const QString &_name = QString(),
const QString &_text = QString(),
bool _isToken = false,
QVariantHash _properties = QVariantHash(),
const QList<CardRelation *> &_relatedCards = QList<CardRelation *>(),
const QList<CardRelation *> &_reverseRelatedCards = QList<CardRelation *>(),
CardInfoPerSetMap _sets = CardInfoPerSetMap(),
bool _cipt = false,
bool _landscapeOrientation = false,
int _tableRow = 0,
bool _upsideDownArt = false);
explicit CardInfo(const QString &_name,
const QString &_text,
bool _isToken,
QVariantHash _properties,
const QList<CardRelation *> &_relatedCards,
const QList<CardRelation *> &_reverseRelatedCards,
CardInfoPerSetMap _sets,
bool _cipt,
bool _landscapeOrientation,
int _tableRow,
bool _upsideDownArt);
CardInfo(const CardInfo &other)
: QObject(other.parent()), name(other.name), simpleName(other.simpleName), pixmapCacheKey(other.pixmapCacheKey),
text(other.text), isToken(other.isToken), properties(other.properties), relatedCards(other.relatedCards),
@@ -223,17 +223,19 @@ public:
}
~CardInfo() override;
static CardInfoPtr newInstance(const QString &_name = QString(),
const QString &_text = QString(),
bool _isToken = false,
QVariantHash _properties = QVariantHash(),
const QList<CardRelation *> &_relatedCards = QList<CardRelation *>(),
const QList<CardRelation *> &_reverseRelatedCards = QList<CardRelation *>(),
CardInfoPerSetMap _sets = CardInfoPerSetMap(),
bool _cipt = false,
bool _landscapeOrientation = false,
int _tableRow = 0,
bool _upsideDownArt = false);
static CardInfoPtr newInstance(const QString &_name);
static CardInfoPtr newInstance(const QString &_name,
const QString &_text,
bool _isToken,
QVariantHash _properties,
const QList<CardRelation *> &_relatedCards,
const QList<CardRelation *> &_reverseRelatedCards,
CardInfoPerSetMap _sets,
bool _cipt,
bool _landscapeOrientation,
int _tableRow,
bool _upsideDownArt);
CardInfoPtr clone() const
{

View File

@@ -140,6 +140,7 @@ void CockatriceXml4Parser::loadCardsFromXml(QXmlStreamReader &xml)
auto _sets = CardInfoPerSetMap();
int tableRow = 0;
bool cipt = false;
bool landscapeOrientation = false;
bool isToken = false;
bool upsideDown = false;
@@ -165,6 +166,8 @@ void CockatriceXml4Parser::loadCardsFromXml(QXmlStreamReader &xml)
tableRow = xml.readElementText(QXmlStreamReader::IncludeChildElements).toInt();
} else if (xmlName == "cipt") {
cipt = (xml.readElementText(QXmlStreamReader::IncludeChildElements) == "1");
} else if (xmlName == "landscapeOrientation") {
landscapeOrientation = (xml.readElementText(QXmlStreamReader::IncludeChildElements) == "1");
} else if (xmlName == "upsidedown") {
upsideDown = (xml.readElementText(QXmlStreamReader::IncludeChildElements) == "1");
// sets
@@ -233,8 +236,9 @@ void CockatriceXml4Parser::loadCardsFromXml(QXmlStreamReader &xml)
}
}
CardInfoPtr newCard = CardInfo::newInstance(name, text, isToken, properties, relatedCards,
reverseRelatedCards, _sets, cipt, tableRow, upsideDown);
CardInfoPtr newCard =
CardInfo::newInstance(name, text, isToken, properties, relatedCards, reverseRelatedCards, _sets, cipt,
landscapeOrientation, tableRow, upsideDown);
emit addCard(newCard);
}
}

View File

@@ -26,7 +26,9 @@ OracleQuery <- 'o' [:] RegexString
CMCQuery <- ('cmc'/'mv') ws? NumericExpression
PowerQuery <- [Pp] 'ow' 'er'? ws? NumericExpression
ToughnessQuery <- [Tt] 'ou' 'ghness'? ws? NumericExpression
RarityQuery <- [rR] ':' RegexString
RarityQuery <- [rR] ':' Rarity
Rarity <- [Cc] 'ommon'? / [Uu] 'ncommon'? / [Rr] 'are'? / [Mm] 'ythic'? / [Ss] 'pecial'? / [a-zA-Z] [a-z]*
FormatQuery <- 'f' ':' Format / Legality ':' Format
Format <- [a-zA-Z] [a-z]*
@@ -42,10 +44,11 @@ ColorQuery <- [cC] 'olor'? <[iI]?> <[:!]> ColorEx*
FieldQuery <- String [:] RegexString / String ws? NumericExpression
NonDoubleQuoteUnlessEscaped <- !["]. / '\"'.
NonSingleQuoteUnlessEscaped <- ![']. / "\'".
NonDoubleQuoteUnlessEscaped <- '\\\"'. / !["].
NonSingleQuoteUnlessEscaped <- "\\\'". / !['].
UnescapedStringListPart <- !['":<>=! ].
String <- UnescapedStringListPart+ / ["] <NonDoubleQuoteUnlessEscaped*> ["] / ['] <NonSingleQuoteUnlessEscaped*> [']
SingleApostropheString <- (UnescapedStringListPart+ ws*)* ['] (UnescapedStringListPart+ ws*)*
String <- UnescapedStringListPart+ / ["] <NonDoubleQuoteUnlessEscaped*> ["] / ['] <NonSingleQuoteUnlessEscaped*> ['] / SingleApostropheString
StringValue <- String / [(] StringList [)]
StringList <- StringListString (ws? [,] ws? StringListString)*
StringListString <- UnescapedStringListPart+
@@ -58,78 +61,92 @@ CompactStringSet <- StringListString ([,+] StringListString)+
NumericExpression <- NumericOperator ws? NumericValue
NumericOperator <- [=:] / <[><!][=]?>
NumericValue <- [0-9]+
)");
std::once_flag init;
static void setupParserRules()
{
auto passthru = [](const peg::SemanticValues &sv) -> Filter { return !sv.empty() ? sv[0].get<Filter>() : nullptr; };
auto passthru = [](const peg::SemanticValues &sv) -> Filter {
return !sv.empty() ? std::any_cast<Filter>(sv[0]) : nullptr;
};
search["Start"] = passthru;
search["QueryPartList"] = [](const peg::SemanticValues &sv) -> Filter {
return [=](CardData x) {
for (int i = 0; i < static_cast<int>(sv.size()); ++i) {
if (!sv[i].get<Filter>()(x))
return false;
}
return true;
return [=](const CardData &x) {
auto matchesFilter = [&x](const std::any &query) { return std::any_cast<Filter>(query)(x); };
return std::all_of(sv.begin(), sv.end(), matchesFilter);
};
};
search["ComplexQueryPart"] = [](const peg::SemanticValues &sv) -> Filter {
return [=](CardData x) {
for (int i = 0; i < static_cast<int>(sv.size()); ++i) {
if (sv[i].get<Filter>()(x))
return true;
}
return false;
return [=](const CardData &x) {
auto matchesFilter = [&x](const std::any &query) { return std::any_cast<Filter>(query)(x); };
return std::any_of(sv.begin(), sv.end(), matchesFilter);
};
};
search["SomewhatComplexQueryPart"] = passthru;
search["QueryPart"] = passthru;
search["NotQuery"] = [](const peg::SemanticValues &sv) -> Filter {
Filter dependent = sv[0].get<Filter>();
return [=](CardData x) -> bool { return !dependent(x); };
const auto dependent = std::any_cast<Filter>(sv[0]);
return [=](const CardData &x) -> bool { return !dependent(x); };
};
search["TypeQuery"] = [](const peg::SemanticValues &sv) -> Filter {
StringMatcher matcher = sv[0].get<StringMatcher>();
return [=](CardData x) -> bool { return matcher(x->getCardType()); };
const auto matcher = std::any_cast<StringMatcher>(sv[0]);
return [=](const CardData &x) -> bool { return matcher(x->getCardType()); };
};
search["SetQuery"] = [](const peg::SemanticValues &sv) -> Filter {
StringMatcher matcher = sv[0].get<StringMatcher>();
return [=](CardData x) -> bool {
for (const auto &set : x->getSets().keys()) {
if (matcher(set))
return true;
}
return false;
auto matcher = std::any_cast<StringMatcher>(sv[0]);
return [=](const CardData &x) -> bool {
QList<QString> sets = x->getSets().keys();
auto matchesSet = [&matcher](const QString &set) { return matcher(set); };
return std::any_of(sets.begin(), sets.end(), matchesSet);
};
};
search["Rarity"] = [](const peg::SemanticValues &sv) -> QString {
switch (tolower(std::string(sv.sv())[0])) {
case 'c':
return "common";
case 'u':
return "uncommon";
case 'r':
return "rare";
case 'm':
return "mythic";
case 's':
return "special";
default:
return QString::fromStdString(std::string(sv.sv()));
}
};
search["RarityQuery"] = [](const peg::SemanticValues &sv) -> Filter {
StringMatcher matcher = sv[0].get<StringMatcher>();
const auto rarity = std::any_cast<QString>(sv[0]);
return [=](const CardData &x) -> bool {
for (const auto &cardInfoPerSetList : x->getSets().values()) {
for (const auto &set : cardInfoPerSetList) {
if (matcher(set.getProperty("rarity")))
return true;
QList<CardInfoPerSet> infos;
for (const auto &setsValue : x->getSets().values()) {
for (const auto &cardInfoPerSet : setsValue) {
infos.append(cardInfoPerSet);
}
}
return false;
auto matchesRarity = [&rarity](const CardInfoPerSet &info) { return rarity == info.getProperty("rarity"); };
return std::any_of(infos.begin(), infos.end(), matchesRarity);
};
};
search["FormatQuery"] = [](const peg::SemanticValues &sv) -> Filter {
if (sv.choice() == 0) {
QString format = sv[0].get<QString>();
return [=](CardData x) -> bool { return x->getProperty(QString("format-%1").arg(format)) == "legal"; };
} else {
QString format = sv[1].get<QString>();
QString legality = sv[0].get<QString>();
return [=](CardData x) -> bool { return x->getProperty(QString("format-%1").arg(format)) == legality; };
const auto format = std::any_cast<QString>(sv[0]);
return
[=](const CardData &x) -> bool { return x->getProperty(QString("format-%1").arg(format)) == "legal"; };
}
const auto format = std::any_cast<QString>(sv[1]);
const auto legality = std::any_cast<QString>(sv[0]);
return [=](const CardData &x) -> bool { return x->getProperty(QString("format-%1").arg(format)) == legality; };
};
search["Legality"] = [](const peg::SemanticValues &sv) -> QString {
switch (tolower(sv.str()[0])) {
switch (tolower(std::string(sv.sv())[0])) {
case 'l':
return "legal";
case 'b':
@@ -142,8 +159,8 @@ static void setupParserRules()
};
search["Format"] = [](const peg::SemanticValues &sv) -> QString {
if (sv.length() == 1) {
switch (tolower(sv.str()[0])) {
if (sv.size() == 1) {
switch (tolower(std::string(sv.sv())[0])) {
case 'm':
return "modern";
case 's':
@@ -159,193 +176,191 @@ static void setupParserRules()
default:
return "";
}
} else {
return QString::fromStdString(sv.str()).toLower();
}
return QString::fromStdString(std::string(sv.sv())).toLower();
};
search["StringValue"] = [](const peg::SemanticValues &sv) -> StringMatcher {
if (sv.choice() == 0) {
auto target = sv[0].get<QString>();
const auto target = std::any_cast<QString>(sv[0]);
return [=](const QString &s) { return s.split(" ").contains(target, Qt::CaseInsensitive); };
} else {
auto target = sv[0].get<QStringList>();
}
const auto target = std::any_cast<QStringList>(sv[0]);
return [=](const QString &s) {
for (const QString &str : target) {
if (s.split(" ").contains(str, Qt::CaseInsensitive)) {
return true;
}
}
return false;
auto containsString = [&s](const QString &str) { return s.split(" ").contains(str, Qt::CaseInsensitive); };
return std::any_of(target.begin(), target.end(), containsString);
};
}
};
search["String"] = [](const peg::SemanticValues &sv) -> QString {
if (sv.choice() == 0) {
return QString::fromStdString(sv.str());
} else {
return QString::fromStdString(sv.token(0));
return QString::fromStdString(std::string(sv.sv()));
}
return QString::fromStdString(std::string(sv.token(0)));
};
search["FlexStringValue"] = [](const peg::SemanticValues &sv) -> StringMatcher {
if (sv.choice() != 1) {
auto target = sv[0].get<QStringList>();
const auto target = std::any_cast<QStringList>(sv[0]);
return [=](const QString &s) {
for (const QString &str : target) {
if (s.split(" ").contains(str, Qt::CaseInsensitive)) {
return true;
}
}
return false;
auto containsString = [&s](const QString &str) {
return s.split(" ").contains(str, Qt::CaseInsensitive);
};
} else {
auto target = sv[0].get<QString>();
return std::any_of(target.begin(), target.end(), containsString);
};
}
const auto target = std::any_cast<QString>(sv[0]);
return [=](const QString &s) { return s.split(" ").contains(target, Qt::CaseInsensitive); };
}
};
search["CompactStringSet"] = search["StringList"] = [](const peg::SemanticValues &sv) -> QStringList {
search["CompactStringSet"] = [](const peg::SemanticValues &sv) -> QStringList {
QStringList result;
for (int i = 0; i < static_cast<int>(sv.size()); ++i) {
result.append(sv[i].get<QString>());
for (const auto &i : sv) {
result.append(std::any_cast<QString>(i));
}
return result;
};
search["StringList"] = [](const peg::SemanticValues &sv) -> QStringList {
QStringList result;
for (const auto &i : sv) {
result.append(std::any_cast<QString>(i));
}
return result;
};
search["StringListString"] = [](const peg::SemanticValues &sv) -> QString {
return QString::fromStdString(sv.str());
return QString::fromStdString(std::string(sv.sv()));
};
search["NumericExpression"] = [](const peg::SemanticValues &sv) -> NumberMatcher {
auto arg = sv[1].get<int>();
auto op = sv[0].get<QString>();
const auto arg = std::any_cast<int>(sv[1]);
const auto op = std::any_cast<QString>(sv[0]);
if (op == ">")
return [=](int s) { return s > arg; };
return [=](const int s) { return s > arg; };
if (op == ">=")
return [=](int s) { return s >= arg; };
return [=](const int s) { return s >= arg; };
if (op == "<")
return [=](int s) { return s < arg; };
return [=](const int s) { return s < arg; };
if (op == "<=")
return [=](int s) { return s <= arg; };
return [=](const int s) { return s <= arg; };
if (op == "=")
return [=](int s) { return s == arg; };
return [=](const int s) { return s == arg; };
if (op == ":")
return [=](int s) { return s == arg; };
return [=](const int s) { return s == arg; };
if (op == "!=")
return [=](int s) { return s != arg; };
return [=](const int s) { return s != arg; };
return [](int) { return false; };
};
search["NumericValue"] = [](const peg::SemanticValues &sv) -> int {
return QString::fromStdString(sv.str()).toInt();
return QString::fromStdString(std::string(sv.sv())).toInt();
};
search["NumericOperator"] = [](const peg::SemanticValues &sv) -> QString {
return QString::fromStdString(sv.str());
return QString::fromStdString(std::string(sv.sv()));
};
search["RegexString"] = [](const peg::SemanticValues &sv) -> StringMatcher {
auto target = sv[0].get<QString>();
auto target = std::any_cast<QString>(sv[0]);
return [=](const QString &s) {
auto sanitizedTarget = QString(target);
sanitizedTarget.replace("\\\"", "\"");
sanitizedTarget.replace("\\'", "'");
return s.QString::contains(sanitizedTarget, Qt::CaseInsensitive);
return s.contains(sanitizedTarget, Qt::CaseInsensitive);
};
};
search["OracleQuery"] = [](const peg::SemanticValues &sv) -> Filter {
StringMatcher matcher = sv[0].get<StringMatcher>();
return [=](CardData x) { return matcher(x->getText()); };
const auto matcher = std::any_cast<StringMatcher>(sv[0]);
return [=](const CardData &x) { return matcher(x->getText()); };
};
search["ColorQuery"] = [](const peg::SemanticValues &sv) -> Filter {
QString parts;
for (int i = 0; i < static_cast<int>(sv.size()); ++i) {
parts += sv[i].get<char>();
for (const auto &i : sv) {
parts += std::any_cast<char>(i);
}
bool idenity = sv.tokens[0].first[0] != 'i';
if (sv.tokens[1].first[0] == ':') {
return [=](CardData x) {
QString match = idenity ? x->getColors() : x->getProperty("coloridentity");
const bool identity = sv.tokens[0][0] != 'i';
if (sv.tokens[1][0] == ':') {
return [=](const CardData &x) {
QString match = identity ? x->getColors() : x->getProperty("coloridentity");
if (parts.contains("m") && match.length() < 2) {
return false;
} else if (parts == "m") {
}
if (parts == "m") {
return true;
}
if (parts.contains("c") && match.length() == 0)
return true;
for (const auto &i : match) {
if (parts.contains(i))
return true;
}
return false;
auto containsColor = [&parts](const QString &s) { return parts.contains(s); };
return std::any_of(match.begin(), match.end(), containsColor);
};
} else {
return [=](CardData x) {
QString match = idenity ? x->getColors() : x->getProperty("colorIdentity");
if (parts.contains("m") && match.length() < 2)
return false;
}
if (parts.contains("c") && match.length() != 0)
return [=](const CardData &x) {
QString match = identity ? x->getColors() : x->getProperty("colorIdentity");
if (parts.contains("m") && match.length() < 2) {
return false;
}
if (parts.contains("c") && match.length() != 0) {
return false;
}
for (const auto &part : parts) {
if (!match.contains(part))
if (!match.contains(part)) {
return false;
}
}
for (const auto &i : match) {
if (!parts.contains(i))
return false;
}
return true;
auto containsColor = [&parts](const QString &s) { return parts.contains(s); };
return std::all_of(match.begin(), match.end(), containsColor);
};
}
};
search["CMCQuery"] = [](const peg::SemanticValues &sv) -> Filter {
NumberMatcher matcher = sv[0].get<NumberMatcher>();
return [=](CardData x) -> bool { return matcher(x->getProperty("cmc").toInt()); };
const auto matcher = std::any_cast<NumberMatcher>(sv[0]);
return [=](const CardData &x) -> bool { return matcher(x->getProperty("cmc").toInt()); };
};
search["PowerQuery"] = [](const peg::SemanticValues &sv) -> Filter {
NumberMatcher matcher = sv[0].get<NumberMatcher>();
return [=](CardData x) -> bool { return matcher(x->getPowTough().split("/")[0].toInt()); };
const auto matcher = std::any_cast<NumberMatcher>(sv[0]);
return [=](const CardData &x) -> bool { return matcher(x->getPowTough().split("/")[0].toInt()); };
};
search["ToughnessQuery"] = [](const peg::SemanticValues &sv) -> Filter {
NumberMatcher matcher = sv[0].get<NumberMatcher>();
return [=](CardData x) -> bool {
const auto matcher = std::any_cast<NumberMatcher>(sv[0]);
return [=](const CardData &x) -> bool {
auto parts = x->getPowTough().split("/");
return matcher(parts.length() == 2 ? parts[1].toInt() : 0);
};
};
search["FieldQuery"] = [](const peg::SemanticValues &sv) -> Filter {
QString field = sv[0].get<QString>();
const auto field = std::any_cast<QString>(sv[0]);
if (sv.choice() == 0) {
StringMatcher matcher = sv[1].get<StringMatcher>();
return [=](CardData x) -> bool { return x->hasProperty(field) ? matcher(x->getProperty(field)) : false; };
} else {
NumberMatcher matcher = sv[1].get<NumberMatcher>();
return [=](CardData x) -> bool {
return x->hasProperty(field) ? matcher(x->getProperty(field).toInt()) : false;
};
const auto matcher = std::any_cast<StringMatcher>(sv[1]);
return [=](const CardData &x) -> bool { return x->hasProperty(field) && matcher(x->getProperty(field)); };
}
const auto matcher = std::any_cast<NumberMatcher>(sv[1]);
return
[=](const CardData &x) -> bool { return x->hasProperty(field) && matcher(x->getProperty(field).toInt()); };
};
search["GenericQuery"] = [](const peg::SemanticValues &sv) -> Filter {
StringMatcher matcher = sv[0].get<StringMatcher>();
return [=](CardData x) { return matcher(x->getName()); };
const auto matcher = std::any_cast<StringMatcher>(sv[0]);
return [=](const CardData &x) { return matcher(x->getName()); };
};
search["Color"] = [](const peg::SemanticValues &sv) -> char { return "WUBRGU"[sv.choice()]; };
search["ColorEx"] = [](const peg::SemanticValues &sv) -> char {
return sv.choice() == 0 ? sv[0].get<char>() : *sv.c_str();
return sv.choice() == 0 ? std::any_cast<char>(sv[0]) : *std::string(sv.sv()).c_str();
};
}
FilterString::FilterString()
{
result = [](CardData) -> bool { return false; };
result = [](const CardData &) -> bool { return false; };
_error = "Not initialized";
}
@@ -358,16 +373,16 @@ FilterString::FilterString(const QString &expr)
_error = QString();
if (ba.isEmpty()) {
result = [](CardData) -> bool { return true; };
result = [](const CardData &) -> bool { return true; };
return;
}
search.log = [&](size_t /*ln*/, size_t col, const std::string &msg) {
search.set_logger([&](size_t /*ln*/, size_t col, const std::string &msg) {
_error = QString("Error at position %1: %2").arg(col).arg(QString::fromStdString(msg));
};
});
if (!search.parse(ba.data(), result)) {
qDebug().nospace() << "FilterString error for " << expr << "; " << qPrintable(_error);
result = [](CardData) -> bool { return false; };
result = [](const CardData &) -> bool { return false; };
}
}

View File

@@ -516,6 +516,10 @@ Player::Player(const ServerInfo_User &info, int _id, bool _local, bool _judge, T
aSelectAll = new QAction(this);
connect(aSelectAll, SIGNAL(triggered()), this, SLOT(actSelectAll()));
aSelectRow = new QAction(this);
connect(aSelectRow, SIGNAL(triggered()), this, SLOT(actSelectRow()));
aSelectColumn = new QAction(this);
connect(aSelectColumn, SIGNAL(triggered()), this, SLOT(actSelectColumn()));
aPlay = new QAction(this);
connect(aPlay, SIGNAL(triggered()), this, SLOT(actPlay()));
@@ -841,6 +845,8 @@ void Player::retranslateUi()
}
aSelectAll->setText(tr("&Select All"));
aSelectRow->setText(tr("S&elect Row"));
aSelectColumn->setText(tr("S&elect Column"));
aPlay->setText(tr("&Play"));
aHide->setText(tr("&Hide"));
@@ -930,6 +936,8 @@ void Player::setShortcutsActive()
aMoveToExile->setShortcuts(shortcuts.getShortcut("Player/aMoveToExile"));
aSelectAll->setShortcuts(shortcuts.getShortcut("Player/aSelectAll"));
aSelectRow->setShortcuts(shortcuts.getShortcut("Player/aSelectRow"));
aSelectColumn->setShortcuts(shortcuts.getShortcut("Player/aSelectColumn"));
QList<QKeySequence> addCCShortCuts;
addCCShortCuts.append(shortcuts.getSingleShortcut("Player/aCCRed"));
@@ -1005,6 +1013,10 @@ void Player::setShortcutsActive()
game->addAction(aClone);
game->addAction(aDrawArrow);
game->addAction(aSelectAll);
// unattach action is only active in card menu if the active card is attached.
// make unattach shortcut always active so that it consistently works when multiple cards are selected.
game->addAction(aUnattach);
}
}
@@ -1565,6 +1577,27 @@ void Player::actMoveBottomCardToTop()
sendGameCommand(cmd);
}
/**
* Selects all cards in the given zone.
*
* @param zone The zone to select from
* @param filter A predicate to filter which cards are selected. Defaults to always returning true.
*/
static void selectCardsInZone(
const CardZone *zone,
std::function<bool(const CardItem *)> filter = [](const CardItem *) { return true; })
{
if (!zone) {
return;
}
for (auto &cardItem : zone->getCards()) {
if (cardItem && filter(cardItem)) {
cardItem->setSelected(true);
}
}
}
void Player::actSelectAll()
{
const CardItem *card = game->getActiveCard();
@@ -1572,13 +1605,31 @@ void Player::actSelectAll()
return;
}
if (const auto *zone = card->getZone()) {
for (auto &cardItem : zone->getCards()) {
if (cardItem) {
cardItem->setSelected(true);
selectCardsInZone(card->getZone());
}
void Player::actSelectRow()
{
const CardItem *card = game->getActiveCard();
if (!card) {
return;
}
auto isSameRow = [card](const CardItem *cardItem) {
return qAbs(card->scenePos().y() - cardItem->scenePos().y()) < 50;
};
selectCardsInZone(card->getZone(), isSameRow);
}
void Player::actSelectColumn()
{
const CardItem *card = game->getActiveCard();
if (!card) {
return;
}
auto isSameColumn = [card](const CardItem *cardItem) { return cardItem->x() == card->x(); };
selectCardsInZone(card->getZone(), isSameColumn);
}
void Player::actDrawBottomCard()
@@ -3461,13 +3512,14 @@ void Player::actAttach()
void Player::actUnattach()
{
if (!game->getActiveCard()) {
return;
}
QList<const ::google::protobuf::Message *> commandList;
for (QGraphicsItem *item : scene()->selectedItems()) {
auto *card = static_cast<CardItem *>(item);
if (!card->getAttachedTo()) {
continue;
}
auto *cmd = new Command_AttachCard;
cmd->set_start_zone(card->getZone()->getName().toStdString());
cmd->set_card_id(card->getId());
@@ -3656,6 +3708,7 @@ void Player::updateCardMenu(const CardItem *card)
cardMenu->addAction(aClone);
cardMenu->addSeparator();
cardMenu->addAction(aSelectAll);
cardMenu->addAction(aSelectColumn);
addRelatedCardView(card, cardMenu);
} else if (writeableCard) {
if (moveMenu->isEmpty()) {
@@ -3714,6 +3767,7 @@ void Player::updateCardMenu(const CardItem *card)
cardMenu->addMenu(moveMenu);
cardMenu->addSeparator();
cardMenu->addAction(aSelectAll);
cardMenu->addAction(aSelectRow);
for (int i = 0; i < aAddCounter.size(); ++i) {
cardMenu->addSeparator();
@@ -3747,6 +3801,7 @@ void Player::updateCardMenu(const CardItem *card)
cardMenu->addMenu(moveMenu);
cardMenu->addSeparator();
cardMenu->addAction(aSelectAll);
cardMenu->addAction(aSelectColumn);
cardMenu->addSeparator();
cardMenu->addAction(aAttach);
@@ -3776,6 +3831,9 @@ void Player::updateCardMenu(const CardItem *card)
cardMenu->addSeparator();
cardMenu->addAction(aSelectAll);
if (card->getZone()->getIsView()) {
cardMenu->addAction(aSelectColumn);
}
addRelatedCardView(card, cardMenu);
}

View File

@@ -186,6 +186,8 @@ public slots:
void actMoveBottomCardToTop();
void actSelectAll();
void actSelectRow();
void actSelectColumn();
void actViewLibrary();
void actViewHand();
@@ -266,7 +268,7 @@ private:
QAction *aPlay, *aPlayFacedown, *aHide, *aTap, *aDoesntUntap, *aAttach, *aUnattach, *aDrawArrow, *aSetPT, *aResetPT,
*aIncP, *aDecP, *aIncT, *aDecT, *aIncPT, *aDecPT, *aFlowP, *aFlowT, *aSetAnnotation, *aFlip, *aPeek, *aClone,
*aMoveToTopLibrary, *aMoveToBottomLibrary, *aMoveToHand, *aMoveToGraveyard, *aMoveToExile,
*aMoveToXfromTopOfLibrary, *aSelectAll;
*aMoveToXfromTopOfLibrary, *aSelectAll, *aSelectRow, *aSelectColumn;
bool movingCardsUntil;
QTimer *moveTopCardTimer;

View File

@@ -138,7 +138,7 @@ ZoneViewWidget::ZoneViewWidget(Player *_player,
// QLabel sizes aren't taken into account until the widget is rendered.
// Force refresh after 1ms to fix glitchy rendering with long QLabels.
auto *lastResizeBeforeVisibleTimer = new QTimer(this);
connect(lastResizeBeforeVisibleTimer, &QTimer::timeout, this, [=] {
connect(lastResizeBeforeVisibleTimer, &QTimer::timeout, this, [=, this] {
resizeToZoneContents();
disconnect(lastResizeBeforeVisibleTimer);
lastResizeBeforeVisibleTimer->deleteLater();

View File

@@ -61,6 +61,34 @@ static void CockatriceLogger(QtMsgType type, const QMessageLogContext &ctx, cons
Logger::getInstance().log(type, ctx, message);
}
#ifdef Q_OS_WIN
// clang-format off
#include <Windows.h>
#include <DbgHelp.h>
#include <tchar.h>
#pragma comment(lib, "DbgHelp.lib") // Link the DbgHelp library
// clang-format on
LONG WINAPI CockatriceUnhandledExceptionFilter(EXCEPTION_POINTERS *pExceptionPointers)
{
HANDLE hDumpFile =
CreateFile(_T("cockatrice.crash.dmp"), GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
if (hDumpFile != INVALID_HANDLE_VALUE) {
MINIDUMP_EXCEPTION_INFORMATION dumpInfo;
dumpInfo.ExceptionPointers = pExceptionPointers;
dumpInfo.ThreadId = GetCurrentThreadId();
dumpInfo.ClientPointers = TRUE;
MiniDumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(), hDumpFile, MiniDumpWithFullMemory, &dumpInfo,
NULL, NULL);
CloseHandle(hDumpFile);
}
return EXCEPTION_EXECUTE_HANDLER;
}
#endif
void installNewTranslator()
{
QString lang = SettingsCache::instance().getLang();
@@ -93,10 +121,10 @@ void installNewTranslator()
QString const generateClientID()
{
QString macList;
foreach (QNetworkInterface interface, QNetworkInterface::allInterfaces()) {
if (interface.hardwareAddress() != "")
if (interface.hardwareAddress() != "00:00:00:00:00:00:00:E0")
macList += interface.hardwareAddress() + ".";
foreach (QNetworkInterface networkInterface, QNetworkInterface::allInterfaces()) {
if (networkInterface.hardwareAddress() != "")
if (networkInterface.hardwareAddress() != "00:00:00:00:00:00:00:E0")
macList += networkInterface.hardwareAddress() + ".";
}
QString strClientID = QCryptographicHash::hash(macList.toUtf8(), QCryptographicHash::Sha1).toHex().right(15);
return strClientID;
@@ -104,9 +132,13 @@ QString const generateClientID()
int main(int argc, char *argv[])
{
#ifdef Q_OS_WIN
SetUnhandledExceptionFilter(CockatriceUnhandledExceptionFilter);
#endif
QApplication app(argc, argv);
QObject::connect(&app, SIGNAL(lastWindowClosed()), &app, SLOT(quit()));
QObject::connect(&app, &QApplication::lastWindowClosed, &app, &QApplication::quit);
qInstallMessageHandler(CockatriceLogger);
#ifdef Q_OS_WIN

View File

@@ -253,7 +253,7 @@ void UserInfoBox::actPassword()
connect(pend,
// we need qoverload here in order to select the right version of this function
QOverload<const Response &, const CommandContainer &, const QVariant &>::of(&PendingCommand::finished),
this, [=](const Response &response, const CommandContainer &, const QVariant &) {
this, [=, this](const Response &response, const CommandContainer &, const QVariant &) {
if (response.response_code() == Response::RespOk) {
changePassword(oldPassword, newPassword);
} else {

View File

@@ -258,6 +258,8 @@ SettingsCache::SettingsCache()
invertVerticalCoordinate = settings->value("table/invert_vertical", false).toBool();
minPlayersForMultiColumnLayout = settings->value("interface/min_players_multicolumn", 4).toInt();
tapAnimation = settings->value("cards/tapanimation", true).toBool();
autoRotateSidewaysLayoutCards = settings->value("cards/autorotatesidewayslayoutcards", true).toBool();
openDeckInNewTab = settings->value("editor/openDeckInNewTab", false).toBool();
rewindBufferingMs = settings->value("replay/rewindBufferingMs", 200).toInt();
chatMention = settings->value("chat/mention", true).toBool();
@@ -301,6 +303,7 @@ SettingsCache::SettingsCache()
spectatorsCanTalk = settings->value("game/spectatorscantalk", false).toBool();
spectatorsCanSeeEverything = settings->value("game/spectatorscanseeeverything", false).toBool();
createGameAsSpectator = settings->value("game/creategameasspectator", false).toBool();
defaultStartingLifeTotal = settings->value("game/defaultstartinglifetotal", 20).toInt();
rememberGameSettings = settings->value("game/remembergamesettings", true).toBool();
clientID = settings->value("personal/clientid", CLIENT_INFO_NOT_SET).toString();
clientVersion = settings->value("personal/clientversion", CLIENT_INFO_NOT_SET).toString();
@@ -628,6 +631,12 @@ void SettingsCache::setTapAnimation(QT_STATE_CHANGED_T _tapAnimation)
settings->setValue("cards/tapanimation", tapAnimation);
}
void SettingsCache::setAutoRotateSidewaysLayoutCards(QT_STATE_CHANGED_T _autoRotateSidewaysLayoutCards)
{
autoRotateSidewaysLayoutCards = static_cast<bool>(_autoRotateSidewaysLayoutCards);
settings->setValue("cards/autorotatesidewayslayoutcards", autoRotateSidewaysLayoutCards);
}
void SettingsCache::setOpenDeckInNewTab(QT_STATE_CHANGED_T _openDeckInNewTab)
{
openDeckInNewTab = static_cast<bool>(_openDeckInNewTab);
@@ -1089,6 +1098,12 @@ void SettingsCache::setCreateGameAsSpectator(const bool _createGameAsSpectator)
settings->setValue("game/creategameasspectator", createGameAsSpectator);
}
void SettingsCache::setDefaultStartingLifeTotal(const int _defaultStartingLifeTotal)
{
defaultStartingLifeTotal = _defaultStartingLifeTotal;
settings->setValue("game/defaultstartinglifetotal", defaultStartingLifeTotal);
};
void SettingsCache::setRememberGameSettings(const bool _rememberGameSettings)
{
rememberGameSettings = _rememberGameSettings;

View File

@@ -120,6 +120,7 @@ private:
bool invertVerticalCoordinate;
int minPlayersForMultiColumnLayout;
bool tapAnimation;
bool autoRotateSidewaysLayoutCards;
bool openDeckInNewTab;
int rewindBufferingMs;
bool chatMention;
@@ -163,6 +164,7 @@ private:
bool spectatorsCanTalk;
bool spectatorsCanSeeEverything;
bool createGameAsSpectator;
int defaultStartingLifeTotal;
int keepalive;
int timeout;
void translateLegacySettings();
@@ -368,6 +370,10 @@ public:
{
return tapAnimation;
}
bool getAutoRotateSidewaysLayoutCards() const
{
return autoRotateSidewaysLayoutCards;
}
bool getOpenDeckInNewTab() const
{
return openDeckInNewTab;
@@ -515,6 +521,10 @@ public:
{
return spectatorsCanSeeEverything;
}
int getDefaultStartingLifeTotal() const
{
return defaultStartingLifeTotal;
}
bool getCreateGameAsSpectator() const
{
return createGameAsSpectator;
@@ -644,6 +654,7 @@ public slots:
void setInvertVerticalCoordinate(QT_STATE_CHANGED_T _invertVerticalCoordinate);
void setMinPlayersForMultiColumnLayout(int _minPlayersForMultiColumnLayout);
void setTapAnimation(QT_STATE_CHANGED_T _tapAnimation);
void setAutoRotateSidewaysLayoutCards(QT_STATE_CHANGED_T _autoRotateSidewaysLayoutCards);
void setOpenDeckInNewTab(QT_STATE_CHANGED_T _openDeckInNewTab);
void setRewindBufferingMs(int _rewindBufferingMs);
void setChatMention(QT_STATE_CHANGED_T _chatMention);
@@ -679,6 +690,7 @@ public slots:
void setSpectatorsCanTalk(const bool _spectatorsCanTalk);
void setSpectatorsCanSeeEverything(const bool _spectatorsCanSeeEverything);
void setCreateGameAsSpectator(const bool _createGameAsSpectator);
void setDefaultStartingLifeTotal(const int _defaultStartingLifeTotal);
void setRememberGameSettings(const bool _rememberGameSettings);
void setNotifyAboutUpdate(QT_STATE_CHANGED_T _notifyaboutupdate);
void setNotifyAboutNewVersion(QT_STATE_CHANGED_T _notifyaboutnewversion);

View File

@@ -0,0 +1,132 @@
#include "shortcut_treeview.h"
#include "cache_settings.h"
#include "shortcuts_settings.h"
#include <QHeaderView>
#include <QSortFilterProxyModel>
ShortcutTreeView::ShortcutTreeView(QWidget *parent) : QTreeView(parent)
{
// model
shortcutsModel = new QStandardItemModel(this);
shortcutsModel->setColumnCount(3);
populateShortcutsModel();
// filter proxy
proxyModel = new QSortFilterProxyModel(this);
proxyModel->setRecursiveFilteringEnabled(true);
proxyModel->setSourceModel(shortcutsModel);
proxyModel->setDynamicSortFilter(true);
proxyModel->setSortCaseSensitivity(Qt::CaseInsensitive);
proxyModel->setFilterCaseSensitivity(Qt::CaseInsensitive);
proxyModel->setFilterKeyColumn(0);
QTreeView::setModel(proxyModel);
// treeview
hideColumn(2);
setUniformRowHeights(true);
setAlternatingRowColors(true);
setSortingEnabled(true);
proxyModel->sort(0, Qt::AscendingOrder);
header()->setSectionResizeMode(QHeaderView::Interactive);
header()->setSortIndicator(0, Qt::AscendingOrder);
setSelectionMode(SingleSelection);
setSelectionBehavior(SelectRows);
expandAll();
connect(&SettingsCache::instance().shortcuts(), &ShortcutsSettings::shortCutChanged, this,
&ShortcutTreeView::refreshShortcuts);
}
void ShortcutTreeView::populateShortcutsModel()
{
QHash<QString, QStandardItem *> parentItems;
QStandardItem *curParent = nullptr;
for (const auto &key : SettingsCache::instance().shortcuts().getAllShortcutKeys()) {
QString name = SettingsCache::instance().shortcuts().getShortcut(key).getName();
QString group = SettingsCache::instance().shortcuts().getShortcut(key).getGroupName();
QString shortcut = SettingsCache::instance().shortcuts().getShortcutString(key);
if (parentItems.contains(group)) {
curParent = parentItems.value(group);
} else {
curParent = new QStandardItem(group);
static QFont font = curParent->font();
font.setBold(true);
curParent->setFont(font);
parentItems.insert(group, curParent);
}
QList<QStandardItem *> list = {};
list << new QStandardItem(name) << new QStandardItem(shortcut) << new QStandardItem(key);
curParent->appendRow(list);
}
for (const auto &parent : parentItems) {
shortcutsModel->appendRow(parent);
}
}
void ShortcutTreeView::retranslateUi()
{
shortcutsModel->setHeaderData(0, Qt::Horizontal, tr("Action"));
shortcutsModel->setHeaderData(1, Qt::Horizontal, tr("Shortcut"));
refreshShortcuts();
}
/**
* Loops over the model and reloads all rows
*/
static void loopOverModel(QAbstractItemModel *model, const QModelIndex &parent = QModelIndex())
{
for (int r = 0; r < model->rowCount(parent); ++r) {
const auto friendlyNameIndex = model->index(r, 0, parent);
if (model->hasChildren(friendlyNameIndex)) {
const auto childIndex = model->index(0, 2, friendlyNameIndex);
const auto key = model->data(childIndex).toString();
const auto shortcutGroupName = SettingsCache::instance().shortcuts().getShortcut(key).getGroupName();
model->setData(friendlyNameIndex, shortcutGroupName);
loopOverModel(model, friendlyNameIndex);
} else {
const auto shortcutSequenceIndex = model->index(r, 1, parent);
const auto keyIndex = model->index(r, 2, parent);
const auto key = model->data(keyIndex).toString();
const auto shortcutKey = SettingsCache::instance().shortcuts().getShortcut(key).getName();
const auto shortcutSequence = SettingsCache::instance().shortcuts().getShortcutString(key);
model->setData(friendlyNameIndex, shortcutKey);
model->setData(shortcutSequenceIndex, shortcutSequence);
}
}
}
void ShortcutTreeView::refreshShortcuts()
{
loopOverModel(shortcutsModel);
}
void ShortcutTreeView::currentChanged(const QModelIndex &current, const QModelIndex & /* previous */)
{
QTreeView::scrollTo(current, QAbstractItemView::EnsureVisible);
if (current.parent().isValid()) {
auto shortcutName = model()->data(model()->index(current.row(), 2, current.parent())).toString();
emit currentItemChanged(shortcutName);
} else {
// emit empty string if the selection is a category header
emit currentItemChanged("");
}
}
void ShortcutTreeView::updateSearchString(const QString &searchString)
{
proxyModel->setFilterFixedString(searchString);
expandAll();
}

View File

@@ -0,0 +1,34 @@
#ifndef SHORTCUT_TREEVIEW_H
#define SHORTCUT_TREEVIEW_H
#include <QModelIndex>
#include <QStandardItemModel>
#include <QTreeView>
class QSortFilterProxyModel;
class ShortcutTreeView : public QTreeView
{
Q_OBJECT
public:
explicit ShortcutTreeView(QWidget *parent);
void retranslateUi();
signals:
void currentItemChanged(const QString &shortcut);
public slots:
void updateSearchString(const QString &searchString);
private:
QStandardItemModel *shortcutsModel;
QSortFilterProxyModel *proxyModel;
void populateShortcutsModel();
private slots:
void refreshShortcuts();
protected:
void currentChanged(const QModelIndex &current, const QModelIndex &previous) override;
};
#endif // SHORTCUT_TREEVIEW_H

View File

@@ -114,6 +114,17 @@ QString ShortcutsSettings::getShortcutString(const QString &name) const
return stringifySequence(getShortcut(name));
}
QString ShortcutsSettings::getShortcutFriendlyName(const QString &shortcutName) const
{
for (auto it = defaultShortCuts.cbegin(); it != defaultShortCuts.cend(); ++it) {
if (shortcutName == it.key()) {
return it.value().getName();
}
}
return {};
}
QString ShortcutsSettings::stringifySequence(const QList<QKeySequence> &Sequence) const
{
QStringList stringSequence;
@@ -150,9 +161,9 @@ void ShortcutsSettings::setShortcuts(const QString &name, const QKeySequence &Se
setShortcuts(name, QList<QKeySequence>{Sequence});
}
void ShortcutsSettings::setShortcuts(const QString &name, const QString &Sequences)
void ShortcutsSettings::setShortcuts(const QString &name, const QString &sequences)
{
setShortcuts(name, parseSequenceString(Sequences));
setShortcuts(name, parseSequenceString(sequences));
}
void ShortcutsSettings::resetAllShortcuts()
@@ -177,33 +188,45 @@ void ShortcutsSettings::clearAllShortcuts()
emit shortCutChanged();
}
bool ShortcutsSettings::isKeyAllowed(const QString &name, const QString &Sequences) const
bool ShortcutsSettings::isKeyAllowed(const QString &name, const QString &sequences) const
{
// if the shortcut is not to be used in deck-editor then it doesn't matter
if (name.startsWith("Player") || name.startsWith("Replays")) {
return true;
}
QString checkSequence = Sequences.split(sep).last();
QString checkSequence = sequences.split(sep).last();
QStringList forbiddenKeys{"Del", "Backspace", "Down", "Up", "Left", "Right",
"Return", "Enter", "Menu", "Ctrl+Alt+-", "Ctrl+Alt+=", "Ctrl+Alt+[",
"Ctrl+Alt+]", "Tab", "Space", "Shift+S", "Shift+Left", "Shift+Right"};
return !forbiddenKeys.contains(checkSequence);
}
bool ShortcutsSettings::isValid(const QString &name, const QString &Sequences) const
/**
* Checks that the shortcut doesn't overlap with an existing shortcut
*
* @param name The name of the shortcut
* @param sequences The shortcut key sequence
* @return Whether the shortcut is valid.
*/
bool ShortcutsSettings::isValid(const QString &name, const QString &sequences) const
{
QString checkSequence = Sequences.split(sep).last();
return findOverlaps(name, sequences).isEmpty();
}
QStringList ShortcutsSettings::findOverlaps(const QString &name, const QString &sequences) const
{
QString checkSequence = sequences.split(sep).last();
QString checkKey = name.left(name.indexOf("/"));
QList<QString> allKeys = shortCuts.keys();
for (const auto &key : allKeys) {
QStringList overlaps;
for (const auto &key : shortCuts.keys()) {
if (key.startsWith(checkKey) || key.startsWith("MainWindow") || checkKey.startsWith("MainWindow")) {
QString storedSequence = stringifySequence(shortCuts.value(key));
QStringList stringSequences = storedSequence.split(sep);
if (stringSequences.contains(checkSequence)) {
return false;
if (storedSequence.split(sep).contains(checkSequence)) {
overlaps.append(getShortcutFriendlyName(key));
}
}
}
return true;
return overlaps;
}

View File

@@ -2,9 +2,7 @@
#define SHORTCUTSSETTINGS_H
#include <QApplication>
#include <QHash>
#include <QKeySequence>
#include <QObject>
#include <QSettings>
class ShortcutGroup
@@ -80,18 +78,18 @@ public:
class ShortcutKey : public QList<QKeySequence>
{
public:
ShortcutKey(const QString &_name = QString(),
QList<QKeySequence> _sequence = QList<QKeySequence>(),
explicit ShortcutKey(const QString &_name = QString(),
QList _sequence = QList(),
ShortcutGroup::Groups _group = ShortcutGroup::Main_Window);
void setSequence(QList<QKeySequence> _sequence)
void setSequence(const QList &_sequence)
{
QList<QKeySequence>::operator=(_sequence);
QList::operator=(_sequence);
};
const QString getName() const
QString getName() const
{
return QApplication::translate("shortcutsTab", name.toUtf8().data());
};
const QString getGroupName() const
QString getGroupName() const
{
return ShortcutGroup::getGroupName(group);
};
@@ -105,13 +103,14 @@ class ShortcutsSettings : public QObject
{
Q_OBJECT
public:
ShortcutsSettings(const QString &settingsFilePath, QObject *parent = nullptr);
explicit ShortcutsSettings(const QString &settingsFilePath, QObject *parent = nullptr);
ShortcutKey getDefaultShortcut(const QString &name) const;
ShortcutKey getShortcut(const QString &name) const;
QKeySequence getSingleShortcut(const QString &name) const;
QString getDefaultShortcutString(const QString &name) const;
QString getShortcutString(const QString &name) const;
QString getShortcutFriendlyName(const QString &shortcutName) const;
QList<QString> getAllShortcutKeys() const
{
return shortCuts.keys();
@@ -119,10 +118,11 @@ public:
void setShortcuts(const QString &name, const QList<QKeySequence> &Sequence);
void setShortcuts(const QString &name, const QKeySequence &Sequence);
void setShortcuts(const QString &name, const QString &Sequences);
void setShortcuts(const QString &name, const QString &sequences);
bool isKeyAllowed(const QString &name, const QString &Sequences) const;
bool isValid(const QString &name, const QString &Sequences) const;
bool isKeyAllowed(const QString &name, const QString &sequences) const;
bool isValid(const QString &name, const QString &sequences) const;
QStringList findOverlaps(const QString &name, const QString &sequences) const;
void resetAllShortcuts();
void clearAllShortcuts();
@@ -456,6 +456,12 @@ private:
{"Player/aSelectAll", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Select All Cards in Zone"),
parseSequenceString("Ctrl+A"),
ShortcutGroup::Playing_Area)},
{"Player/aSelectRow", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Select All Cards in Row"),
parseSequenceString("Ctrl+Shift+X"),
ShortcutGroup::Playing_Area)},
{"Player/aSelectColumn", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Select All Cards in Column"),
parseSequenceString("Ctrl+Shift+C"),
ShortcutGroup::Playing_Area)},
{"Player/aMoveToBottomLibrary", ShortcutKey(QT_TRANSLATE_NOOP("shortcutsTab", "Bottom of Library"),
parseSequenceString("Ctrl+B"),
ShortcutGroup::Move_selected)},

View File

@@ -154,26 +154,49 @@ int SequenceEdit::translateModifiers(Qt::KeyboardModifiers state, const QString
return result;
}
void SequenceEdit::finishShortcut()
/**
*Validates that shortcut is valid (is a valid shortcut key sequence and doesn't conflict with any other shortcuts).
*Displays warning messages if it's not valid.
*
* @param sequence The shortcut key sequence
* @return True if the sequence isn't already self-contained
*/
bool SequenceEdit::validateShortcut(const QKeySequence &sequence)
{
QKeySequence sequence(keys);
if (!sequence.isEmpty() && valid) {
QString sequenceString = sequence.toString();
if (SettingsCache::instance().shortcuts().isKeyAllowed(shortcutName, sequenceString)) {
if (SettingsCache::instance().shortcuts().isValid(shortcutName, sequenceString)) {
if (sequence.isEmpty() || !valid) {
return true;
}
const auto &shortcutsSettings = SettingsCache::instance().shortcuts();
const QString sequenceString = sequence.toString();
if (!shortcutsSettings.isKeyAllowed(shortcutName, sequenceString)) {
QToolTip::showText(lineEdit->mapToGlobal(QPoint()), tr("Invalid key"));
return true;
}
if (!shortcutsSettings.isValid(shortcutName, sequenceString)) {
auto overlaps = shortcutsSettings.findOverlaps(shortcutName, sequenceString);
QToolTip::showText(lineEdit->mapToGlobal(QPoint()),
tr("Shortcut already in use by:") + " " + overlaps.join(", "));
return true;
}
if (!lineEdit->text().isEmpty()) {
if (lineEdit->text().contains(sequenceString)) {
return;
return false;
}
lineEdit->setText(lineEdit->text() + ";");
}
lineEdit->setText(lineEdit->text() + sequenceString);
} else {
QToolTip::showText(lineEdit->mapToGlobal(QPoint()), tr("Shortcut already in use"));
}
} else {
QToolTip::showText(lineEdit->mapToGlobal(QPoint()), tr("Invalid key"));
return true;
}
void SequenceEdit::finishShortcut()
{
if (!validateShortcut(QKeySequence(keys))) {
return;
}
currentKey = 0;

View File

@@ -36,6 +36,7 @@ private:
void processKey(QKeyEvent *e);
int translateModifiers(Qt::KeyboardModifiers state, const QString &text);
bool validateShortcut(const QKeySequence &sequence);
void finishShortcut();
void updateSettings();
};

View File

@@ -51,9 +51,9 @@ double Expression::eval(const peg::Ast &ast)
{
const auto &nodes = ast.nodes;
if (ast.name == "NUMBER") {
return stod(ast.token);
return stod(std::string(ast.token));
} else if (ast.name == "FUNCTION") {
QString name = QString::fromStdString(nodes[0]->token);
QString name = QString::fromStdString(std::string(nodes[0]->token));
if (!fns.contains(name))
return 0;
return fns[name](eval(*nodes[1]));

File diff suppressed because it is too large Load Diff

View File

@@ -38,6 +38,7 @@ message Command_CreateGame {
repeated uint32 game_type_ids = 10;
optional bool join_as_judge = 11;
optional bool join_as_spectator = 12;
optional uint32 starting_life_total = 13;
}
message Command_JoinGame {

View File

@@ -61,15 +61,16 @@ Server_Game::Server_Game(const ServerInfo_User &_creatorInfo,
bool _spectatorsNeedPassword,
bool _spectatorsCanTalk,
bool _spectatorsSeeEverything,
int _startingLifeTotal,
Server_Room *_room)
: QObject(), room(_room), nextPlayerId(0), hostId(0), creatorInfo(new ServerInfo_User(_creatorInfo)),
gameStarted(false), gameClosed(false), gameId(_gameId), password(_password), maxPlayers(_maxPlayers),
gameTypes(_gameTypes), activePlayer(-1), activePhase(-1), onlyBuddies(_onlyBuddies),
onlyRegistered(_onlyRegistered), spectatorsAllowed(_spectatorsAllowed),
spectatorsNeedPassword(_spectatorsNeedPassword), spectatorsCanTalk(_spectatorsCanTalk),
spectatorsSeeEverything(_spectatorsSeeEverything), inactivityCounter(0), startTimeOfThisGame(0),
secondsElapsed(0), firstGameStarted(false), turnOrderReversed(false), startTime(QDateTime::currentDateTime()),
pingClock(nullptr),
spectatorsSeeEverything(_spectatorsSeeEverything), startingLifeTotal(_startingLifeTotal), inactivityCounter(0),
startTimeOfThisGame(0), secondsElapsed(0), firstGameStarted(false), turnOrderReversed(false),
startTime(QDateTime::currentDateTime()), pingClock(nullptr),
#if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0))
gameMutex()
#else

View File

@@ -66,6 +66,7 @@ private:
bool spectatorsNeedPassword;
bool spectatorsCanTalk;
bool spectatorsSeeEverything;
int startingLifeTotal;
int inactivityCounter;
int startTimeOfThisGame, secondsElapsed;
bool firstGameStarted;
@@ -105,6 +106,7 @@ public:
bool _spectatorsNeedPassword,
bool _spectatorsCanTalk,
bool _spectatorsSeeEverything,
int startingLifeTotal,
Server_Room *parent);
~Server_Game();
Server_Room *getRoom() const
@@ -162,6 +164,10 @@ public:
{
return spectatorsSeeEverything;
}
int getStartingLifeTotal() const
{
return startingLifeTotal;
}
Response::ResponseCode
checkJoin(ServerInfo_User *user, const QString &_password, bool spectator, bool overrideRestrictions, bool asJudge);
bool containsUser(const QString &userName) const;

View File

@@ -178,7 +178,7 @@ void Server_Player::setupZones()
addZone(new Server_CardZone(this, "grave", false, ServerInfo_Zone::PublicZone));
addZone(new Server_CardZone(this, "rfg", false, ServerInfo_Zone::PublicZone));
addCounter(new Server_Counter(0, "life", makeColor(255, 255, 255), 25, 20));
addCounter(new Server_Counter(0, "life", makeColor(255, 255, 255), 25, game->getStartingLifeTotal()));
addCounter(new Server_Counter(1, "w", makeColor(255, 255, 150), 20, 0));
addCounter(new Server_Counter(2, "u", makeColor(150, 150, 255), 20, 0));
addCounter(new Server_Counter(3, "b", makeColor(150, 150, 150), 20, 0));
@@ -450,9 +450,13 @@ Response::ResponseCode Server_Player::moveCard(GameEventStorage &ges,
if (!card) {
return Response::RespNameNotFound;
}
if (card->getParentCard()) {
// do not allow attached cards to move around on the table
if (card->getParentCard() && targetzone->getName() == "table") {
continue;
}
// do not allow cards with attachments to stack with other cards
if (!card->getAttachedCards().isEmpty() && !targetzone->isColumnEmpty(xCoord, yCoord)) {
continue;
}

View File

@@ -815,13 +815,14 @@ Server_ProtocolHandler::cmdCreateGame(const Command_CreateGame &cmd, Server_Room
}
QString description = nameFromStdString(cmd.description());
int startingLifeTotal = cmd.has_starting_life_total() ? cmd.starting_life_total() : 20;
// When server doesn't permit registered users to exist, do not honor only-reg setting
bool onlyRegisteredUsers = cmd.only_registered() && (server->permitUnregisteredUsers());
Server_Game *game = new Server_Game(
copyUserInfo(false), gameId, description, QString::fromStdString(cmd.password()), cmd.max_players(), gameTypes,
cmd.only_buddies(), onlyRegisteredUsers, cmd.spectators_allowed(), cmd.spectators_need_password(),
cmd.spectators_can_talk(), cmd.spectators_see_everything(), room);
cmd.spectators_can_talk(), cmd.spectators_see_everything(), startingLifeTotal, room);
game->addPlayer(this, rc, asSpectator, asJudge, false);
room->addGame(game);

View File

@@ -196,6 +196,9 @@ void SettingsCache::setMinPlayersForMultiColumnLayout(int /* _minPlayersForMulti
void SettingsCache::setTapAnimation(QT_STATE_CHANGED_T /* _tapAnimation */)
{
}
void SettingsCache::setAutoRotateSidewaysLayoutCards(QT_STATE_CHANGED_T /* _autoRotateSidewaysLayoutCards */)
{
}
void SettingsCache::setOpenDeckInNewTab(QT_STATE_CHANGED_T /* _openDeckInNewTab */)
{
}
@@ -300,6 +303,9 @@ void SettingsCache::setSpectatorsCanSeeEverything(const bool /* _spectatorsCanSe
void SettingsCache::setCreateGameAsSpectator(const bool /* _createGameAsSpectator */)
{
}
void SettingsCache::setDefaultStartingLifeTotal(const int /* _startingLifeTotal */)
{
}
void SettingsCache::setRememberGameSettings(const bool /* _rememberGameSettings */)
{
}

View File

@@ -200,6 +200,9 @@ void SettingsCache::setMinPlayersForMultiColumnLayout(int /* _minPlayersForMulti
void SettingsCache::setTapAnimation(QT_STATE_CHANGED_T /* _tapAnimation */)
{
}
void SettingsCache::setAutoRotateSidewaysLayoutCards(QT_STATE_CHANGED_T /* _autoRotateSidewaysLayoutCards */)
{
}
void SettingsCache::setOpenDeckInNewTab(QT_STATE_CHANGED_T /* _openDeckInNewTab */)
{
}
@@ -304,6 +307,9 @@ void SettingsCache::setSpectatorsCanSeeEverything(const bool /* _spectatorsCanSe
void SettingsCache::setCreateGameAsSpectator(const bool /* _createGameAsSpectator */)
{
}
void SettingsCache::setDefaultStartingLifeTotal(const int /* _startingLifeTotal */)
{
}
void SettingsCache::setRememberGameSettings(const bool /* _rememberGameSettings */)
{
}