db backend: mostly feature-complete
This commit is contained in:
parent
872bb95acf
commit
0afe9ca6bf
12 changed files with 497 additions and 160 deletions
67
TagEditor.ui
Normal file
67
TagEditor.ui
Normal file
|
|
@ -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>
|
||||
|
|
@ -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->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->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->actionNew_Project, &QAction::triggered,
|
||||
&proj, &SQLiteSaveFile::clearNew);
|
||||
connect(ui->actionQuit, &QAction::triggered, &QApplication::quit);
|
||||
connect(ui->actionNew_Project, &QAction::triggered, [=]() {
|
||||
if (!showConfirmDiscardDialog())
|
||||
return;
|
||||
|
||||
proj.clearNew();
|
||||
});
|
||||
connect(ui->actionQuit, &QAction::triggered, [=]() {
|
||||
if (!showConfirmDiscardDialog())
|
||||
return;
|
||||
|
||||
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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -4,15 +4,85 @@
|
|||
|
||||
#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::open(const QString &filename)
|
||||
{
|
||||
qDebug() << "open";
|
||||
{
|
||||
QMutexLocker l(&m_dbMut);
|
||||
QFile f(filename);
|
||||
if (!f.exists()) {
|
||||
setError(FileNotFoundError, QString("File \"%1\"does not exist.").arg(filename));
|
||||
return false;
|
||||
}
|
||||
|
||||
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::clearNew()
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
QSqlDatabase old_db = m_db;
|
||||
m_db = new_db;
|
||||
|
||||
if (!initDb(true)) {
|
||||
m_db.close();
|
||||
m_db = old_db;
|
||||
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;
|
||||
}
|
||||
|
||||
bool SQLiteSaveFile::initDb(bool setCreationDate)
|
||||
|
|
@ -33,38 +103,6 @@ bool SQLiteSaveFile::initDb(bool setCreationDate)
|
|||
return true;
|
||||
}
|
||||
|
||||
bool SQLiteSaveFile::connect()
|
||||
{
|
||||
QMutexLocker l(&dbMut);
|
||||
resetError();
|
||||
m_isOpen = false;
|
||||
imageData = QByteArray();
|
||||
|
||||
bool newlyCreated = QFile(filename).exists();
|
||||
|
||||
db.setDatabaseName(filename);
|
||||
if (!db.open()) {
|
||||
setDatabaseError(db);
|
||||
db.close();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!initDb(newlyCreated)) {
|
||||
db.close();
|
||||
return false;
|
||||
}
|
||||
|
||||
m_isOpen = true;
|
||||
|
||||
/* 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);
|
||||
|
||||
imageData = q.value(0).toByteArray();
|
||||
return true;
|
||||
}
|
||||
|
||||
static sqlite3 *getSqliteHandle(QSqlDatabase &db) {
|
||||
QVariant v = db.driver()->handle();
|
||||
assert (v.isValid());
|
||||
|
|
@ -74,18 +112,16 @@ 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;
|
||||
}
|
||||
if (!new_db.open())
|
||||
goto err_cleanup;
|
||||
|
||||
sqlite3 *old_handle = getSqliteHandle(db);
|
||||
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)
|
||||
|
|
@ -97,43 +133,29 @@ bool SQLiteSaveFile::saveAs(const QString &filename)
|
|||
if (sqlite3_backup_finish(bck) != SQLITE_DONE)
|
||||
goto err_cleanup;
|
||||
|
||||
db = new_db;
|
||||
old_db.close();
|
||||
m_db.close();
|
||||
m_db = new_db;
|
||||
m_memory = false;
|
||||
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();
|
||||
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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
const QVariant getMetaLocked(const QString &key) const;
|
||||
|
||||
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);
|
||||
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;
|
||||
|
||||
Error lastError;
|
||||
QString lastErrorString;
|
||||
QSqlDatabase db;
|
||||
QMutex dbMut;
|
||||
QString filename;
|
||||
QByteArray imageData;
|
||||
bool m_isOpen;
|
||||
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;
|
||||
};
|
||||
|
||||
|
||||
|
|
|
|||
31
tagitem.cpp
Normal file
31
tagitem.cpp
Normal file
|
|
@ -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);
|
||||
}
|
||||
32
tagitem.h
Normal file
32
tagitem.h
Normal file
|
|
@ -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
|
||||
79
tagscene.cpp
79
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());
|
||||
}
|
||||
|
|
|
|||
30
tagscene.h
30
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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue