Basic model/view action works
This commit is contained in:
parent
752f270cf8
commit
2deadc6cfb
15 changed files with 439 additions and 133 deletions
|
|
@ -29,6 +29,8 @@ Numberator::Numberator(QWidget *parent)
|
||||||
connect(ui->actionReload_Image, &QAction::triggered,
|
connect(ui->actionReload_Image, &QAction::triggered,
|
||||||
&proj, &SQLiteSaveFile::reloadImageFromDisk);
|
&proj, &SQLiteSaveFile::reloadImageFromDisk);
|
||||||
|
|
||||||
|
ui->graphicsView->setProject(&proj);
|
||||||
|
|
||||||
tagsDockUi->tagList->setModel(&tagListModel);
|
tagsDockUi->tagList->setModel(&tagListModel);
|
||||||
tagsDockUi->propertyTable->setModel(&tagPropTableModel);
|
tagsDockUi->propertyTable->setModel(&tagPropTableModel);
|
||||||
|
|
||||||
|
|
@ -78,11 +80,15 @@ Numberator::Numberator(QWidget *parent)
|
||||||
});
|
});
|
||||||
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::selectionChanged,
|
||||||
[=](const QModelIndex ¤t, const QModelIndex &previous) {
|
[=](const QItemSelection &selected, const QItemSelection &deselected) {
|
||||||
Q_UNUSED(previous);
|
Q_UNUSED(deselected);
|
||||||
tagPropTableModel.showTag(tagListModel.getTag(current));
|
tagPropTableModel.showTag(tagListModel.getTag(selected.indexes().first()));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
connect(ui->actionZoom_to_fit, &QAction::triggered, ui->graphicsView, &TagView::zoomToFit);
|
||||||
|
connect(ui->actionZoom_in, &QAction::triggered, ui->graphicsView, &TagView::zoomIn);
|
||||||
|
connect(ui->actionZoom_out, &QAction::triggered, ui->graphicsView, &TagView::zoomOut);
|
||||||
}
|
}
|
||||||
|
|
||||||
Numberator::~Numberator()
|
Numberator::~Numberator()
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@
|
||||||
#include "sqlitebackend.h"
|
#include "sqlitebackend.h"
|
||||||
#include "taglistmodel.h"
|
#include "taglistmodel.h"
|
||||||
#include "tagproptablemodel.h"
|
#include "tagproptablemodel.h"
|
||||||
|
#include "tagview.h"
|
||||||
|
|
||||||
#include <QFileDialog>
|
#include <QFileDialog>
|
||||||
#include <QMainWindow>
|
#include <QMainWindow>
|
||||||
|
|
@ -41,5 +42,6 @@ private:
|
||||||
SQLiteSaveFile proj;
|
SQLiteSaveFile proj;
|
||||||
TagListModel tagListModel;
|
TagListModel tagListModel;
|
||||||
TagPropTableModel tagPropTableModel;
|
TagPropTableModel tagPropTableModel;
|
||||||
|
TagView m_tagView;
|
||||||
};
|
};
|
||||||
#endif // NUMBERATOR_H
|
#endif // NUMBERATOR_H
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@
|
||||||
<widget class="QWidget" name="centralwidget">
|
<widget class="QWidget" name="centralwidget">
|
||||||
<layout class="QVBoxLayout" name="verticalLayout">
|
<layout class="QVBoxLayout" name="verticalLayout">
|
||||||
<item>
|
<item>
|
||||||
<widget class="QGraphicsView" name="graphicsView"/>
|
<widget class="TagView" name="graphicsView"/>
|
||||||
</item>
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
|
|
@ -69,6 +69,9 @@
|
||||||
<property name="title">
|
<property name="title">
|
||||||
<string>View</string>
|
<string>View</string>
|
||||||
</property>
|
</property>
|
||||||
|
<addaction name="actionZoom_to_fit"/>
|
||||||
|
<addaction name="actionZoom_in"/>
|
||||||
|
<addaction name="actionZoom_out"/>
|
||||||
</widget>
|
</widget>
|
||||||
<addaction name="menuFile"/>
|
<addaction name="menuFile"/>
|
||||||
<addaction name="menuEdit"/>
|
<addaction name="menuEdit"/>
|
||||||
|
|
@ -85,11 +88,17 @@
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Undo</string>
|
<string>Undo</string>
|
||||||
</property>
|
</property>
|
||||||
|
<property name="shortcut">
|
||||||
|
<string>Ctrl+Z</string>
|
||||||
|
</property>
|
||||||
</action>
|
</action>
|
||||||
<action name="actionRedo">
|
<action name="actionRedo">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Redo</string>
|
<string>Redo</string>
|
||||||
</property>
|
</property>
|
||||||
|
<property name="shortcut">
|
||||||
|
<string>Ctrl+R</string>
|
||||||
|
</property>
|
||||||
</action>
|
</action>
|
||||||
<action name="actionCopy">
|
<action name="actionCopy">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
|
|
@ -100,46 +109,73 @@
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Cut</string>
|
<string>Cut</string>
|
||||||
</property>
|
</property>
|
||||||
|
<property name="shortcut">
|
||||||
|
<string>Ctrl+X</string>
|
||||||
|
</property>
|
||||||
</action>
|
</action>
|
||||||
<action name="actionPaste">
|
<action name="actionPaste">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Paste</string>
|
<string>Paste</string>
|
||||||
</property>
|
</property>
|
||||||
|
<property name="shortcut">
|
||||||
|
<string>Ctrl+V</string>
|
||||||
|
</property>
|
||||||
</action>
|
</action>
|
||||||
<action name="actionNew_Tag">
|
<action name="actionNew_Tag">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>New Tag</string>
|
<string>New Tag</string>
|
||||||
</property>
|
</property>
|
||||||
|
<property name="shortcut">
|
||||||
|
<string>Ctrl+A</string>
|
||||||
|
</property>
|
||||||
</action>
|
</action>
|
||||||
<action name="actionNew_Project">
|
<action name="actionNew_Project">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>New Project</string>
|
<string>New Project</string>
|
||||||
</property>
|
</property>
|
||||||
|
<property name="shortcut">
|
||||||
|
<string>Ctrl+N</string>
|
||||||
|
</property>
|
||||||
</action>
|
</action>
|
||||||
<action name="actionOpen_Project">
|
<action name="actionOpen_Project">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Open Project</string>
|
<string>Open Project</string>
|
||||||
</property>
|
</property>
|
||||||
|
<property name="shortcut">
|
||||||
|
<string>Ctrl+O</string>
|
||||||
|
</property>
|
||||||
</action>
|
</action>
|
||||||
<action name="actionImport_Image">
|
<action name="actionImport_Image">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Import Image</string>
|
<string>Import Image</string>
|
||||||
</property>
|
</property>
|
||||||
|
<property name="shortcut">
|
||||||
|
<string>Ctrl+I</string>
|
||||||
|
</property>
|
||||||
</action>
|
</action>
|
||||||
<action name="actionReload_Image">
|
<action name="actionReload_Image">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Reload Image</string>
|
<string>Reload Image</string>
|
||||||
</property>
|
</property>
|
||||||
|
<property name="shortcut">
|
||||||
|
<string>F5</string>
|
||||||
|
</property>
|
||||||
</action>
|
</action>
|
||||||
<action name="actionSave_Project">
|
<action name="actionSave_Project">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Save Project</string>
|
<string>Save Project as...</string>
|
||||||
|
</property>
|
||||||
|
<property name="shortcut">
|
||||||
|
<string>Ctrl+Shift+S</string>
|
||||||
</property>
|
</property>
|
||||||
</action>
|
</action>
|
||||||
<action name="actionExport_PDF">
|
<action name="actionExport_PDF">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Export PDF</string>
|
<string>Export PDF</string>
|
||||||
</property>
|
</property>
|
||||||
|
<property name="shortcut">
|
||||||
|
<string>Ctrl+E</string>
|
||||||
|
</property>
|
||||||
</action>
|
</action>
|
||||||
<action name="actionExport_PNG">
|
<action name="actionExport_PNG">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
|
|
@ -150,8 +186,36 @@
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Exit</string>
|
<string>Exit</string>
|
||||||
</property>
|
</property>
|
||||||
|
<property name="shortcut">
|
||||||
|
<string>Ctrl+Q</string>
|
||||||
|
</property>
|
||||||
|
</action>
|
||||||
|
<action name="actionZoom_to_fit">
|
||||||
|
<property name="text">
|
||||||
|
<string>Zoom to fit</string>
|
||||||
|
</property>
|
||||||
|
<property name="shortcut">
|
||||||
|
<string>Ctrl+0</string>
|
||||||
|
</property>
|
||||||
|
</action>
|
||||||
|
<action name="actionZoom_in">
|
||||||
|
<property name="text">
|
||||||
|
<string>Zoom in</string>
|
||||||
|
</property>
|
||||||
|
</action>
|
||||||
|
<action name="actionZoom_out">
|
||||||
|
<property name="text">
|
||||||
|
<string>Zoom out</string>
|
||||||
|
</property>
|
||||||
</action>
|
</action>
|
||||||
</widget>
|
</widget>
|
||||||
|
<customwidgets>
|
||||||
|
<customwidget>
|
||||||
|
<class>TagView</class>
|
||||||
|
<extends>QGraphicsView</extends>
|
||||||
|
<header>tagview.h</header>
|
||||||
|
</customwidget>
|
||||||
|
</customwidgets>
|
||||||
<resources/>
|
<resources/>
|
||||||
<connections/>
|
<connections/>
|
||||||
</ui>
|
</ui>
|
||||||
|
|
|
||||||
|
|
@ -14,8 +14,8 @@ SQLiteSaveFile::SQLiteSaveFile(QObject *parent) :
|
||||||
|
|
||||||
bool SQLiteSaveFile::open(const QString &filename)
|
bool SQLiteSaveFile::open(const QString &filename)
|
||||||
{
|
{
|
||||||
qDebug() << "open";
|
auto dbg = qDebug() << "open";
|
||||||
{
|
{ /* Emit signals only after unlocking mutex to allow accesses by receivers */
|
||||||
QMutexLocker l(&m_dbMut);
|
QMutexLocker l(&m_dbMut);
|
||||||
QFile f(filename);
|
QFile f(filename);
|
||||||
if (!f.exists()) {
|
if (!f.exists()) {
|
||||||
|
|
@ -44,15 +44,16 @@ bool SQLiteSaveFile::open(const QString &filename)
|
||||||
m_dirty = false;
|
m_dirty = false;
|
||||||
m_open = true;
|
m_open = true;
|
||||||
m_image = q.value(0).toByteArray();
|
m_image = q.value(0).toByteArray();
|
||||||
|
dbg << QString("Loaded %1 byte image").arg(m_image.size());
|
||||||
}
|
}
|
||||||
fileReload(); /* Call after unlocking mutex to allow accesses by receivers */
|
fileReload();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool SQLiteSaveFile::clearNew()
|
bool SQLiteSaveFile::clearNew()
|
||||||
{
|
{
|
||||||
qDebug() << "clearNew";
|
qDebug() << "clearNew";
|
||||||
{
|
{ /* Emit signals only after unlocking mutex to allow accesses by receivers */
|
||||||
QMutexLocker l(&m_dbMut);
|
QMutexLocker l(&m_dbMut);
|
||||||
|
|
||||||
QSqlDatabase new_db(QSqlDatabase::addDatabase("QSQLITE", QUuid::createUuid().toString()));
|
QSqlDatabase new_db(QSqlDatabase::addDatabase("QSQLITE", QUuid::createUuid().toString()));
|
||||||
|
|
@ -148,7 +149,7 @@ bool SQLiteSaveFile::saveAs(const QString &filename)
|
||||||
|
|
||||||
QList<Tag> SQLiteSaveFile::getAllTags()
|
QList<Tag> SQLiteSaveFile::getAllTags()
|
||||||
{
|
{
|
||||||
qDebug() << "getAllTags";
|
auto dbg = qDebug() << "getAllTags()";
|
||||||
QMutexLocker l(&m_dbMut);
|
QMutexLocker l(&m_dbMut);
|
||||||
resetError();
|
resetError();
|
||||||
QList<Tag> rv;
|
QList<Tag> rv;
|
||||||
|
|
@ -170,56 +171,134 @@ QList<Tag> SQLiteSaveFile::getAllTags()
|
||||||
if (!setDatabaseError(q))
|
if (!setDatabaseError(q))
|
||||||
return QList<Tag>();
|
return QList<Tag>();
|
||||||
|
|
||||||
|
dbg << QString("%1 tags").arg(rv.size());
|
||||||
return rv;
|
return rv;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool SQLiteSaveFile::updateTag(Tag tag)
|
bool SQLiteSaveFile::updateTag(Tag tag)
|
||||||
{
|
{
|
||||||
QMutexLocker l(&m_dbMut);
|
auto dbg = qDebug() << "updating tag" << tag.id << tag.name;
|
||||||
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
|
QMutexLocker l(&m_dbMut);
|
||||||
}))
|
QSqlQuery q(m_db);
|
||||||
return false;
|
q.prepare("SELECT 1 FROM tags WHERE id=? AND name=?");
|
||||||
|
q.addBindValue(tag.id);
|
||||||
|
q.addBindValue(tag.name);
|
||||||
|
if (!q.exec()) {
|
||||||
|
setDatabaseError(q);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
bool nameChanged = !q.next();
|
||||||
|
if (!setDatabaseError(q))
|
||||||
|
return false;
|
||||||
|
|
||||||
m_dirty = true;
|
if (nameChanged)
|
||||||
|
setMetaLocked("lastTagName", tag.name);
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
dbg << "calling handlers";
|
||||||
tagChange(TagChange::CHANGED, tag);
|
tagChange(TagChange::CHANGED, tag);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool SQLiteSaveFile::deleteTag(Tag tag)
|
bool SQLiteSaveFile::deleteTag(Tag tag)
|
||||||
{
|
{
|
||||||
QMutexLocker l(&m_dbMut);
|
{
|
||||||
if (!runSql("DELETE FROM tags WHERE id=?", {tag.id}))
|
QMutexLocker l(&m_dbMut);
|
||||||
return false;
|
if (!runSql("DELETE FROM tags WHERE id=?", {tag.id}))
|
||||||
|
return false;
|
||||||
|
|
||||||
m_dirty = true;
|
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)
|
||||||
{
|
{
|
||||||
qDebug() << "createTag";
|
|
||||||
QMutexLocker l(&m_dbMut);
|
|
||||||
resetError();
|
|
||||||
QSqlQuery q(m_db);
|
QSqlQuery q(m_db);
|
||||||
q.prepare("INSERT INTO tags(name, anchor_x, anchor_y, meta) VALUES (?, ?, ?, ?)");
|
auto dbg = qDebug() << "createTag";
|
||||||
q.addBindValue(tag.name);
|
{
|
||||||
q.addBindValue(tag.anchor.x());
|
QMutexLocker l(&m_dbMut);
|
||||||
q.addBindValue(tag.anchor.y());
|
resetError();
|
||||||
q.addBindValue(QJsonDocument::fromVariant(tag.metadata).toJson());
|
if (!runSql("INSERT INTO tags(name, anchor_x, anchor_y, meta) VALUES (?, ?, ?, ?)", {
|
||||||
|
tag.name,
|
||||||
|
tag.anchor.x(),
|
||||||
|
tag.anchor.y(),
|
||||||
|
QJsonDocument::fromVariant(tag.metadata).toJson()
|
||||||
|
})) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
m_dirty = true;
|
||||||
|
}
|
||||||
|
auto id = q.lastInsertId().toLongLong();
|
||||||
|
dbg << "id" << id;
|
||||||
|
tagChange(TagChange::CREATED, Tag(id, tag));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
if (!setDatabaseError(q))
|
QString SQLiteSaveFile::getNextAutoTagName()
|
||||||
|
{
|
||||||
|
QVariant lookupResult = getMeta("lastTagName");
|
||||||
|
QString lastTagName = "U0";
|
||||||
|
if (lookupResult.isValid() && !lookupResult.toString().isNull())
|
||||||
|
lastTagName = lookupResult.toString();
|
||||||
|
QString newName = "U1";
|
||||||
|
|
||||||
|
QRegularExpression name_re("^(.*?)(\\d+)$");
|
||||||
|
auto res = name_re.match(lastTagName);
|
||||||
|
if (res.hasMatch()) {
|
||||||
|
bool ok = false;
|
||||||
|
int numericSuffix = res.captured(2).toInt(&ok);
|
||||||
|
QString stringPrefix = res.captured(1);
|
||||||
|
qDebug() << "Name has match" << stringPrefix << numericSuffix;
|
||||||
|
|
||||||
|
if (ok) {
|
||||||
|
do {
|
||||||
|
numericSuffix ++;
|
||||||
|
newName = QString("%1%2").arg(stringPrefix).arg(numericSuffix);
|
||||||
|
} while (numericSuffix<10000 && !tagNameIsFree(newName));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return newName;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SQLiteSaveFile::createTagAt(const QPointF &anchor)
|
||||||
|
{
|
||||||
|
QString newName = getNextAutoTagName();
|
||||||
|
if (!setMeta("lastTagName", newName))
|
||||||
|
return false;
|
||||||
|
return createTag(Tag(newName, anchor));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SQLiteSaveFile::tagNameIsFree(const QString &name)
|
||||||
|
{
|
||||||
|
QMutexLocker l(&m_dbMut);
|
||||||
|
QSqlQuery q(m_db);
|
||||||
|
q.prepare("SELECT name FROM tags WHERE name=?");
|
||||||
|
q.addBindValue(name);
|
||||||
|
if (!q.exec()) {
|
||||||
|
setDatabaseError(q);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (q.next())
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
Tag created_tag(q.lastInsertId().toLongLong(), tag);
|
setDatabaseError(q);
|
||||||
m_dirty = true;
|
|
||||||
tagChange(TagChange::CREATED, created_tag);
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool SQLiteSaveFile::setMetaLocked(const QString &key, const QVariant &value)
|
bool SQLiteSaveFile::setMetaLocked(const QString &key, const QVariant &value)
|
||||||
{
|
{
|
||||||
|
qDebug() << QString("setMeta: %1=%2").arg(key).arg(value.toString());
|
||||||
return runSql("INSERT OR REPLACE INTO metadata(key, value) VALUES (?, ?)", {key, value});
|
return runSql("INSERT OR REPLACE INTO metadata(key, value) VALUES (?, ?)", {key, value});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -232,15 +311,15 @@ bool SQLiteSaveFile::setMetaLocked(std::initializer_list<QPair<QString, QVariant
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool SQLiteSaveFile::setMeta(const QString &key, const QVariant &value) {
|
bool SQLiteSaveFile::setMeta(const QString &key, const QVariant &value, bool setDirty) {
|
||||||
QMutexLocker l(&m_dbMut);
|
QMutexLocker l(&m_dbMut);
|
||||||
m_dirty = true;
|
m_dirty = m_dirty || setDirty;
|
||||||
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, bool setDirty) {
|
||||||
QMutexLocker l(&m_dbMut);
|
QMutexLocker l(&m_dbMut);
|
||||||
m_dirty = true;
|
m_dirty = m_dirty || setDirty;
|
||||||
return setMetaLocked(metas);
|
return setMetaLocked(metas);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -251,7 +330,6 @@ const QVariant SQLiteSaveFile::getMeta(const QString &key) const {
|
||||||
|
|
||||||
const QVariant SQLiteSaveFile::getMetaLocked(const QString &key) const
|
const QVariant SQLiteSaveFile::getMetaLocked(const QString &key) const
|
||||||
{
|
{
|
||||||
qDebug() << "getMeta " << key;
|
|
||||||
resetError();
|
resetError();
|
||||||
QSqlQuery q(m_db);
|
QSqlQuery q(m_db);
|
||||||
q.prepare("SELECT value FROM metadata WHERE key=?");
|
q.prepare("SELECT value FROM metadata WHERE key=?");
|
||||||
|
|
@ -267,6 +345,7 @@ const QVariant SQLiteSaveFile::getMetaLocked(const QString &key) const
|
||||||
return QVariant();
|
return QVariant();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
qDebug() << QString("getMeta: %1=%2").arg(key).arg(q.value(0).toString());
|
||||||
return q.value(0);
|
return q.value(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -288,29 +367,35 @@ bool SQLiteSaveFile::runSql(QString query, std::initializer_list<QVariant> bindi
|
||||||
|
|
||||||
bool SQLiteSaveFile::loadImageFromDisk(const QString &filename)
|
bool SQLiteSaveFile::loadImageFromDisk(const QString &filename)
|
||||||
{
|
{
|
||||||
QMutexLocker l(&m_dbMut);
|
{
|
||||||
QFile f(filename);
|
QMutexLocker l(&m_dbMut);
|
||||||
resetError();
|
QFile f(filename);
|
||||||
|
resetError();
|
||||||
if (!f.open(QIODevice::ReadOnly)) {
|
|
||||||
setError(ImageOpenError, QString("Failed to open image: %1").arg(f.errorString()));
|
if (!f.open(QIODevice::ReadOnly)) {
|
||||||
return false;
|
setError(ImageOpenError, QString("Failed to open image: %1").arg(f.errorString()));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_image = f.readAll();
|
||||||
|
if (f.error() != QFileDevice::NoError) {
|
||||||
|
setError(ImageReadError, QString("Failed to read image: %1").arg(f.errorString()));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!setMetaLocked({
|
||||||
|
{"imagePathOriginal", f.fileName()},
|
||||||
|
{"imagePathAbsolute", QFileInfo(f).absoluteFilePath()},
|
||||||
|
{"imageLoadedTime", QDateTime::currentDateTimeUtc().toMSecsSinceEpoch()}}))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
m_dirty = true;
|
||||||
|
if (!runSql("INSERT OR REPLACE INTO blobs(name, data) VALUES ('image', ?)", {m_image}))
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
/* Emit signal with mutex unlocked */
|
||||||
m_image = f.readAll();
|
imageLoaded(m_image);
|
||||||
if (f.error() != QFileDevice::NoError) {
|
return true;
|
||||||
setError(ImageReadError, QString("Failed to read image: %1").arg(f.errorString()));
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!setMetaLocked({
|
|
||||||
{"imagePathOriginal", f.fileName()},
|
|
||||||
{"imagePathAbsolute", QFileInfo(f).absoluteFilePath()},
|
|
||||||
{"imageLoadedTime", QDateTime::currentDateTimeUtc().toMSecsSinceEpoch()}}))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
m_dirty = true;
|
|
||||||
return runSql("INSERT OR REPLACE INTO blobs(name, data) VALUES ('image', ?)", {m_image});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool SQLiteSaveFile::reloadImageFromDisk()
|
bool SQLiteSaveFile::reloadImageFromDisk()
|
||||||
|
|
@ -344,6 +429,15 @@ Tag::Tag(long long id, const Tag &other)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Tag::Tag(QString name, const QPointF &anchor, const QVariantMap metadata)
|
||||||
|
: id(-1)
|
||||||
|
, name(name)
|
||||||
|
, anchor(anchor)
|
||||||
|
, metadata(metadata)
|
||||||
|
, valid(false)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
bool SQLiteSaveFile::setDatabaseError(const QSqlQuery &q) const
|
bool SQLiteSaveFile::setDatabaseError(const QSqlQuery &q) const
|
||||||
{
|
{
|
||||||
if (!q.lastError().isValid())
|
if (!q.lastError().isValid())
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ public:
|
||||||
Tag() : valid(false) {}
|
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(long long int id, const Tag &other);
|
Tag(long long int id, const Tag &other);
|
||||||
|
Tag(QString name, const QPointF &anchor, const QVariantMap metadata=QVariantMap());
|
||||||
|
|
||||||
bool isValid() { return valid; }
|
bool isValid() { return valid; }
|
||||||
|
|
||||||
|
|
@ -44,13 +45,17 @@ public:
|
||||||
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 createTagAt(const QPointF &anchor);
|
||||||
|
QString getNextAutoTagName();
|
||||||
|
|
||||||
|
bool tagNameIsFree(const QString &name);
|
||||||
|
|
||||||
bool isMemory() { return m_memory; } /* backend db points to temporary memory db */
|
bool isMemory() { return m_memory; } /* backend db points to temporary memory db */
|
||||||
bool isDirty() { return m_dirty; } /* backend db was changed since opening */
|
bool isDirty() { return m_dirty; } /* backend db was changed since opening */
|
||||||
bool isOpen() { return m_open; } /* backend db is open */
|
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 setDirty=false);
|
||||||
bool setMeta(std::initializer_list<QPair<QString, QVariant>> metas);
|
bool setMeta(std::initializer_list<QPair<QString, QVariant>> metas, bool setDirty=false);
|
||||||
const QVariant getMeta(const QString &key) const;
|
const QVariant getMeta(const QString &key) const;
|
||||||
|
|
||||||
const QString &errorString() const { return m_lastErrorString; }
|
const QString &errorString() const { return m_lastErrorString; }
|
||||||
|
|
@ -87,6 +92,7 @@ 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) const;
|
void fileIOError(Error e, QString errorName, QString description) const;
|
||||||
|
void imageLoaded(const QByteArray &image);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool connect();
|
bool connect();
|
||||||
|
|
|
||||||
67
tagitem.cpp
67
tagitem.cpp
|
|
@ -1,25 +1,86 @@
|
||||||
#include "tagitem.h"
|
#include "tagitem.h"
|
||||||
|
|
||||||
|
#include <QPainter>
|
||||||
|
#include <QGuiApplication>
|
||||||
|
|
||||||
TagItem::TagItem(const Tag &tag)
|
TagItem::TagItem(const Tag &tag)
|
||||||
: valid(true)
|
: valid(true)
|
||||||
{
|
{
|
||||||
setFlags(QGraphicsItem::ItemIsMovable
|
setFlags(QGraphicsItem::ItemIsMovable
|
||||||
| QGraphicsItem::ItemIsSelectable
|
| QGraphicsItem::ItemIsSelectable
|
||||||
| QGraphicsItem::ItemIsFocusable);
|
| QGraphicsItem::ItemIsFocusable
|
||||||
/* TODO text_it.setFlags(QGraphicsItem::ItemIgnoresTransformations);
|
| QGraphicsItem::ItemIgnoresTransformations
|
||||||
*/
|
| QGraphicsItem::ItemSendsGeometryChanges);
|
||||||
|
QFont font(QGuiApplication::font());
|
||||||
|
font.setPointSize(18);
|
||||||
|
setFont(font);
|
||||||
tagUpdated(tag);
|
tagUpdated(tag);
|
||||||
}
|
}
|
||||||
|
|
||||||
void TagItem::tagUpdated(const Tag &tag)
|
void TagItem::tagUpdated(const Tag &tag)
|
||||||
{
|
{
|
||||||
m_tag = tag;
|
m_tag = tag;
|
||||||
|
qDebug() << "TagItem updated" << tag.name << tag.anchor;
|
||||||
setText(tag.name);
|
setText(tag.name);
|
||||||
setPos(tag.anchor);
|
setPos(tag.anchor);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QRectF TagItem::boundingRect() const
|
||||||
|
{
|
||||||
|
QRectF parentRect(QGraphicsSimpleTextItem::boundingRect());
|
||||||
|
parentRect.translate(-(parentRect.bottomRight() - parentRect.topLeft()) * 0.5);
|
||||||
|
return parentRect.marginsAdded(QMargins(5, 5, 5, 5));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* For some reason this is not exposed through the public API so we have to copy-paste it here m( */
|
||||||
|
static void paintSelectionHighlightBorder(QPainter *painter, const QStyleOptionGraphicsItem *option, TagItem *item)
|
||||||
|
{
|
||||||
|
const QRectF mbrect = painter->transform().mapRect(item->boundingRect());
|
||||||
|
if (qMin(mbrect.width(), mbrect.height()) < qreal(1.0))
|
||||||
|
return;
|
||||||
|
|
||||||
|
const qreal pad = item->pen().widthF() / 2;
|
||||||
|
const qreal penWidth = 0; // cosmetic pen
|
||||||
|
const QColor fgcolor = option->palette.windowText().color();
|
||||||
|
const QColor bgcolor( // ensure good contrast against fgcolor
|
||||||
|
fgcolor.red() > 127 ? 0 : 255,
|
||||||
|
fgcolor.green() > 127 ? 0 : 255,
|
||||||
|
fgcolor.blue() > 127 ? 0 : 255);
|
||||||
|
painter->setPen(QPen(bgcolor, penWidth, Qt::SolidLine));
|
||||||
|
painter->setBrush(Qt::NoBrush);
|
||||||
|
painter->drawRect(item->boundingRect().adjusted(pad, pad, -pad, -pad));
|
||||||
|
painter->setPen(QPen(option->palette.windowText(), 0, Qt::DashLine));
|
||||||
|
painter->setBrush(Qt::NoBrush);
|
||||||
|
painter->drawRect(item->boundingRect().adjusted(pad, pad, -pad, -pad));
|
||||||
|
}
|
||||||
|
|
||||||
|
void TagItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
|
||||||
|
{
|
||||||
|
painter->setBrush(Qt::white);
|
||||||
|
painter->drawRect(boundingRect());
|
||||||
|
|
||||||
|
QRectF parentRect(QGraphicsSimpleTextItem::boundingRect());
|
||||||
|
QPointF pos = (parentRect.bottomRight() - parentRect.topLeft()) * 0.5;
|
||||||
|
painter->translate(-pos);
|
||||||
|
|
||||||
|
QStyleOptionGraphicsItem newOption(*option);
|
||||||
|
newOption.state = ~(QStyle::State_Selected |QStyle::State_HasFocus);
|
||||||
|
QGraphicsSimpleTextItem::paint(painter, &newOption, widget);
|
||||||
|
|
||||||
|
painter->translate(pos);
|
||||||
|
if (option->state & (QStyle::State_Selected | QStyle::State_HasFocus))
|
||||||
|
paintSelectionHighlightBorder(painter, option, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
QPainterPath TagItem::shape() const
|
||||||
|
{
|
||||||
|
QPainterPath path;
|
||||||
|
return QGraphicsItem::shape();
|
||||||
|
}
|
||||||
|
|
||||||
QVariant TagItem::itemChange(QGraphicsItem::GraphicsItemChange change, const QVariant &value)
|
QVariant TagItem::itemChange(QGraphicsItem::GraphicsItemChange change, const QVariant &value)
|
||||||
{
|
{
|
||||||
|
qDebug() << "itemChange" << m_tag.name << this->boundingRect() << change;
|
||||||
if (change == ItemPositionChange) {
|
if (change == ItemPositionChange) {
|
||||||
/* https://gist.github.com/csukuangfj/c2a06416062bec9ed99eddd705c21275#file-qgraphicsscenetest-cpp-L90
|
/* https://gist.github.com/csukuangfj/c2a06416062bec9ed99eddd705c21275#file-qgraphicsscenetest-cpp-L90
|
||||||
*
|
*
|
||||||
|
|
|
||||||
|
|
@ -14,13 +14,14 @@ public:
|
||||||
|
|
||||||
enum { TagItemType = UserType + 1 };
|
enum { TagItemType = UserType + 1 };
|
||||||
int type() const override { return TagItemType; }
|
int type() const override { return TagItemType; }
|
||||||
|
|
||||||
bool isValid() { return valid; }
|
bool isValid() { return valid; }
|
||||||
|
|
||||||
void tagUpdated(const Tag &tag);
|
void tagUpdated(const Tag &tag);
|
||||||
|
|
||||||
Tag tag() { return m_tag; }
|
Tag tag() { return m_tag; }
|
||||||
|
|
||||||
|
virtual QRectF boundingRect() const override;
|
||||||
|
virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override;
|
||||||
|
virtual QPainterPath shape() const override;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
QVariant itemChange(QGraphicsItem::GraphicsItemChange change, const QVariant &value) override;
|
QVariant itemChange(QGraphicsItem::GraphicsItemChange change, const QVariant &value) override;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ TagListModel::TagListModel(SQLiteSaveFile &backend)
|
||||||
: backend(backend)
|
: backend(backend)
|
||||||
, cached_tags(backend.getAllTags())
|
, cached_tags(backend.getAllTags())
|
||||||
{
|
{
|
||||||
|
qDebug() << "connecting TagListModel" << &backend;
|
||||||
connect(&backend, &SQLiteSaveFile::tagChange,
|
connect(&backend, &SQLiteSaveFile::tagChange,
|
||||||
[=](TagChange change, const Tag &tag) { Q_UNUSED(change); Q_UNUSED(tag); reloadTags(); });
|
[=](TagChange change, const Tag &tag) { Q_UNUSED(change); Q_UNUSED(tag); reloadTags(); });
|
||||||
connect(&backend, &SQLiteSaveFile::fileReload,
|
connect(&backend, &SQLiteSaveFile::fileReload,
|
||||||
|
|
@ -28,10 +29,11 @@ QVariant TagListModel::data(const QModelIndex &index, int role) const
|
||||||
if (!index.isValid())
|
if (!index.isValid())
|
||||||
return QVariant();
|
return QVariant();
|
||||||
|
|
||||||
if (role != Qt::DisplayRole)
|
if (role != Qt::DisplayRole && role != Qt::EditRole)
|
||||||
return QVariant();
|
return QVariant();
|
||||||
|
|
||||||
return cached_tags.at(index.row()).name;
|
auto rv = cached_tags.at(index.row()).name;
|
||||||
|
return rv;
|
||||||
}
|
}
|
||||||
|
|
||||||
QVariant TagListModel::headerData(int section, Qt::Orientation orientation, int role) const
|
QVariant TagListModel::headerData(int section, Qt::Orientation orientation, int role) const
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,8 @@
|
||||||
|
|
||||||
class TagListModel : public QAbstractListModel
|
class TagListModel : public QAbstractListModel
|
||||||
{
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
TagListModel(SQLiteSaveFile &backend);
|
TagListModel(SQLiteSaveFile &backend);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,26 +1,24 @@
|
||||||
#include "tagproptablemodel.h"
|
#include "tagproptablemodel.h"
|
||||||
|
|
||||||
TagPropTableModel::TagPropTableModel(SQLiteSaveFile &backend, const Tag tag)
|
TagPropTableModel::TagPropTableModel(SQLiteSaveFile &backend, const Tag tag)
|
||||||
: backend(backend)
|
: TagPropTableModel(backend, true)
|
||||||
, tag_cached(tag)
|
|
||||||
, tagIsValid(true)
|
|
||||||
{
|
{
|
||||||
|
tag_cached = tag;
|
||||||
tag_keys = tag_cached.metadata.keys();
|
tag_keys = tag_cached.metadata.keys();
|
||||||
tag_keys.sort();
|
tag_keys.sort();
|
||||||
connect(&backend, &SQLiteSaveFile::tagChange,
|
qDebug() << "connecting TagPropTableModel" << &backend;
|
||||||
this, &TagPropTableModel::tagChange);
|
|
||||||
connect(&backend, &SQLiteSaveFile::fileReload,
|
|
||||||
[=]() {
|
|
||||||
beginResetModel();
|
|
||||||
tagIsValid = false;
|
|
||||||
endResetModel();
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TagPropTableModel::TagPropTableModel(SQLiteSaveFile &backend)
|
TagPropTableModel::TagPropTableModel(SQLiteSaveFile &backend, bool tagIsValid)
|
||||||
: backend(backend)
|
: backend(backend)
|
||||||
, tagIsValid(false)
|
, tagIsValid(tagIsValid)
|
||||||
{
|
{
|
||||||
|
connect(&backend, &SQLiteSaveFile::tagChange, this, &TagPropTableModel::tagChange);
|
||||||
|
connect(&backend, &SQLiteSaveFile::fileReload, [=]() {
|
||||||
|
beginResetModel();
|
||||||
|
this->tagIsValid = false;
|
||||||
|
endResetModel();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
int TagPropTableModel::rowCount(const QModelIndex &parent) const
|
int TagPropTableModel::rowCount(const QModelIndex &parent) const
|
||||||
|
|
@ -42,7 +40,7 @@ QVariant TagPropTableModel::data(const QModelIndex &index, int role) const
|
||||||
if (!index.isValid())
|
if (!index.isValid())
|
||||||
return QVariant();
|
return QVariant();
|
||||||
|
|
||||||
if (role != Qt::DisplayRole)
|
if (role != Qt::DisplayRole && role != Qt::EditRole)
|
||||||
return QVariant();
|
return QVariant();
|
||||||
|
|
||||||
bool label = index.column() == 0;
|
bool label = index.column() == 0;
|
||||||
|
|
@ -50,7 +48,7 @@ QVariant TagPropTableModel::data(const QModelIndex &index, int role) const
|
||||||
switch (index.row()) {
|
switch (index.row()) {
|
||||||
case 0: return label ? QVariant("Database ID") : tag_cached.id; break;
|
case 0: return label ? QVariant("Database ID") : tag_cached.id; break;
|
||||||
case 1: return label ? QVariant("Label") : tag_cached.name; break;
|
case 1: return label ? QVariant("Label") : tag_cached.name; break;
|
||||||
case 2: return label ? QVariant("Anchor") : tag_cached.anchor; break;
|
case 2: return label ? QVariant("Anchor") : QString("%1, %2").arg(tag_cached.anchor.x()).arg(tag_cached.anchor.y()); break;
|
||||||
}
|
}
|
||||||
|
|
||||||
int idx = index.row() - 3;
|
int idx = index.row() - 3;
|
||||||
|
|
@ -100,18 +98,19 @@ bool TagPropTableModel::setData(const QModelIndex &index, const QVariant &value,
|
||||||
if (role != Qt::EditRole)
|
if (role != Qt::EditRole)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if (index.row() == 1)
|
if (index.row() == 1) {
|
||||||
tag_cached.name = value.toString();
|
tag_cached.name = value.toString();
|
||||||
else if (index.row() < 3)
|
} else if (index.row() < 3) {
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
int idx = index.row() - 3;
|
|
||||||
if (index.column() == 0) { /* key changed */
|
|
||||||
/* move value to new key and delete old key */
|
|
||||||
tag_cached.metadata[value.toString()] = tag_cached.metadata[tag_keys[idx]];
|
|
||||||
tag_cached.metadata.remove(tag_keys[idx]);
|
|
||||||
} else {
|
} else {
|
||||||
tag_cached.metadata[tag_keys[idx]] = value.toString();
|
int idx = index.row() - 3;
|
||||||
|
if (index.column() == 0) { /* key changed */
|
||||||
|
/* move value to new key and delete old key */
|
||||||
|
tag_cached.metadata[value.toString()] = tag_cached.metadata[tag_keys[idx]];
|
||||||
|
tag_cached.metadata.remove(tag_keys[idx]);
|
||||||
|
} else {
|
||||||
|
tag_cached.metadata[tag_keys[idx]] = value.toString();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
backend.updateTag(tag_cached);
|
backend.updateTag(tag_cached);
|
||||||
|
|
@ -120,13 +119,17 @@ bool TagPropTableModel::setData(const QModelIndex &index, const QVariant &value,
|
||||||
|
|
||||||
void TagPropTableModel::tagChange(TagChange change, const Tag &tag)
|
void TagPropTableModel::tagChange(TagChange change, const Tag &tag)
|
||||||
{
|
{
|
||||||
|
auto dbg = qDebug() << QString("TagPropTableModel::tagChange(%1, %2 \"%3\")").arg(change).arg(tag.id).arg(tag.name);
|
||||||
if (tag.id != tag_cached.id)
|
if (tag.id != tag_cached.id)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
assert(change != TagChange::CREATED);
|
assert(change != TagChange::CREATED);
|
||||||
if (change == TagChange::CHANGED) {
|
if (change == TagChange::CHANGED) {
|
||||||
|
dbg << "changed";
|
||||||
showTag(tag);
|
showTag(tag);
|
||||||
|
|
||||||
} else if (change == TagChange::DELETED) {
|
} else if (change == TagChange::DELETED) {
|
||||||
|
dbg << "deleted";
|
||||||
beginResetModel();
|
beginResetModel();
|
||||||
tagIsValid = false;
|
tagIsValid = false;
|
||||||
endResetModel();
|
endResetModel();
|
||||||
|
|
@ -140,5 +143,6 @@ void TagPropTableModel::showTag(const Tag &tag)
|
||||||
tag_cached = tag;
|
tag_cached = tag;
|
||||||
tag_keys = tag_cached.metadata.keys();
|
tag_keys = tag_cached.metadata.keys();
|
||||||
tag_keys.sort();
|
tag_keys.sort();
|
||||||
|
tagIsValid = true;
|
||||||
endResetModel();
|
endResetModel();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,8 +9,10 @@
|
||||||
|
|
||||||
class TagPropTableModel : public QAbstractTableModel
|
class TagPropTableModel : public QAbstractTableModel
|
||||||
{
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
TagPropTableModel(SQLiteSaveFile &backend);
|
TagPropTableModel(SQLiteSaveFile &backend) : TagPropTableModel(backend, false) {};
|
||||||
TagPropTableModel(SQLiteSaveFile &backend, const Tag tag);
|
TagPropTableModel(SQLiteSaveFile &backend, const Tag tag);
|
||||||
|
|
||||||
int rowCount(const QModelIndex &parent=QModelIndex()) const override;
|
int rowCount(const QModelIndex &parent=QModelIndex()) const override;
|
||||||
|
|
@ -27,6 +29,8 @@ private slots:
|
||||||
void tagChange(TagChange change, const Tag &tag);
|
void tagChange(TagChange change, const Tag &tag);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
TagPropTableModel(SQLiteSaveFile &backend, bool tagIsValid);
|
||||||
|
|
||||||
SQLiteSaveFile &backend;
|
SQLiteSaveFile &backend;
|
||||||
Tag tag_cached;
|
Tag tag_cached;
|
||||||
QStringList tag_keys;
|
QStringList tag_keys;
|
||||||
|
|
|
||||||
51
tagscene.cpp
51
tagscene.cpp
|
|
@ -2,15 +2,16 @@
|
||||||
|
|
||||||
#include <QGraphicsSceneMouseEvent>
|
#include <QGraphicsSceneMouseEvent>
|
||||||
|
|
||||||
TagScene::TagScene(SQLiteSaveFile &proj)
|
void TagScene::setProject(SQLiteSaveFile *proj) {
|
||||||
: proj(proj)
|
if (m_proj)
|
||||||
{
|
disconnect(m_proj, nullptr, this, nullptr);
|
||||||
reloadPicture();
|
m_proj = proj;
|
||||||
reloadTags();
|
|
||||||
|
|
||||||
connect(&proj, &SQLiteSaveFile::tagChange, this, &TagScene::tagChanged);
|
reloadScene();
|
||||||
connect(&proj, &SQLiteSaveFile::fileReload,
|
|
||||||
[=]() { reloadTags(); });
|
connect(m_proj, &SQLiteSaveFile::tagChange, this, &TagScene::tagChanged);
|
||||||
|
connect(m_proj, &SQLiteSaveFile::fileReload, this, &TagScene::reloadScene);
|
||||||
|
connect(m_proj, &SQLiteSaveFile::imageLoaded, this, &TagScene::reloadPicture);
|
||||||
}
|
}
|
||||||
|
|
||||||
void TagScene::tagChanged(TagChange change, const Tag &tag)
|
void TagScene::tagChanged(TagChange change, const Tag &tag)
|
||||||
|
|
@ -40,11 +41,22 @@ void TagScene::tagChanged(TagChange change, const Tag &tag)
|
||||||
|
|
||||||
void TagScene::reloadPicture()
|
void TagScene::reloadPicture()
|
||||||
{
|
{
|
||||||
pix.loadFromData(proj.getImage());
|
if (!m_proj)
|
||||||
|
return;
|
||||||
|
|
||||||
|
pix.loadFromData(m_proj->getImage());
|
||||||
if (pix_it)
|
if (pix_it)
|
||||||
removeItem(pix_it);
|
removeItem(pix_it);
|
||||||
pix_it = new QGraphicsPixmapItem(pix);
|
pix_it = new QGraphicsPixmapItem(pix);
|
||||||
|
pix_it->setZValue(-1);
|
||||||
addItem(pix_it);
|
addItem(pix_it);
|
||||||
|
QRectF imgBounds = pix_it->boundingRect();
|
||||||
|
imgBounds = imgBounds.marginsAdded(QMargins(imgBounds.width() * 0.5, imgBounds.height() * 0.5, imgBounds.width() * 0.5, imgBounds.height() * 0.5));
|
||||||
|
setSceneRect(itemsBoundingRect().united(imgBounds));
|
||||||
|
qDebug() << "TagScene: reloadPicture()" << pix_it->boundingRect() << pix_it << pix << pix_it->isActive();
|
||||||
|
|
||||||
|
invalidate();
|
||||||
|
imageLoaded();
|
||||||
}
|
}
|
||||||
|
|
||||||
void TagScene::addTag(const Tag tag) {
|
void TagScene::addTag(const Tag tag) {
|
||||||
|
|
@ -53,16 +65,20 @@ void TagScene::addTag(const Tag tag) {
|
||||||
tags[tag.id] = it;
|
tags[tag.id] = it;
|
||||||
}
|
}
|
||||||
|
|
||||||
void TagScene::reloadTags()
|
void TagScene::reloadScene()
|
||||||
{
|
{
|
||||||
clear();
|
clear();
|
||||||
for (auto *it : tags.values()) {
|
for (auto *it : tags.values())
|
||||||
delete it;
|
delete it;
|
||||||
}
|
pix_it = nullptr;
|
||||||
|
|
||||||
for (const Tag &tag : proj.getAllTags()) {
|
if (!m_proj)
|
||||||
|
return;
|
||||||
|
|
||||||
|
for (const Tag &tag : m_proj->getAllTags())
|
||||||
addTag(tag);
|
addTag(tag);
|
||||||
}
|
|
||||||
|
reloadPicture(); /* calls invalidate() for us */
|
||||||
}
|
}
|
||||||
|
|
||||||
void TagScene::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event)
|
void TagScene::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event)
|
||||||
|
|
@ -70,11 +86,18 @@ void TagScene::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event)
|
||||||
QGraphicsItem *it = itemAt(event->scenePos(), QTransform());
|
QGraphicsItem *it = itemAt(event->scenePos(), QTransform());
|
||||||
if (!it) {
|
if (!it) {
|
||||||
QGraphicsScene::mouseDoubleClickEvent(event);
|
QGraphicsScene::mouseDoubleClickEvent(event);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (it == pix_it) {
|
||||||
|
m_proj->createTagAt(event->scenePos());
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
TagItem *tagitem = qgraphicsitem_cast<TagItem *>(it);
|
TagItem *tagitem = qgraphicsitem_cast<TagItem *>(it);
|
||||||
if (!tagitem) {
|
if (!tagitem) {
|
||||||
QGraphicsScene::mouseDoubleClickEvent(event);
|
QGraphicsScene::mouseDoubleClickEvent(event);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
tagDoubleClicked(tagitem->tag());
|
tagDoubleClicked(tagitem->tag());
|
||||||
|
|
|
||||||
12
tagscene.h
12
tagscene.h
|
|
@ -14,14 +14,18 @@ class TagScene : public QGraphicsScene
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
TagScene(SQLiteSaveFile &proj);
|
TagScene() {}
|
||||||
|
const QGraphicsPixmapItem *backgroundPixmapItem() const { return pix_it; }
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void reloadPicture();
|
void reloadPicture();
|
||||||
void reloadTags();
|
void reloadScene();
|
||||||
|
|
||||||
|
void setProject(SQLiteSaveFile *proj);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void tagDoubleClicked(const Tag &tag);
|
void tagDoubleClicked(const Tag &tag);
|
||||||
|
void imageLoaded();
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event) override;
|
void mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event) override;
|
||||||
|
|
@ -32,9 +36,9 @@ private slots:
|
||||||
private:
|
private:
|
||||||
void addTag(const Tag tag);
|
void addTag(const Tag tag);
|
||||||
|
|
||||||
SQLiteSaveFile &proj;
|
SQLiteSaveFile *m_proj = nullptr;
|
||||||
|
|
||||||
QGraphicsPixmapItem *pix_it;
|
QGraphicsPixmapItem *pix_it = nullptr;
|
||||||
QPixmap pix;
|
QPixmap pix;
|
||||||
QMap<long long int, TagItem*> tags;
|
QMap<long long int, TagItem*> tags;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
59
tagview.cpp
59
tagview.cpp
|
|
@ -4,34 +4,41 @@
|
||||||
#include <QScrollBar>
|
#include <QScrollBar>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
|
|
||||||
TagView::TagView(SQLiteSaveFile &proj)
|
TagView::TagView(QWidget *parent)
|
||||||
: scene(proj)
|
: QGraphicsView(parent)
|
||||||
, proj(proj)
|
, saveCenterTimer(this)
|
||||||
, saveCenterTimer()
|
|
||||||
{
|
{
|
||||||
setDragMode(QGraphicsView::ScrollHandDrag);
|
setDragMode(QGraphicsView::ScrollHandDrag);
|
||||||
setScene(&scene);
|
setScene(&m_scene);
|
||||||
connect(&scene, &TagScene::tagDoubleClicked, this, &TagView::tagDoubleClicked);
|
connect(&m_scene, &TagScene::tagDoubleClicked, this, &TagView::tagDoubleClicked);
|
||||||
|
connect(&m_scene, &TagScene::imageLoaded, this, &TagView::zoomToFit);
|
||||||
|
|
||||||
saveCenterTimer.setSingleShot(true);
|
saveCenterTimer.setSingleShot(true);
|
||||||
saveCenterTimer.setInterval(500);
|
saveCenterTimer.setInterval(500);
|
||||||
connect(&saveCenterTimer, &QTimer::timeout,
|
connect(&saveCenterTimer, &QTimer::timeout, this, &TagView::saveCenter);
|
||||||
this, &TagView::saveCenter);
|
}
|
||||||
|
|
||||||
|
TagView::~TagView()
|
||||||
|
{
|
||||||
|
were_done = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void TagView::zoomToFit()
|
void TagView::zoomToFit()
|
||||||
{
|
{
|
||||||
QTransform tx = QTransform().rotate(-rotation);
|
QTransform tx = QTransform().rotate(-rotation);
|
||||||
QRectF rect = tx.mapRect(scene.itemsBoundingRect());
|
QRectF rect = tx.mapRect(m_scene.itemsBoundingRect());
|
||||||
QRectF vp = viewport()->rect();
|
QRectF vp = viewport()->rect();
|
||||||
|
|
||||||
setZoom(qMin(vp.width()/rect.width(), vp.height()/rect.height()));
|
setZoom(qMin(vp.width()/rect.width(), vp.height()/rect.height()));
|
||||||
|
centerOn(m_scene.backgroundPixmapItem());
|
||||||
|
qDebug() << "TagView::zoomToFit():" << sceneRect() << rect << viewport()->rect();
|
||||||
}
|
}
|
||||||
|
|
||||||
void TagView::setZoom(qreal zoom)
|
void TagView::setZoom(qreal zoom)
|
||||||
{
|
{
|
||||||
this->zoom = zoom;
|
this->zoom = zoom;
|
||||||
proj.setMeta("view_zoom", zoom);
|
if (m_proj)
|
||||||
|
m_proj->setMeta("view_zoom", zoom);
|
||||||
setTransform(QTransform::fromScale(zoom, zoom).rotate(rotation));
|
setTransform(QTransform::fromScale(zoom, zoom).rotate(rotation));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -47,7 +54,15 @@ void TagView::rotate(int angle)
|
||||||
if (tmp < 0)
|
if (tmp < 0)
|
||||||
tmp += 360;
|
tmp += 360;
|
||||||
rotation = tmp;
|
rotation = tmp;
|
||||||
proj.setMeta("view_rotation", rotation);
|
if (m_proj)
|
||||||
|
m_proj->setMeta("view_rotation", rotation);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TagView::setProject(SQLiteSaveFile *proj)
|
||||||
|
{
|
||||||
|
m_proj = proj;
|
||||||
|
m_scene.setProject(m_proj);
|
||||||
|
restoreViewport();
|
||||||
}
|
}
|
||||||
|
|
||||||
void TagView::wheelEvent(QWheelEvent *evt)
|
void TagView::wheelEvent(QWheelEvent *evt)
|
||||||
|
|
@ -63,22 +78,34 @@ void TagView::wheelEvent(QWheelEvent *evt)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void TagView::scrollContentsBy(int dx, int dy)
|
||||||
|
{
|
||||||
|
QGraphicsView::scrollContentsBy(dx, dy);
|
||||||
|
/* Hackety hack: On object destruction this method is called downstream in the destructor chain. Prevent segfaults from calling an uninitialized timer. */
|
||||||
|
if (!were_done)
|
||||||
|
saveCenterTimer.start();
|
||||||
|
}
|
||||||
|
|
||||||
void TagView::saveCenter()
|
void TagView::saveCenter()
|
||||||
{
|
{
|
||||||
QPointF p = mapToScene(viewport()->rect().center());
|
QPointF p = mapToScene(viewport()->rect().center());
|
||||||
proj.setMeta("view_center", QJsonDocument(QJsonArray({p.x(), p.y()})).toJson());
|
if (m_proj)
|
||||||
|
m_proj->setMeta("view_center", QJsonDocument(QJsonArray({p.x(), p.y()})).toJson());
|
||||||
}
|
}
|
||||||
|
|
||||||
void TagView::restoreViewport()
|
void TagView::restoreViewport()
|
||||||
{
|
{
|
||||||
QVariant v_rot = proj.getMeta("view_rotation");
|
if (!m_proj)
|
||||||
|
return;
|
||||||
|
|
||||||
|
QVariant v_rot = m_proj->getMeta("view_rotation");
|
||||||
if (v_rot.isValid()) {
|
if (v_rot.isValid()) {
|
||||||
rotation = v_rot.toInt();
|
rotation = v_rot.toInt();
|
||||||
} else {
|
} else {
|
||||||
rotation = 0;
|
rotation = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
QVariant v_zoom = proj.getMeta("view_zoom");
|
QVariant v_zoom = m_proj->getMeta("view_zoom");
|
||||||
if (v_zoom.isValid()) {
|
if (v_zoom.isValid()) {
|
||||||
zoom = v_zoom.toDouble();
|
zoom = v_zoom.toDouble();
|
||||||
setTransform(QTransform::fromScale(zoom, zoom).rotate(rotation));
|
setTransform(QTransform::fromScale(zoom, zoom).rotate(rotation));
|
||||||
|
|
@ -86,13 +113,13 @@ void TagView::restoreViewport()
|
||||||
zoomToFit();
|
zoomToFit();
|
||||||
}
|
}
|
||||||
|
|
||||||
QVariant v_center = proj.getMeta("view_center");
|
QVariant v_center = m_proj->getMeta("view_center");
|
||||||
if (v_center.isValid()) {
|
if (v_center.isValid()) {
|
||||||
QJsonArray arr = QJsonDocument::fromJson(v_center.toByteArray()).toVariant().toJsonArray();
|
QJsonArray arr = QJsonDocument::fromJson(v_center.toByteArray()).toVariant().toJsonArray();
|
||||||
assert(arr.size() == 2);
|
assert(arr.size() == 2);
|
||||||
assert(arr[0].isDouble() && arr[1].isDouble());
|
assert(arr[0].isDouble() && arr[1].isDouble());
|
||||||
centerOn(QPointF(arr[0].toDouble(), arr[1].toDouble()));
|
centerOn(QPointF(arr[0].toDouble(), arr[1].toDouble()));
|
||||||
} else {
|
} else {
|
||||||
centerOn(scene.itemsBoundingRect().center());
|
centerOn(m_scene.itemsBoundingRect().center());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
14
tagview.h
14
tagview.h
|
|
@ -13,19 +13,24 @@ class TagView : public QGraphicsView
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
TagView(SQLiteSaveFile &proj);
|
TagView(QWidget *parent=nullptr);
|
||||||
|
virtual ~TagView();
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void zoomToFit();
|
void zoomToFit();
|
||||||
void setZoom(qreal zoom);
|
void setZoom(qreal zoom);
|
||||||
void zoomIn(qreal delta);
|
void zoomIn(qreal delta=120);
|
||||||
|
void zoomOut(qreal delta=120) { zoomIn(-delta); }
|
||||||
void rotate(int angle);
|
void rotate(int angle);
|
||||||
|
|
||||||
|
void setProject(SQLiteSaveFile *proj);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void tagDoubleClicked(const Tag &tag);
|
void tagDoubleClicked(const Tag &tag);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void wheelEvent(QWheelEvent *evt) override;
|
void wheelEvent(QWheelEvent *evt) override;
|
||||||
|
void scrollContentsBy(int dx, int dy) override;
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void saveCenter();
|
void saveCenter();
|
||||||
|
|
@ -33,9 +38,10 @@ private slots:
|
||||||
private:
|
private:
|
||||||
void restoreViewport();
|
void restoreViewport();
|
||||||
|
|
||||||
TagScene scene;
|
|
||||||
SQLiteSaveFile &proj;
|
|
||||||
QTimer saveCenterTimer;
|
QTimer saveCenterTimer;
|
||||||
|
bool were_done = false;
|
||||||
|
TagScene m_scene;
|
||||||
|
SQLiteSaveFile *m_proj = nullptr;
|
||||||
int rotation;
|
int rotation;
|
||||||
double zoom;
|
double zoom;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue