From 34888d4d8bdbc8c73052554f39e24fe7b34c9ed9 Mon Sep 17 00:00:00 2001 From: "Steinar H. Gunderson" Date: Sun, 30 Apr 2023 23:53:58 +0200 Subject: [PATCH] Begin showing events from the database. (No modification yet.) --- mainwindow.h | 9 ++- mainwindow.ui | 2 +- meson.build | 4 +- stats.cpp | 182 +++++++++++++++++++++++++++++++++++++++++++++++++- 4 files changed, 191 insertions(+), 6 deletions(-) diff --git a/mainwindow.h b/mainwindow.h index 052e15d..5f2188c 100644 --- a/mainwindow.h +++ b/mainwindow.h @@ -3,6 +3,9 @@ #include #include #include +#include "ui_mainwindow.h" + +class QAbstractItemModel; class MainWindow : public QMainWindow { @@ -10,12 +13,14 @@ class MainWindow : public QMainWindow public: MainWindow(); - - QMediaPlayer *player; + void setModel(QAbstractItemModel *model); private: void seek(int64_t delta_ms); + + Ui::MainWindow *ui; bool seeking = false; bool playing = true; std::optional buffered_seek; + QMediaPlayer *player; }; diff --git a/mainwindow.ui b/mainwindow.ui index a64b1e3..c95640f 100644 --- a/mainwindow.ui +++ b/mainwindow.ui @@ -35,7 +35,7 @@ - + diff --git a/meson.build b/meson.build index 4df5b37..dd159e1 100644 --- a/meson.build +++ b/meson.build @@ -2,9 +2,11 @@ project('stats', 'cpp', default_options: ['buildtype=debugoptimized'], version: qt6 = import('qt6') qt6deps = dependency('qt6', modules: ['Core', 'Gui', 'Widgets', 'Multimedia', 'MultimediaWidgets']) +sqlite3dep = dependency('sqlite3') + qt_files = qt6.preprocess( moc_headers: ['mainwindow.h'], ui_files: ['mainwindow.ui'], dependencies: qt6deps) -executable('stats', 'stats.cpp', qt_files, dependencies: [qt6deps]) +executable('stats', 'stats.cpp', qt_files, dependencies: [qt6deps, sqlite3dep]) diff --git a/stats.cpp b/stats.cpp index 1edee11..a740c1d 100644 --- a/stats.cpp +++ b/stats.cpp @@ -5,10 +5,15 @@ #include #include #include +#include +#include +#include #include #include "mainwindow.h" #include "ui_mainwindow.h" +using namespace std; + std::string format_timestamp(uint64_t pos) { int ms = pos % 1000; @@ -23,6 +28,150 @@ std::string format_timestamp(uint64_t pos) return buf; } +class EventsModel : public QAbstractTableModel +{ +public: + EventsModel(sqlite3 *db) : db(db) {} + + int rowCount(const QModelIndex &parent) const override + { + refresh_if_needed(); + return events.size(); + } + int columnCount(const QModelIndex &column) const override + { + return 3; + } + QVariant headerData(int section, Qt::Orientation orientation, int role) const override + { + if (role != Qt::DisplayRole) { + return QVariant(); + } + if (orientation == Qt::Horizontal) { + if (section == 0) { + return "Time"; + } else if (section == 1) { + return "Player"; + } else { + return "Type"; + } + } else { + return ""; + } + } + + QVariant data(const QModelIndex &index, int role) const override + { + if (role != Qt::DisplayRole) { + return QVariant(); + } + refresh_if_needed(); + if (index.column() == 0) { + return QString::fromUtf8(format_timestamp(events[index.row()].t)); + } else if (index.column() == 1) { + optional player_id = events[index.row()].player_id; + if (player_id) { + const Player &p = players[*player_id]; + return QString::fromUtf8(p.name + " (" + p.number + ")"); + } else { + return QVariant(); + } + } else if (index.column() == 2) { + return QString::fromUtf8(events[index.row()].type); + } + return QVariant(); + } + +private: + struct Player { + int player_id; + string number; + string name; + }; + mutable map players; + + struct Event { + uint64_t t; + optional player_id; + string type; + }; + mutable vector events; + mutable bool stale = true; + + sqlite3 *db; + + void refresh_if_needed() const; +}; + +void EventsModel::refresh_if_needed() const +{ + if (!stale) { + return; + } + + players.clear(); + events.clear(); + stale = false; + + // Read the players. + sqlite3_stmt *stmt; + int ret = sqlite3_prepare_v2(db, "SELECT player, number, name FROM player", -1, &stmt, 0); + if (ret != SQLITE_OK) { + fprintf(stderr, "SELECT prepare: %s\n", sqlite3_errmsg(db)); + abort(); + } + for ( ;; ) { + ret = sqlite3_step(stmt); + if (ret == SQLITE_ROW) { + Player p; + p.player_id = sqlite3_column_int(stmt, 0); + p.number = (const char *)sqlite3_column_text(stmt, 1); + p.name = (const char *) sqlite3_column_text(stmt, 2); + players[p.player_id] = move(p); + } else if (ret == SQLITE_DONE) { + break; + } else { + fprintf(stderr, "SELECT step: %s\n", sqlite3_errmsg(db)); + abort(); + } + } + ret = sqlite3_finalize(stmt); + if (ret != SQLITE_OK) { + fprintf(stderr, "SELECT finalize: %s\n", sqlite3_errmsg(db)); + abort(); + } + + // Read the events. + ret = sqlite3_prepare_v2(db, "SELECT t, player, type FROM event", -1, &stmt, 0); + if (ret != SQLITE_OK) { + fprintf(stderr, "SELECT prepare: %s\n", sqlite3_errmsg(db)); + abort(); + } + for ( ;; ) { + ret = sqlite3_step(stmt); + if (ret == SQLITE_ROW) { + Event e; + e.t = sqlite3_column_int(stmt, 0); + e.player_id = sqlite3_column_int(stmt, 1); + e.type = (const char *)sqlite3_column_text(stmt, 2); + events.push_back(move(e)); + } else if (ret == SQLITE_DONE) { + break; + } else { + fprintf(stderr, "SELECT step: %s\n", sqlite3_errmsg(db)); + abort(); + } + } + ret = sqlite3_finalize(stmt); + if (ret != SQLITE_OK) { + fprintf(stderr, "SELECT finalize: %s\n", sqlite3_errmsg(db)); + abort(); + } + + // TODO what if data changes externally? + //emit dataChanged(QModelIndex( +} + MainWindow::MainWindow() { player = new QMediaPlayer; @@ -30,10 +179,10 @@ MainWindow::MainWindow() player->setSource(QUrl::fromLocalFile("/home/sesse/dev/stats/ultimate-prores.mkv")); player->play(); - Ui::MainWindow *ui = new Ui::MainWindow; + ui = new Ui::MainWindow; ui->setupUi(this); - connect(player, &QMediaPlayer::positionChanged, [ui, this](uint64_t pos) { + connect(player, &QMediaPlayer::positionChanged, [this](uint64_t pos) { ui->timestamp->setText(QString::fromUtf8(format_timestamp(pos))); if (buffered_seek) { player->setPosition(*buffered_seek); @@ -77,6 +226,11 @@ MainWindow::MainWindow() }); } +void MainWindow::setModel(QAbstractItemModel *model) +{ + ui->event_view->setModel(model); +} + void MainWindow::seek(int64_t delta_ms) { int64_t current_pos = buffered_seek ? *buffered_seek : player->position(); @@ -88,11 +242,35 @@ void MainWindow::seek(int64_t delta_ms) } } +sqlite3 *open_db(const char *filename) +{ + sqlite3 *db; + int ret = sqlite3_open(filename, &db); + if (ret != SQLITE_OK) { + fprintf(stderr, "%s: %s\n", filename, sqlite3_errmsg(db)); + exit(1); + } + + sqlite3_exec(db, R"( + CREATE TABLE IF NOT EXISTS player (player INTEGER PRIMARY KEY, number VARCHAR, name VARCHAR); + )", nullptr, nullptr, nullptr); // Ignore errors. + + sqlite3_exec(db, R"( + CREATE TABLE IF NOT EXISTS event (t INTEGER, player INTEGER, type VARCHAR, FOREIGN KEY (player) REFERENCES player(player)); + )", nullptr, nullptr, nullptr); // Ignore errors. + + sqlite3_exec(db, "PRAGMA journal_mode=WAL", nullptr, nullptr, nullptr); // Ignore errors. + sqlite3_exec(db, "PRAGMA synchronous=NORMAL", nullptr, nullptr, nullptr); // Ignore errors. + return db; +} + int main(int argc, char *argv[]) { QApplication app(argc, argv); + sqlite3 *db = open_db("ultimate.db"); MainWindow mainWindow; + mainWindow.setModel(new EventsModel(db)); mainWindow.resize(QSize(1280, 720)); mainWindow.show(); -- 2.39.2