diff --git a/cockatrice/CMakeLists.txt b/cockatrice/CMakeLists.txt
index d6f46fc9..405889af 100644
--- a/cockatrice/CMakeLists.txt
+++ b/cockatrice/CMakeLists.txt
@@ -58,6 +58,7 @@ SET(cockatrice_SOURCES
src/tab_supervisor.cpp
src/tab_admin.cpp
src/tab_userlists.cpp
+ src/replay_timeline_widget.cpp
src/chatview.cpp
src/userlist.cpp
src/userinfobox.cpp
@@ -128,6 +129,7 @@ SET(cockatrice_HEADERS
src/tab_supervisor.h
src/tab_admin.h
src/tab_userlists.h
+ src/replay_timeline_widget.h
src/chatview.h
src/userlist.h
src/userinfobox.h
diff --git a/cockatrice/cockatrice.qrc b/cockatrice/cockatrice.qrc
index dd7416de..c6bb75eb 100644
--- a/cockatrice/cockatrice.qrc
+++ b/cockatrice/cockatrice.qrc
@@ -38,6 +38,14 @@
resources/icon_conceded.svg
resources/icon_player.svg
resources/icon_spectator.svg
+
+ resources/replay_start.svg
+ resources/replay_stop.svg
+ resources/replay_fastforward.svg
+ resources/replay_rewind.svg
+ resources/replay_toend.svg
+ resources/replay_tostart.svg
+ resources/replay_pause.svg
resources/genders/male.svg
resources/genders/female.svg
diff --git a/cockatrice/resources/replay_fastforward.svg b/cockatrice/resources/replay_fastforward.svg
new file mode 100644
index 00000000..f46a38c4
--- /dev/null
+++ b/cockatrice/resources/replay_fastforward.svg
@@ -0,0 +1,180 @@
+
+
+
+
diff --git a/cockatrice/resources/replay_pause.svg b/cockatrice/resources/replay_pause.svg
new file mode 100644
index 00000000..89bf0b7e
--- /dev/null
+++ b/cockatrice/resources/replay_pause.svg
@@ -0,0 +1,187 @@
+
+
+
+
diff --git a/cockatrice/resources/replay_rewind.svg b/cockatrice/resources/replay_rewind.svg
new file mode 100644
index 00000000..9700cc0a
--- /dev/null
+++ b/cockatrice/resources/replay_rewind.svg
@@ -0,0 +1,178 @@
+
+
+
+
diff --git a/cockatrice/resources/replay_start.svg b/cockatrice/resources/replay_start.svg
new file mode 100644
index 00000000..30af90d0
--- /dev/null
+++ b/cockatrice/resources/replay_start.svg
@@ -0,0 +1,130 @@
+
+
+
+
diff --git a/cockatrice/resources/replay_stop.svg b/cockatrice/resources/replay_stop.svg
new file mode 100644
index 00000000..17da8995
--- /dev/null
+++ b/cockatrice/resources/replay_stop.svg
@@ -0,0 +1,127 @@
+
+
+
+
diff --git a/cockatrice/resources/replay_toend.svg b/cockatrice/resources/replay_toend.svg
new file mode 100644
index 00000000..3f6b82b6
--- /dev/null
+++ b/cockatrice/resources/replay_toend.svg
@@ -0,0 +1,177 @@
+
+
+
+
diff --git a/cockatrice/resources/replay_tostart.svg b/cockatrice/resources/replay_tostart.svg
new file mode 100644
index 00000000..9b8b0936
--- /dev/null
+++ b/cockatrice/resources/replay_tostart.svg
@@ -0,0 +1,184 @@
+
+
+
+
diff --git a/cockatrice/src/replay_timeline_widget.cpp b/cockatrice/src/replay_timeline_widget.cpp
new file mode 100644
index 00000000..552b3a35
--- /dev/null
+++ b/cockatrice/src/replay_timeline_widget.cpp
@@ -0,0 +1,96 @@
+#include "replay_timeline_widget.h"
+#include
+#include
+#include
+#include
+
+ReplayTimelineWidget::ReplayTimelineWidget(QWidget *parent)
+ : QWidget(parent), maxBinValue(1), maxTime(1), timeScaleFactor(1.0), currentTime(0), currentEvent(0)
+{
+ replayTimer = new QTimer(this);
+ connect(replayTimer, SIGNAL(timeout()), this, SLOT(replayTimerTimeout()));
+}
+
+const int ReplayTimelineWidget::binLength = 5000;
+
+void ReplayTimelineWidget::setTimeline(const QList &_replayTimeline)
+{
+ replayTimeline = _replayTimeline;
+ histogram.clear();
+ int binEndTime = binLength - 1;
+ int binValue = 0;
+ for (int i = 0; i < replayTimeline.size(); ++i) {
+ if (replayTimeline[i] > binEndTime) {
+ histogram.append(binValue);
+ if (binValue > maxBinValue)
+ maxBinValue = binValue;
+ while (replayTimeline[i] > binEndTime + binLength) {
+ histogram.append(0);
+ binEndTime += binLength;
+ }
+ binValue = 1;
+ binEndTime += binLength;
+ } else
+ ++binValue;
+ }
+ histogram.append(binValue);
+ if (!replayTimeline.isEmpty())
+ maxTime = replayTimeline.last();
+
+ update();
+}
+
+void ReplayTimelineWidget::paintEvent(QPaintEvent *event)
+{
+ QPainter painter(this);
+ painter.drawRect(0, 0, width() - 1, height() - 1);
+
+ qreal binWidth = (qreal) width() / histogram.size();
+ QPainterPath path;
+ path.moveTo(0, height() - 1);
+ for (int i = 0; i < histogram.size(); ++i)
+ path.lineTo(round(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);
+ painter.fillRect(0, 0, (width() - 1) * currentTime / maxTime, height() - 1, barColor);
+}
+
+QSize ReplayTimelineWidget::sizeHint() const
+{
+ return QSize(-1, 50);
+}
+
+void ReplayTimelineWidget::replayTimerTimeout()
+{
+ currentTime += 200;
+ while ((currentEvent < replayTimeline.size()) && (replayTimeline[currentEvent] < currentTime)) {
+ emit processNextEvent();
+ ++currentEvent;
+ }
+ if (currentEvent == replayTimeline.size()) {
+ emit replayFinished();
+ replayTimer->stop();
+ }
+
+ if (!(currentTime % 1000))
+ update();
+}
+
+void ReplayTimelineWidget::setTimeScaleFactor(qreal _timeScaleFactor)
+{
+ timeScaleFactor = _timeScaleFactor;
+ replayTimer->setInterval(200 / timeScaleFactor);
+}
+
+void ReplayTimelineWidget::startReplay()
+{
+ replayTimer->start(200 / timeScaleFactor);
+}
+
+void ReplayTimelineWidget::stopReplay()
+{
+ replayTimer->stop();
+}
diff --git a/cockatrice/src/replay_timeline_widget.h b/cockatrice/src/replay_timeline_widget.h
new file mode 100644
index 00000000..d5fd0ce4
--- /dev/null
+++ b/cockatrice/src/replay_timeline_widget.h
@@ -0,0 +1,39 @@
+#ifndef REPLAY_TIMELINE_WIDGET
+#define REPLAY_TIMELINE_WIDGET
+
+#include
+#include
+
+class QPaintEvent;
+class QTimer;
+
+class ReplayTimelineWidget : public QWidget {
+ Q_OBJECT
+signals:
+ void processNextEvent();
+ void replayFinished();
+private:
+ QTimer *replayTimer;
+ QList replayTimeline;
+ QList histogram;
+ static const int binLength;
+ int maxBinValue, maxTime;
+ qreal timeScaleFactor;
+ int currentTime;
+ int currentEvent;
+private slots:
+ void replayTimerTimeout();
+public:
+ ReplayTimelineWidget(QWidget *parent = 0);
+ void setTimeline(const QList &_replayTimeline);
+ QSize sizeHint() const;
+ void setTimeScaleFactor(qreal _timeScaleFactor);
+ int getCurrentEvent() const { return currentEvent; }
+public slots:
+ void startReplay();
+ void stopReplay();
+protected:
+ void paintEvent(QPaintEvent *event);
+};
+
+#endif
diff --git a/cockatrice/src/tab_game.cpp b/cockatrice/src/tab_game.cpp
index a4c0bf4b..254f7d5c 100644
--- a/cockatrice/src/tab_game.cpp
+++ b/cockatrice/src/tab_game.cpp
@@ -6,6 +6,8 @@
#include
#include
#include
+#include
+
#include "tab_game.h"
#include "tab_supervisor.h"
#include "cardinfowidget.h"
@@ -26,6 +28,7 @@
#include "main.h"
#include "settingscache.h"
#include "carddatabase.h"
+#include "replay_timeline_widget.h"
#include
#include "pending_command.h"
@@ -195,7 +198,7 @@ void DeckViewContainer::setDeck(DeckList *deck)
deckView->setDeck(deck);
readyStartButton->setEnabled(true);
}
-
+#include
TabGame::TabGame(GameReplay *_replay)
: Tab(0),
hostId(-1),
@@ -213,6 +216,24 @@ TabGame::TabGame(GameReplay *_replay)
gameId = replay->game_info().game_id();
gameDescription = QString::fromStdString(replay->game_info().description());
+ // Create list: event number -> time [ms]
+ // Distribute simultaneous events evenly across 1 second.
+ int lastEventTimestamp = -1;
+ const int eventCount = replay->event_list_size();
+ for (int i = 0; i < eventCount; ++i) {
+ int j = i + 1;
+ while ((j < eventCount) && (replay->event_list(j).seconds_elapsed() == lastEventTimestamp))
+ ++j;
+
+ const int numberEventsThisSecond = j - i;
+ for (int k = 0; k < numberEventsThisSecond; ++k)
+ replayTimeline.append(replay->event_list(i + k).seconds_elapsed() * 1000 + (int) ((qreal) k / (qreal) numberEventsThisSecond * 1000));
+
+ if (j < eventCount)
+ lastEventTimestamp = replay->event_list(j).seconds_elapsed();
+ i += numberEventsThisSecond - 1;
+ }
+
phasesToolbar = new PhasesToolbar;
phasesToolbar->hide();
@@ -241,6 +262,40 @@ TabGame::TabGame(GameReplay *_replay)
QWidget *messageLogLayoutWidget = new QWidget;
messageLogLayoutWidget->setLayout(messageLogLayout);
+ timelineWidget = new ReplayTimelineWidget;
+ timelineWidget->setTimeline(replayTimeline);
+ connect(timelineWidget, SIGNAL(processNextEvent()), this, SLOT(replayNextEvent()));
+ connect(timelineWidget, SIGNAL(replayFinished()), this, SLOT(replayFinished()));
+
+ replayToStartButton = new QToolButton;
+ replayToStartButton->setIconSize(QSize(32, 32));
+ replayToStartButton->setIcon(QIcon(":/resources/replay_tostart.svg"));
+ connect(replayToStartButton, SIGNAL(clicked()), this, SLOT(replayToStartButtonClicked()));
+ replayStartButton = new QToolButton;
+ replayStartButton->setIconSize(QSize(32, 32));
+ replayStartButton->setIcon(QIcon(":/resources/replay_start.svg"));
+ connect(replayStartButton, SIGNAL(clicked()), this, SLOT(replayStartButtonClicked()));
+ replayPauseButton = new QToolButton;
+ replayPauseButton->setIconSize(QSize(32, 32));
+ replayPauseButton->setEnabled(false);
+ replayPauseButton->setIcon(QIcon(":/resources/replay_pause.svg"));
+ connect(replayPauseButton, SIGNAL(clicked()), this, SLOT(replayPauseButtonClicked()));
+ replayStopButton = new QToolButton;
+ replayStopButton->setIconSize(QSize(32, 32));
+ replayStopButton->setEnabled(false);
+ replayStopButton->setIcon(QIcon(":/resources/replay_stop.svg"));
+ connect(replayStopButton, SIGNAL(clicked()), this, SLOT(replayStopButtonClicked()));
+ replayFastForwardButton = new QToolButton;
+ replayFastForwardButton->setIconSize(QSize(32, 32));
+ replayFastForwardButton->setEnabled(false);
+ replayFastForwardButton->setIcon(QIcon(":/resources/replay_fastforward.svg"));
+ replayFastForwardButton->setCheckable(true);
+ connect(replayFastForwardButton, SIGNAL(toggled(bool)), this, SLOT(replayFastForwardButtonToggled(bool)));
+ replayToEndButton = new QToolButton;
+ replayToEndButton->setIconSize(QSize(32, 32));
+ replayToEndButton->setIcon(QIcon(":/resources/replay_toend.svg"));
+ connect(replayStopButton, SIGNAL(clicked()), this, SLOT(replayToEndButtonClicked()));
+
splitter = new QSplitter(Qt::Vertical);
splitter->addWidget(cardInfo);
splitter->addWidget(playerListWidget);
@@ -251,6 +306,19 @@ TabGame::TabGame(GameReplay *_replay)
mainLayout->addLayout(deckViewContainerLayout, 10);
mainLayout->addWidget(splitter);
+ QHBoxLayout *replayControlLayout = new QHBoxLayout;
+ replayControlLayout->addWidget(timelineWidget, 10);
+ replayControlLayout->addWidget(replayToStartButton);
+ replayControlLayout->addWidget(replayStartButton);
+ replayControlLayout->addWidget(replayPauseButton);
+ replayControlLayout->addWidget(replayStopButton);
+ replayControlLayout->addWidget(replayFastForwardButton);
+ replayControlLayout->addWidget(replayToEndButton);
+
+ QVBoxLayout *superMainLayout = new QVBoxLayout;
+ superMainLayout->addLayout(mainLayout);
+ superMainLayout->addLayout(replayControlLayout);
+
aNextPhase = 0;
aNextTurn = 0;
aRemoveLocalArrows = 0;
@@ -263,16 +331,11 @@ TabGame::TabGame(GameReplay *_replay)
tabMenu->addAction(aLeaveGame);
retranslateUi();
- setLayout(mainLayout);
+ setLayout(superMainLayout);
splitter->restoreState(settingsCache->getTabGameSplitterSizes());
messageLog->logReplayStarted(gameId);
-
- gameTimer = new QTimer(this);
- gameTimer->setInterval(1000);
- connect(gameTimer, SIGNAL(timeout()), this, SLOT(nextReplayStep()));
- gameTimer->start();
}
TabGame::TabGame(TabSupervisor *_tabSupervisor, QList &_clients, const Event_GameJoined &event)
@@ -461,15 +524,62 @@ void TabGame::closeRequest()
actLeaveGame();
}
-void TabGame::nextReplayStep()
+void TabGame::replayNextEvent()
{
- if (replay->event_list_size() <= currentReplayStep) {
- QMessageBox::information(this, "", "done");
- gameTimer->stop();
- return;
- }
- processGameEventContainer(replay->event_list(currentReplayStep), 0);
- ++currentReplayStep;
+ processGameEventContainer(replay->event_list(timelineWidget->getCurrentEvent()), 0);
+}
+
+void TabGame::replayFinished()
+{
+ replayStartButton->setEnabled(true);
+ replayPauseButton->setEnabled(false);
+ replayStopButton->setEnabled(false);
+ replayFastForwardButton->setEnabled(false);
+}
+
+void TabGame::replayToStartButtonClicked()
+{
+ // XXX
+}
+
+void TabGame::replayStartButtonClicked()
+{
+ replayStartButton->setEnabled(false);
+ replayPauseButton->setEnabled(true);
+ replayStopButton->setEnabled(true);
+ replayFastForwardButton->setEnabled(true);
+
+ timelineWidget->startReplay();
+}
+
+void TabGame::replayPauseButtonClicked()
+{
+ replayStartButton->setEnabled(true);
+ replayPauseButton->setEnabled(false);
+ replayFastForwardButton->setEnabled(false);
+
+ timelineWidget->stopReplay();
+}
+
+void TabGame::replayStopButtonClicked()
+{
+ replayStartButton->setEnabled(true);
+ replayPauseButton->setEnabled(false);
+ replayStopButton->setEnabled(false);
+ replayFastForwardButton->setEnabled(false);
+
+ timelineWidget->stopReplay();
+ // XXX to start
+}
+
+void TabGame::replayFastForwardButtonToggled(bool checked)
+{
+ timelineWidget->setTimeScaleFactor(checked ? 10.0 : 1.0);
+}
+
+void TabGame::replayToEndButtonClicked()
+{
+ // XXX
}
void TabGame::incrementGameTime()
diff --git a/cockatrice/src/tab_game.h b/cockatrice/src/tab_game.h
index 8678a316..8c40a6fe 100644
--- a/cockatrice/src/tab_game.h
+++ b/cockatrice/src/tab_game.h
@@ -18,11 +18,13 @@ class QSplitter;
class QLabel;
class QLineEdit;
class QPushButton;
+class QToolButton;
class QMenu;
class ZoneViewLayout;
class ZoneViewWidget;
class PhasesToolbar;
class PlayerListWidget;
+class ReplayTimelineWidget;
class Response;
class GameEventContainer;
class GameEventContext;
@@ -108,8 +110,13 @@ private:
QStringList phasesList;
int currentPhase;
int activePlayer;
+
+ // Replay related members
GameReplay *replay;
int currentReplayStep;
+ QList replayTimeline;
+ ReplayTimelineWidget *timelineWidget;
+ QToolButton *replayToStartButton, *replayStartButton, *replayPauseButton, *replayStopButton, *replayFastForwardButton, *replayToEndButton;
QSplitter *splitter;
CardInfoWidget *cardInfo;
@@ -158,7 +165,15 @@ signals:
void containerProcessingDone();
void openMessageDialog(const QString &userName, bool focus);
private slots:
- void nextReplayStep();
+ void replayNextEvent();
+ void replayFinished();
+ void replayToStartButtonClicked();
+ void replayStartButtonClicked();
+ void replayPauseButtonClicked();
+ void replayStopButtonClicked();
+ void replayFastForwardButtonToggled(bool checked);
+ void replayToEndButtonClicked();
+
void incrementGameTime();
void adminLockChanged(bool lock);
void newCardAdded(AbstractCardItem *card);