diff --git a/DOMjura.pro b/DOMjura.pro index 750cd52..bd4faab 100644 --- a/DOMjura.pro +++ b/DOMjura.pro @@ -5,6 +5,7 @@ #------------------------------------------------- QT += core gui xml network opengl widgets +greaterThan(QT_MAJOR_VERSION, 5): QT += openglwidgets CONFIG += c++11 TARGET = DOMjura diff --git a/README.md b/README.md index 65996ec..211de68 100644 --- a/README.md +++ b/README.md @@ -25,6 +25,25 @@ make Note that the last line may be different on Mac OS X and Windows. +## Weighted scoring + +DOMjura ranks teams by total score first and total penalty time second. Problem scores +are read from the DOMjudge problem data using the `points` or `score` field when +available. If no score is exposed, DOMjura automatically scores problem names ending +in `1`, `2`, or `3` as `2`, `5`, and `10` points respectively, so contests with +problems `A1` through `D3` work without extra configuration. + +To override the automatic scores, set the `problemScores` Qt setting or the +`DOMJURA_PROBLEM_SCORES` environment variable with comma- or semicolon-separated +entries: + +```bash +problemScores=A1=2,A2=5,A3=10 +``` + +Problem IDs, short names, and names are all accepted as keys. Problems without an +explicit score default to `1`, which preserves the original solved-count behavior. + ## Todo In the future, DOMjura will support a mode in which one can use it during the contest as a live scorebaord. This is still in the works and is thus disabled in the UI. diff --git a/contest.cpp b/contest.cpp index c2dd4f4..e1ab30f 100644 --- a/contest.cpp +++ b/contest.cpp @@ -1,9 +1,31 @@ #include "contest.h" #include +#include namespace DJ { namespace Model { +namespace { +int durationToSeconds(QString duration) { + QStringList parts = duration.split(':'); + if (parts.size() != 3) { + return 0; + } + + bool okHours = false; + bool okMinutes = false; + bool okSeconds = false; + int hours = parts[0].toInt(&okHours); + int minutes = parts[1].toInt(&okMinutes); + double seconds = parts[2].toDouble(&okSeconds); + if (!okHours || !okMinutes || !okSeconds) { + return 0; + } + + return hours * 3600 + minutes * 60 + static_cast(seconds); +} +} // namespace + Contest::Contest(QJsonObject contest, QObject *parent) : QObject(parent) { this->id = contest.value("id").toString(); this->name = contest.value("name").toString("Unknown"); @@ -13,12 +35,10 @@ Contest::Contest(QJsonObject contest, QObject *parent) : QObject(parent) { this->end_time = QDateTime::fromString(contest.value("end_time").toString(), Qt::DateFormat::ISODate); this->freeze_time = QDateTime(this->end_time); - QString freeze_duration = contest.value("scoreboard_freeze_duration").toString(); - freeze_duration = freeze_duration.size() < 12 ? "0" + freeze_duration : freeze_duration; - QTime freeze = QTime::fromString(freeze_duration, Qt::ISODateWithMs); - this->freeze_time.setTime(QTime(this->freeze_time.time().hour()-freeze.hour(), - this->freeze_time.time().minute()-freeze.minute(), - this->freeze_time.time().second()-freeze.second())); + int freezeDurationSeconds = durationToSeconds(contest.value("scoreboard_freeze_duration").toString()); + if (freezeDurationSeconds > 0) { + this->freeze_time = this->end_time.addSecs(-freezeDurationSeconds); + } } Contest::~Contest() { diff --git a/defines.h b/defines.h index 8b626ac..e41c482 100644 --- a/defines.h +++ b/defines.h @@ -58,7 +58,7 @@ #define RIGHT_MARGIN QSettings().value("rightMargin", 10).toInt() /** Helper define to compute the width of the name column. */ -#define NAME_WIDTH (QApplication::desktop()->screenGeometry().width() - LEFT_MARGIN - RIGHT_MARGIN - RANK_WIDTH - SOLVED_WIDTH - TIME_WIDTH) +#define NAME_WIDTH (QApplication::primaryScreen()->geometry().width() - LEFT_MARGIN - RIGHT_MARGIN - RANK_WIDTH - SOLVED_WIDTH - TIME_WIDTH) /** How many pixels from the right the legenda will be drawn. */ @@ -78,7 +78,7 @@ #define PROBS_BELOW_MARGIN QSettings().value("probsBelowMargin", 5).toInt() /** The time to wait */ -#define TIME_TO_WAIT QSettings().value("timeToWait", 1000).toInt() +#define TIME_TO_WAIT QSettings().value("timeToWait", 350).toInt() /** The time to scroll */ #define TIME_TO_SCROLL QSettings().value("timeToScroll", 1000).toInt() @@ -96,13 +96,16 @@ #define TIME_FOR_WINNER QSettings().value("timeForWinner", 2000).toInt() /** The time to move */ -#define TIME_TO_MOVE QSettings().value("timeToMove", 200).toInt() +#define TIME_TO_MOVE QSettings().value("timeToMove", 90).toInt() /** The initial time to move */ -#define TIME_TO_MOVE_INIT QSettings().value("timeToMoveInit", 500).toInt() +#define TIME_TO_MOVE_INIT QSettings().value("timeToMoveInit", 350).toInt() +/** The maximum time to move a team into place + */ +#define TIME_TO_MOVE_MAX QSettings().value("timeToMoveMax", 2500).toInt() /** The time to blink */ -#define TIME_TO_BLINK QSettings().value("timeToBlink", 2000).toInt() +#define TIME_TO_BLINK QSettings().value("timeToBlink", 650).toInt() /** The X offset of the branding image */ #define BRANDING_IMAGE_OFFSET_X QSettings().value("brandingImageOffsetX", 5).toInt() @@ -113,7 +116,7 @@ #include #include #include -#include +#include #include namespace DJ { @@ -153,7 +156,7 @@ struct ResultTeam { QString name; /**< Name of this team. */ QString id; /**< ID for this team. */ int rank; /**< Current rank of this team. */ - int solved; /**< How many problems this team solved. */ + int solved; /**< Score shown in the solved column. */ int time; /**< The total time for this team. */ QList problems; /**< The list of problems for this team. */ }; diff --git a/domjudgeapimanager.cpp b/domjudgeapimanager.cpp index 7f9a7c6..e0e193b 100644 --- a/domjudgeapimanager.cpp +++ b/domjudgeapimanager.cpp @@ -68,7 +68,20 @@ void DomjudgeApiManager::loadJudgings(QString cid) { DomjudgeApiManager::DomjudgeApiRequest::DomjudgeApiRequest(QString method, QList> arguments) { DomjudgeApiManager *apiManager = DomjudgeApiManager::sharedApiManager(); - QUrl url(apiManager->protocol + apiManager->url + "/api/" + method); + QString baseUrl = apiManager->url.trimmed(); + if (baseUrl.startsWith("http://")) { + baseUrl = baseUrl.mid(QString("http://").length()); + } else if (baseUrl.startsWith("https://")) { + baseUrl = baseUrl.mid(QString("https://").length()); + } + while (baseUrl.endsWith("/")) { + baseUrl.chop(1); + } + if (baseUrl.endsWith("/api")) { + baseUrl.chop(QString("/api").length()); + } + + QUrl url(apiManager->protocol + baseUrl + "/api/" + method); QUrlQuery urlQuery; foreach (auto argument, arguments) { @@ -145,6 +158,7 @@ bool DomjudgeApiManager::processReply(QNetworkReply *reply, auto possibleRedirectURL = reply->attribute(QNetworkRequest::RedirectionTargetAttribute).toUrl(); if (!possibleRedirectURL.isEmpty()) { auto newRequest = QNetworkRequest(possibleRedirectURL); + newRequest.setRawHeader("Authorization", reply->request().rawHeader("Authorization")); if (requests->contains(reply->request())) { requests->append(newRequest); requests->removeOne(reply->request()); diff --git a/gradientcache.cpp b/gradientcache.cpp index aab374c..691924f 100644 --- a/gradientcache.cpp +++ b/gradientcache.cpp @@ -4,6 +4,7 @@ #include #include #include +#include namespace DJ { namespace View { @@ -105,7 +106,7 @@ QPixmap GradientCache::getColorGradientHighlighted(QColor color1, QColor color2) QPixmap GradientCache::getMedalGradient(Medal medal) { if (!this->medalGradient.contains(medal)) { - int screenWidth = QApplication::desktop()->screenGeometry().width(); + int screenWidth = QApplication::primaryScreen()->geometry().width(); QPixmap pm(screenWidth, TEAMITEM_HEIGHT); QPainter *painter = new QPainter(&pm); QLinearGradient gradient(0, 0, screenWidth, 0); @@ -143,7 +144,7 @@ QPixmap GradientCache::getMedalGradient(Medal medal) { QPixmap GradientCache::getOddEvenHighlightedGradient(int oddEvenHighlighted) { if (!this->oddEvenHighlightGradient.contains(oddEvenHighlighted)) { - int screenWidth = QApplication::desktop()->screenGeometry().width(); + int screenWidth = QApplication::primaryScreen()->geometry().width(); QPixmap pm(screenWidth, TEAMITEM_HEIGHT); QPainter *painter = new QPainter(&pm); if (oddEvenHighlighted == 2) { // highlighted diff --git a/headergraphicsitem.cpp b/headergraphicsitem.cpp index fc2178d..b844dd5 100644 --- a/headergraphicsitem.cpp +++ b/headergraphicsitem.cpp @@ -1,10 +1,10 @@ #include "headergraphicsitem.h" -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include #include "defines.h" @@ -36,13 +36,13 @@ HeaderGraphicsItem::HeaderGraphicsItem(double screenWidth, QGraphicsItem *parent this->nameTextItem->setFont(font); this->timeTextItem = new QGraphicsSimpleTextItem("Time", this); - this->timeTextItem->setPos(QApplication::desktop()->screenGeometry().width() - RIGHT_MARGIN - fm.width("Time"), HEADER_HEIGHT - fm.height()); + this->timeTextItem->setPos(QApplication::primaryScreen()->geometry().width() - RIGHT_MARGIN - fm.horizontalAdvance("Time"), HEADER_HEIGHT - fm.height()); this->timeTextItem->setPen(QPen(Qt::white)); this->timeTextItem->setBrush(QBrush(Qt::white)); this->timeTextItem->setFont(font); - this->solvedTextItem = new QGraphicsSimpleTextItem("Solved", this); - this->solvedTextItem->setPos(QApplication::desktop()->screenGeometry().width() - RIGHT_MARGIN - TIME_WIDTH - fm.width("Solved"), HEADER_HEIGHT - fm.height()); + this->solvedTextItem = new QGraphicsSimpleTextItem("Score", this); + this->solvedTextItem->setPos(QApplication::primaryScreen()->geometry().width() - RIGHT_MARGIN - TIME_WIDTH - fm.horizontalAdvance("Score"), HEADER_HEIGHT - fm.height()); this->solvedTextItem->setPen(QPen(Qt::white)); this->solvedTextItem->setBrush(QBrush(Qt::white)); this->solvedTextItem->setFont(font); @@ -62,8 +62,8 @@ void HeaderGraphicsItem::paint(QPainter *painter, const QStyleOptionGraphicsItem this->rankTextItem->setPos(LEFT_MARGIN, HEADER_HEIGHT - fm.height()); this->nameTextItem->setPos(LEFT_MARGIN + RANK_WIDTH, HEADER_HEIGHT - fm.height()); - this->timeTextItem->setPos(QApplication::desktop()->screenGeometry().width() - RIGHT_MARGIN - fm.width("Time"), HEADER_HEIGHT - fm.height()); - this->solvedTextItem->setPos(QApplication::desktop()->screenGeometry().width() - RIGHT_MARGIN - TIME_WIDTH - fm.width("Solved"), HEADER_HEIGHT - fm.height()); + this->timeTextItem->setPos(QApplication::primaryScreen()->geometry().width() - RIGHT_MARGIN - fm.horizontalAdvance("Time"), HEADER_HEIGHT - fm.height()); + this->solvedTextItem->setPos(QApplication::primaryScreen()->geometry().width() - RIGHT_MARGIN - TIME_WIDTH - fm.horizontalAdvance("Score"), HEADER_HEIGHT - fm.height()); QLinearGradient gradient(0, 0, screenWidth, 0); gradient.setColorAt(0, QColor(0, 0, 0)); diff --git a/maincontroller.cpp b/maincontroller.cpp index fcadd16..b9be3ad 100644 --- a/maincontroller.cpp +++ b/maincontroller.cpp @@ -247,10 +247,10 @@ void MainController::processJudgingData(QJsonDocument judgingData) { Model::RankedTeam *rankedTeam = ranking.at(i); team.name = rankedTeam->getName(); team.id = rankedTeam->getId(); - team.solved = rankedTeam->getNumSolved(); + team.solved = rankedTeam->getTotalScore(); if (i > 0) { Model::RankedTeam *prevTeam = ranking.at(i - 1); - if (rankedTeam->getNumSolved() == prevTeam->getNumSolved() + if (rankedTeam->getTotalScore() == prevTeam->getTotalScore() && rankedTeam->getTotalTime() == prevTeam->getTotalTime()) { team.rank = curRank; } else { @@ -351,10 +351,10 @@ void MainController::updateStanding() { Model::RankedTeam *rankedTeam = ranking.at(i); team.name = rankedTeam->getName(); team.id = rankedTeam->getId(); - team.solved = rankedTeam->getNumSolved(); + team.solved = rankedTeam->getTotalScore(); if (i > 0) { Model::RankedTeam *prevTeam = ranking.at(i - 1); - if (rankedTeam->getNumSolved() == prevTeam->getNumSolved() + if (rankedTeam->getTotalScore() == prevTeam->getTotalScore() && rankedTeam->getTotalTime() == prevTeam->getTotalTime()) { team.rank = curRank; } else { @@ -397,10 +397,10 @@ void MainController::updateStanding() { Model::RankedTeam *rankedTeam = ranking.at(i); team.name = rankedTeam->getName(); team.id = rankedTeam->getId(); - team.solved = rankedTeam->getNumSolved(); + team.solved = rankedTeam->getTotalScore(); if (i > 0) { Model::RankedTeam *prevTeam = ranking.at(i - 1); - if (rankedTeam->getNumSolved() == prevTeam->getNumSolved() + if (rankedTeam->getTotalScore() == prevTeam->getTotalScore() && rankedTeam->getTotalTime() == prevTeam->getTotalTime()) { team.rank = curRank; } else { diff --git a/problem.cpp b/problem.cpp index 06973e9..2353b17 100644 --- a/problem.cpp +++ b/problem.cpp @@ -1,15 +1,112 @@ #include "problem.h" #include +#include +#include +#include +#include +#include +#include namespace DJ { namespace Model { +namespace { +int jsonScoreValue(QJsonValue value, int defaultScore) { + bool ok = false; + if (value.isUndefined() || value.isNull()) { + return defaultScore; + } + if (value.isDouble()) { + return value.toInt(defaultScore); + } + + int score = value.toString().toInt(&ok); + return ok ? score : defaultScore; +} + +int scoreFromName(QString name, int defaultScore) { + QRegularExpression regex("(\\d+)\\s+points?", QRegularExpression::CaseInsensitiveOption); + QRegularExpressionMatch match = regex.match(name); + if (!match.hasMatch()) { + return defaultScore; + } + + bool ok = false; + int score = match.captured(1).toInt(&ok); + return ok ? score : defaultScore; +} + +int inferredScore(QString id, QString shortname, QString name, int defaultScore) { + QStringList keys; + keys << shortname << id << name; + + foreach (QString key, keys) { + key = key.trimmed(); + if (key.endsWith("1")) { + return 2; + } + if (key.endsWith("2")) { + return 5; + } + if (key.endsWith("3")) { + return 10; + } + } + + return defaultScore; +} + +int configuredScore(QString id, QString shortname, QString name, int defaultScore) { + QVariant setting = QSettings().value("problemScores"); + QStringList entries = setting.toStringList(); + QString raw = setting.toString(); + QByteArray envScores = qgetenv("DOMJURA_PROBLEM_SCORES"); + if (!envScores.isEmpty()) { + raw.append(','); + raw.append(QString::fromUtf8(envScores)); + } + if (!raw.isEmpty()) { + raw.replace(';', ','); + entries.append(raw.split(',', Qt::SkipEmptyParts)); + } + + foreach (QString entry, entries) { + entry = entry.trimmed(); + int separator = entry.indexOf('='); + if (separator < 0) { + separator = entry.indexOf(':'); + } + if (separator < 0) { + continue; + } + + QString key = entry.left(separator).trimmed(); + QString value = entry.mid(separator + 1).trimmed(); + bool ok = false; + int score = value.toInt(&ok); + if (ok && (key == id || key == shortname || key == name)) { + return score; + } + } + + return defaultScore; +} +} // namespace + Problem::Problem(QJsonObject problem, QObject *parent) : QObject(parent) { this->id = problem.value("id").toString(); this->name = problem.value("name").toString("UNKNOWN"); this->shortname = problem.value("short_name").toString("?"); this->color = problem.value("color").toString(); this->rgb = problem.value("rgb").toString(); + + int apiScore = jsonScoreValue(problem.value("points"), -1); + apiScore = jsonScoreValue(problem.value("score"), apiScore); + int defaultScore = scoreFromName(this->name, inferredScore(this->id, this->shortname, this->name, 1)); + if (apiScore >= 0) { + defaultScore = apiScore; + } + this->score = configuredScore(this->id, this->shortname, this->name, defaultScore); } QString Problem::getId() { @@ -32,6 +129,10 @@ QString Problem::getRGB() { return this->rgb; } +int Problem::getScore() { + return this->score; +} + QString Problem::toString() { QString s; s += " id = " + this->id + "\n"; @@ -39,6 +140,7 @@ QString Problem::toString() { s += "shortname = " + this->shortname + "\n"; s += " color = " + this->color + "\n"; s += " rgb = " + this->rgb + "\n"; + s += " score = " + QString::number(this->score) + "\n"; return s; } } diff --git a/problem.h b/problem.h index b0585b7..c8c4217 100644 --- a/problem.h +++ b/problem.h @@ -39,6 +39,10 @@ class Problem : public QObject { * \return The rgb of this problem. */ QString getRGB(); + /** Returns the score awarded for solving this problem. + * \return The score awarded for solving this problem. + */ + int getScore(); /** Returns a string representing this problem. * \return A string representation of this problem. * Useful for debug printing. @@ -51,6 +55,7 @@ class Problem : public QObject { QString shortname; QString color; QString rgb; + int score; }; } } diff --git a/problemgraphicsitem.cpp b/problemgraphicsitem.cpp index 9d48b0d..02e2161 100644 --- a/problemgraphicsitem.cpp +++ b/problemgraphicsitem.cpp @@ -42,7 +42,7 @@ void ProblemGraphicsItem::paint(QPainter *painter, const QStyleOptionGraphicsIte textItem->setPen(QPen(Qt::black)); textItem->setText(this->problemId); QFontMetrics fm(textItem->font()); - int w = fm.width(textItem->text()); + int w = fm.horizontalAdvance(textItem->text()); int h = fm.height(); textItem->setPos(width - w - 2, height-h+4); @@ -54,7 +54,7 @@ void ProblemGraphicsItem::paint(QPainter *painter, const QStyleOptionGraphicsIte textItem->setPen(QPen(Qt::black)); textItem->setText(QString::number(this->numTries) + "-" + QString::number(this->time)); QFontMetrics fm(textItem->font()); - int w = fm.width(textItem->text()); + int w = fm.horizontalAdvance(textItem->text()); int h = fm.height(); textItem->setPos(width/2 - w/2, height/2-h/2); if (this->highlighted) { @@ -69,7 +69,7 @@ void ProblemGraphicsItem::paint(QPainter *painter, const QStyleOptionGraphicsIte textItem->setPen(QPen(Qt::black)); textItem->setText(QString::number(this->numTries) + "-" + QString::number(this->time)); QFontMetrics fm(textItem->font()); - int w = fm.width(textItem->text()); + int w = fm.horizontalAdvance(textItem->text()); int h = fm.height(); textItem->setPos(width/2 - w/2, height/2-h/2); if (this->highlighted) { @@ -85,7 +85,7 @@ void ProblemGraphicsItem::paint(QPainter *painter, const QStyleOptionGraphicsIte textItem->setPen(QPen(Qt::black)); textItem->setText(QString::number(this->numTries) + "-" + QString::number(this->time)); QFontMetrics fm(textItem->font()); - int w = fm.width(textItem->text()); + int w = fm.horizontalAdvance(textItem->text()); int h = fm.height(); textItem->setPos(width/2 - w/2, height/2-h/2); if (this->highlighted) { diff --git a/rankedteam.cpp b/rankedteam.cpp index 7949894..d7ff7ac 100644 --- a/rankedteam.cpp +++ b/rankedteam.cpp @@ -5,6 +5,9 @@ namespace Model { RankedTeam::RankedTeam(QString id, QString name, QObject *parent) : QObject(parent) { this->id = id; this->name = name; + this->numSolved = 0; + this->totalScore = 0; + this->totalTime = 0; } void RankedTeam::setProblem(QString id, RankedProblem *problem, Contest *contest) { @@ -23,11 +26,13 @@ void RankedTeam::setProblem(QString id, RankedProblem *problem, Contest *contest void RankedTeam::recalculateData(Contest *contest) { this->numSolved = 0; + this->totalScore = 0; this->totalTime = 0; for (int i = 0; i < this->problems.size(); i++) { RankedProblem *problem = this->problems.at(i); if (problem->problemState == SOLVED) { this->numSolved++; + this->totalScore += problem->score; this->totalTime += problem->timeFirstCorrectTry + (contest->getPenaltyMinutes() * (problem->tries - 1)); } } @@ -37,6 +42,10 @@ int RankedTeam::getNumSolved() { return this->numSolved; } +int RankedTeam::getTotalScore() { + return this->totalScore; +} + int RankedTeam::getTotalTime() { return this->totalTime; } diff --git a/rankedteam.h b/rankedteam.h index f362a4d..ba4dc25 100644 --- a/rankedteam.h +++ b/rankedteam.h @@ -23,6 +23,7 @@ struct RankedProblem { int total_tries; /**< The number of total tries for this problem. */ int timeLastTry; /**< The time of the last try for this problem. */ int timeFirstCorrectTry; /**< The time of the last (propably) correct try for this problem. */ + int score; /**< The score awarded for solving this problem. */ /** Makes a copy of a ranked problem. */ @@ -35,6 +36,7 @@ struct RankedProblem { c->total_tries = this->total_tries; c->timeLastTry = this->timeLastTry; c->timeFirstCorrectTry = this->timeFirstCorrectTry; + c->score = this->score; return c; } }; @@ -60,6 +62,10 @@ class RankedTeam : public QObject { * \return the number of solved problems. */ int getNumSolved(); + /** Returns the total score for this team. + * \return The total score for this team. + */ + int getTotalScore(); /** Returns the total time for thist eam. * \return The total time for thist eam. */ @@ -96,6 +102,7 @@ class RankedTeam : public QObject { QList problems; QHash problemsHash; int numSolved; + int totalScore; int totalTime; }; } // namespace Model diff --git a/resultswindow.cpp b/resultswindow.cpp index de66720..bf7051f 100644 --- a/resultswindow.cpp +++ b/resultswindow.cpp @@ -1,12 +1,12 @@ #include "resultswindow.h" -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include #include #include "gradientcache.h" @@ -20,18 +20,19 @@ ResultsWindow::ResultsWindow(QWidget *parent) : QGraphicsView(parent) { setFrameShape(QFrame::NoFrame); this->started = false; - this->offset = 0.0; - this->canDoNextStep = true; - this->resolvDone = false; + this->offset = 0.0; + this->canDoNextStep = true; + this->paused = false; + this->resolvDone = false; this->lastResolvTeam = -1; this->currentResolvIndex = -1; this->scene = new QGraphicsScene(this); - this->scene->setSceneRect(QApplication::desktop()->screenGeometry()); + this->scene->setSceneRect(QApplication::primaryScreen()->geometry()); this->scene->setBackgroundBrush(Qt::black); this->setScene(this->scene); - this->setGeometry(QApplication::desktop()->screenGeometry()); + this->setGeometry(QApplication::primaryScreen()->geometry()); this->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); this->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); @@ -39,20 +40,20 @@ ResultsWindow::ResultsWindow(QWidget *parent) : QGraphicsView(parent) { this->setViewportUpdateMode(FullViewportUpdate); this->setCacheMode(CacheBackground); - this->setRenderHints(QPainter::Antialiasing | QPainter::SmoothPixmapTransform | QPainter::TextAntialiasing); - if (USE_OPENGL) { - this->setViewport(new QGLWidget(QGLFormat(QGL::SampleBuffers))); - } else { - this->setViewport(new QWidget); - } - - this->headerItem = new HeaderGraphicsItem(QApplication::desktop()->screenGeometry().width()); + this->setRenderHints(QPainter::Antialiasing | QPainter::SmoothPixmapTransform | QPainter::TextAntialiasing); + if (USE_OPENGL) { + this->setViewport(new QOpenGLWidget); + } else { + this->setViewport(new QWidget); + } + + this->headerItem = new HeaderGraphicsItem(QApplication::primaryScreen()->geometry().width()); this->headerItem->setPos(0, 0); this->legendaItem = new LegendaGraphicsItem(); QRectF legendaRect = this->legendaItem->boundingRect(); - this->legendaItem->setPos(QApplication::desktop()->screenGeometry().width() - legendaRect.width() - LEGENDA_RIGHT_OFFSET, - QApplication::desktop()->screenGeometry().height() - legendaRect.height() - LEGENDA_BOTTOM_OFFSET); + this->legendaItem->setPos(QApplication::primaryScreen()->geometry().width() - legendaRect.width() - LEGENDA_RIGHT_OFFSET, + QApplication::primaryScreen()->geometry().height() - legendaRect.height() - LEGENDA_BOTTOM_OFFSET); this->legendaItem->setZValue(1); this->winnerItem = new WinnerGraphicsItem; @@ -82,7 +83,7 @@ void ResultsWindow::setTeams(QList teams, bool animated, int lastRes this->currentResolvIndex = currentTeam; this->teamItems.at(this->lastResolvTeam)->setHighlighted(true); - int screenHeight = QApplication::desktop()->screenGeometry().height(); + int screenHeight = QApplication::primaryScreen()->geometry().height(); int itemToScrollHeight = HEADER_HEIGHT + (this->lastResolvTeam + 1) * TEAMITEM_HEIGHT + RESOLV_BELOW_OFFSET; int toScroll = qMax(0, itemToScrollHeight - screenHeight); QPointF toScrollPoint(0, toScroll); @@ -184,22 +185,54 @@ void ResultsWindow::keyPressEvent(QKeyEvent *event) { case Qt::Key_X: close(); break; - case Qt::Key_Enter: - case Qt::Key_Return: - case Qt::Key_Space: - if (this->canDoNextStep) { - doNextStep(); - } - } -} - -void ResultsWindow::mousePressEvent(QMouseEvent *event) { - if (event->button() == Qt::LeftButton) { - if (this->canDoNextStep) { - doNextStep(); - } - } -} + case Qt::Key_Enter: + case Qt::Key_Return: + case Qt::Key_Space: + if (!this->paused && this->canDoNextStep) { + doNextStep(); + } + break; + case Qt::Key_P: + this->togglePaused(); + break; + } +} + +void ResultsWindow::mousePressEvent(QMouseEvent *event) { + if (event->button() == Qt::LeftButton) { + if (!this->paused && this->canDoNextStep) { + doNextStep(); + } + } +} + +void ResultsWindow::togglePaused() { + this->paused = !this->paused; + + foreach (QAbstractAnimation *animation, this->runningAnimations) { + if (this->paused) { + animation->pause(); + } else { + animation->resume(); + } + } + + if (this->paused) { + this->pausedTimerRemaining.clear(); + foreach (QTimer *timer, this->runningTimers) { + int remaining = timer->remainingTime(); + this->pausedTimerRemaining[timer] = remaining > 0 ? remaining : 1; + timer->stop(); + } + } else { + foreach (QTimer *timer, this->pausedTimerRemaining.keys()) { + if (this->runningTimers.contains(timer)) { + timer->start(this->pausedTimerRemaining[timer]); + } + } + this->pausedTimerRemaining.clear(); + } +} void ResultsWindow::stopAnimations() { foreach (QAbstractAnimation *animation, this->runningAnimations) { @@ -211,15 +244,17 @@ void ResultsWindow::stopAnimations() { timer->stop(); delete timer; } - this->runningTimers.clear(); -} - -void ResultsWindow::reload() { - if (USE_OPENGL) { - this->setViewport(new QGLWidget(QGLFormat(QGL::SampleBuffers))); - } else { - this->setViewport(new QWidget); - } + this->runningTimers.clear(); + this->pausedTimerRemaining.clear(); + this->paused = false; +} + +void ResultsWindow::reload() { + if (USE_OPENGL) { + this->setViewport(new QOpenGLWidget); + } else { + this->setViewport(new QWidget); + } QString filename = BRANDING_IMAGE; // Update branding image @@ -237,8 +272,10 @@ void ResultsWindow::reload() { this->offset = 0.0; this->started = false; - this->canDoNextStep = true; - this->currentResolvIndex = -1; + this->canDoNextStep = true; + this->paused = false; + this->pausedTimerRemaining.clear(); + this->currentResolvIndex = -1; this->lastResolvTeam = -1; this->resolvDone = false; this->headerItem->setPos(0, 0); @@ -254,8 +291,8 @@ void ResultsWindow::setResolvDone() { void ResultsWindow::hideLegendAfterTimeout() { this->legendaItem->setOpacity(1); QRectF legendaRect = this->legendaItem->boundingRect(); - this->legendaItem->setPos(QApplication::desktop()->screenGeometry().width() - legendaRect.width() - LEGENDA_RIGHT_OFFSET, - QApplication::desktop()->screenGeometry().height() - legendaRect.height() - LEGENDA_BOTTOM_OFFSET); + this->legendaItem->setPos(QApplication::primaryScreen()->geometry().width() - legendaRect.width() - LEGENDA_RIGHT_OFFSET, + QApplication::primaryScreen()->geometry().height() - legendaRect.height() - LEGENDA_BOTTOM_OFFSET); QTimer *legendaTimer = new QTimer(this); legendaTimer->setSingleShot(true); connect(legendaTimer, SIGNAL(timeout()), this, SLOT(hideLegenda())); @@ -269,7 +306,7 @@ void ResultsWindow::doNextStep() { QParallelAnimationGroup *scrollToBottomAnim = new QParallelAnimationGroup; connect(scrollToBottomAnim, SIGNAL(finished()), this, SLOT(animationDone())); - int screenHeight = QApplication::desktop()->screenGeometry().height(); + int screenHeight = QApplication::primaryScreen()->geometry().height(); int totalItemsHeight = HEADER_HEIGHT + this->teamItems.size() * TEAMITEM_HEIGHT + SCROLL_BELOW_OFFSET; int toScroll = qMax(0, totalItemsHeight - screenHeight); QPointF toScrollPoint(0, toScroll); @@ -358,7 +395,7 @@ void ResultsWindow::animationDone() { teamThatMoves->setRank(resultTeam.rank); teamThatMoves->setTime(resultTeam.time); teamThatMoves->setSolved(resultTeam.solved); - int tme = TIME_TO_MOVE_INIT + TIME_TO_MOVE * (this->lastResolvTeam - moveTo); + int tme = qMin(TIME_TO_MOVE_INIT + TIME_TO_MOVE * (this->lastResolvTeam - moveTo), TIME_TO_MOVE_MAX); if (tme == TIME_TO_MOVE_INIT) { QTimer *timer = new QTimer; timer->setSingleShot(true); @@ -494,7 +531,7 @@ void ResultsWindow::resizeImage() { } else { size = QSize(0, 0); } - QRect screenSize = QApplication::desktop()->screenGeometry(); + QRect screenSize = QApplication::primaryScreen()->geometry(); QPointF labelPos; labelPos.setX(screenSize.width() - size.width() - BRANDING_IMAGE_OFFSET_X); labelPos.setY(screenSize.height() - size.height() - BRANDING_IMAGE_OFFSET_Y); diff --git a/resultswindow.h b/resultswindow.h index 20d76b7..12297bc 100644 --- a/resultswindow.h +++ b/resultswindow.h @@ -8,10 +8,11 @@ #include #include #include -#include -#include -#include -#include +#include +#include +#include +#include +#include #include "headergraphicsitem.h" #include "teamgraphicsitem.h" @@ -87,9 +88,10 @@ private slots: void timerMoveUpDone(); private: - void hideLegendAfterTimeout(); - void doNextStep(); - void resizeImage(); + void hideLegendAfterTimeout(); + void doNextStep(); + void resizeImage(); + void togglePaused(); QGraphicsPixmapItem *pixmap; QGraphicsProxyWidget *proxyImage; @@ -100,18 +102,20 @@ private slots: WinnerGraphicsItem *winnerItem; QList teamItems; QList teams; - bool started; - bool canDoNextStep; - bool resolvDone; + bool started; + bool canDoNextStep; + bool paused; + bool resolvDone; int currentResolvIndex; QList teamsToSet; int lastResolvTeam; int lastResolvProblem; qreal offset; - QList runningAnimations; - QList runningTimers; -}; + QList runningAnimations; + QList runningTimers; + QHash pausedTimerRemaining; +}; } // namespace View } // namespace DJ diff --git a/standingscontroller.cpp b/standingscontroller.cpp index 51fd5f5..6bb69e0 100644 --- a/standingscontroller.cpp +++ b/standingscontroller.cpp @@ -1,5 +1,6 @@ #include "standingscontroller.h" +#include #include #include @@ -27,7 +28,7 @@ void StandingsController::initStandings() { this->currentRanking.clear(); // Then, add all the teams QList problems = this->problems.values(); - qSort(problems.begin(), problems.end(), problemLessThan); + std::sort(problems.begin(), problems.end(), problemLessThan); foreach (Model::Team *team, this->teams) { Model::RankedTeam *rankedTeam = new Model::RankedTeam(team->getId(), team->getName(), this); // For each team, add the problems @@ -40,6 +41,7 @@ void StandingsController::initStandings() { rankedProblem->total_tries = 0; rankedProblem->timeLastTry = 0; rankedProblem->timeFirstCorrectTry = 0; + rankedProblem->score = problem->getScore(); rankedTeam->setProblem(rankedProblem->id, rankedProblem, this->contest); } this->currentRanking.append(rankedTeam); @@ -105,7 +107,7 @@ void StandingsController::initStandings() { } } - qSort(this->currentRanking.begin(), this->currentRanking.end(), rankedTeamLessThan); + std::sort(this->currentRanking.begin(), this->currentRanking.end(), rankedTeamLessThan); this->currentPos = this->currentRanking.size() - 1; this->currentProblem = 0; } @@ -127,7 +129,7 @@ bool StandingsController::nextStanding() { this->lastResolvedProblem = this->currentProblem; // Update problem team->setProblem(problem->id, problem, this->contest); - qSort(this->currentRanking.begin(), this->currentRanking.end(), rankedTeamLessThan); + std::sort(this->currentRanking.begin(), this->currentRanking.end(), rankedTeamLessThan); this->currentProblem = 0; return true; } else { @@ -161,7 +163,7 @@ QString StandingsController::toString() { Model::RankedTeam *team = this->currentRanking.at(i); if (i > 0) { Model::RankedTeam *prevTeam = this->currentRanking.at(i - 1); - if (team->getNumSolved() == prevTeam->getNumSolved() + if (team->getTotalScore() == prevTeam->getTotalScore() && team->getTotalTime() == prevTeam->getTotalTime()) { s += QString::number(curRank) + ". " + team->getName() + " "; } else { @@ -172,7 +174,7 @@ QString StandingsController::toString() { s += "1. " + team->getName() + " "; curRank = 1; } - s += QString::number(team->getNumSolved()) + " " + QString::number(team->getTotalTime()) + "\n"; + s += QString::number(team->getTotalScore()) + " " + QString::number(team->getTotalTime()) + "\n"; for (int j = 0; j < team->getNumProblems(); j++) { Model::RankedProblem *problem = team->getProblem(j); s += problem->shortname + ": " + QString::number(problem->tries) + " - " + QString::number(problem->timeLastTry) + " "; @@ -218,14 +220,14 @@ int StandingsController::getCurrentPos() { } bool rankedTeamLessThan(Model::RankedTeam *team1, Model::RankedTeam *team2) { - if (team1->getNumSolved() == team2->getNumSolved()) { + if (team1->getTotalScore() == team2->getTotalScore()) { if (team1->getTotalTime() == team2->getTotalTime()) { return team1->getName() < team2->getName(); } else { return team1->getTotalTime() < team2->getTotalTime(); } } else { - return team1->getNumSolved() > team2->getNumSolved(); + return team1->getTotalScore() > team2->getTotalScore(); } } diff --git a/teamgraphicsitem.cpp b/teamgraphicsitem.cpp index 122b05a..c9a5d16 100644 --- a/teamgraphicsitem.cpp +++ b/teamgraphicsitem.cpp @@ -1,9 +1,9 @@ -#include "teamgraphicsitem.h" - -#include -#include -#include -#include +#include "teamgraphicsitem.h" + +#include +#include +#include +#include #include "gradientcache.h" #include "defines.h" @@ -17,7 +17,7 @@ namespace View { TeamGraphicsItem::TeamGraphicsItem(QList problemItems, QGraphicsItem *parent) : QObject(), QGraphicsItem(parent) { - this->screenWidth = QApplication::desktop()->screenGeometry().width(); + this->screenWidth = QApplication::primaryScreen()->geometry().width(); this->problemItems = problemItems; this->highlighted = false; this->setCacheMode(DeviceCoordinateCache); @@ -116,7 +116,7 @@ void TeamGraphicsItem::setName(QString name) { void TeamGraphicsItem::setSolved(int solved) { QString txt = QString::number(solved); QFontMetrics fm(this->solvedItem->font()); - int fw = fm.width(txt); + int fw = fm.horizontalAdvance(txt); if (solved < 0) { this->solvedItem->setText(""); } else { @@ -133,7 +133,7 @@ void TeamGraphicsItem::setMedal(Medal medal) { void TeamGraphicsItem::setTime(int time) { QString txt = QString::number(time); QFontMetrics fm(this->timeItem->font()); - int fw = fm.width(txt); + int fw = fm.horizontalAdvance(txt); if (time < 0) { this->timeItem->setText(""); } else { diff --git a/winnergraphicsitem.cpp b/winnergraphicsitem.cpp index 0721998..426395b 100644 --- a/winnergraphicsitem.cpp +++ b/winnergraphicsitem.cpp @@ -1,7 +1,7 @@ #include "winnergraphicsitem.h" #include -#include +#include #include #include #include @@ -18,7 +18,7 @@ WinnerGraphicsItem::WinnerGraphicsItem(QGraphicsItem *parent) : } QRectF WinnerGraphicsItem::boundingRect() const { - QRect screenSize = QApplication::desktop()->screenGeometry(); + QRect screenSize = QApplication::primaryScreen()->geometry(); return QRectF(0, 0, screenSize.width(), screenSize.height()); } @@ -67,7 +67,7 @@ void WinnerGraphicsItem::reAddItems() { proposedLine += " "; } proposedLine += word; - int newWidth = fm.width(proposedLine); + int newWidth = fm.horizontalAdvance(proposedLine); if (newWidth <= myWidth) { currentLine = proposedLine; } else { @@ -102,7 +102,7 @@ void WinnerGraphicsItem::reAddItems() { int offsetFromCenter = -((total * fm.height()) / 2) + (i * fm.height()); - textItem->setPos(centerWidth - fm.width(textItem->text()) / 2, centerHeight + offsetFromCenter); + textItem->setPos(centerWidth - fm.horizontalAdvance(textItem->text()) / 2, centerHeight + offsetFromCenter); this->textItems.append(textItem); ++i;