Adding will happen in a future step. Deleting, even further out.
--- /dev/null
+#include "edit_player_dialog.h"
+
+EditPlayerDialog::EditPlayerDialog(const std::string &number, const std::string &gender, const std::string &name)
+{
+ ui = new Ui::EditPlayerDialog;
+ ui->setupUi(this);
+
+ ui->number->setText(QString::fromUtf8(number));
+ ui->gender->setText(QString::fromUtf8(gender));
+ ui->name->setText(QString::fromUtf8(name));
+
+ setModal(true);
+}
--- /dev/null
+#include <QDialog>
+#include <string>
+#include "ui_edit_player_dialog.h"
+
+class EditPlayerDialog : public QDialog
+{
+ Q_OBJECT
+
+public:
+ EditPlayerDialog(const std::string &number, const std::string &gender, const std::string &name);
+ std::string number() const { return ui->number->text().toStdString(); }
+ std::string gender() const { return ui->gender->text().toStdString(); }
+ std::string name() const { return ui->name->text().toStdString(); }
+
+private:
+ Ui::EditPlayerDialog *ui;
+};
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>EditPlayerDialog</class>
+ <widget class="QDialog" name="EditPlayerDialog">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>400</width>
+ <height>191</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Edit player</string>
+ </property>
+ <property name="modal">
+ <bool>true</bool>
+ </property>
+ <layout class="QGridLayout" name="gridLayout_2">
+ <item row="0" column="0">
+ <layout class="QGridLayout" name="gridLayout">
+ <item row="0" column="1">
+ <widget class="QLineEdit" name="number">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>60</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="maxLength">
+ <number>3</number>
+ </property>
+ </widget>
+ </item>
+ <item row="3" column="1">
+ <widget class="QDialogButtonBox" name="buttonBox">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="standardButtons">
+ <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="1">
+ <widget class="QLineEdit" name="name"/>
+ </item>
+ <item row="1" column="0">
+ <widget class="QLabel" name="label_3">
+ <property name="text">
+ <string>Gender (optional):</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="0">
+ <widget class="QLabel" name="label_2">
+ <property name="text">
+ <string>Number (optional):</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <widget class="QLineEdit" name="gender">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Fixed">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>25</width>
+ <height>16777215</height>
+ </size>
+ </property>
+ <property name="inputMask">
+ <string/>
+ </property>
+ <property name="maxLength">
+ <number>1</number>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="0">
+ <widget class="QLabel" name="label">
+ <property name="text">
+ <string>Name:</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ <tabstops>
+ <tabstop>number</tabstop>
+ <tabstop>gender</tabstop>
+ <tabstop>name</tabstop>
+ </tabstops>
+ <resources/>
+ <connections>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>accepted()</signal>
+ <receiver>EditPlayerDialog</receiver>
+ <slot>accept()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>248</x>
+ <y>254</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>157</x>
+ <y>274</y>
+ </hint>
+ </hints>
+ </connection>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>rejected()</signal>
+ <receiver>EditPlayerDialog</receiver>
+ <slot>reject()</slot>
+ <hints>
+ <hint type="sourcelabel">
+ <x>316</x>
+ <y>260</y>
+ </hint>
+ <hint type="destinationlabel">
+ <x>286</x>
+ <y>274</y>
+ </hint>
+ </hints>
+ </connection>
+ </connections>
+</ui>
#include <optional>
#include <sqlite3.h>
#include "mainwindow.h"
+#include "edit_player_dialog.h"
#include "ui_mainwindow.h"
#include "events.h"
#include "players.h"
connect(ui->clear_player_list, &QPushButton::clicked, [this] {
ui->player_view->selectionModel()->clear();
});
+ connect(ui->player_view, &QTableView::doubleClicked, [this](const QModelIndex &index) {
+ open_edit_player_dialog(index.row());
+ });
connect(ui->player_view->selectionModel(), &QItemSelectionModel::selectionChanged, [this] {
update_gender_ratio(ui->video->get_position());
});
update_ui_from_time(ui->video->get_position());
}
+void MainWindow::open_edit_player_dialog(unsigned row)
+{
+ int player_id = players->get_player_id(row);
+ string number = players->get_player_number(row);
+ string gender = players->get_player_gender(row);
+ string name = players->get_player_name(row);
+
+ EditPlayerDialog *dialog = new EditPlayerDialog(number, gender, name);
+ connect(dialog, &QDialog::finished, [this, dialog, player_id](int result) {
+ if (result == QDialog::Accepted) {
+ players->edit_player(player_id, dialog->number(), dialog->gender(), dialog->name());
+ update_ui_from_time(ui->video->get_position());
+ }
+ });
+ dialog->show();
+}
void delete_current_event();
void make_substitution();
void formation_double_clicked(bool offense, unsigned row);
+ void open_edit_player_dialog(unsigned row);
void update_ui_from_time(uint64_t t);
void update_status(uint64_t t);
libswscaledep = dependency('libswscale')
qt_files = qt6.preprocess(
- moc_headers: ['mainwindow.h', 'clickable_label.h', 'video_widget.h'],
- ui_files: ['mainwindow.ui'],
+ moc_headers: ['mainwindow.h', 'edit_player_dialog.h', 'clickable_label.h', 'video_widget.h'],
+ ui_files: ['mainwindow.ui', 'edit_player_dialog.ui'],
dependencies: qt6deps)
executable('pkanalytics',
- ['main.cpp', 'mainwindow.cpp', 'events.cpp', 'players.cpp', 'formations.cpp', 'json.cpp', 'video_widget.cpp', 'ffmpeg_raii.cpp'],
+ ['main.cpp', 'mainwindow.cpp', 'events.cpp', 'players.cpp', 'formations.cpp', 'json.cpp', 'video_widget.cpp', 'ffmpeg_raii.cpp', 'edit_player_dialog.cpp'],
qt_files,
dependencies: [qt6deps, sqlite3dep, libavcodecdep, libavformatdep, libavutildep, libswscaledep])
PlayersModel::PlayersModel(sqlite3 *db) : db(db)
{
- load_data();
+ players = load_data();
}
QVariant PlayersModel::headerData(int section, Qt::Orientation orientation, int role) const
return it->gender;
}
-void PlayersModel::load_data()
+void PlayersModel::edit_player(int player_id, const string &number, const string &gender, const string &name)
{
- players.clear();
+ auto it = find_if(players.begin(), players.end(), [player_id](const Player &p) { return p.player_id == int(player_id); });
+ int old_row = distance(players.begin(), it);
+
+ sqlite3_stmt *stmt;
+ int ret = sqlite3_prepare_v2(db, "UPDATE player SET number=?, gender=?, name=? WHERE player=?", -1, &stmt, 0);
+ if (ret != SQLITE_OK) {
+ fprintf(stderr, "UPDATE prepare: %s\n", sqlite3_errmsg(db));
+ abort();
+ }
+
+ sqlite3_bind_text(stmt, 1, number.data(), number.size(), SQLITE_STATIC);
+ sqlite3_bind_text(stmt, 2, gender.data(), gender.size(), SQLITE_STATIC);
+ sqlite3_bind_text(stmt, 3, name.data(), name.size(), SQLITE_STATIC);
+ sqlite3_bind_int64(stmt, 4, player_id);
+
+ ret = sqlite3_step(stmt);
+ if (ret != SQLITE_DONE) {
+ fprintf(stderr, "UPDATE 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();
+ }
+
+ vector<Player> new_players = load_data();
+ it = find_if(new_players.begin(), new_players.end(), [player_id](const Player &p) { return p.player_id == int(player_id); });
+ int new_row = distance(new_players.begin(), it);
+
+ if (old_row == new_row) {
+ players = std::move(new_players);
+ emit dataChanged(createIndex(old_row, 0), createIndex(old_row, 2));
+ } else {
+ emit beginMoveRows(QModelIndex(), old_row, old_row, QModelIndex(), new_row);
+ players = std::move(new_players);
+ emit endInsertRows();
+ }
+}
+
+std::vector<PlayersModel::Player> PlayersModel::load_data()
+{
+ std::vector<Player> new_players;
// Read the players.
sqlite3_stmt *stmt;
p.number = (const char *)sqlite3_column_text(stmt, 1);
p.name = (const char *)sqlite3_column_text(stmt, 2);
p.gender = (const char *)sqlite3_column_text(stmt, 3);
- players.push_back(p);
+ new_players.push_back(p);
} else if (ret == SQLITE_DONE) {
break;
} else {
fprintf(stderr, "SELECT finalize: %s\n", sqlite3_errmsg(db));
abort();
}
+
+ return new_players;
}
QVariant headerData(int section, Qt::Orientation orientation, int role) const override;
QVariant data(const QModelIndex &index, int role) const override;
- int get_player_id(unsigned row) const {
- return players[row].player_id;
- }
- std::string get_player_gender(unsigned row) const {
- return players[row].gender;
- }
+ int get_player_id(unsigned row) const { return players[row].player_id; }
+ std::string get_player_number(unsigned row) const { return players[row].number; }
+ std::string get_player_name(unsigned row) const { return players[row].name; }
+ std::string get_player_gender(unsigned row) const { return players[row].gender; }
+
std::string get_player_name_by_id(unsigned player_id);
std::string get_player_gender_by_id(unsigned player_id);
QModelIndex get_row_start_qt(unsigned row) const {
return createIndex(row, 2);
}
+ void edit_player(int player_id, const std::string &number, const std::string &gender, const std::string &name);
+
private:
struct Player {
int player_id;
sqlite3 *db;
- void load_data();
+ std::vector<Player> load_data();
};
#endif // !defined(_PLAYERS_H)