From 0afe9ca6bf53da111e445d2aea7cfc94e7e3c601 Mon Sep 17 00:00:00 2001 From: jaseg Date: Sun, 9 Aug 2020 17:12:14 +0200 Subject: db backend: mostly feature-complete --- TagEditor.ui | 67 ++++++++++++++ numberator.cpp | 72 +++++++++++---- numberator.h | 6 +- numberator.pro | 6 +- sqlitebackend.cpp | 260 +++++++++++++++++++++++++++++++----------------------- sqlitebackend.h | 65 ++++++++------ tagitem.cpp | 31 +++++++ tagitem.h | 32 +++++++ tagscene.cpp | 77 +++++++++++++++- tagscene.h | 30 ++++++- tagview.cpp | 4 +- tagview.h | 5 ++ 12 files changed, 496 insertions(+), 159 deletions(-) create mode 100644 TagEditor.ui create mode 100644 tagitem.cpp create mode 100644 tagitem.h diff --git a/TagEditor.ui b/TagEditor.ui new file mode 100644 index 0000000..a8a942b --- /dev/null +++ b/TagEditor.ui @@ -0,0 +1,67 @@ + + + Dialog + + + + 0 + 0 + 400 + 300 + + + + Dialog + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + buttonBox + accepted() + Dialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + Dialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/numberator.cpp b/numberator.cpp index 2497403..912c5e3 100644 --- a/numberator.cpp +++ b/numberator.cpp @@ -2,6 +2,8 @@ #include "ui_numberator.h" #include "ui_TagListDock.h" +#include + Numberator::Numberator(QWidget *parent) : QMainWindow(parent) , ui(new Ui::Numberator) @@ -14,6 +16,12 @@ Numberator::Numberator(QWidget *parent) { ui->setupUi(this); + connect(&proj, &SQLiteSaveFile::fileIOError, [=](auto e, QString errorName, QString description) { + Q_UNUSED(e); + qDebug() << errorName << ": " << description; + QMessageBox::critical(this, errorName, description); + }); + QDockWidget *dock = new QDockWidget(this); tagsDockUi->setupUi(dock); addDockWidget(Qt::LeftDockWidgetArea, dock); @@ -45,26 +53,29 @@ Numberator::Numberator(QWidget *parent) connect(&saveOpenDialog, &QFileDialog::accepted, [=]() { settings.setValue("MainWindow/SaveAsFileDialogState", this->saveOpenDialog.saveState()); }); - connect(ui->actionSave_Project, &QAction::triggered, [=](bool checked){ - Q_UNUSED(checked); - this->saveOpenDialog.setWindowTitle("Save Project as..."); - disconnect(&this->saveOpenDialog, &QFileDialog::fileSelected, nullptr, nullptr); - connect(&this->saveOpenDialog, &QFileDialog::fileSelected, - &this->proj, &SQLiteSaveFile::saveAs); - this->saveOpenDialog.open(); + connect(ui->actionSave_Project, &QAction::triggered, this, &Numberator::showSaveDialog); + connect(ui->actionOpen_Project, &QAction::triggered, [=](){ + if (!showConfirmDiscardDialog()) + return; + + saveOpenDialog.setWindowTitle("Open Project..."); + saveOpenDialog.setAcceptMode(QFileDialog::AcceptOpen); + if (saveOpenDialog.exec() == QDialog::Accepted) + proj.open(saveOpenDialog.selectedFiles().value(0)); }); - connect(ui->actionOpen_Project, &QAction::triggered, [=](bool checked){ - Q_UNUSED(checked); - this->saveOpenDialog.setWindowTitle("Open Project..."); - disconnect(&this->saveOpenDialog, &QFileDialog::fileSelected, nullptr, nullptr); - connect(&this->saveOpenDialog, &QFileDialog::fileSelected, - this, &Numberator::openFile); - this->saveOpenDialog.open(); + + connect(ui->actionNew_Project, &QAction::triggered, [=]() { + if (!showConfirmDiscardDialog()) + return; + + proj.clearNew(); }); + connect(ui->actionQuit, &QAction::triggered, [=]() { + if (!showConfirmDiscardDialog()) + return; - connect(ui->actionNew_Project, &QAction::triggered, - &proj, &SQLiteSaveFile::clearNew); - connect(ui->actionQuit, &QAction::triggered, &QApplication::quit); + QApplication::quit(); + }); connect(ui->actionAbout, &QAction::triggered, &aboutDialog, &AboutDialog::open); connect(tagsDockUi->tagList->selectionModel(), &QItemSelectionModel::currentChanged, @@ -79,8 +90,33 @@ Numberator::~Numberator() delete ui; } -void Numberator::openFile(const QString &path) +bool Numberator::showConfirmDiscardDialog() { + if (!proj.isMemory() || !proj.isDirty()) + return true; + + auto btn = QMessageBox::warning(this, "Discard unsaved changes?", "This document contains unsaved changes. Do you want to save these changes?", + QMessageBox::Save | QMessageBox::Discard | QMessageBox::Cancel); + + if (btn == QMessageBox::Cancel) + return false; + + if (btn == QMessageBox::Save) + return showSaveDialog(); + /* else, the discard button was clicked */ + return true; } +bool Numberator::showSaveDialog() +{ + saveOpenDialog.setWindowTitle("Save Project as..."); + saveOpenDialog.setAcceptMode(QFileDialog::AcceptSave); + if (saveOpenDialog.exec() == QDialog::Accepted) { + QString fn = this->saveOpenDialog.selectedFiles().value(0); + qDebug() << QString("Calling saveas(%1)").arg(fn); + return proj.saveAs(fn); + } + + return false; +} diff --git a/numberator.h b/numberator.h index 21203cd..bf661db 100644 --- a/numberator.h +++ b/numberator.h @@ -24,8 +24,10 @@ public: Numberator(QWidget *parent = nullptr); ~Numberator(); -private slots: - void openFile(const QString &path); + bool showConfirmDiscardDialog(); + +public slots: + bool showSaveDialog(); /* return true if saved successfully (i.e. no error, not cancelled), false otherwise */ private: Ui::Numberator *ui; diff --git a/numberator.pro b/numberator.pro index 8783811..124dd61 100644 --- a/numberator.pro +++ b/numberator.pro @@ -6,7 +6,8 @@ CONFIG += c++2a win32:INCLUDEPATH += $$[QT_INSTALL_PREFIX]/../Src/qtbase/src/3rdparty/sqlite -win32:SOURCES += $$[QT_INSTALL_PREFIX]/../Src/qtbase/src/3rdparty/sqlite/sqlite3.c +win32:LIBS += -L$$[QT_INSTALL_PLUGINS]/sqldrivers -lqsqlite +#win32:SOURCES += $$[QT_INSTALL_PREFIX]/../Src/qtbase/src/3rdparty/sqlite/sqlite3.c unix:LIBS += -lsqlite3 # The following define makes your compiler emit warnings if you use @@ -25,6 +26,7 @@ SOURCES += \ main.cpp \ numberator.cpp \ sqlitebackend.cpp \ + tagitem.cpp \ taglistmodel.cpp \ tagproptablemodel.cpp \ tagview.cpp \ @@ -34,12 +36,14 @@ HEADERS += \ aboutdialog.h \ numberator.h \ sqlitebackend.h \ + tagitem.h \ taglistmodel.h \ tagproptablemodel.h \ tagview.h \ tagscene.h FORMS += \ + TagEditor.ui \ TagListDock.ui \ aboutdialog.ui \ numberator.ui diff --git a/sqlitebackend.cpp b/sqlitebackend.cpp index b95e18d..434f1e6 100644 --- a/sqlitebackend.cpp +++ b/sqlitebackend.cpp @@ -4,64 +4,102 @@ #include "sqlitebackend.h" -SQLiteSaveFile::SQLiteSaveFile(QObject *parent, QString filename) : +SQLiteSaveFile::SQLiteSaveFile(QObject *parent) : QObject(parent) - , lastError(NoError) - , lastErrorString(QString()) - , db(QSqlDatabase::addDatabase("QSQLITE", QUuid::createUuid().toString())) - , filename(filename) - , m_isOpen(false) + , m_lastError(NoError) + , m_lastErrorString(QString()) + , m_open(false) + , m_dirty(false) { - connect(); + clearNew(); } -bool SQLiteSaveFile::initDb(bool setCreationDate) +bool SQLiteSaveFile::open(const QString &filename) { - for (auto const &q: { - "CREATE TABLE IF NOT EXISTS metadata (key TEXT, value TEXT)", - "CREATE TABLE IF NOT EXISTS tags (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT, anchor_x REAL, anchor_y REAL, meta TEXT)", - "CREATE TABLE IF NOT EXISTS blobs (name TEXT, data BLOB)"}) { - if (!runSql(q)) + qDebug() << "open"; + { + QMutexLocker l(&m_dbMut); + QFile f(filename); + if (!f.exists()) { + setError(FileNotFoundError, QString("File \"%1\"does not exist.").arg(filename)); return false; - } + } - if (setCreationDate) { - if (!setMetaLocked("creationTime", QDateTime::currentDateTimeUtc().toMSecsSinceEpoch())) + QSqlDatabase new_db(QSqlDatabase::addDatabase("QSQLITE", QUuid::createUuid().toString())); + new_db.setDatabaseName(f.fileName()); + if (!new_db.open()) { + setDatabaseError(new_db); + new_db.close(); return false; + } + + m_db.close(); + m_db = new_db; + + /* Try to load image, ignore if image is unset */ + QSqlQuery q("SELECT data FROM blobs WHERE name = 'image'", m_db); + if (!q.exec()) + return setDatabaseError(q); + if (!q.next()) + return setDatabaseError(q); + + m_memory = false; + m_dirty = false; + m_open = true; + m_image = q.value(0).toByteArray(); } - + fileReload(); /* Call after unlocking mutex to allow accesses by receivers */ return true; } -bool SQLiteSaveFile::connect() +bool SQLiteSaveFile::clearNew() { - QMutexLocker l(&dbMut); - resetError(); - m_isOpen = false; - imageData = QByteArray(); + qDebug() << "clearNew"; + { + QMutexLocker l(&m_dbMut); + + QSqlDatabase new_db(QSqlDatabase::addDatabase("QSQLITE", QUuid::createUuid().toString())); + new_db.setDatabaseName(":memory:"); + if (!new_db.open()) { + setDatabaseError(new_db); + new_db.close(); + return false; + } - bool newlyCreated = QFile(filename).exists(); + QSqlDatabase old_db = m_db; + m_db = new_db; - db.setDatabaseName(filename); - if (!db.open()) { - setDatabaseError(db); - db.close(); - return false; - } + if (!initDb(true)) { + m_db.close(); + m_db = old_db; + return false; + } - if (!initDb(newlyCreated)) { - db.close(); - return false; + old_db.close(); + m_memory = true; + m_dirty = false; + m_open = true; + m_image = QByteArray(); } + fileReload(); /* Call after unlocking mutex to allow accesses by receivers */ + return true; +} - m_isOpen = true; +bool SQLiteSaveFile::initDb(bool setCreationDate) +{ + for (auto const &q: { + "CREATE TABLE IF NOT EXISTS metadata (key TEXT, value TEXT)", + "CREATE TABLE IF NOT EXISTS tags (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT, anchor_x REAL, anchor_y REAL, meta TEXT)", + "CREATE TABLE IF NOT EXISTS blobs (name TEXT, data BLOB)"}) { + if (!runSql(q)) + return false; + } - /* Try to load image, ignore if image is unset */ - QSqlQuery q("SELECT data FROM blobs WHERE name = 'image'", db); - if (!q.next()) - return setDatabaseError(q); + if (setCreationDate) { + if (!setMetaLocked("creationTime", QDateTime::currentDateTimeUtc().toMSecsSinceEpoch())) + return false; + } - imageData = q.value(0).toByteArray(); return true; } @@ -74,66 +112,50 @@ static sqlite3 *getSqliteHandle(QSqlDatabase &db) { bool SQLiteSaveFile::saveAs(const QString &filename) { - QMutexLocker l(&dbMut); + qDebug() << "saveAs" << filename; + QMutexLocker l(&m_dbMut); QFile f(filename); - QSqlDatabase old_db = db; QSqlDatabase new_db(QSqlDatabase::addDatabase("QSQLITE", QUuid::createUuid().toString())); - new_db.setDatabaseName(f.fileName()); - if (!new_db.open()) { - setDatabaseError(new_db); - new_db.close(); - return false; + { + new_db.setDatabaseName(f.fileName()); + if (!new_db.open()) + goto err_cleanup; + + sqlite3 *old_handle = getSqliteHandle(m_db); + sqlite3 *new_handle = getSqliteHandle(new_db); + sqlite3_backup *bck = sqlite3_backup_init(new_handle, "main", old_handle, "main"); + if (!bck) + goto err_cleanup; + + if (sqlite3_backup_step(bck, -1) != SQLITE_DONE) + goto err_cleanup; + + if (sqlite3_backup_finish(bck) != SQLITE_DONE) + goto err_cleanup; + + m_db.close(); + m_db = new_db; + m_memory = false; + return true; } - - sqlite3 *old_handle = getSqliteHandle(db); - sqlite3 *new_handle = getSqliteHandle(new_db); - sqlite3_backup *bck = sqlite3_backup_init(new_handle, "main", old_handle, "main"); - if (!bck) - goto err_cleanup; - - if (sqlite3_backup_step(bck, -1) != SQLITE_DONE) - goto err_cleanup; - - if (sqlite3_backup_finish(bck) != SQLITE_DONE) - goto err_cleanup; - - db = new_db; - old_db.close(); - return true; - err_cleanup: setDatabaseError(new_db); new_db.close(); return false; } -bool SQLiteSaveFile::clearNew() -{ - QMutexLocker l(&dbMut); - - QSqlDatabase new_db(QSqlDatabase::addDatabase("QSQLITE", QUuid::createUuid().toString())); - new_db.setDatabaseName(":memory:"); - if (!new_db.open()) { - setDatabaseError(new_db); - new_db.close(); - return false; - } - - if (!initDb()) { - db.close(); - return false; - } - - imageData = QByteArray(); - return true; -} - QList SQLiteSaveFile::getAllTags() { - QMutexLocker l(&dbMut); + qDebug() << "getAllTags"; + QMutexLocker l(&m_dbMut); resetError(); QList rv; - QSqlQuery q("SELECT (id, name, anchor_x, anchor_y, meta) FROM tags", db); + + QSqlQuery q("SELECT id, name, anchor_x, anchor_y, meta FROM tags", m_db); + if (!q.exec()) { + setDatabaseError(q); + return QList(); + } while (q.next()) { rv << Tag { @@ -153,39 +175,44 @@ QList SQLiteSaveFile::getAllTags() bool SQLiteSaveFile::updateTag(Tag tag) { - QMutexLocker l(&dbMut); + QMutexLocker l(&m_dbMut); if (!runSql("UPDATE tags SET name=?, anchor_x=?, anchor_y=?, meta=? WHERE id=?", { tag.name, tag.anchor.x(), tag.anchor.y(), QJsonDocument::fromVariant(tag.metadata).toJson(), tag.id })) return false; + m_dirty = true; tagChange(TagChange::CHANGED, tag); return true; } bool SQLiteSaveFile::deleteTag(Tag tag) { - QMutexLocker l(&dbMut); + QMutexLocker l(&m_dbMut); if (!runSql("DELETE FROM tags WHERE id=?", {tag.id})) return false; + m_dirty = true; tagChange(TagChange::DELETED, tag); return true; } bool SQLiteSaveFile::createTag(Tag tag) { - QMutexLocker l(&dbMut); + qDebug() << "createTag"; + QMutexLocker l(&m_dbMut); resetError(); - QSqlQuery q("INSERT INTO tags(name, anchor_x, anchor_y, meta) VALUES (?, ?, ?, ?)", db); + QSqlQuery q("INSERT INTO tags(name, anchor_x, anchor_y, meta) VALUES (?, ?, ?, ?)", m_db); q.addBindValue(tag.name); q.addBindValue(tag.anchor.x()); q.addBindValue(tag.anchor.y()); q.addBindValue(QJsonDocument::fromVariant(tag.metadata).toJson()); + if (!q.exec()) return setDatabaseError(q); Tag created_tag(q.lastInsertId().toLongLong(), tag); + m_dirty = true; tagChange(TagChange::CREATED, created_tag); return true; } @@ -205,25 +232,34 @@ bool SQLiteSaveFile::setMetaLocked(std::initializer_list> metas) { - QMutexLocker l(&dbMut); + QMutexLocker l(&m_dbMut); + m_dirty = true; return setMetaLocked(metas); } -QVariant SQLiteSaveFile::getMeta(const QString &key) { - QMutexLocker l(&dbMut); +const QVariant SQLiteSaveFile::getMeta(const QString &key) const { + QMutexLocker l(&m_dbMut); return getMetaLocked(key); } -QVariant SQLiteSaveFile::getMetaLocked(const QString &key) +const QVariant SQLiteSaveFile::getMetaLocked(const QString &key) const { + qDebug() << "getMeta " << key; resetError(); - QSqlQuery q("SELECT value FROM metadata WHERE key=?", db); + QSqlQuery q("SELECT value FROM metadata WHERE key=?", m_db); q.addBindValue(key); + + if (!q.exec()) { + setDatabaseError(q); + return QVariant(); + } + if (!q.next()) { setDatabaseError(q); return QVariant(); @@ -234,18 +270,22 @@ QVariant SQLiteSaveFile::getMetaLocked(const QString &key) bool SQLiteSaveFile::runSql(QString query, std::initializer_list bindings) { + qDebug() << "runSql " << query; resetError(); - QSqlQuery q(query, db); - for (const QVariant &v : bindings) + QSqlQuery q(query, m_db); + for (const QVariant &v : bindings) { q.addBindValue(v); + } - q.exec(); - return setDatabaseError(q); + if (!q.exec()) { + return setDatabaseError(q); + } + return true; } bool SQLiteSaveFile::loadImageFromDisk(const QString &filename) { - QMutexLocker l(&dbMut); + QMutexLocker l(&m_dbMut); QFile f(filename); resetError(); @@ -254,7 +294,7 @@ bool SQLiteSaveFile::loadImageFromDisk(const QString &filename) return false; } - imageData = f.readAll(); + m_image = f.readAll(); if (f.error() != QFileDevice::NoError) { setError(ImageReadError, QString("Failed to read image: %1").arg(f.errorString())); return false; @@ -266,7 +306,8 @@ bool SQLiteSaveFile::loadImageFromDisk(const QString &filename) {"imageLoadedTime", QDateTime::currentDateTimeUtc().toMSecsSinceEpoch()}})) return false; - return runSql("INSERT OR REPLACE INTO blobs(name, data) VALUES ('image', ?)", {imageData}); + m_dirty = true; + return runSql("INSERT OR REPLACE INTO blobs(name, data) VALUES ('image', ?)", {m_image}); } bool SQLiteSaveFile::reloadImageFromDisk() @@ -287,11 +328,7 @@ Tag::Tag(long long id, QString name, qreal anchor_x, qreal anchor_y, QByteArray , name(name) , anchor(QPointF(anchor_x, anchor_y)) , metadata(QJsonDocument::fromJson(metadata).object().toVariantMap()) -{ -} - -Tag::Tag() - : id(-1) + , valid(true) { } @@ -300,23 +337,26 @@ Tag::Tag(long long id, const Tag &other) , name(other.name) , anchor(other.anchor) , metadata(other.metadata) + , valid(true) { } -bool SQLiteSaveFile::setDatabaseError(const QSqlQuery &q) +bool SQLiteSaveFile::setDatabaseError(const QSqlQuery &q) const { if (!q.lastError().isValid()) return true; - setError(SQLiteError, QString("Project file database error: %1").arg(q.lastError().text())); + qDebug() << "Query error: " << q.lastError().text(); + setError(SQLiteError, QString("Project file database error executing %1: %2").arg(q.executedQuery()).arg(q.lastError().text())); return false; } -bool SQLiteSaveFile::setDatabaseError(const QSqlDatabase &db) +bool SQLiteSaveFile::setDatabaseError(const QSqlDatabase &db) const { if (!db.lastError().isValid()) return true; + qDebug() << "Database error: " << db.lastError().text(); setError(SQLiteError, QString("Project file database error: %1").arg(db.lastError().text())); return false; } diff --git a/sqlitebackend.h b/sqlitebackend.h index 4dfd2f9..52faedc 100644 --- a/sqlitebackend.h +++ b/sqlitebackend.h @@ -16,86 +16,101 @@ enum TagChange { class Tag { public: + Tag() : valid(false) {} Tag(long long int id, QString name, qreal anchor_x, qreal anchor_y, QByteArray metadata); - Tag(); Tag(long long int id, const Tag &other); + bool isValid() { return valid; } + long long int id; QString name; QPointF anchor; QVariantMap metadata; + +private: + bool valid; }; class SQLiteSaveFile : public QObject { Q_OBJECT public: - explicit SQLiteSaveFile(QObject *parent = nullptr, QString filename = ":memory:"); - bool connect(); - bool isOpen() { return m_isOpen; } + explicit SQLiteSaveFile(QObject *parent = nullptr); QList getAllTags(); - QByteArray getImage(); + const QByteArray &getImage() const { return m_image; }; bool updateTag(Tag tag); bool deleteTag(Tag tag); bool createTag(Tag tag); + bool isMemory() { return m_memory; } /* backend db points to temporary memory db */ + bool isDirty() { return m_dirty; } /* backend db was changed since opening */ + bool isOpen() { return m_open; } /* backend db is open */ + bool setMeta(const QString &key, const QVariant &value); bool setMeta(std::initializer_list> metas); - QVariant getMeta(const QString &key); + const QVariant getMeta(const QString &key) const; - QString errorString() { return lastErrorString; } + const QString &errorString() const { return m_lastErrorString; } enum Error { NoError = 0, + FileNotFoundError, SQLiteError, ImageOpenError, - ImageReadError + ImageReadError, + MaxError }; - inline const static QString errorNames[] = { + inline const static QString errorNames[MaxError] = { [NoError] = "No Error", + [FileNotFoundError] = "File not found", [SQLiteError] = "Database Error", [ImageOpenError] = "Error Opening Image", [ImageReadError] = "Error Reading Image" }; Error error(); - void resetError() { lastError = NoError; lastErrorString = QString(); } + void resetError() const { m_lastError = NoError; m_lastErrorString = QString(); } public slots: /** Save this project file under a new name. This changes the backend database this project file object points to, and copies all data. * Callers can continue to use the same project file object afterwards. */ bool saveAs(const QString &filename); + bool open(const QString &filename); bool reloadImageFromDisk(); - bool loadImageFromDisk(const QString &filename); + bool loadImageFromDisk(const QString &m_filename); bool clearNew(); signals: void tagChange(TagChange change, const Tag &tag); void fileReload(); - void fileIOError(Error e, QString errorName, QString description); + void fileIOError(Error e, QString errorName, QString description) const; private: + bool connect(); bool initDb(bool setCreationDate=true); bool runSql(QString query, std::initializer_list bindings={}); bool setMetaLocked(const QString &key, const QVariant &value); bool setMetaLocked(std::initializer_list> metas); - QVariant getMetaLocked(const QString &key); - - void setError(Error e, QString desc) { lastError = e; lastErrorString = desc; fileIOError(e, errorNames[e], desc); } - bool setDatabaseError(const QSqlQuery &q); - bool setDatabaseError(const QSqlDatabase &db); - - Error lastError; - QString lastErrorString; - QSqlDatabase db; - QMutex dbMut; - QString filename; - QByteArray imageData; - bool m_isOpen; + const QVariant getMetaLocked(const QString &key) const; + + void setError(Error e, QString desc) const { m_lastError = e; m_lastErrorString = desc; fileIOError(e, errorNames[e], desc); } + bool setDatabaseError(const QSqlQuery &q) const; + bool setDatabaseError(const QSqlDatabase &m_db) const; + + mutable Error m_lastError; + mutable QString m_lastErrorString; + QSqlDatabase m_db; + mutable QMutex m_dbMut; + QString m_filename; + QByteArray m_image; + + bool m_open; + bool m_dirty; + bool m_memory; }; diff --git a/tagitem.cpp b/tagitem.cpp new file mode 100644 index 0000000..d251428 --- /dev/null +++ b/tagitem.cpp @@ -0,0 +1,31 @@ +#include "tagitem.h" + +TagItem::TagItem(const Tag &tag) + : valid(true) +{ + setFlags(QGraphicsItem::ItemIsMovable + | QGraphicsItem::ItemIsSelectable + | QGraphicsItem::ItemIsFocusable); + /* TODO text_it.setFlags(QGraphicsItem::ItemIgnoresTransformations); + */ + tagUpdated(tag); +} + +void TagItem::tagUpdated(const Tag &tag) +{ + m_tag = tag; + setText(tag.name); + setPos(tag.anchor); +} + +QVariant TagItem::itemChange(QGraphicsItem::GraphicsItemChange change, const QVariant &value) +{ + if (change == ItemPositionChange) { + /* https://gist.github.com/csukuangfj/c2a06416062bec9ed99eddd705c21275#file-qgraphicsscenetest-cpp-L90 + * + */ + /* FIXME */ + + } + return QGraphicsItem::itemChange(change, value); +} diff --git a/tagitem.h b/tagitem.h new file mode 100644 index 0000000..a248243 --- /dev/null +++ b/tagitem.h @@ -0,0 +1,32 @@ +#ifndef TAGITEM_H +#define TAGITEM_H + +#include "sqlitebackend.h" + +#include + + +class TagItem : public QGraphicsSimpleTextItem +{ +public: + TagItem(const Tag &tag); + TagItem() : valid(false) {}; + + enum { TagItemType = UserType + 1 }; + int type() const override { return TagItemType; } + + bool isValid() { return valid; } + + void tagUpdated(const Tag &tag); + + Tag tag() { return m_tag; } + +protected: + QVariant itemChange(QGraphicsItem::GraphicsItemChange change, const QVariant &value) override; + +private: + Tag m_tag; + bool valid; +}; + +#endif // TAGITEM_H diff --git a/tagscene.cpp b/tagscene.cpp index 5b028a0..fb52574 100644 --- a/tagscene.cpp +++ b/tagscene.cpp @@ -1,6 +1,81 @@ #include "tagscene.h" -TagScene::TagScene() +#include + +TagScene::TagScene(SQLiteSaveFile &proj) + : proj(proj) +{ + reloadPicture(); + reloadTags(); + + connect(&proj, &SQLiteSaveFile::tagChange, this, &TagScene::tagChanged); + connect(&proj, &SQLiteSaveFile::fileReload, + [=]() { reloadTags(); }); +} + +void TagScene::tagChanged(TagChange change, const Tag &tag) +{ + TagItem *it; + + switch(change) + { + case TagChange::CREATED: + addTag(tag); + break; + + case TagChange::DELETED: + it = tags.take(tag.id); + assert(it); + removeItem(it); + break; + + case TagChange::CHANGED: + it = tags[tag.id]; + assert(it); + + it->tagUpdated(tag); + break; + } +} + +void TagScene::reloadPicture() +{ + pix.loadFromData(proj.getImage()); + if (pix_it) + removeItem(pix_it); + pix_it = new QGraphicsPixmapItem(pix); + addItem(pix_it); +} + +void TagScene::addTag(const Tag tag) { + TagItem *it = new TagItem(tag); + addItem(it); + tags[tag.id] = it; +} + +void TagScene::reloadTags() { + clear(); + for (auto *it : tags.values()) { + delete it; + } + + for (const Tag &tag : proj.getAllTags()) { + addTag(tag); + } +} + +void TagScene::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event) +{ + QGraphicsItem *it = itemAt(event->scenePos(), QTransform()); + if (!it) { + QGraphicsScene::mouseDoubleClickEvent(event); + } + + TagItem *tagitem = qgraphicsitem_cast(it); + if (!tagitem) { + QGraphicsScene::mouseDoubleClickEvent(event); + } + tagDoubleClicked(tagitem->tag()); } diff --git a/tagscene.h b/tagscene.h index 823609e..006fffd 100644 --- a/tagscene.h +++ b/tagscene.h @@ -1,14 +1,42 @@ #ifndef TAGSCENE_H #define TAGSCENE_H +#include "sqlitebackend.h" +#include "tagitem.h" + +#include #include class TagScene : public QGraphicsScene { + Q_OBJECT + public: - TagScene(); + TagScene(SQLiteSaveFile &proj); + +public slots: + void reloadPicture(); + void reloadTags(); + +signals: + void tagDoubleClicked(const Tag &tag); + +protected: + void mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event) override; + +private slots: + void tagChanged(TagChange change, const Tag &tag); + +private: + void addTag(const Tag tag); + + SQLiteSaveFile &proj; + + QGraphicsPixmapItem *pix_it; + QPixmap pix; + QMap tags; }; #endif // TAGSCENE_H diff --git a/tagview.cpp b/tagview.cpp index abaefcb..b4e83dd 100644 --- a/tagview.cpp +++ b/tagview.cpp @@ -5,11 +5,13 @@ #include TagView::TagView(SQLiteSaveFile &proj) - : proj(proj) + : scene(proj) + , proj(proj) , saveCenterTimer() { setDragMode(QGraphicsView::ScrollHandDrag); setScene(&scene); + connect(&scene, &TagScene::tagDoubleClicked, this, &TagView::tagDoubleClicked); saveCenterTimer.setSingleShot(true); saveCenterTimer.setInterval(500); diff --git a/tagview.h b/tagview.h index eedd019..71b418c 100644 --- a/tagview.h +++ b/tagview.h @@ -10,6 +10,8 @@ class TagView : public QGraphicsView { + Q_OBJECT + public: TagView(SQLiteSaveFile &proj); @@ -19,6 +21,9 @@ public slots: void zoomIn(qreal delta); void rotate(int angle); +signals: + void tagDoubleClicked(const Tag &tag); + protected: void wheelEvent(QWheelEvent *evt) override; -- cgit