db backend: mostly feature-complete

This commit is contained in:
jaseg 2020-08-09 17:12:14 +02:00
parent 872bb95acf
commit 0afe9ca6bf
12 changed files with 497 additions and 160 deletions

67
TagEditor.ui Normal file
View 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>

View file

@ -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;
}

View file

@ -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;

View file

@ -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

View file

@ -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;
}

View file

@ -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
View 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
View 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

View file

@ -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());
}

View file

@ -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

View file

@ -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);

View file

@ -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;