summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorjaseg <git-bigdata-wsl-arch@jaseg.de>2020-08-09 17:12:14 +0200
committerjaseg <git-bigdata-wsl-arch@jaseg.de>2020-08-09 17:12:14 +0200
commit0afe9ca6bf53da111e445d2aea7cfc94e7e3c601 (patch)
treedba69d8eb86e81a1a56a388eafa85817762cf87f
parent872bb95acf6fabd639da57cd41a4844d7e6dd0f0 (diff)
downloadnumberator-0afe9ca6bf53da111e445d2aea7cfc94e7e3c601.tar.gz
numberator-0afe9ca6bf53da111e445d2aea7cfc94e7e3c601.tar.bz2
numberator-0afe9ca6bf53da111e445d2aea7cfc94e7e3c601.zip
db backend: mostly feature-complete
-rw-r--r--TagEditor.ui67
-rw-r--r--numberator.cpp72
-rw-r--r--numberator.h6
-rw-r--r--numberator.pro6
-rw-r--r--sqlitebackend.cpp260
-rw-r--r--sqlitebackend.h65
-rw-r--r--tagitem.cpp31
-rw-r--r--tagitem.h32
-rw-r--r--tagscene.cpp77
-rw-r--r--tagscene.h30
-rw-r--r--tagview.cpp4
-rw-r--r--tagview.h5
12 files changed, 496 insertions, 159 deletions
diff --git a/TagEditor.ui b/TagEditor.ui
new file mode 100644
index 0000000..a8a942b
--- /dev/null
+++ b/TagEditor.ui
@@ -0,0 +1,67 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>Dialog</class>
+ <widget class="QDialog" name="Dialog">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>400</width>
+ <height>300</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Dialog</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <widget class="QPlainTextEdit" name="textEdit"/>
+ </item>
+ <item>
+ <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>
+ </layout>
+ </widget>
+ <resources/>
+ <connections>
+ <connection>
+ <sender>buttonBox</sender>
+ <signal>accepted()</signal>
+ <receiver>Dialog</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>Dialog</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>
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 <QMessageBox>
+
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<Tag> SQLiteSaveFile::getAllTags()
{
- QMutexLocker l(&dbMut);
+ qDebug() << "getAllTags";
+ QMutexLocker l(&m_dbMut);
resetError();
QList<Tag> 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<Tag>();
+ }
while (q.next()) {
rv << Tag {
@@ -153,39 +175,44 @@ QList<Tag> 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<QPair<QString, QVariant
}
bool SQLiteSaveFile::setMeta(const QString &key, const QVariant &value) {
- QMutexLocker l(&dbMut);
+ QMutexLocker l(&m_dbMut);
+ m_dirty = true;
return setMetaLocked(key, value);
}
bool SQLiteSaveFile::setMeta(std::initializer_list<QPair<QString, QVariant>> 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<QVariant> 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<Tag> 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<QPair<QString, QVariant>> 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<QVariant> bindings={});
bool setMetaLocked(const QString &key, const QVariant &value);
bool setMetaLocked(std::initializer_list<QPair<QString, QVariant>> 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 <QGraphicsPixmapItem>
+
+
+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 <QGraphicsSceneMouseEvent>
+
+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<TagItem *>(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 <QGraphicsPixmapItem>
#include <QGraphicsScene>
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<long long int, TagItem*> 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 <cmath>
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;