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_numberator.h"
|
||||||
#include "ui_TagListDock.h"
|
#include "ui_TagListDock.h"
|
||||||
|
|
||||||
|
#include <QMessageBox>
|
||||||
|
|
||||||
Numberator::Numberator(QWidget *parent)
|
Numberator::Numberator(QWidget *parent)
|
||||||
: QMainWindow(parent)
|
: QMainWindow(parent)
|
||||||
, ui(new Ui::Numberator)
|
, ui(new Ui::Numberator)
|
||||||
|
|
@ -14,6 +16,12 @@ Numberator::Numberator(QWidget *parent)
|
||||||
{
|
{
|
||||||
ui->setupUi(this);
|
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);
|
QDockWidget *dock = new QDockWidget(this);
|
||||||
tagsDockUi->setupUi(dock);
|
tagsDockUi->setupUi(dock);
|
||||||
addDockWidget(Qt::LeftDockWidgetArea, dock);
|
addDockWidget(Qt::LeftDockWidgetArea, dock);
|
||||||
|
|
@ -45,26 +53,29 @@ Numberator::Numberator(QWidget *parent)
|
||||||
connect(&saveOpenDialog, &QFileDialog::accepted, [=]() {
|
connect(&saveOpenDialog, &QFileDialog::accepted, [=]() {
|
||||||
settings.setValue("MainWindow/SaveAsFileDialogState", this->saveOpenDialog.saveState());
|
settings.setValue("MainWindow/SaveAsFileDialogState", this->saveOpenDialog.saveState());
|
||||||
});
|
});
|
||||||
connect(ui->actionSave_Project, &QAction::triggered, [=](bool checked){
|
connect(ui->actionSave_Project, &QAction::triggered, this, &Numberator::showSaveDialog);
|
||||||
Q_UNUSED(checked);
|
connect(ui->actionOpen_Project, &QAction::triggered, [=](){
|
||||||
this->saveOpenDialog.setWindowTitle("Save Project as...");
|
if (!showConfirmDiscardDialog())
|
||||||
disconnect(&this->saveOpenDialog, &QFileDialog::fileSelected, nullptr, nullptr);
|
return;
|
||||||
connect(&this->saveOpenDialog, &QFileDialog::fileSelected,
|
|
||||||
&this->proj, &SQLiteSaveFile::saveAs);
|
saveOpenDialog.setWindowTitle("Open Project...");
|
||||||
this->saveOpenDialog.open();
|
saveOpenDialog.setAcceptMode(QFileDialog::AcceptOpen);
|
||||||
});
|
if (saveOpenDialog.exec() == QDialog::Accepted)
|
||||||
connect(ui->actionOpen_Project, &QAction::triggered, [=](bool checked){
|
proj.open(saveOpenDialog.selectedFiles().value(0));
|
||||||
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,
|
connect(ui->actionNew_Project, &QAction::triggered, [=]() {
|
||||||
&proj, &SQLiteSaveFile::clearNew);
|
if (!showConfirmDiscardDialog())
|
||||||
connect(ui->actionQuit, &QAction::triggered, &QApplication::quit);
|
return;
|
||||||
|
|
||||||
|
proj.clearNew();
|
||||||
|
});
|
||||||
|
connect(ui->actionQuit, &QAction::triggered, [=]() {
|
||||||
|
if (!showConfirmDiscardDialog())
|
||||||
|
return;
|
||||||
|
|
||||||
|
QApplication::quit();
|
||||||
|
});
|
||||||
connect(ui->actionAbout, &QAction::triggered, &aboutDialog, &AboutDialog::open);
|
connect(ui->actionAbout, &QAction::triggered, &aboutDialog, &AboutDialog::open);
|
||||||
|
|
||||||
connect(tagsDockUi->tagList->selectionModel(), &QItemSelectionModel::currentChanged,
|
connect(tagsDockUi->tagList->selectionModel(), &QItemSelectionModel::currentChanged,
|
||||||
|
|
@ -79,8 +90,33 @@ Numberator::~Numberator()
|
||||||
delete ui;
|
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(QWidget *parent = nullptr);
|
||||||
~Numberator();
|
~Numberator();
|
||||||
|
|
||||||
private slots:
|
bool showConfirmDiscardDialog();
|
||||||
void openFile(const QString &path);
|
|
||||||
|
public slots:
|
||||||
|
bool showSaveDialog(); /* return true if saved successfully (i.e. no error, not cancelled), false otherwise */
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Ui::Numberator *ui;
|
Ui::Numberator *ui;
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,8 @@ CONFIG += c++2a
|
||||||
|
|
||||||
|
|
||||||
win32:INCLUDEPATH += $$[QT_INSTALL_PREFIX]/../Src/qtbase/src/3rdparty/sqlite
|
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
|
unix:LIBS += -lsqlite3
|
||||||
|
|
||||||
# The following define makes your compiler emit warnings if you use
|
# The following define makes your compiler emit warnings if you use
|
||||||
|
|
@ -25,6 +26,7 @@ SOURCES += \
|
||||||
main.cpp \
|
main.cpp \
|
||||||
numberator.cpp \
|
numberator.cpp \
|
||||||
sqlitebackend.cpp \
|
sqlitebackend.cpp \
|
||||||
|
tagitem.cpp \
|
||||||
taglistmodel.cpp \
|
taglistmodel.cpp \
|
||||||
tagproptablemodel.cpp \
|
tagproptablemodel.cpp \
|
||||||
tagview.cpp \
|
tagview.cpp \
|
||||||
|
|
@ -34,12 +36,14 @@ HEADERS += \
|
||||||
aboutdialog.h \
|
aboutdialog.h \
|
||||||
numberator.h \
|
numberator.h \
|
||||||
sqlitebackend.h \
|
sqlitebackend.h \
|
||||||
|
tagitem.h \
|
||||||
taglistmodel.h \
|
taglistmodel.h \
|
||||||
tagproptablemodel.h \
|
tagproptablemodel.h \
|
||||||
tagview.h \
|
tagview.h \
|
||||||
tagscene.h
|
tagscene.h
|
||||||
|
|
||||||
FORMS += \
|
FORMS += \
|
||||||
|
TagEditor.ui \
|
||||||
TagListDock.ui \
|
TagListDock.ui \
|
||||||
aboutdialog.ui \
|
aboutdialog.ui \
|
||||||
numberator.ui
|
numberator.ui
|
||||||
|
|
|
||||||
|
|
@ -4,15 +4,85 @@
|
||||||
|
|
||||||
#include "sqlitebackend.h"
|
#include "sqlitebackend.h"
|
||||||
|
|
||||||
SQLiteSaveFile::SQLiteSaveFile(QObject *parent, QString filename) :
|
SQLiteSaveFile::SQLiteSaveFile(QObject *parent) :
|
||||||
QObject(parent)
|
QObject(parent)
|
||||||
, lastError(NoError)
|
, m_lastError(NoError)
|
||||||
, lastErrorString(QString())
|
, m_lastErrorString(QString())
|
||||||
, db(QSqlDatabase::addDatabase("QSQLITE", QUuid::createUuid().toString()))
|
, m_open(false)
|
||||||
, filename(filename)
|
, m_dirty(false)
|
||||||
, m_isOpen(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)
|
bool SQLiteSaveFile::initDb(bool setCreationDate)
|
||||||
|
|
@ -33,38 +103,6 @@ bool SQLiteSaveFile::initDb(bool setCreationDate)
|
||||||
return true;
|
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) {
|
static sqlite3 *getSqliteHandle(QSqlDatabase &db) {
|
||||||
QVariant v = db.driver()->handle();
|
QVariant v = db.driver()->handle();
|
||||||
assert (v.isValid());
|
assert (v.isValid());
|
||||||
|
|
@ -74,66 +112,50 @@ static sqlite3 *getSqliteHandle(QSqlDatabase &db) {
|
||||||
|
|
||||||
bool SQLiteSaveFile::saveAs(const QString &filename)
|
bool SQLiteSaveFile::saveAs(const QString &filename)
|
||||||
{
|
{
|
||||||
QMutexLocker l(&dbMut);
|
qDebug() << "saveAs" << filename;
|
||||||
|
QMutexLocker l(&m_dbMut);
|
||||||
QFile f(filename);
|
QFile f(filename);
|
||||||
QSqlDatabase old_db = db;
|
|
||||||
QSqlDatabase new_db(QSqlDatabase::addDatabase("QSQLITE", QUuid::createUuid().toString()));
|
QSqlDatabase new_db(QSqlDatabase::addDatabase("QSQLITE", QUuid::createUuid().toString()));
|
||||||
new_db.setDatabaseName(f.fileName());
|
{
|
||||||
if (!new_db.open()) {
|
new_db.setDatabaseName(f.fileName());
|
||||||
setDatabaseError(new_db);
|
if (!new_db.open())
|
||||||
new_db.close();
|
goto err_cleanup;
|
||||||
return false;
|
|
||||||
|
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:
|
err_cleanup:
|
||||||
setDatabaseError(new_db);
|
setDatabaseError(new_db);
|
||||||
new_db.close();
|
new_db.close();
|
||||||
return false;
|
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()
|
QList<Tag> SQLiteSaveFile::getAllTags()
|
||||||
{
|
{
|
||||||
QMutexLocker l(&dbMut);
|
qDebug() << "getAllTags";
|
||||||
|
QMutexLocker l(&m_dbMut);
|
||||||
resetError();
|
resetError();
|
||||||
QList<Tag> rv;
|
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()) {
|
while (q.next()) {
|
||||||
rv << Tag {
|
rv << Tag {
|
||||||
|
|
@ -153,39 +175,44 @@ QList<Tag> SQLiteSaveFile::getAllTags()
|
||||||
|
|
||||||
bool SQLiteSaveFile::updateTag(Tag tag)
|
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=?", {
|
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
|
tag.name, tag.anchor.x(), tag.anchor.y(), QJsonDocument::fromVariant(tag.metadata).toJson(), tag.id
|
||||||
}))
|
}))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
m_dirty = true;
|
||||||
tagChange(TagChange::CHANGED, tag);
|
tagChange(TagChange::CHANGED, tag);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool SQLiteSaveFile::deleteTag(Tag tag)
|
bool SQLiteSaveFile::deleteTag(Tag tag)
|
||||||
{
|
{
|
||||||
QMutexLocker l(&dbMut);
|
QMutexLocker l(&m_dbMut);
|
||||||
if (!runSql("DELETE FROM tags WHERE id=?", {tag.id}))
|
if (!runSql("DELETE FROM tags WHERE id=?", {tag.id}))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
m_dirty = true;
|
||||||
tagChange(TagChange::DELETED, tag);
|
tagChange(TagChange::DELETED, tag);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool SQLiteSaveFile::createTag(Tag tag)
|
bool SQLiteSaveFile::createTag(Tag tag)
|
||||||
{
|
{
|
||||||
QMutexLocker l(&dbMut);
|
qDebug() << "createTag";
|
||||||
|
QMutexLocker l(&m_dbMut);
|
||||||
resetError();
|
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.name);
|
||||||
q.addBindValue(tag.anchor.x());
|
q.addBindValue(tag.anchor.x());
|
||||||
q.addBindValue(tag.anchor.y());
|
q.addBindValue(tag.anchor.y());
|
||||||
q.addBindValue(QJsonDocument::fromVariant(tag.metadata).toJson());
|
q.addBindValue(QJsonDocument::fromVariant(tag.metadata).toJson());
|
||||||
|
|
||||||
if (!q.exec())
|
if (!q.exec())
|
||||||
return setDatabaseError(q);
|
return setDatabaseError(q);
|
||||||
|
|
||||||
Tag created_tag(q.lastInsertId().toLongLong(), tag);
|
Tag created_tag(q.lastInsertId().toLongLong(), tag);
|
||||||
|
m_dirty = true;
|
||||||
tagChange(TagChange::CREATED, created_tag);
|
tagChange(TagChange::CREATED, created_tag);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
@ -205,25 +232,34 @@ bool SQLiteSaveFile::setMetaLocked(std::initializer_list<QPair<QString, QVariant
|
||||||
}
|
}
|
||||||
|
|
||||||
bool SQLiteSaveFile::setMeta(const QString &key, const QVariant &value) {
|
bool SQLiteSaveFile::setMeta(const QString &key, const QVariant &value) {
|
||||||
QMutexLocker l(&dbMut);
|
QMutexLocker l(&m_dbMut);
|
||||||
|
m_dirty = true;
|
||||||
return setMetaLocked(key, value);
|
return setMetaLocked(key, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool SQLiteSaveFile::setMeta(std::initializer_list<QPair<QString, QVariant>> metas) {
|
bool SQLiteSaveFile::setMeta(std::initializer_list<QPair<QString, QVariant>> metas) {
|
||||||
QMutexLocker l(&dbMut);
|
QMutexLocker l(&m_dbMut);
|
||||||
|
m_dirty = true;
|
||||||
return setMetaLocked(metas);
|
return setMetaLocked(metas);
|
||||||
}
|
}
|
||||||
|
|
||||||
QVariant SQLiteSaveFile::getMeta(const QString &key) {
|
const QVariant SQLiteSaveFile::getMeta(const QString &key) const {
|
||||||
QMutexLocker l(&dbMut);
|
QMutexLocker l(&m_dbMut);
|
||||||
return getMetaLocked(key);
|
return getMetaLocked(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
QVariant SQLiteSaveFile::getMetaLocked(const QString &key)
|
const QVariant SQLiteSaveFile::getMetaLocked(const QString &key) const
|
||||||
{
|
{
|
||||||
|
qDebug() << "getMeta " << key;
|
||||||
resetError();
|
resetError();
|
||||||
QSqlQuery q("SELECT value FROM metadata WHERE key=?", db);
|
QSqlQuery q("SELECT value FROM metadata WHERE key=?", m_db);
|
||||||
q.addBindValue(key);
|
q.addBindValue(key);
|
||||||
|
|
||||||
|
if (!q.exec()) {
|
||||||
|
setDatabaseError(q);
|
||||||
|
return QVariant();
|
||||||
|
}
|
||||||
|
|
||||||
if (!q.next()) {
|
if (!q.next()) {
|
||||||
setDatabaseError(q);
|
setDatabaseError(q);
|
||||||
return QVariant();
|
return QVariant();
|
||||||
|
|
@ -234,18 +270,22 @@ QVariant SQLiteSaveFile::getMetaLocked(const QString &key)
|
||||||
|
|
||||||
bool SQLiteSaveFile::runSql(QString query, std::initializer_list<QVariant> bindings)
|
bool SQLiteSaveFile::runSql(QString query, std::initializer_list<QVariant> bindings)
|
||||||
{
|
{
|
||||||
|
qDebug() << "runSql " << query;
|
||||||
resetError();
|
resetError();
|
||||||
QSqlQuery q(query, db);
|
QSqlQuery q(query, m_db);
|
||||||
for (const QVariant &v : bindings)
|
for (const QVariant &v : bindings) {
|
||||||
q.addBindValue(v);
|
q.addBindValue(v);
|
||||||
|
}
|
||||||
|
|
||||||
q.exec();
|
if (!q.exec()) {
|
||||||
return setDatabaseError(q);
|
return setDatabaseError(q);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool SQLiteSaveFile::loadImageFromDisk(const QString &filename)
|
bool SQLiteSaveFile::loadImageFromDisk(const QString &filename)
|
||||||
{
|
{
|
||||||
QMutexLocker l(&dbMut);
|
QMutexLocker l(&m_dbMut);
|
||||||
QFile f(filename);
|
QFile f(filename);
|
||||||
resetError();
|
resetError();
|
||||||
|
|
||||||
|
|
@ -254,7 +294,7 @@ bool SQLiteSaveFile::loadImageFromDisk(const QString &filename)
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
imageData = f.readAll();
|
m_image = f.readAll();
|
||||||
if (f.error() != QFileDevice::NoError) {
|
if (f.error() != QFileDevice::NoError) {
|
||||||
setError(ImageReadError, QString("Failed to read image: %1").arg(f.errorString()));
|
setError(ImageReadError, QString("Failed to read image: %1").arg(f.errorString()));
|
||||||
return false;
|
return false;
|
||||||
|
|
@ -266,7 +306,8 @@ bool SQLiteSaveFile::loadImageFromDisk(const QString &filename)
|
||||||
{"imageLoadedTime", QDateTime::currentDateTimeUtc().toMSecsSinceEpoch()}}))
|
{"imageLoadedTime", QDateTime::currentDateTimeUtc().toMSecsSinceEpoch()}}))
|
||||||
return false;
|
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()
|
bool SQLiteSaveFile::reloadImageFromDisk()
|
||||||
|
|
@ -287,11 +328,7 @@ Tag::Tag(long long id, QString name, qreal anchor_x, qreal anchor_y, QByteArray
|
||||||
, name(name)
|
, name(name)
|
||||||
, anchor(QPointF(anchor_x, anchor_y))
|
, anchor(QPointF(anchor_x, anchor_y))
|
||||||
, metadata(QJsonDocument::fromJson(metadata).object().toVariantMap())
|
, metadata(QJsonDocument::fromJson(metadata).object().toVariantMap())
|
||||||
{
|
, valid(true)
|
||||||
}
|
|
||||||
|
|
||||||
Tag::Tag()
|
|
||||||
: id(-1)
|
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -300,23 +337,26 @@ Tag::Tag(long long id, const Tag &other)
|
||||||
, name(other.name)
|
, name(other.name)
|
||||||
, anchor(other.anchor)
|
, anchor(other.anchor)
|
||||||
, metadata(other.metadata)
|
, metadata(other.metadata)
|
||||||
|
, valid(true)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
bool SQLiteSaveFile::setDatabaseError(const QSqlQuery &q)
|
bool SQLiteSaveFile::setDatabaseError(const QSqlQuery &q) const
|
||||||
{
|
{
|
||||||
if (!q.lastError().isValid())
|
if (!q.lastError().isValid())
|
||||||
return true;
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool SQLiteSaveFile::setDatabaseError(const QSqlDatabase &db)
|
bool SQLiteSaveFile::setDatabaseError(const QSqlDatabase &db) const
|
||||||
{
|
{
|
||||||
if (!db.lastError().isValid())
|
if (!db.lastError().isValid())
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
|
qDebug() << "Database error: " << db.lastError().text();
|
||||||
setError(SQLiteError, QString("Project file database error: %1").arg(db.lastError().text()));
|
setError(SQLiteError, QString("Project file database error: %1").arg(db.lastError().text()));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,86 +16,101 @@ enum TagChange {
|
||||||
class Tag
|
class Tag
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
Tag() : valid(false) {}
|
||||||
Tag(long long int id, QString name, qreal anchor_x, qreal anchor_y, QByteArray metadata);
|
Tag(long long int id, QString name, qreal anchor_x, qreal anchor_y, QByteArray metadata);
|
||||||
Tag();
|
|
||||||
Tag(long long int id, const Tag &other);
|
Tag(long long int id, const Tag &other);
|
||||||
|
|
||||||
|
bool isValid() { return valid; }
|
||||||
|
|
||||||
long long int id;
|
long long int id;
|
||||||
QString name;
|
QString name;
|
||||||
QPointF anchor;
|
QPointF anchor;
|
||||||
QVariantMap metadata;
|
QVariantMap metadata;
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool valid;
|
||||||
};
|
};
|
||||||
|
|
||||||
class SQLiteSaveFile : public QObject
|
class SQLiteSaveFile : public QObject
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
explicit SQLiteSaveFile(QObject *parent = nullptr, QString filename = ":memory:");
|
explicit SQLiteSaveFile(QObject *parent = nullptr);
|
||||||
bool connect();
|
|
||||||
bool isOpen() { return m_isOpen; }
|
|
||||||
|
|
||||||
QList<Tag> getAllTags();
|
QList<Tag> getAllTags();
|
||||||
|
|
||||||
QByteArray getImage();
|
const QByteArray &getImage() const { return m_image; };
|
||||||
|
|
||||||
bool updateTag(Tag tag);
|
bool updateTag(Tag tag);
|
||||||
bool deleteTag(Tag tag);
|
bool deleteTag(Tag tag);
|
||||||
bool createTag(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(const QString &key, const QVariant &value);
|
||||||
bool setMeta(std::initializer_list<QPair<QString, QVariant>> metas);
|
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 {
|
enum Error {
|
||||||
NoError = 0,
|
NoError = 0,
|
||||||
|
FileNotFoundError,
|
||||||
SQLiteError,
|
SQLiteError,
|
||||||
ImageOpenError,
|
ImageOpenError,
|
||||||
ImageReadError
|
ImageReadError,
|
||||||
|
MaxError
|
||||||
};
|
};
|
||||||
inline const static QString errorNames[] = {
|
inline const static QString errorNames[MaxError] = {
|
||||||
[NoError] = "No Error",
|
[NoError] = "No Error",
|
||||||
|
[FileNotFoundError] = "File not found",
|
||||||
[SQLiteError] = "Database Error",
|
[SQLiteError] = "Database Error",
|
||||||
[ImageOpenError] = "Error Opening Image",
|
[ImageOpenError] = "Error Opening Image",
|
||||||
[ImageReadError] = "Error Reading Image"
|
[ImageReadError] = "Error Reading Image"
|
||||||
};
|
};
|
||||||
|
|
||||||
Error error();
|
Error error();
|
||||||
void resetError() { lastError = NoError; lastErrorString = QString(); }
|
void resetError() const { m_lastError = NoError; m_lastErrorString = QString(); }
|
||||||
|
|
||||||
public slots:
|
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.
|
/** 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.
|
* Callers can continue to use the same project file object afterwards.
|
||||||
*/
|
*/
|
||||||
bool saveAs(const QString &filename);
|
bool saveAs(const QString &filename);
|
||||||
|
bool open(const QString &filename);
|
||||||
bool reloadImageFromDisk();
|
bool reloadImageFromDisk();
|
||||||
bool loadImageFromDisk(const QString &filename);
|
bool loadImageFromDisk(const QString &m_filename);
|
||||||
bool clearNew();
|
bool clearNew();
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void tagChange(TagChange change, const Tag &tag);
|
void tagChange(TagChange change, const Tag &tag);
|
||||||
void fileReload();
|
void fileReload();
|
||||||
void fileIOError(Error e, QString errorName, QString description);
|
void fileIOError(Error e, QString errorName, QString description) const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
bool connect();
|
||||||
bool initDb(bool setCreationDate=true);
|
bool initDb(bool setCreationDate=true);
|
||||||
bool runSql(QString query, std::initializer_list<QVariant> bindings={});
|
bool runSql(QString query, std::initializer_list<QVariant> bindings={});
|
||||||
|
|
||||||
bool setMetaLocked(const QString &key, const QVariant &value);
|
bool setMetaLocked(const QString &key, const QVariant &value);
|
||||||
bool setMetaLocked(std::initializer_list<QPair<QString, QVariant>> metas);
|
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); }
|
void setError(Error e, QString desc) const { m_lastError = e; m_lastErrorString = desc; fileIOError(e, errorNames[e], desc); }
|
||||||
bool setDatabaseError(const QSqlQuery &q);
|
bool setDatabaseError(const QSqlQuery &q) const;
|
||||||
bool setDatabaseError(const QSqlDatabase &db);
|
bool setDatabaseError(const QSqlDatabase &m_db) const;
|
||||||
|
|
||||||
Error lastError;
|
mutable Error m_lastError;
|
||||||
QString lastErrorString;
|
mutable QString m_lastErrorString;
|
||||||
QSqlDatabase db;
|
QSqlDatabase m_db;
|
||||||
QMutex dbMut;
|
mutable QMutex m_dbMut;
|
||||||
QString filename;
|
QString m_filename;
|
||||||
QByteArray imageData;
|
QByteArray m_image;
|
||||||
bool m_isOpen;
|
|
||||||
|
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"
|
#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
|
#ifndef TAGSCENE_H
|
||||||
#define TAGSCENE_H
|
#define TAGSCENE_H
|
||||||
|
|
||||||
|
#include "sqlitebackend.h"
|
||||||
|
#include "tagitem.h"
|
||||||
|
|
||||||
|
#include <QGraphicsPixmapItem>
|
||||||
#include <QGraphicsScene>
|
#include <QGraphicsScene>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class TagScene : public QGraphicsScene
|
class TagScene : public QGraphicsScene
|
||||||
{
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
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
|
#endif // TAGSCENE_H
|
||||||
|
|
|
||||||
|
|
@ -5,11 +5,13 @@
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
|
|
||||||
TagView::TagView(SQLiteSaveFile &proj)
|
TagView::TagView(SQLiteSaveFile &proj)
|
||||||
: proj(proj)
|
: scene(proj)
|
||||||
|
, proj(proj)
|
||||||
, saveCenterTimer()
|
, saveCenterTimer()
|
||||||
{
|
{
|
||||||
setDragMode(QGraphicsView::ScrollHandDrag);
|
setDragMode(QGraphicsView::ScrollHandDrag);
|
||||||
setScene(&scene);
|
setScene(&scene);
|
||||||
|
connect(&scene, &TagScene::tagDoubleClicked, this, &TagView::tagDoubleClicked);
|
||||||
|
|
||||||
saveCenterTimer.setSingleShot(true);
|
saveCenterTimer.setSingleShot(true);
|
||||||
saveCenterTimer.setInterval(500);
|
saveCenterTimer.setInterval(500);
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,8 @@
|
||||||
|
|
||||||
class TagView : public QGraphicsView
|
class TagView : public QGraphicsView
|
||||||
{
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
TagView(SQLiteSaveFile &proj);
|
TagView(SQLiteSaveFile &proj);
|
||||||
|
|
||||||
|
|
@ -19,6 +21,9 @@ public slots:
|
||||||
void zoomIn(qreal delta);
|
void zoomIn(qreal delta);
|
||||||
void rotate(int angle);
|
void rotate(int angle);
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void tagDoubleClicked(const Tag &tag);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void wheelEvent(QWheelEvent *evt) override;
|
void wheelEvent(QWheelEvent *evt) override;
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue