mirror of
https://github.com/Cockatrice/Cockatrice.git
synced 2026-11-08 02:53:25 -08:00
Compare commits
4 commits
fea12a74e2
...
96b0888a73
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
96b0888a73 | ||
|
|
bbf0425655 | ||
|
|
fec693ca2a | ||
|
|
677692500b |
17 changed files with 268 additions and 22 deletions
|
|
@ -12,6 +12,7 @@
|
|||
#include "libcockatrice/card/printing/exact_card.h"
|
||||
#include "libcockatrice/utility/card_ref.h"
|
||||
|
||||
#include <QApplication>
|
||||
#include <QBrush>
|
||||
#include <QChar>
|
||||
#include <QCursor>
|
||||
|
|
@ -28,6 +29,7 @@
|
|||
#include <QPixmap>
|
||||
#include <QPointF>
|
||||
#include <QRectF>
|
||||
#include <QTimer>
|
||||
#include <QTransform>
|
||||
#include <QVariant>
|
||||
#include <QtGlobal>
|
||||
|
|
@ -42,7 +44,7 @@ class QSizeF;
|
|||
|
||||
AbstractCardItem::AbstractCardItem(QGraphicsItem *parent, const CardRef &cardRef, PlayerLogic *_owner, int _id)
|
||||
: ArrowTarget(_owner, parent), id(_id), cardRef(cardRef), tapped(false), facedown(false), tapAngle(0),
|
||||
bgColor(Qt::transparent), isHovered(false), realZValue(0)
|
||||
bgColor(Qt::transparent), isHovered(false), realZValue(0), pendingSelectionCollapse(false)
|
||||
{
|
||||
setCursor(Qt::OpenHandCursor);
|
||||
setFlag(ItemIsSelectable);
|
||||
|
|
@ -344,6 +346,16 @@ void AbstractCardItem::setFaceDown(bool _facedown)
|
|||
|
||||
void AbstractCardItem::mousePressEvent(QGraphicsSceneMouseEvent *event)
|
||||
{
|
||||
cancelPendingSelectionCollapse();
|
||||
if (scene() != nullptr) {
|
||||
// A new press starts a new gesture: no card may collapse the selection out from under it afterwards.
|
||||
for (QGraphicsItem *selectedItem : scene()->selectedItems()) {
|
||||
auto *selectedCard = qgraphicsitem_cast<AbstractCardItem *>(selectedItem);
|
||||
if (selectedCard != nullptr && selectedCard != this) {
|
||||
selectedCard->cancelPendingSelectionCollapse();
|
||||
}
|
||||
}
|
||||
}
|
||||
if ((event->modifiers() & Qt::AltModifier) && event->button() == Qt::LeftButton) {
|
||||
emit cardShiftClicked(cardRef.name);
|
||||
} else if ((event->modifiers() & Qt::ControlModifier)) {
|
||||
|
|
@ -351,6 +363,10 @@ void AbstractCardItem::mousePressEvent(QGraphicsSceneMouseEvent *event)
|
|||
} else if (!isSelected()) {
|
||||
scene()->clearSelection();
|
||||
setSelected(true);
|
||||
} else if (event->button() == Qt::LeftButton && scene()->selectedItems().count() > 1) {
|
||||
// Part of a multi-selection; collapse only if this turns out to be a click rather than a drag,
|
||||
// so that dragging the whole selection keeps it intact.
|
||||
pendingSelectionCollapse = true;
|
||||
}
|
||||
if (event->button() == Qt::LeftButton) {
|
||||
setCursor(Qt::ClosedHandCursor);
|
||||
|
|
@ -362,6 +378,32 @@ void AbstractCardItem::mousePressEvent(QGraphicsSceneMouseEvent *event)
|
|||
|
||||
void AbstractCardItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
|
||||
{
|
||||
if (pendingSelectionCollapse) {
|
||||
pendingSelectionCollapse = false;
|
||||
bool dragged = (event->screenPos() - event->buttonDownScreenPos(event->button())).manhattanLength() >=
|
||||
2 * QApplication::startDragDistance();
|
||||
if (!dragged) {
|
||||
if (doubleClickActsOnSelection()) {
|
||||
// A double-click may still arrive and must act on the whole multi-selection, so collapse only
|
||||
// once the double-click window has passed without one. The generation guard (and the receiver
|
||||
// context) drops the collapse if the item is gone, deselected, or a double-click cancelled it;
|
||||
// the count guard drops it if the selection changed meanwhile (rubber band, ctrl-toggle).
|
||||
const int generation = ++selectionCollapseGeneration;
|
||||
const int armedSelectionCount = scene()->selectedItems().count();
|
||||
QTimer::singleShot(QApplication::doubleClickInterval(), this, [this, generation, armedSelectionCount] {
|
||||
if (generation == selectionCollapseGeneration && scene() != nullptr && isSelected() &&
|
||||
scene()->selectedItems().count() == armedSelectionCount && armedSelectionCount > 1) {
|
||||
scene()->clearSelection();
|
||||
setSelected(true);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
scene()->clearSelection();
|
||||
setSelected(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (event->button() == Qt::MiddleButton) {
|
||||
emit deleteCardInfoPopup(cardRef.name);
|
||||
}
|
||||
|
|
@ -370,6 +412,17 @@ void AbstractCardItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
|
|||
event->accept();
|
||||
}
|
||||
|
||||
void AbstractCardItem::cancelPendingSelectionCollapse()
|
||||
{
|
||||
pendingSelectionCollapse = false;
|
||||
++selectionCollapseGeneration;
|
||||
}
|
||||
|
||||
bool AbstractCardItem::doubleClickActsOnSelection() const
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
void AbstractCardItem::processHoverEvent()
|
||||
{
|
||||
emit hovered(this);
|
||||
|
|
|
|||
|
|
@ -41,6 +41,8 @@ protected:
|
|||
private:
|
||||
bool isHovered;
|
||||
qreal realZValue;
|
||||
bool pendingSelectionCollapse;
|
||||
int selectionCollapseGeneration = 0; ///< Guards the deferred collapse timer against stale firings
|
||||
private slots:
|
||||
void pixmapUpdated();
|
||||
|
||||
|
|
@ -130,6 +132,8 @@ public:
|
|||
}
|
||||
void setFaceDown(bool _facedown);
|
||||
void processHoverEvent();
|
||||
/** @brief Drop a pending collapse and disarm any deferred collapse timer. */
|
||||
void cancelPendingSelectionCollapse();
|
||||
void deleteCardInfoPopup()
|
||||
{
|
||||
emit deleteCardInfoPopup(cardRef.name);
|
||||
|
|
@ -144,6 +148,16 @@ protected:
|
|||
void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) override;
|
||||
QVariant itemChange(QGraphicsItem::GraphicsItemChange change, const QVariant &value) override;
|
||||
void cacheBgColor();
|
||||
/**
|
||||
* @brief Whether a double-click on this item acts on the whole selection.
|
||||
*
|
||||
* When true, a release without drag defers the multi-selection collapse
|
||||
* until the double-click window passes, so a following double-click still
|
||||
* sees every selected card. Subclasses that handle double-clicks on the
|
||||
* selection must return true here and call
|
||||
* cancelPendingSelectionCollapse() from their double-click handler.
|
||||
*/
|
||||
virtual bool doubleClickActsOnSelection() const;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -518,6 +518,9 @@ void CardItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
|
|||
|
||||
void CardItem::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event)
|
||||
{
|
||||
// The preceding release deferred its multi-selection collapse for exactly this: the double-click plays
|
||||
// every selected card, so the collapse must not run.
|
||||
cancelPendingSelectionCollapse();
|
||||
if ((event->modifiers() != Qt::AltModifier) && (event->buttons() == Qt::LeftButton) &&
|
||||
(SettingsCache::instance().userInterface().getDoubleClickToPlay())) {
|
||||
handleClickedToPlay(event->modifiers().testFlag(Qt::ShiftModifier));
|
||||
|
|
@ -525,6 +528,11 @@ void CardItem::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event)
|
|||
event->accept();
|
||||
}
|
||||
|
||||
bool CardItem::doubleClickActsOnSelection() const
|
||||
{
|
||||
return SettingsCache::instance().userInterface().getDoubleClickToPlay();
|
||||
}
|
||||
|
||||
bool CardItem::animationEvent()
|
||||
{
|
||||
int rotation = ROTATION_DEGREES_PER_FRAME;
|
||||
|
|
|
|||
|
|
@ -175,6 +175,7 @@ protected:
|
|||
void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) override;
|
||||
void mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event) override;
|
||||
QVariant itemChange(GraphicsItemChange change, const QVariant &value) override;
|
||||
bool doubleClickActsOnSelection() const override;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -182,6 +182,15 @@ void DeckView::mouseDoubleClickEvent(QMouseEvent *event)
|
|||
QList<MoveCard_ToZone> result;
|
||||
QList<QGraphicsItem *> sel = scene()->selectedItems();
|
||||
|
||||
// The double-click goes to the view, not the card, so cancel any pending
|
||||
// selection collapse on the cards before reading the selection.
|
||||
for (auto *item : sel) {
|
||||
if (auto *c = dynamic_cast<DeckViewCard *>(item)) {
|
||||
c->cancelPendingSelectionCollapse();
|
||||
}
|
||||
}
|
||||
sel = scene()->selectedItems();
|
||||
|
||||
for (int i = 0; i < sel.size(); i++) {
|
||||
auto *c = static_cast<DeckViewCard *>(sel.at(i));
|
||||
auto *zone = static_cast<DeckViewCardContainer *>(c->parentItem());
|
||||
|
|
|
|||
|
|
@ -49,6 +49,10 @@ public:
|
|||
{
|
||||
return originZone;
|
||||
}
|
||||
bool doubleClickActsOnSelection() const override
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
protected:
|
||||
void mouseMoveEvent(QGraphicsSceneMouseEvent *event) override;
|
||||
|
|
|
|||
|
|
@ -127,6 +127,7 @@ void GameView::startRubberBand(const QPointF &_selectionOrigin)
|
|||
selectionOrigin = _selectionOrigin;
|
||||
previousBandRect = QRect();
|
||||
rubberBand->setGeometry(QRect(mapFromScene(selectionOrigin), QSize(0, 0)));
|
||||
rubberBand->raise();
|
||||
rubberBand->show();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@
|
|||
#include <QGroupBox>
|
||||
#include <QMessageBox>
|
||||
#include <QObject>
|
||||
#include <QPushButton>
|
||||
#include <QSharedPointer>
|
||||
#include <QString>
|
||||
#include <QStringList>
|
||||
|
|
@ -84,10 +85,9 @@ AppearanceSettingsPage::AppearanceSettingsPage()
|
|||
const QString currentStyle = cfg.styleName;
|
||||
const int styleSeedIdx = currentStyle.isEmpty() ? 0 : styleCombo.findData(currentStyle);
|
||||
styleCombo.setCurrentIndex(styleSeedIdx >= 0 ? styleSeedIdx : 0);
|
||||
previousStyle = styleCombo.currentData().toString();
|
||||
|
||||
connect(&styleCombo, &QComboBox::currentIndexChanged, this,
|
||||
[this] { themeManager->setStyleName(styleCombo.currentData().toString()); });
|
||||
connect(&styleCombo, &QComboBox::currentIndexChanged, this, &AppearanceSettingsPage::updateStyleWarning);
|
||||
connect(&styleCombo, &QComboBox::currentIndexChanged, this, &AppearanceSettingsPage::styleComboChanged);
|
||||
|
||||
connect(themeManager, &ThemeManager::themeChanged, this, [this, dirPath] {
|
||||
const QString newDir = themeManager->getAvailableThemes().value(SettingsCache::instance().getThemeName());
|
||||
|
|
@ -104,6 +104,7 @@ AppearanceSettingsPage::AppearanceSettingsPage()
|
|||
const int styleIdx = currentStyle.isEmpty() ? 0 : styleCombo.findData(currentStyle);
|
||||
styleCombo.setCurrentIndex(styleIdx >= 0 ? styleIdx : 0);
|
||||
styleCombo.blockSignals(false);
|
||||
previousStyle = styleCombo.currentData().toString();
|
||||
|
||||
updateStyleWarning();
|
||||
});
|
||||
|
|
@ -467,12 +468,88 @@ void AppearanceSettingsPage::updateHomeTabSettingsVisibility()
|
|||
homeTabDisplayCardNameCheckBox.setVisible(visible);
|
||||
}
|
||||
|
||||
static bool isBrokenStyle(const QString &style)
|
||||
{
|
||||
return style.compare("windows11", Qt::CaseInsensitive) == 0 ||
|
||||
style.compare("windowsvista", Qt::CaseInsensitive) == 0;
|
||||
}
|
||||
|
||||
static int findFusionIndex(const QComboBox &combo)
|
||||
{
|
||||
int fusionIdx = combo.findData(QStringLiteral("Fusion"));
|
||||
if (fusionIdx >= 0) {
|
||||
return fusionIdx;
|
||||
}
|
||||
for (int i = 0; i < combo.count(); ++i) {
|
||||
if (combo.itemData(i).toString().compare(QStringLiteral("Fusion"), Qt::CaseInsensitive) == 0) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
void AppearanceSettingsPage::updateStyleWarning()
|
||||
{
|
||||
const QString style = styleCombo.currentData().toString();
|
||||
const bool broken =
|
||||
style.compare("windows11", Qt::CaseInsensitive) == 0 || style.compare("windowsvista", Qt::CaseInsensitive) == 0;
|
||||
styleWarningLabel.setVisible(broken);
|
||||
styleWarningLabel.setVisible(isBrokenStyle(styleCombo.currentData().toString()));
|
||||
}
|
||||
|
||||
void AppearanceSettingsPage::styleComboChanged(int index)
|
||||
{
|
||||
if (index < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const QString newStyle = styleCombo.itemData(index).toString();
|
||||
if (!isBrokenStyle(newStyle)) {
|
||||
themeManager->setStyleName(newStyle);
|
||||
previousStyle = newStyle;
|
||||
updateStyleWarning();
|
||||
return;
|
||||
}
|
||||
|
||||
QMessageBox msgBox(this);
|
||||
msgBox.setWindowTitle(tr("Broken style"));
|
||||
msgBox.setIcon(QMessageBox::Warning);
|
||||
msgBox.setText(tr("The \"%1\" style is broken and can break the game view: dragging cards across zones can "
|
||||
"shrink the board to a tiny grey window that can only be fixed by rejoining the game.")
|
||||
.arg(newStyle));
|
||||
msgBox.setInformativeText(
|
||||
tr("It can also render controls such as combo boxes unreadable, especially in dark mode.\n\n"
|
||||
"Fusion is the recommended style on every platform. Switch to Fusion instead?"));
|
||||
|
||||
int fusionIdx = findFusionIndex(styleCombo);
|
||||
QPushButton *fusionButton = nullptr;
|
||||
if (fusionIdx >= 0) {
|
||||
fusionButton = msgBox.addButton(tr("Use Fusion instead"), QMessageBox::AcceptRole);
|
||||
}
|
||||
QPushButton *keepButton = msgBox.addButton(tr("Use %1 anyway").arg(newStyle), QMessageBox::DestructiveRole);
|
||||
msgBox.addButton(QMessageBox::Cancel);
|
||||
if (fusionButton != nullptr) {
|
||||
msgBox.setDefaultButton(fusionButton);
|
||||
} else {
|
||||
msgBox.setDefaultButton(keepButton);
|
||||
}
|
||||
|
||||
msgBox.exec();
|
||||
|
||||
const QAbstractButton *clicked = msgBox.clickedButton();
|
||||
if (fusionButton != nullptr && clicked == fusionButton) {
|
||||
styleCombo.setCurrentIndex(fusionIdx);
|
||||
return;
|
||||
}
|
||||
if (clicked == keepButton) {
|
||||
themeManager->setStyleName(newStyle);
|
||||
previousStyle = newStyle;
|
||||
updateStyleWarning();
|
||||
return;
|
||||
}
|
||||
|
||||
// Cancel: revert to the previously applied style without re-prompting
|
||||
styleCombo.blockSignals(true);
|
||||
const int previousIdx = styleCombo.findData(previousStyle);
|
||||
styleCombo.setCurrentIndex(previousIdx >= 0 ? previousIdx : 0);
|
||||
styleCombo.blockSignals(false);
|
||||
updateStyleWarning();
|
||||
}
|
||||
|
||||
void AppearanceSettingsPage::showShortcutsChanged(QT_STATE_CHANGED_T value)
|
||||
|
|
@ -538,9 +615,10 @@ void AppearanceSettingsPage::retranslateUi()
|
|||
styleComboLabel.setText(tr("Active theme style:"));
|
||||
styleCombo.setToolTip(tr("Qt widget style saved to this theme (\"System\" lets the application decide)"));
|
||||
styleWarningLabel.setText(
|
||||
tr("Warning: the Windows 11 and Windows Vista styles are buggy and can render controls such as combo boxes "
|
||||
"unreadable, especially in dark mode. Fusion is the recommended style on every platform: it is Qt's "
|
||||
"cross-platform style and the one Cockatrice is developed against."));
|
||||
tr("Warning: the Windows 11 and Windows Vista styles are broken and can break the game view (dragging "
|
||||
"cards can shrink the board to a tiny grey window that requires rejoining to fix), and can render "
|
||||
"controls such as combo boxes unreadable, especially in dark mode. Fusion is the recommended style on "
|
||||
"every platform: it is Qt's cross-platform style and the one Cockatrice is developed against."));
|
||||
updateStyleWarning();
|
||||
editPaletteButton.setText(tr("Edit theme palette"));
|
||||
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ private slots:
|
|||
void editPalette();
|
||||
void updateHomeTabSettingsVisibility();
|
||||
void updateStyleWarning();
|
||||
void styleComboChanged(int index);
|
||||
void showShortcutsChanged(QT_STATE_CHANGED_T enabled);
|
||||
void overrideAllCardArtWithPersonalPreferenceToggled(QT_STATE_CHANGED_T enabled);
|
||||
|
||||
|
|
@ -40,6 +41,7 @@ private:
|
|||
QComboBox styleCombo;
|
||||
QLabel styleWarningLabel;
|
||||
QPushButton editPaletteButton;
|
||||
QString previousStyle;
|
||||
|
||||
QLabel homeTabBackgroundSourceLabel;
|
||||
QComboBox homeTabBackgroundSourceBox;
|
||||
|
|
|
|||
|
|
@ -1253,7 +1253,7 @@ void MainWindow::createCardUpdateProcess(bool background)
|
|||
<< dir.absoluteFilePath(binaryName);
|
||||
QMessageBox::warning(this, tr("Error"),
|
||||
tr("Unable to run the card database updater: ") + dir.absoluteFilePath(binaryName));
|
||||
exitCardDatabaseUpdate();
|
||||
exitCardDatabaseUpdate(false);
|
||||
emit cardDatabaseUpdateFinished(false);
|
||||
return;
|
||||
}
|
||||
|
|
@ -1330,7 +1330,7 @@ void MainWindow::cardUpdateStandardError()
|
|||
}
|
||||
}
|
||||
|
||||
void MainWindow::exitCardDatabaseUpdate()
|
||||
void MainWindow::exitCardDatabaseUpdate(bool doReload)
|
||||
{
|
||||
if (!cardUpdateProcess) {
|
||||
return;
|
||||
|
|
@ -1339,9 +1339,11 @@ void MainWindow::exitCardDatabaseUpdate()
|
|||
cardUpdateProcess = nullptr;
|
||||
statusBar()->clearMessage();
|
||||
|
||||
// This can be the first successful load of the session (e.g. the onboarding
|
||||
// download), so it must own the set check for that load.
|
||||
reloadCardDatabaseAndCheckSets();
|
||||
if (doReload) {
|
||||
// This can be the first successful load of the session (e.g. the onboarding
|
||||
// download), so it must own the set check for that load.
|
||||
reloadCardDatabaseAndCheckSets();
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::reloadCardDatabaseAndCheckSets()
|
||||
|
|
@ -1387,7 +1389,7 @@ void MainWindow::cardUpdateError(QProcess::ProcessError err)
|
|||
}
|
||||
|
||||
qCWarning(CardDatabaseUpdateLog) << "The card database updater exited with an error:" << error;
|
||||
exitCardDatabaseUpdate();
|
||||
exitCardDatabaseUpdate(false);
|
||||
QMessageBox::warning(this, tr("Error"), tr("The card database updater exited with an error:\n%1").arg(error));
|
||||
emit cardDatabaseUpdateFinished(false);
|
||||
}
|
||||
|
|
@ -1400,12 +1402,14 @@ void MainWindow::cardUpdateFinished(int exitCode, QProcess::ExitStatus exitStatu
|
|||
cardUpdateProgressOutput(); // drain any progress lines not yet parsed
|
||||
|
||||
const bool success = (exitStatus == QProcess::NormalExit) && (exitCode == 0);
|
||||
if (exitStatus == QProcess::NormalExit) {
|
||||
if (success) {
|
||||
SettingsCache::instance().updates().setLastCardUpdateCheck(QDateTime::currentDateTime().date());
|
||||
} else if (exitStatus == QProcess::NormalExit) {
|
||||
statusBar()->showMessage(tr("Card database update cancelled."), 10000);
|
||||
}
|
||||
qCInfo(CardDatabaseUpdateLog) << "The card database updater finished with exit code" << exitCode << "exit status"
|
||||
<< static_cast<int>(exitStatus) << "success:" << success;
|
||||
exitCardDatabaseUpdate();
|
||||
exitCardDatabaseUpdate(success);
|
||||
notifyCardUpdateFinishedFromTray(success);
|
||||
emit cardDatabaseUpdateFinished(success);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -157,7 +157,7 @@ private:
|
|||
return "oracle";
|
||||
}
|
||||
void createCardUpdateProcess(bool background = false);
|
||||
void exitCardDatabaseUpdate();
|
||||
void exitCardDatabaseUpdate(bool doReload);
|
||||
/**
|
||||
* @brief Reloads the card database on a worker thread and runs checkUnknownSets() once the finished
|
||||
* snapshot has been swapped in.
|
||||
|
|
|
|||
|
|
@ -85,5 +85,8 @@ int main(int argc, char *argv[])
|
|||
QTimer::singleShot(0, &wizard, [&wizard]() { wizard.runInBackground(); });
|
||||
}
|
||||
|
||||
return app.exec();
|
||||
const int execResult = app.exec();
|
||||
// A cancel only counts when nothing was written yet: closing the wizard
|
||||
// after the card database was already saved on disk is still an update.
|
||||
return (wizard.wasCancelled() && !wizard.hasSavedDatabase()) ? 1 : execResult;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -128,12 +128,20 @@ void OracleWizard::accept()
|
|||
|
||||
void OracleWizard::reject()
|
||||
{
|
||||
cancelled = true;
|
||||
|
||||
// The wizard is being closed while a page may still run a worker on the
|
||||
// importer. Ask it to stop before the wizard (and the importer child) is
|
||||
// destroyed, so the worker thread never touches freed memory.
|
||||
if (auto *active = dynamic_cast<OracleWizardPage *>(currentPage())) {
|
||||
active->cancelWork();
|
||||
}
|
||||
|
||||
const auto replies = nam->findChildren<QNetworkReply *>();
|
||||
for (QNetworkReply *reply : replies) {
|
||||
reply->abort();
|
||||
}
|
||||
|
||||
QWizard::reject();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -20,6 +20,19 @@ public:
|
|||
explicit OracleWizard(QWidget *parent = nullptr);
|
||||
void accept() override;
|
||||
void reject() override;
|
||||
bool wasCancelled()
|
||||
{
|
||||
return cancelled;
|
||||
}
|
||||
/** @brief Records that the card database was written to disk, so a later cancel still counts as an update. */
|
||||
void markDatabaseSaved()
|
||||
{
|
||||
databaseSaved = true;
|
||||
}
|
||||
bool hasSavedDatabase()
|
||||
{
|
||||
return databaseSaved;
|
||||
}
|
||||
void enableButtons();
|
||||
void disableButtons();
|
||||
void retranslateUi();
|
||||
|
|
@ -67,6 +80,8 @@ private:
|
|||
QByteArray tokensData;
|
||||
QString cardSourceUrl;
|
||||
QString cardSourceVersion;
|
||||
bool cancelled = false;
|
||||
bool databaseSaved = false;
|
||||
|
||||
void migrateOracleSettings();
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -484,6 +484,10 @@ void LoadSetsPage::actDownloadFinishedSetsFile()
|
|||
{
|
||||
// check for a reply
|
||||
auto *reply = dynamic_cast<QNetworkReply *>(sender());
|
||||
if (wizard()->wasCancelled()) {
|
||||
reply->deleteLater();
|
||||
return;
|
||||
}
|
||||
auto errorCode = reply->error();
|
||||
if (errorCode != QNetworkReply::NoError) {
|
||||
QMessageBox::critical(this, tr("Error"), tr("Network error: %1.").arg(reply->errorString()));
|
||||
|
|
@ -895,6 +899,7 @@ bool SaveSetsPage::validatePage()
|
|||
return false;
|
||||
}
|
||||
|
||||
wizard()->markDatabaseSaved();
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -178,6 +178,10 @@ void SimpleDownloadFilePage::actDownloadFinished()
|
|||
{
|
||||
// check for a reply
|
||||
auto *reply = dynamic_cast<QNetworkReply *>(sender());
|
||||
if (wizard()->wasCancelled()) {
|
||||
reply->deleteLater();
|
||||
return;
|
||||
}
|
||||
QNetworkReply::NetworkError errorCode = reply->error();
|
||||
if (errorCode != QNetworkReply::NoError) {
|
||||
QMessageBox::critical(this, tr("Error"), tr("Network error: %1.").arg(reply->errorString()));
|
||||
|
|
@ -242,6 +246,7 @@ bool SimpleDownloadFilePage::saveToFile()
|
|||
|
||||
// clean saved downloadData
|
||||
downloadData = QByteArray();
|
||||
wizard()->markDatabaseSaved();
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@
|
|||
#include <QImage>
|
||||
#include <QImageWriter>
|
||||
#include <QLoggingCategory>
|
||||
#include <QScopeGuard>
|
||||
#include <QSharedPointer>
|
||||
#include <QStandardPaths>
|
||||
#include <QString>
|
||||
|
|
@ -223,8 +224,43 @@ int main(int argc, char **argv)
|
|||
}
|
||||
|
||||
QCoreApplication app(argc, argv);
|
||||
|
||||
// Every TEST() runs as its own process and CI runs them in parallel (ctest -j4),
|
||||
// while the picture paths round-trip through the process-global SettingsCache
|
||||
// file. Parallel cases must therefore never share one settings directory: on
|
||||
// platforms where QStandardPaths ignores the $HOME override above (macOS
|
||||
// resolves the home directory via system APIs), every case would otherwise read
|
||||
// and write the same paths.ini, and a sibling's setPicsPath can land between our
|
||||
// own set and the loader's get — so the loader searches a foreign (or empty)
|
||||
// directory and flakily returns a null image. Scope the settings to this process
|
||||
// with a pid-unique application name; PIDs are never reused by live processes.
|
||||
QCoreApplication::setOrganizationName(QStringLiteral("CockatriceTest"));
|
||||
QCoreApplication::setApplicationName(
|
||||
QStringLiteral("loader-local-matching-test-%1").arg(QCoreApplication::applicationPid()));
|
||||
|
||||
QLoggingCategory::setFilterRules("card_picture_loader.*=false\nsettings_cache.*=false");
|
||||
|
||||
::testing::InitGoogleTest(&argc, argv);
|
||||
|
||||
// CMake's gtest discovery runs this binary with --gtest_list_tests, which lists
|
||||
// without running any test body. Arming the cleanup guard here would make it the
|
||||
// sole SettingsCache::instance() call in that mode, instantiating the
|
||||
// Q_GLOBAL_STATIC singleton (and its QNetworkAccessManagers) just to tear down at
|
||||
// exit — which LeakSanitizer reports as leaks and fails discovery on the sanitizer
|
||||
// leg. Skip the sandbox cleanup entirely when only listing.
|
||||
if (::testing::GTEST_FLAG(list_tests)) {
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
|
||||
// Best-effort cleanup of this process's settings sandbox. QStandardPaths test
|
||||
// mode is never auto-cleaned, and on macOS the $HOME override above is ignored,
|
||||
// so without this every run litters ~/.qttest with one pid-unique directory.
|
||||
// Safe under parallel runs: the pid-unique application name above guarantees
|
||||
// this directory belongs to this process alone.
|
||||
const auto cleanupSandbox = qScopeGuard([] {
|
||||
QDir(SettingsCache::instance().getDataPath()).removeRecursively();
|
||||
QDir(SettingsCache::instance().getCachePath()).removeRecursively();
|
||||
});
|
||||
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue