mirror of
https://github.com/Cockatrice/Cockatrice.git
synced 2026-06-15 03:28:49 -07:00
[Move refactor] Reparent orphan classes (#6236)
* Move orphaned classes to their correct parent folders. --------- Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
This commit is contained in:
parent
1ef07309d6
commit
d9c65d4ae0
143 changed files with 171 additions and 169 deletions
|
|
@ -0,0 +1,81 @@
|
|||
#include "deck_stats_interface.h"
|
||||
|
||||
#include <QDesktopServices>
|
||||
#include <QMessageBox>
|
||||
#include <QNetworkAccessManager>
|
||||
#include <QNetworkReply>
|
||||
#include <QNetworkRequest>
|
||||
#include <QRegularExpression>
|
||||
#include <QUrlQuery>
|
||||
#include <libcockatrice/deck_list/deck_list.h>
|
||||
#include <libcockatrice/deck_list/deck_list_card_node.h>
|
||||
|
||||
DeckStatsInterface::DeckStatsInterface(CardDatabase &_cardDatabase, QObject *parent)
|
||||
: QObject(parent), cardDatabase(_cardDatabase)
|
||||
{
|
||||
manager = new QNetworkAccessManager(this);
|
||||
connect(manager, &QNetworkAccessManager::finished, this, &DeckStatsInterface::queryFinished);
|
||||
}
|
||||
|
||||
void DeckStatsInterface::queryFinished(QNetworkReply *reply)
|
||||
{
|
||||
if (reply->error() != QNetworkReply::NoError) {
|
||||
QMessageBox::critical(nullptr, tr("Error"), reply->errorString());
|
||||
reply->deleteLater();
|
||||
deleteLater();
|
||||
return;
|
||||
}
|
||||
|
||||
QString data(reply->readAll());
|
||||
reply->deleteLater();
|
||||
|
||||
static const QRegularExpression rx("<meta property=\"og:url\" content=\"([^\"]+)\"");
|
||||
auto match = rx.match(data);
|
||||
if (!match.hasMatch()) {
|
||||
QMessageBox::critical(nullptr, tr("Error"), tr("The reply from the server could not be parsed."));
|
||||
deleteLater();
|
||||
return;
|
||||
}
|
||||
|
||||
QString deckUrl = match.captured(1);
|
||||
QDesktopServices::openUrl(deckUrl);
|
||||
|
||||
deleteLater();
|
||||
}
|
||||
|
||||
void DeckStatsInterface::getAnalyzeRequestData(DeckList *deck, QByteArray *data)
|
||||
{
|
||||
DeckList deckWithoutTokens;
|
||||
copyDeckWithoutTokens(*deck, deckWithoutTokens);
|
||||
|
||||
QUrl params;
|
||||
QUrlQuery urlQuery;
|
||||
urlQuery.addQueryItem("deck", deckWithoutTokens.writeToString_Plain());
|
||||
urlQuery.addQueryItem("decktitle", deck->getName());
|
||||
params.setQuery(urlQuery);
|
||||
data->append(params.query(QUrl::EncodeReserved).toUtf8());
|
||||
}
|
||||
|
||||
void DeckStatsInterface::analyzeDeck(DeckList *deck)
|
||||
{
|
||||
QByteArray data;
|
||||
getAnalyzeRequestData(deck, &data);
|
||||
|
||||
QNetworkRequest request(QUrl("https://deckstats.net/index.php"));
|
||||
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");
|
||||
|
||||
manager->post(request, data);
|
||||
}
|
||||
|
||||
void DeckStatsInterface::copyDeckWithoutTokens(DeckList &source, DeckList &destination)
|
||||
{
|
||||
auto copyIfNotAToken = [this, &destination](const auto node, const auto card) {
|
||||
CardInfoPtr dbCard = cardDatabase.query()->getCardInfo(card->getName());
|
||||
if (dbCard && !dbCard->getIsToken()) {
|
||||
DecklistCardNode *addedCard = destination.addCard(card->getName(), node->getName(), -1);
|
||||
addedCard->setNumber(card->getNumber());
|
||||
}
|
||||
};
|
||||
|
||||
source.forEachCard(copyIfNotAToken);
|
||||
}
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
/**
|
||||
* @file deck_stats_interface.h
|
||||
* @ingroup ApiInterfaces
|
||||
* @brief TODO: Document this.
|
||||
*/
|
||||
|
||||
#ifndef DECKSTATS_INTERFACE_H
|
||||
#define DECKSTATS_INTERFACE_H
|
||||
|
||||
#include <QObject>
|
||||
#include <libcockatrice/card/card_database/card_database.h>
|
||||
#include <libcockatrice/deck_list/deck_list.h>
|
||||
|
||||
class QByteArray;
|
||||
class QNetworkAccessManager;
|
||||
class QNetworkReply;
|
||||
class DeckList;
|
||||
|
||||
class DeckStatsInterface : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
private:
|
||||
QNetworkAccessManager *manager;
|
||||
|
||||
CardDatabase &cardDatabase;
|
||||
|
||||
/**
|
||||
* Deckstats doesn't recognize token cards, and instead tries to find the
|
||||
* closest non-token card instead. So we construct a new deck which has no
|
||||
* tokens.
|
||||
*/
|
||||
void copyDeckWithoutTokens(DeckList &source, DeckList &destination);
|
||||
|
||||
private slots:
|
||||
void queryFinished(QNetworkReply *reply);
|
||||
void getAnalyzeRequestData(DeckList *deck, QByteArray *data);
|
||||
|
||||
public:
|
||||
explicit DeckStatsInterface(CardDatabase &_cardDatabase, QObject *parent = nullptr);
|
||||
void analyzeDeck(DeckList *deck);
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,111 @@
|
|||
#include "tapped_out_interface.h"
|
||||
|
||||
#include <QDesktopServices>
|
||||
#include <QMessageBox>
|
||||
#include <QNetworkAccessManager>
|
||||
#include <QNetworkReply>
|
||||
#include <QNetworkRequest>
|
||||
#include <QRegularExpression>
|
||||
#include <QUrlQuery>
|
||||
#include <libcockatrice/deck_list/deck_list.h>
|
||||
#include <libcockatrice/deck_list/deck_list_card_node.h>
|
||||
|
||||
TappedOutInterface::TappedOutInterface(CardDatabase &_cardDatabase, QObject *parent)
|
||||
: QObject(parent), cardDatabase(_cardDatabase)
|
||||
{
|
||||
manager = new QNetworkAccessManager(this);
|
||||
connect(manager, &QNetworkAccessManager::finished, this, &TappedOutInterface::queryFinished);
|
||||
}
|
||||
|
||||
void TappedOutInterface::queryFinished(QNetworkReply *reply)
|
||||
{
|
||||
if (reply->error() != QNetworkReply::NoError) {
|
||||
QMessageBox::critical(nullptr, tr("Error"), reply->errorString());
|
||||
reply->deleteLater();
|
||||
deleteLater();
|
||||
return;
|
||||
}
|
||||
|
||||
int httpStatus = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
||||
if (reply->hasRawHeader("Location")) {
|
||||
/*
|
||||
* If the reply contains a "Location" header, a relative URL to the deck on TappedOut
|
||||
* can be extracted from the header. The http status is a 302 "redirect".
|
||||
*/
|
||||
QString deckUrl = reply->rawHeader("Location");
|
||||
qCInfo(TappedOutInterfaceLog) << "Tappedout: good reply, http status" << httpStatus << "location" << deckUrl;
|
||||
QDesktopServices::openUrl("https://tappedout.net" + deckUrl);
|
||||
} else {
|
||||
/*
|
||||
* Otherwise, the deck has not been parsed correctly. Error messages can be extracted
|
||||
* from the html. Css pseudo selector for errors: $("div.alert-danger > ul > li")
|
||||
*/
|
||||
QString data(reply->readAll());
|
||||
QStringList errorMessageList = {tr("Unable to analyze the deck.")};
|
||||
|
||||
static const QRegularExpression rx("<div class=\"alert alert-danger.*?<ul>(.*?)</ul>");
|
||||
auto match = rx.match(data);
|
||||
if (match.hasMatch()) {
|
||||
QString errors = match.captured(1);
|
||||
static const QRegularExpression rx2("<li>(.*?)</li>");
|
||||
static const QRegularExpression rxremove("<[^>]*>");
|
||||
auto matchIterator = rx2.globalMatch(errors);
|
||||
while (matchIterator.hasNext()) {
|
||||
auto match2 = matchIterator.next();
|
||||
errorMessageList.append(match2.captured(1).remove(rxremove).simplified());
|
||||
}
|
||||
}
|
||||
|
||||
QString errorMessage = errorMessageList.join("\n");
|
||||
qCWarning(TappedOutInterfaceLog) << "Tappedout: bad reply, http status" << httpStatus << "size" << data.size()
|
||||
<< "message" << errorMessage;
|
||||
|
||||
QMessageBox::critical(nullptr, tr("Error"), errorMessage);
|
||||
}
|
||||
|
||||
reply->deleteLater();
|
||||
deleteLater();
|
||||
}
|
||||
|
||||
void TappedOutInterface::getAnalyzeRequestData(DeckList *deck, QByteArray *data)
|
||||
{
|
||||
DeckList mainboard, sideboard;
|
||||
copyDeckSplitMainAndSide(*deck, mainboard, sideboard);
|
||||
|
||||
QUrl params;
|
||||
QUrlQuery urlQuery;
|
||||
urlQuery.addQueryItem("name", deck->getName());
|
||||
urlQuery.addQueryItem("mainboard", mainboard.writeToString_Plain(false, true));
|
||||
urlQuery.addQueryItem("sideboard", sideboard.writeToString_Plain(false, true));
|
||||
params.setQuery(urlQuery);
|
||||
data->append(params.query(QUrl::EncodeReserved).toUtf8());
|
||||
}
|
||||
|
||||
void TappedOutInterface::analyzeDeck(DeckList *deck)
|
||||
{
|
||||
QByteArray data;
|
||||
getAnalyzeRequestData(deck, &data);
|
||||
|
||||
QNetworkRequest request(QUrl("https://tappedout.net/mtg-decks/paste/"));
|
||||
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");
|
||||
|
||||
manager->post(request, data);
|
||||
}
|
||||
|
||||
void TappedOutInterface::copyDeckSplitMainAndSide(DeckList &source, DeckList &mainboard, DeckList &sideboard)
|
||||
{
|
||||
auto copyMainOrSide = [this, &mainboard, &sideboard](const auto node, const auto card) {
|
||||
CardInfoPtr dbCard = cardDatabase.query()->getCardInfo(card->getName());
|
||||
if (!dbCard || dbCard->getIsToken())
|
||||
return;
|
||||
|
||||
DecklistCardNode *addedCard;
|
||||
if (node->getName() == DECK_ZONE_SIDE)
|
||||
addedCard = sideboard.addCard(card->getName(), node->getName(), -1);
|
||||
else
|
||||
addedCard = mainboard.addCard(card->getName(), node->getName(), -1);
|
||||
addedCard->setNumber(card->getNumber());
|
||||
};
|
||||
|
||||
source.forEachCard(copyMainOrSide);
|
||||
}
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
/**
|
||||
* @file tapped_out_interface.h
|
||||
* @ingroup ApiInterfaces
|
||||
* @brief TODO: Document this.
|
||||
*/
|
||||
|
||||
#ifndef TAPPEDOUT_INTERFACE_H
|
||||
#define TAPPEDOUT_INTERFACE_H
|
||||
|
||||
#include <QLoggingCategory>
|
||||
#include <QObject>
|
||||
#include <libcockatrice/card/card_database/card_database.h>
|
||||
#include <libcockatrice/deck_list/deck_list.h>
|
||||
|
||||
inline Q_LOGGING_CATEGORY(TappedOutInterfaceLog, "tapped_out_interface");
|
||||
|
||||
class QByteArray;
|
||||
class QNetworkAccessManager;
|
||||
class QNetworkReply;
|
||||
class DeckList;
|
||||
|
||||
/**
|
||||
* TappedOutInterface exists in order to support the "Analyze on TappedOut" feature.
|
||||
* An http POST request is sent and the result is retrieved from the reply. Parsing
|
||||
* logic is implemented in TappedOutInterface::queryFinished().
|
||||
*/
|
||||
|
||||
class TappedOutInterface : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
private:
|
||||
QNetworkAccessManager *manager;
|
||||
|
||||
CardDatabase &cardDatabase;
|
||||
void copyDeckSplitMainAndSide(DeckList &source, DeckList &mainboard, DeckList &sideboard);
|
||||
private slots:
|
||||
void queryFinished(QNetworkReply *reply);
|
||||
void getAnalyzeRequestData(DeckList *deck, QByteArray *data);
|
||||
|
||||
public:
|
||||
explicit TappedOutInterface(CardDatabase &_cardDatabase, QObject *parent = nullptr);
|
||||
void analyzeDeck(DeckList *deck);
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
@ -6,10 +6,9 @@
|
|||
|
||||
#ifndef INTERFACE_JSON_DECK_PARSER_H
|
||||
#define INTERFACE_JSON_DECK_PARSER_H
|
||||
#include "../../../deck/deck_loader.h"
|
||||
|
||||
#include <QJsonArray>
|
||||
#include <QJsonObject>
|
||||
#include <libcockatrice/deck_list/deck_loader.h>
|
||||
|
||||
class IJsonDeckParser
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,194 +0,0 @@
|
|||
#include "replay_timeline_widget.h"
|
||||
|
||||
#include <QPainter>
|
||||
#include <QPainterPath>
|
||||
#include <QPalette>
|
||||
#include <QTimer>
|
||||
#include <libcockatrice/settings/cache_settings.h>
|
||||
|
||||
ReplayTimelineWidget::ReplayTimelineWidget(QWidget *parent)
|
||||
: QWidget(parent), maxBinValue(1), maxTime(1), timeScaleFactor(1.0), currentVisualTime(0), currentProcessedTime(0),
|
||||
currentEvent(0)
|
||||
{
|
||||
replayTimer = new QTimer(this);
|
||||
connect(replayTimer, &QTimer::timeout, this, &ReplayTimelineWidget::replayTimerTimeout);
|
||||
|
||||
rewindBufferingTimer = new QTimer(this);
|
||||
rewindBufferingTimer->setSingleShot(true);
|
||||
connect(rewindBufferingTimer, &QTimer::timeout, this, &ReplayTimelineWidget::processRewind);
|
||||
}
|
||||
|
||||
void ReplayTimelineWidget::setTimeline(const QList<int> &_replayTimeline)
|
||||
{
|
||||
replayTimeline = _replayTimeline;
|
||||
histogram.clear();
|
||||
int binEndTime = BIN_LENGTH - 1;
|
||||
int binValue = 0;
|
||||
for (int i : replayTimeline) {
|
||||
if (i > binEndTime) {
|
||||
histogram.append(binValue);
|
||||
if (binValue > maxBinValue)
|
||||
maxBinValue = binValue;
|
||||
while (i > binEndTime + BIN_LENGTH) {
|
||||
histogram.append(0);
|
||||
binEndTime += BIN_LENGTH;
|
||||
}
|
||||
binValue = 1;
|
||||
binEndTime += BIN_LENGTH;
|
||||
} else
|
||||
++binValue;
|
||||
}
|
||||
histogram.append(binValue);
|
||||
if (!replayTimeline.isEmpty())
|
||||
maxTime = replayTimeline.last();
|
||||
|
||||
update();
|
||||
}
|
||||
|
||||
void ReplayTimelineWidget::paintEvent(QPaintEvent * /* event */)
|
||||
{
|
||||
QPainter painter(this);
|
||||
painter.drawRect(0, 0, width() - 1, height() - 1);
|
||||
|
||||
qreal binWidth = (qreal)width() / histogram.size();
|
||||
QPainterPath path;
|
||||
path.moveTo(0, height() - 1);
|
||||
for (int i = 0; i < histogram.size(); ++i)
|
||||
path.lineTo(qRound(i * binWidth), (height() - 1) * (1.0 - (qreal)histogram[i] / maxBinValue));
|
||||
path.lineTo(width() - 1, height() - 1);
|
||||
path.lineTo(0, height() - 1);
|
||||
painter.fillPath(path, Qt::black);
|
||||
|
||||
const QColor barColor = QColor::fromHsv(120, 255, 255, 100);
|
||||
quint64 w = (quint64)(width() - 1) * (quint64)currentVisualTime / maxTime;
|
||||
painter.fillRect(0, 0, static_cast<int>(w), height() - 1, barColor);
|
||||
}
|
||||
|
||||
void ReplayTimelineWidget::mousePressEvent(QMouseEvent *event)
|
||||
{
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
|
||||
int newTime = static_cast<int>((qint64)maxTime * (qint64)event->position().x() / width());
|
||||
#else
|
||||
int newTime = static_cast<int>((qint64)maxTime * (qint64)event->x() / width());
|
||||
#endif
|
||||
// don't buffer rewinds from clicks, since clicks usually don't happen fast enough to require buffering
|
||||
skipToTime(newTime, false);
|
||||
}
|
||||
|
||||
void ReplayTimelineWidget::skipToTime(int newTime, bool doRewindBuffering)
|
||||
{
|
||||
// check boundary conditions
|
||||
if (newTime < 0) {
|
||||
newTime = 0;
|
||||
}
|
||||
if (newTime > maxTime) {
|
||||
newTime = maxTime;
|
||||
}
|
||||
|
||||
newTime -= newTime % TIMER_INTERVAL_MS; // Time should always be a multiple of the interval
|
||||
|
||||
const bool isBackwardsSkip = newTime < currentProcessedTime;
|
||||
currentVisualTime = newTime;
|
||||
|
||||
if (isBackwardsSkip) {
|
||||
handleBackwardsSkip(doRewindBuffering);
|
||||
} else {
|
||||
processNewEvents(FORWARD_SKIP);
|
||||
}
|
||||
|
||||
update();
|
||||
}
|
||||
|
||||
/// @param doRewindBuffering When true, if multiple backward skips are made in quick succession, only a single rewind
|
||||
/// is processed at the end. When false, the backwards skip will always cause an immediate rewind
|
||||
void ReplayTimelineWidget::handleBackwardsSkip(bool doRewindBuffering)
|
||||
{
|
||||
if (doRewindBuffering) {
|
||||
// We use a one-shot timer to implement the rewind buffering.
|
||||
// The rewind only happens once the timer runs out.
|
||||
// If another backwards skip happens, the timer will just get reset instead of rewinding.
|
||||
rewindBufferingTimer->stop();
|
||||
rewindBufferingTimer->start(SettingsCache::instance().getRewindBufferingMs());
|
||||
} else {
|
||||
// otherwise, process the rewind immediately
|
||||
processRewind();
|
||||
}
|
||||
}
|
||||
|
||||
void ReplayTimelineWidget::processRewind()
|
||||
{
|
||||
// stop any queued-up rewinds
|
||||
rewindBufferingTimer->stop();
|
||||
|
||||
// process the rewind
|
||||
currentEvent = 0;
|
||||
emit rewound();
|
||||
processNewEvents(BACKWARD_SKIP);
|
||||
}
|
||||
|
||||
QSize ReplayTimelineWidget::sizeHint() const
|
||||
{
|
||||
return {-1, 50};
|
||||
}
|
||||
|
||||
QSize ReplayTimelineWidget::minimumSizeHint() const
|
||||
{
|
||||
return {400, 50};
|
||||
}
|
||||
|
||||
void ReplayTimelineWidget::replayTimerTimeout()
|
||||
{
|
||||
currentVisualTime += TIMER_INTERVAL_MS;
|
||||
|
||||
processNewEvents(NORMAL_PLAYBACK);
|
||||
|
||||
if (!(currentVisualTime % 1000))
|
||||
update();
|
||||
}
|
||||
|
||||
/// Processes all unprocessed events up to the current time.
|
||||
void ReplayTimelineWidget::processNewEvents(PlaybackMode playbackMode)
|
||||
{
|
||||
currentProcessedTime = currentVisualTime;
|
||||
|
||||
while ((currentEvent < replayTimeline.size()) && (replayTimeline[currentEvent] < currentProcessedTime)) {
|
||||
EventProcessingOptions options;
|
||||
|
||||
// backwards skip => always skip reveal windows
|
||||
// forwards skip => skip reveal windows that don't happen within a big skip of the target
|
||||
if (playbackMode == BACKWARD_SKIP || currentProcessedTime - replayTimeline[currentEvent] > BIG_SKIP_MS)
|
||||
options |= SKIP_REVEAL_WINDOW;
|
||||
|
||||
// backwards skip => always skip tap animation
|
||||
if (playbackMode == BACKWARD_SKIP)
|
||||
options |= SKIP_TAP_ANIMATION;
|
||||
|
||||
emit processNextEvent(options);
|
||||
++currentEvent;
|
||||
}
|
||||
if (currentEvent == replayTimeline.size()) {
|
||||
emit replayFinished();
|
||||
replayTimer->stop();
|
||||
}
|
||||
}
|
||||
|
||||
void ReplayTimelineWidget::setTimeScaleFactor(qreal _timeScaleFactor)
|
||||
{
|
||||
timeScaleFactor = _timeScaleFactor;
|
||||
replayTimer->setInterval(static_cast<int>(TIMER_INTERVAL_MS / timeScaleFactor));
|
||||
}
|
||||
|
||||
void ReplayTimelineWidget::startReplay()
|
||||
{
|
||||
replayTimer->start(static_cast<int>(TIMER_INTERVAL_MS / timeScaleFactor));
|
||||
}
|
||||
|
||||
void ReplayTimelineWidget::stopReplay()
|
||||
{
|
||||
replayTimer->stop();
|
||||
}
|
||||
|
||||
void ReplayTimelineWidget::skipByAmount(int amount)
|
||||
{
|
||||
skipToTime(currentVisualTime + amount, amount < 0);
|
||||
}
|
||||
|
|
@ -1,80 +0,0 @@
|
|||
/**
|
||||
* @file replay_timeline_widget.h
|
||||
* @ingroup Replay
|
||||
* @brief TODO: Document this.
|
||||
*/
|
||||
|
||||
#ifndef REPLAY_TIMELINE_WIDGET
|
||||
#define REPLAY_TIMELINE_WIDGET
|
||||
|
||||
#include "../../game/player/event_processing_options.h"
|
||||
|
||||
#include <QList>
|
||||
#include <QMouseEvent>
|
||||
#include <QWidget>
|
||||
|
||||
class QPaintEvent;
|
||||
class QTimer;
|
||||
|
||||
class ReplayTimelineWidget : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
signals:
|
||||
void processNextEvent(EventProcessingOptions options);
|
||||
void replayFinished();
|
||||
void rewound();
|
||||
|
||||
private:
|
||||
enum PlaybackMode
|
||||
{
|
||||
NORMAL_PLAYBACK,
|
||||
FORWARD_SKIP,
|
||||
BACKWARD_SKIP
|
||||
};
|
||||
|
||||
static constexpr int TIMER_INTERVAL_MS = 200;
|
||||
static constexpr int BIN_LENGTH = 5000;
|
||||
|
||||
QTimer *replayTimer;
|
||||
QTimer *rewindBufferingTimer;
|
||||
QList<int> replayTimeline;
|
||||
QList<int> histogram;
|
||||
int maxBinValue, maxTime;
|
||||
qreal timeScaleFactor;
|
||||
int currentVisualTime; // time currently displayed by the timeline
|
||||
int currentProcessedTime; // time that events are currently processed up to. Could differ from visual time due to
|
||||
// rewind buffering
|
||||
int currentEvent;
|
||||
|
||||
void skipToTime(int newTime, bool doRewindBuffering);
|
||||
void handleBackwardsSkip(bool doRewindBuffering);
|
||||
void processRewind();
|
||||
void processNewEvents(PlaybackMode playbackMode);
|
||||
private slots:
|
||||
void replayTimerTimeout();
|
||||
|
||||
public:
|
||||
static constexpr int SMALL_SKIP_MS = 1000;
|
||||
static constexpr int BIG_SKIP_MS = 10000;
|
||||
static constexpr qreal FAST_FORWARD_SCALE_FACTOR = 10.0;
|
||||
|
||||
explicit ReplayTimelineWidget(QWidget *parent = nullptr);
|
||||
void setTimeline(const QList<int> &_replayTimeline);
|
||||
QSize sizeHint() const override;
|
||||
QSize minimumSizeHint() const override;
|
||||
void setTimeScaleFactor(qreal _timeScaleFactor);
|
||||
int getCurrentEvent() const
|
||||
{
|
||||
return currentEvent;
|
||||
}
|
||||
public slots:
|
||||
void startReplay();
|
||||
void stopReplay();
|
||||
void skipByAmount(int amount); // use a negative amount to skip backwards
|
||||
|
||||
protected:
|
||||
void paintEvent(QPaintEvent *event) override;
|
||||
void mousePressEvent(QMouseEvent *event) override;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
@ -1,303 +0,0 @@
|
|||
#include "sets_model.h"
|
||||
|
||||
#include <QSortFilterProxyModel>
|
||||
|
||||
SetsModel::SetsModel(CardDatabase *_db, QObject *parent) : QAbstractTableModel(parent), sets(_db->getSetList())
|
||||
{
|
||||
sets.sortByKey();
|
||||
for (const CardSetPtr &set : sets) {
|
||||
if (set->getEnabled())
|
||||
enabledSets.insert(set);
|
||||
}
|
||||
}
|
||||
|
||||
SetsModel::~SetsModel() = default;
|
||||
|
||||
int SetsModel::rowCount(const QModelIndex &parent) const
|
||||
{
|
||||
if (parent.isValid())
|
||||
return 0;
|
||||
else
|
||||
return sets.size();
|
||||
}
|
||||
|
||||
QVariant SetsModel::data(const QModelIndex &index, int role) const
|
||||
{
|
||||
if (!index.isValid() || (index.column() >= NUM_COLS) || (index.row() >= rowCount()))
|
||||
return QVariant();
|
||||
|
||||
CardSetPtr set = sets[index.row()];
|
||||
|
||||
if (index.column() == EnabledCol) {
|
||||
switch (role) {
|
||||
case SortRole:
|
||||
return enabledSets.contains(set) ? "1" : "0";
|
||||
case Qt::CheckStateRole:
|
||||
return static_cast<int>(enabledSets.contains(set) ? Qt::Checked : Qt::Unchecked);
|
||||
case Qt::DisplayRole:
|
||||
default:
|
||||
return QVariant();
|
||||
}
|
||||
}
|
||||
|
||||
if (role != Qt::DisplayRole && role != SortRole)
|
||||
return QVariant();
|
||||
|
||||
switch (index.column()) {
|
||||
case SortKeyCol:
|
||||
return QString("%1").arg(set->getSortKey(), 8, 10, QChar('0'));
|
||||
case IsKnownCol:
|
||||
return set->getIsKnown();
|
||||
case SetTypeCol:
|
||||
return set->getSetType();
|
||||
case ShortNameCol:
|
||||
return set->getShortName();
|
||||
case LongNameCol:
|
||||
return set->getLongName();
|
||||
case ReleaseDateCol:
|
||||
return set->getReleaseDate().toString(Qt::ISODate);
|
||||
default:
|
||||
return QVariant();
|
||||
}
|
||||
}
|
||||
|
||||
bool SetsModel::setData(const QModelIndex &index, const QVariant &value, int role)
|
||||
{
|
||||
if (role == Qt::CheckStateRole && index.column() == EnabledCol) {
|
||||
toggleRow(index.row(), value == Qt::Checked);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
QVariant SetsModel::headerData(int section, Qt::Orientation orientation, int role) const
|
||||
{
|
||||
if ((role != Qt::DisplayRole) || (orientation != Qt::Horizontal))
|
||||
return QVariant();
|
||||
switch (section) {
|
||||
case SortKeyCol:
|
||||
return QString("Key"); /* no tr() for translations needed, column just used for sorting --> hidden */
|
||||
case IsKnownCol:
|
||||
return QString(
|
||||
"Is known"); /* no tr() for translations needed, column is just used for sorting --> hidden */
|
||||
case EnabledCol:
|
||||
return tr("Enabled");
|
||||
case SetTypeCol:
|
||||
return tr("Set type");
|
||||
case ShortNameCol:
|
||||
return tr("Set code");
|
||||
case LongNameCol:
|
||||
return tr("Long name");
|
||||
case ReleaseDateCol:
|
||||
return tr("Release date");
|
||||
default:
|
||||
return QVariant();
|
||||
}
|
||||
}
|
||||
|
||||
Qt::ItemFlags SetsModel::flags(const QModelIndex &index) const
|
||||
{
|
||||
if (!index.isValid())
|
||||
return Qt::NoItemFlags;
|
||||
|
||||
Qt::ItemFlags flags = QAbstractTableModel::flags(index) | Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled;
|
||||
|
||||
if (index.column() == EnabledCol)
|
||||
flags |= Qt::ItemIsUserCheckable;
|
||||
|
||||
return flags;
|
||||
}
|
||||
|
||||
Qt::DropActions SetsModel::supportedDropActions() const
|
||||
{
|
||||
return Qt::MoveAction;
|
||||
}
|
||||
|
||||
QMimeData *SetsModel::mimeData(const QModelIndexList &indexes) const
|
||||
{
|
||||
if (indexes.isEmpty())
|
||||
return 0;
|
||||
|
||||
SetsMimeData *result = new SetsMimeData(indexes[0].row());
|
||||
return qobject_cast<QMimeData *>(result);
|
||||
}
|
||||
|
||||
bool SetsModel::dropMimeData(const QMimeData *data,
|
||||
Qt::DropAction action,
|
||||
int row,
|
||||
int /*column*/,
|
||||
const QModelIndex &parent)
|
||||
{
|
||||
if (action != Qt::MoveAction)
|
||||
return false;
|
||||
if (row == -1) {
|
||||
if (!parent.isValid())
|
||||
return false;
|
||||
row = parent.row();
|
||||
}
|
||||
int oldRow = qobject_cast<const SetsMimeData *>(data)->getOldRow();
|
||||
if (oldRow < row)
|
||||
row--;
|
||||
|
||||
swapRows(oldRow, row);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void SetsModel::toggleRow(int row, bool enable)
|
||||
{
|
||||
CardSetPtr temp = sets.at(row);
|
||||
|
||||
if (enable)
|
||||
enabledSets.insert(temp);
|
||||
else
|
||||
enabledSets.remove(temp);
|
||||
|
||||
emit dataChanged(index(row, EnabledCol), index(row, EnabledCol));
|
||||
}
|
||||
|
||||
void SetsModel::toggleRow(int row)
|
||||
{
|
||||
CardSetPtr tmp = sets.at(row);
|
||||
|
||||
if (tmp == nullptr)
|
||||
return;
|
||||
|
||||
if (enabledSets.contains(tmp))
|
||||
enabledSets.remove(tmp);
|
||||
else
|
||||
enabledSets.insert(tmp);
|
||||
|
||||
emit dataChanged(index(row, EnabledCol), index(row, EnabledCol));
|
||||
}
|
||||
|
||||
void SetsModel::toggleAll(bool enabled)
|
||||
{
|
||||
enabledSets.clear();
|
||||
|
||||
if (enabled)
|
||||
for (CardSetPtr set : sets)
|
||||
enabledSets.insert(set);
|
||||
|
||||
emit dataChanged(index(0, 0), index(rowCount() - 1, columnCount() - 1));
|
||||
}
|
||||
|
||||
void SetsModel::swapRows(int oldRow, int newRow)
|
||||
{
|
||||
beginRemoveRows(QModelIndex(), oldRow, oldRow);
|
||||
CardSetPtr temp = sets.takeAt(oldRow);
|
||||
endRemoveRows();
|
||||
|
||||
beginInsertRows(QModelIndex(), newRow, newRow);
|
||||
sets.insert(newRow, temp);
|
||||
endInsertRows();
|
||||
|
||||
emit dataChanged(index(0, 0), index(rowCount() - 1, columnCount() - 1));
|
||||
}
|
||||
|
||||
void SetsModel::restoreOriginalOrder()
|
||||
{
|
||||
int numRows = rowCount();
|
||||
sets.defaultSort();
|
||||
emit dataChanged(index(0, 0), index(numRows - 1, columnCount() - 1));
|
||||
}
|
||||
|
||||
void SetsModel::sort(int column, Qt::SortOrder order)
|
||||
{
|
||||
QMultiMap<QString, CardSetPtr> setMap;
|
||||
int numRows = rowCount();
|
||||
int row;
|
||||
|
||||
for (row = 0; row < numRows; ++row)
|
||||
setMap.insert(index(row, column).data(SetsModel::SortRole).toString(), sets.at(row));
|
||||
|
||||
QList<CardSetPtr> tmp = setMap.values();
|
||||
sets.clear();
|
||||
if (order == Qt::AscendingOrder) {
|
||||
for (row = 0; row < tmp.size(); row++) {
|
||||
sets.append(tmp.at(row));
|
||||
}
|
||||
} else {
|
||||
for (row = tmp.size() - 1; row >= 0; row--) {
|
||||
sets.append(tmp.at(row));
|
||||
}
|
||||
}
|
||||
|
||||
emit dataChanged(index(0, 0), index(numRows - 1, columnCount() - 1));
|
||||
}
|
||||
|
||||
void SetsModel::save(CardDatabase *db)
|
||||
{
|
||||
// order
|
||||
for (int i = 0; i < sets.size(); i++)
|
||||
sets[i]->setSortKey(static_cast<unsigned int>(i + 1));
|
||||
|
||||
// enabled sets
|
||||
for (const CardSetPtr &set : sets)
|
||||
set->setEnabled(enabledSets.contains(set));
|
||||
|
||||
sets.sortByKey();
|
||||
|
||||
db->notifyEnabledSetsChanged();
|
||||
}
|
||||
|
||||
void SetsModel::restore(CardDatabase *db)
|
||||
{
|
||||
// order
|
||||
sets = db->getSetList();
|
||||
sets.sortByKey();
|
||||
|
||||
// enabled sets
|
||||
enabledSets.clear();
|
||||
for (const CardSetPtr &set : sets) {
|
||||
if (set->getEnabled())
|
||||
enabledSets.insert(set);
|
||||
}
|
||||
|
||||
emit dataChanged(index(0, 0), index(rowCount() - 1, columnCount() - 1));
|
||||
}
|
||||
|
||||
QStringList SetsModel::mimeTypes() const
|
||||
{
|
||||
return QStringList() << "application/x-cockatricecardset";
|
||||
}
|
||||
|
||||
SetsDisplayModel::SetsDisplayModel(QObject *parent) : QSortFilterProxyModel(parent)
|
||||
{
|
||||
setFilterCaseSensitivity(Qt::CaseInsensitive);
|
||||
setSortCaseSensitivity(Qt::CaseInsensitive);
|
||||
}
|
||||
|
||||
void SetsDisplayModel::fetchMore(const QModelIndex &index)
|
||||
{
|
||||
int itemsToFetch = sourceModel()->rowCount(index);
|
||||
|
||||
beginInsertRows(QModelIndex(), 0, itemsToFetch - 1);
|
||||
|
||||
endInsertRows();
|
||||
}
|
||||
|
||||
bool SetsDisplayModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const
|
||||
{
|
||||
auto typeIndex = sourceModel()->index(sourceRow, SetsModel::SetTypeCol, sourceParent);
|
||||
auto nameIndex = sourceModel()->index(sourceRow, SetsModel::LongNameCol, sourceParent);
|
||||
auto shortNameIndex = sourceModel()->index(sourceRow, SetsModel::ShortNameCol, sourceParent);
|
||||
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(5, 12, 0))
|
||||
const auto filter = filterRegularExpression();
|
||||
#else
|
||||
const auto filter = filterRegExp();
|
||||
#endif
|
||||
|
||||
return (sourceModel()->data(typeIndex).toString().contains(filter) ||
|
||||
sourceModel()->data(nameIndex).toString().contains(filter) ||
|
||||
sourceModel()->data(shortNameIndex).toString().contains(filter));
|
||||
}
|
||||
|
||||
bool SetsDisplayModel::lessThan(const QModelIndex &left, const QModelIndex &right) const
|
||||
{
|
||||
QString leftString = sourceModel()->data(left, SetsModel::SortRole).toString();
|
||||
QString rightString = sourceModel()->data(right, SetsModel::SortRole).toString();
|
||||
|
||||
return QString::localeAwareCompare(leftString, rightString) < 0;
|
||||
}
|
||||
|
|
@ -1,105 +0,0 @@
|
|||
/**
|
||||
* @file sets_model.h
|
||||
* @ingroup CardDatabaseModels
|
||||
* @brief TODO: Document this.
|
||||
*/
|
||||
|
||||
#ifndef SETSMODEL_H
|
||||
#define SETSMODEL_H
|
||||
|
||||
#include <QAbstractTableModel>
|
||||
#include <QMimeData>
|
||||
#include <QSet>
|
||||
#include <QSortFilterProxyModel>
|
||||
#include <libcockatrice/card/card_database/card_database.h>
|
||||
|
||||
class SetsProxyModel;
|
||||
|
||||
class SetsMimeData : public QMimeData
|
||||
{
|
||||
Q_OBJECT
|
||||
private:
|
||||
int oldRow;
|
||||
|
||||
public:
|
||||
SetsMimeData(int _oldRow) : oldRow(_oldRow)
|
||||
{
|
||||
}
|
||||
int getOldRow() const
|
||||
{
|
||||
return oldRow;
|
||||
}
|
||||
QStringList formats() const
|
||||
{
|
||||
return QStringList() << "application/x-cockatricecardset";
|
||||
}
|
||||
};
|
||||
|
||||
class SetsModel : public QAbstractTableModel
|
||||
{
|
||||
Q_OBJECT
|
||||
friend class SetsProxyModel;
|
||||
|
||||
private:
|
||||
static const int NUM_COLS = 7;
|
||||
CardSetList sets;
|
||||
QSet<CardSetPtr> enabledSets;
|
||||
|
||||
public:
|
||||
enum SetsColumns
|
||||
{
|
||||
SortKeyCol,
|
||||
IsKnownCol,
|
||||
EnabledCol,
|
||||
LongNameCol,
|
||||
ShortNameCol,
|
||||
SetTypeCol,
|
||||
ReleaseDateCol,
|
||||
PriorityCol
|
||||
};
|
||||
enum Role
|
||||
{
|
||||
SortRole = Qt::UserRole
|
||||
};
|
||||
|
||||
explicit SetsModel(CardDatabase *_db, QObject *parent = nullptr);
|
||||
~SetsModel() override;
|
||||
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||
int columnCount(const QModelIndex &parent = QModelIndex()) const override
|
||||
{
|
||||
Q_UNUSED(parent);
|
||||
return NUM_COLS;
|
||||
}
|
||||
QVariant data(const QModelIndex &index, int role) const override;
|
||||
bool setData(const QModelIndex &index, const QVariant &value, int role) override;
|
||||
QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
|
||||
Qt::ItemFlags flags(const QModelIndex &index) const override;
|
||||
Qt::DropActions supportedDropActions() const override;
|
||||
|
||||
QMimeData *mimeData(const QModelIndexList &indexes) const override;
|
||||
bool
|
||||
dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) override;
|
||||
QStringList mimeTypes() const override;
|
||||
void swapRows(int oldRow, int newRow);
|
||||
void toggleRow(int row, bool enable);
|
||||
void toggleRow(int row);
|
||||
void toggleAll(bool);
|
||||
void sort(int column, Qt::SortOrder order = Qt::AscendingOrder) override;
|
||||
void save(CardDatabase *db);
|
||||
void restore(CardDatabase *db);
|
||||
void restoreOriginalOrder();
|
||||
};
|
||||
|
||||
class SetsDisplayModel : public QSortFilterProxyModel
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit SetsDisplayModel(QObject *parent = nullptr);
|
||||
|
||||
protected:
|
||||
bool lessThan(const QModelIndex &left, const QModelIndex &right) const override;
|
||||
bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override;
|
||||
void fetchMore(const QModelIndex &index) override;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
#include "spoiler_background_updater.h"
|
||||
|
||||
#include "../../interface/window_main.h"
|
||||
#include "../../main.h"
|
||||
#include "../../../../interface/window_main.h"
|
||||
#include "../../../../main.h"
|
||||
|
||||
#include <QApplication>
|
||||
#include <QCryptographicHash>
|
||||
|
|
@ -0,0 +1,66 @@
|
|||
#include "update_downloader.h"
|
||||
|
||||
#include <QDebug>
|
||||
#include <QUrl>
|
||||
|
||||
UpdateDownloader::UpdateDownloader(QObject *parent) : QObject(parent), response(nullptr)
|
||||
{
|
||||
netMan = new QNetworkAccessManager(this);
|
||||
}
|
||||
|
||||
void UpdateDownloader::beginDownload(QUrl downloadUrl)
|
||||
{
|
||||
// Save the original URL because we need it for the filename
|
||||
if (originalUrl.isEmpty())
|
||||
originalUrl = downloadUrl;
|
||||
|
||||
response = netMan->get(QNetworkRequest(downloadUrl));
|
||||
connect(response, &QNetworkReply::finished, this, &UpdateDownloader::fileFinished);
|
||||
connect(response, &QNetworkReply::downloadProgress, this, &UpdateDownloader::downloadProgress);
|
||||
connect(this, &UpdateDownloader::stopDownload, response, &QNetworkReply::abort);
|
||||
}
|
||||
|
||||
void UpdateDownloader::downloadError(QNetworkReply::NetworkError)
|
||||
{
|
||||
if (response == nullptr)
|
||||
return;
|
||||
|
||||
emit error(response->errorString().toUtf8());
|
||||
}
|
||||
|
||||
void UpdateDownloader::fileFinished()
|
||||
{
|
||||
// If we finished but there's a redirect, follow it
|
||||
QVariant redirect = response->attribute(QNetworkRequest::RedirectionTargetAttribute);
|
||||
if (!redirect.isNull()) {
|
||||
beginDownload(redirect.toUrl());
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle any errors we had
|
||||
if (response->error()) {
|
||||
emit error(response->errorString());
|
||||
return;
|
||||
}
|
||||
|
||||
// Work out the file name of the download
|
||||
QString fileName = QDir::temp().path() + QDir::separator() + originalUrl.toString().section('/', -1);
|
||||
|
||||
// Save the build in a temporary directory
|
||||
QFile file(fileName);
|
||||
if (!file.open(QIODevice::WriteOnly)) {
|
||||
emit error(tr("Could not open the file for reading."));
|
||||
return;
|
||||
}
|
||||
|
||||
file.write(response->readAll());
|
||||
file.close();
|
||||
|
||||
// Emit the success signal with a URL to the download file
|
||||
emit downloadSuccessful(QUrl::fromLocalFile(fileName));
|
||||
}
|
||||
|
||||
void UpdateDownloader::downloadProgress(qint64 bytesRead, qint64 totalBytes)
|
||||
{
|
||||
emit progressMade(bytesRead, totalBytes);
|
||||
}
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
/**
|
||||
* @file update_downloader.h
|
||||
* @ingroup ClientUpdate
|
||||
* @brief TODO: Document this.
|
||||
*/
|
||||
|
||||
#ifndef COCKATRICE_UPDATEDOWNLOADER_H
|
||||
#define COCKATRICE_UPDATEDOWNLOADER_H
|
||||
|
||||
#include <QDate>
|
||||
#include <QObject>
|
||||
#include <QUrl>
|
||||
#include <QtNetwork>
|
||||
|
||||
class UpdateDownloader : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit UpdateDownloader(QObject *parent);
|
||||
void beginDownload(QUrl url);
|
||||
signals:
|
||||
void downloadSuccessful(QUrl filepath);
|
||||
void progressMade(qint64 bytesRead, qint64 totalBytes);
|
||||
void error(QString errorString);
|
||||
void stopDownload();
|
||||
|
||||
private:
|
||||
QUrl originalUrl;
|
||||
QNetworkAccessManager *netMan;
|
||||
QNetworkReply *response;
|
||||
private slots:
|
||||
void fileFinished();
|
||||
void downloadProgress(qint64 bytesRead, qint64 totalBytes);
|
||||
void downloadError(QNetworkReply::NetworkError);
|
||||
};
|
||||
|
||||
#endif // COCKATRICE_UPDATEDOWNLOADER_H
|
||||
Loading…
Add table
Add a link
Reference in a new issue