summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--numberator.cpp14
-rw-r--r--numberator.h2
-rw-r--r--numberator.ui68
-rw-r--r--sqlitebackend.cpp200
-rw-r--r--sqlitebackend.h10
-rw-r--r--tagitem.cpp67
-rw-r--r--tagitem.h7
-rw-r--r--taglistmodel.cpp6
-rw-r--r--taglistmodel.h2
-rw-r--r--tagproptablemodel.cpp52
-rw-r--r--tagproptablemodel.h6
-rw-r--r--tagscene.cpp51
-rw-r--r--tagscene.h12
-rw-r--r--tagview.cpp59
-rw-r--r--tagview.h14
15 files changed, 438 insertions, 132 deletions
diff --git a/numberator.cpp b/numberator.cpp
index 912c5e3..29c0d6b 100644
--- a/numberator.cpp
+++ b/numberator.cpp
@@ -29,6 +29,8 @@ Numberator::Numberator(QWidget *parent)
connect(ui->actionReload_Image, &QAction::triggered,
&proj, &SQLiteSaveFile::reloadImageFromDisk);
+ ui->graphicsView->setProject(&proj);
+
tagsDockUi->tagList->setModel(&tagListModel);
tagsDockUi->propertyTable->setModel(&tagPropTableModel);
@@ -78,11 +80,15 @@ Numberator::Numberator(QWidget *parent)
});
connect(ui->actionAbout, &QAction::triggered, &aboutDialog, &AboutDialog::open);
- connect(tagsDockUi->tagList->selectionModel(), &QItemSelectionModel::currentChanged,
- [=](const QModelIndex &current, const QModelIndex &previous) {
- Q_UNUSED(previous);
- tagPropTableModel.showTag(tagListModel.getTag(current));
+ connect(tagsDockUi->tagList->selectionModel(), &QItemSelectionModel::selectionChanged,
+ [=](const QItemSelection &selected, const QItemSelection &deselected) {
+ Q_UNUSED(deselected);
+ 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()
diff --git a/numberator.h b/numberator.h
index bf661db..4192196 100644
--- a/numberator.h
+++ b/numberator.h
@@ -5,6 +5,7 @@
#include "sqlitebackend.h"
#include "taglistmodel.h"
#include "tagproptablemodel.h"
+#include "tagview.h"
#include <QFileDialog>
#include <QMainWindow>
@@ -41,5 +42,6 @@ private:
SQLiteSaveFile proj;
TagListModel tagListModel;
TagPropTableModel tagPropTableModel;
+ TagView m_tagView;
};
#endif // NUMBERATOR_H
diff --git a/numberator.ui b/numberator.ui
index 8bf3371..70b9e14 100644
--- a/numberator.ui
+++ b/numberator.ui
@@ -16,7 +16,7 @@
<widget class="QWidget" name="centralwidget">
<layout class="QVBoxLayout" name="verticalLayout">
<item>
- <widget class="QGraphicsView" name="graphicsView"/>
+ <widget class="TagView" name="graphicsView"/>
</item>
</layout>
</widget>
@@ -69,6 +69,9 @@
<property name="title">
<string>View</string>
</property>
+ <addaction name="actionZoom_to_fit"/>
+ <addaction name="actionZoom_in"/>
+ <addaction name="actionZoom_out"/>
</widget>
<addaction name="menuFile"/>
<addaction name="menuEdit"/>
@@ -85,11 +88,17 @@
<property name="text">
<string>Undo</string>
</property>
+ <property name="shortcut">
+ <string>Ctrl+Z</string>
+ </property>
</action>
<action name="actionRedo">
<property name="text">
<string>Redo</string>
</property>
+ <property name="shortcut">
+ <string>Ctrl+R</string>
+ </property>
</action>
<action name="actionCopy">
<property name="text">
@@ -100,46 +109,73 @@
<property name="text">
<string>Cut</string>
</property>
+ <property name="shortcut">
+ <string>Ctrl+X</string>
+ </property>
</action>
<action name="actionPaste">
<property name="text">
<string>Paste</string>
</property>
+ <property name="shortcut">
+ <string>Ctrl+V</string>
+ </property>
</action>
<action name="actionNew_Tag">
<property name="text">
<string>New Tag</string>
</property>
+ <property name="shortcut">
+ <string>Ctrl+A</string>
+ </property>
</action>
<action name="actionNew_Project">
<property name="text">
<string>New Project</string>
</property>
+ <property name="shortcut">
+ <string>Ctrl+N</string>
+ </property>
</action>
<action name="actionOpen_Project">
<property name="text">
<string>Open Project</string>
</property>
+ <property name="shortcut">
+ <string>Ctrl+O</string>
+ </property>
</action>
<action name="actionImport_Image">
<property name="text">
<string>Import Image</string>
</property>
+ <property name="shortcut">
+ <string>Ctrl+I</string>
+ </property>
</action>
<action name="actionReload_Image">
<property name="text">
<string>Reload Image</string>
</property>
+ <property name="shortcut">
+ <string>F5</string>
+ </property>
</action>
<action name="actionSave_Project">
<property name="text">
- <string>Save Project</string>
+ <string>Save Project as...</string>
+ </property>
+ <property name="shortcut">
+ <string>Ctrl+Shift+S</string>
</property>
</action>
<action name="actionExport_PDF">
<property name="text">
<string>Export PDF</string>
</property>
+ <property name="shortcut">
+ <string>Ctrl+E</string>
+ </property>
</action>
<action name="actionExport_PNG">
<property name="text">
@@ -150,8 +186,36 @@
<property name="text">
<string>Exit</string>
</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>
</widget>
+ <customwidgets>
+ <customwidget>
+ <class>TagView</class>
+ <extends>QGraphicsView</extends>
+ <header>tagview.h</header>
+ </customwidget>
+ </customwidgets>
<resources/>
<connections/>
</ui>
diff --git a/sqlitebackend.cpp b/sqlitebackend.cpp
index 89b7b07..4a9f8b9 100644
--- a/sqlitebackend.cpp
+++ b/sqlitebackend.cpp
@@ -14,8 +14,8 @@ SQLiteSaveFile::SQLiteSaveFile(QObject *parent) :
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);
QFile f(filename);
if (!f.exists()) {
@@ -44,15 +44,16 @@ bool SQLiteSaveFile::open(const QString &filename)
m_dirty = false;
m_open = true;
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;
}
bool SQLiteSaveFile::clearNew()
{
qDebug() << "clearNew";
- {
+ { /* Emit signals only after unlocking mutex to allow accesses by receivers */
QMutexLocker l(&m_dbMut);
QSqlDatabase new_db(QSqlDatabase::addDatabase("QSQLITE", QUuid::createUuid().toString()));
@@ -148,7 +149,7 @@ bool SQLiteSaveFile::saveAs(const QString &filename)
QList<Tag> SQLiteSaveFile::getAllTags()
{
- qDebug() << "getAllTags";
+ auto dbg = qDebug() << "getAllTags()";
QMutexLocker l(&m_dbMut);
resetError();
QList<Tag> rv;
@@ -170,56 +171,134 @@ QList<Tag> SQLiteSaveFile::getAllTags()
if (!setDatabaseError(q))
return QList<Tag>();
+ dbg << QString("%1 tags").arg(rv.size());
return rv;
}
bool SQLiteSaveFile::updateTag(Tag tag)
{
- 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;
+ auto dbg = qDebug() << "updating tag" << tag.id << tag.name;
+ {
+ QMutexLocker l(&m_dbMut);
+ QSqlQuery q(m_db);
+ 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);
return true;
}
bool SQLiteSaveFile::deleteTag(Tag tag)
{
- QMutexLocker l(&m_dbMut);
- if (!runSql("DELETE FROM tags WHERE id=?", {tag.id}))
- return false;
+ {
+ QMutexLocker l(&m_dbMut);
+ if (!runSql("DELETE FROM tags WHERE id=?", {tag.id}))
+ return false;
- m_dirty = true;
+ m_dirty = true;
+ }
tagChange(TagChange::DELETED, tag);
return true;
}
bool SQLiteSaveFile::createTag(Tag tag)
{
- qDebug() << "createTag";
+ QSqlQuery q(m_db);
+ auto dbg = qDebug() << "createTag";
+ {
+ QMutexLocker l(&m_dbMut);
+ resetError();
+ 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;
+}
+
+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);
- resetError();
QSqlQuery q(m_db);
- q.prepare("INSERT INTO tags(name, anchor_x, anchor_y, meta) VALUES (?, ?, ?, ?)");
- q.addBindValue(tag.name);
- q.addBindValue(tag.anchor.x());
- q.addBindValue(tag.anchor.y());
- q.addBindValue(QJsonDocument::fromVariant(tag.metadata).toJson());
+ q.prepare("SELECT name FROM tags WHERE name=?");
+ q.addBindValue(name);
+ if (!q.exec()) {
+ setDatabaseError(q);
+ return true;
+ }
- if (!setDatabaseError(q))
+ if (q.next())
return false;
- Tag created_tag(q.lastInsertId().toLongLong(), tag);
- m_dirty = true;
- tagChange(TagChange::CREATED, created_tag);
+ setDatabaseError(q);
return true;
}
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});
}
@@ -232,15 +311,15 @@ bool SQLiteSaveFile::setMetaLocked(std::initializer_list<QPair<QString, QVariant
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);
- m_dirty = true;
+ m_dirty = m_dirty || setDirty;
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);
- m_dirty = true;
+ m_dirty = m_dirty || setDirty;
return setMetaLocked(metas);
}
@@ -251,7 +330,6 @@ const QVariant SQLiteSaveFile::getMeta(const QString &key) const {
const QVariant SQLiteSaveFile::getMetaLocked(const QString &key) const
{
- qDebug() << "getMeta " << key;
resetError();
QSqlQuery q(m_db);
q.prepare("SELECT value FROM metadata WHERE key=?");
@@ -267,6 +345,7 @@ const QVariant SQLiteSaveFile::getMetaLocked(const QString &key) const
return QVariant();
}
+ qDebug() << QString("getMeta: %1=%2").arg(key).arg(q.value(0).toString());
return q.value(0);
}
@@ -288,29 +367,35 @@ bool SQLiteSaveFile::runSql(QString query, std::initializer_list<QVariant> bindi
bool SQLiteSaveFile::loadImageFromDisk(const QString &filename)
{
- QMutexLocker l(&m_dbMut);
- QFile f(filename);
- resetError();
-
- if (!f.open(QIODevice::ReadOnly)) {
- 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;
+ {
+ QMutexLocker l(&m_dbMut);
+ QFile f(filename);
+ resetError();
+
+ if (!f.open(QIODevice::ReadOnly)) {
+ 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;
}
-
- 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});
+ /* Emit signal with mutex unlocked */
+ imageLoaded(m_image);
+ return true;
}
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
{
if (!q.lastError().isValid())
diff --git a/sqlitebackend.h b/sqlitebackend.h
index 8bfce00..0e7d339 100644
--- a/sqlitebackend.h
+++ b/sqlitebackend.h
@@ -19,6 +19,7 @@ public:
Tag() : valid(false) {}
Tag(long long int id, QString name, qreal anchor_x, qreal anchor_y, QByteArray metadata);
Tag(long long int id, const Tag &other);
+ Tag(QString name, const QPointF &anchor, const QVariantMap metadata=QVariantMap());
bool isValid() { return valid; }
@@ -44,13 +45,17 @@ public:
bool updateTag(Tag tag);
bool deleteTag(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 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);
+ bool setMeta(const QString &key, const QVariant &value, bool setDirty=false);
+ bool setMeta(std::initializer_list<QPair<QString, QVariant>> metas, bool setDirty=false);
const QVariant getMeta(const QString &key) const;
const QString &errorString() const { return m_lastErrorString; }
@@ -87,6 +92,7 @@ signals:
void tagChange(TagChange change, const Tag &tag);
void fileReload();
void fileIOError(Error e, QString errorName, QString description) const;
+ void imageLoaded(const QByteArray &image);
private:
bool connect();
diff --git a/tagitem.cpp b/tagitem.cpp
index d251428..e493b81 100644
--- a/tagitem.cpp
+++ b/tagitem.cpp
@@ -1,25 +1,86 @@
#include "tagitem.h"
+#include <QPainter>
+#include <QGuiApplication>
+
TagItem::TagItem(const Tag &tag)
: valid(true)
{
setFlags(QGraphicsItem::ItemIsMovable
| QGraphicsItem::ItemIsSelectable
- | QGraphicsItem::ItemIsFocusable);
- /* TODO text_it.setFlags(QGraphicsItem::ItemIgnoresTransformations);
- */
+ | QGraphicsItem::ItemIsFocusable
+ | QGraphicsItem::ItemIgnoresTransformations
+ | QGraphicsItem::ItemSendsGeometryChanges);
+ QFont font(QGuiApplication::font());
+ font.setPointSize(18);
+ setFont(font);
tagUpdated(tag);
}
void TagItem::tagUpdated(const Tag &tag)
{
m_tag = tag;
+ qDebug() << "TagItem updated" << tag.name << tag.anchor;
setText(tag.name);
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)
{
+ qDebug() << "itemChange" << m_tag.name << this->boundingRect() << change;
if (change == ItemPositionChange) {
/* https://gist.github.com/csukuangfj/c2a06416062bec9ed99eddd705c21275#file-qgraphicsscenetest-cpp-L90
*
diff --git a/tagitem.h b/tagitem.h
index a248243..3863d40 100644
--- a/tagitem.h
+++ b/tagitem.h
@@ -14,13 +14,14 @@ public:
enum { TagItemType = UserType + 1 };
int type() const override { return TagItemType; }
-
bool isValid() { return valid; }
-
void tagUpdated(const Tag &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:
QVariant itemChange(QGraphicsItem::GraphicsItemChange change, const QVariant &value) override;
diff --git a/taglistmodel.cpp b/taglistmodel.cpp
index e3ba006..d68fc57 100644
--- a/taglistmodel.cpp
+++ b/taglistmodel.cpp
@@ -4,6 +4,7 @@ TagListModel::TagListModel(SQLiteSaveFile &backend)
: backend(backend)
, cached_tags(backend.getAllTags())
{
+ qDebug() << "connecting TagListModel" << &backend;
connect(&backend, &SQLiteSaveFile::tagChange,
[=](TagChange change, const Tag &tag) { Q_UNUSED(change); Q_UNUSED(tag); reloadTags(); });
connect(&backend, &SQLiteSaveFile::fileReload,
@@ -28,10 +29,11 @@ QVariant TagListModel::data(const QModelIndex &index, int role) const
if (!index.isValid())
return QVariant();
- if (role != Qt::DisplayRole)
+ if (role != Qt::DisplayRole && role != Qt::EditRole)
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
diff --git a/taglistmodel.h b/taglistmodel.h
index 41fa520..d4bd5df 100644
--- a/taglistmodel.h
+++ b/taglistmodel.h
@@ -7,6 +7,8 @@
class TagListModel : public QAbstractListModel
{
+ Q_OBJECT
+
public:
TagListModel(SQLiteSaveFile &backend);
diff --git a/tagproptablemodel.cpp b/tagproptablemodel.cpp
index 711494d..4dc5e6b 100644
--- a/tagproptablemodel.cpp
+++ b/tagproptablemodel.cpp
@@ -1,26 +1,24 @@
#include "tagproptablemodel.h"
TagPropTableModel::TagPropTableModel(SQLiteSaveFile &backend, const Tag tag)
- : backend(backend)
- , tag_cached(tag)
- , tagIsValid(true)
+ : TagPropTableModel(backend, true)
{
+ tag_cached = tag;
tag_keys = tag_cached.metadata.keys();
tag_keys.sort();
- connect(&backend, &SQLiteSaveFile::tagChange,
- this, &TagPropTableModel::tagChange);
- connect(&backend, &SQLiteSaveFile::fileReload,
- [=]() {
- beginResetModel();
- tagIsValid = false;
- endResetModel();
- });
+ qDebug() << "connecting TagPropTableModel" << &backend;
}
-TagPropTableModel::TagPropTableModel(SQLiteSaveFile &backend)
+TagPropTableModel::TagPropTableModel(SQLiteSaveFile &backend, bool tagIsValid)
: 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
@@ -42,7 +40,7 @@ QVariant TagPropTableModel::data(const QModelIndex &index, int role) const
if (!index.isValid())
return QVariant();
- if (role != Qt::DisplayRole)
+ if (role != Qt::DisplayRole && role != Qt::EditRole)
return QVariant();
bool label = index.column() == 0;
@@ -50,7 +48,7 @@ QVariant TagPropTableModel::data(const QModelIndex &index, int role) const
switch (index.row()) {
case 0: return label ? QVariant("Database ID") : tag_cached.id; 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;
@@ -100,18 +98,19 @@ bool TagPropTableModel::setData(const QModelIndex &index, const QVariant &value,
if (role != Qt::EditRole)
return false;
- if (index.row() == 1)
+ if (index.row() == 1) {
tag_cached.name = value.toString();
- else if (index.row() < 3)
+ } else if (index.row() < 3) {
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 {
- 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);
@@ -120,13 +119,17 @@ bool TagPropTableModel::setData(const QModelIndex &index, const QVariant &value,
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)
return;
assert(change != TagChange::CREATED);
if (change == TagChange::CHANGED) {
+ dbg << "changed";
showTag(tag);
+
} else if (change == TagChange::DELETED) {
+ dbg << "deleted";
beginResetModel();
tagIsValid = false;
endResetModel();
@@ -140,5 +143,6 @@ void TagPropTableModel::showTag(const Tag &tag)
tag_cached = tag;
tag_keys = tag_cached.metadata.keys();
tag_keys.sort();
+ tagIsValid = true;
endResetModel();
}
diff --git a/tagproptablemodel.h b/tagproptablemodel.h
index b127f90..09ab8b4 100644
--- a/tagproptablemodel.h
+++ b/tagproptablemodel.h
@@ -9,8 +9,10 @@
class TagPropTableModel : public QAbstractTableModel
{
+ Q_OBJECT
+
public:
- TagPropTableModel(SQLiteSaveFile &backend);
+ TagPropTableModel(SQLiteSaveFile &backend) : TagPropTableModel(backend, false) {};
TagPropTableModel(SQLiteSaveFile &backend, const Tag tag);
int rowCount(const QModelIndex &parent=QModelIndex()) const override;
@@ -27,6 +29,8 @@ private slots:
void tagChange(TagChange change, const Tag &tag);
private:
+ TagPropTableModel(SQLiteSaveFile &backend, bool tagIsValid);
+
SQLiteSaveFile &backend;
Tag tag_cached;
QStringList tag_keys;
diff --git a/tagscene.cpp b/tagscene.cpp
index fb52574..a6efd17 100644
--- a/tagscene.cpp
+++ b/tagscene.cpp
@@ -2,15 +2,16 @@
#include <QGraphicsSceneMouseEvent>
-TagScene::TagScene(SQLiteSaveFile &proj)
- : proj(proj)
-{
- reloadPicture();
- reloadTags();
+void TagScene::setProject(SQLiteSaveFile *proj) {
+ if (m_proj)
+ disconnect(m_proj, nullptr, this, nullptr);
+ m_proj = proj;
+
+ reloadScene();
- connect(&proj, &SQLiteSaveFile::tagChange, this, &TagScene::tagChanged);
- 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)
@@ -40,11 +41,22 @@ void TagScene::tagChanged(TagChange change, const Tag &tag)
void TagScene::reloadPicture()
{
- pix.loadFromData(proj.getImage());
+ if (!m_proj)
+ return;
+
+ pix.loadFromData(m_proj->getImage());
if (pix_it)
removeItem(pix_it);
pix_it = new QGraphicsPixmapItem(pix);
+ pix_it->setZValue(-1);
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) {
@@ -53,16 +65,20 @@ void TagScene::addTag(const Tag tag) {
tags[tag.id] = it;
}
-void TagScene::reloadTags()
+void TagScene::reloadScene()
{
clear();
- for (auto *it : tags.values()) {
+ for (auto *it : tags.values())
delete it;
- }
+ pix_it = nullptr;
- for (const Tag &tag : proj.getAllTags()) {
+ if (!m_proj)
+ return;
+
+ for (const Tag &tag : m_proj->getAllTags())
addTag(tag);
- }
+
+ reloadPicture(); /* calls invalidate() for us */
}
void TagScene::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event)
@@ -70,11 +86,18 @@ void TagScene::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event)
QGraphicsItem *it = itemAt(event->scenePos(), QTransform());
if (!it) {
QGraphicsScene::mouseDoubleClickEvent(event);
+ return;
+ }
+
+ if (it == pix_it) {
+ m_proj->createTagAt(event->scenePos());
+ return;
}
TagItem *tagitem = qgraphicsitem_cast<TagItem *>(it);
if (!tagitem) {
QGraphicsScene::mouseDoubleClickEvent(event);
+ return;
}
tagDoubleClicked(tagitem->tag());
diff --git a/tagscene.h b/tagscene.h
index 006fffd..4c81fe5 100644
--- a/tagscene.h
+++ b/tagscene.h
@@ -14,14 +14,18 @@ class TagScene : public QGraphicsScene
Q_OBJECT
public:
- TagScene(SQLiteSaveFile &proj);
+ TagScene() {}
+ const QGraphicsPixmapItem *backgroundPixmapItem() const { return pix_it; }
public slots:
void reloadPicture();
- void reloadTags();
+ void reloadScene();
+
+ void setProject(SQLiteSaveFile *proj);
signals:
void tagDoubleClicked(const Tag &tag);
+ void imageLoaded();
protected:
void mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event) override;
@@ -32,9 +36,9 @@ private slots:
private:
void addTag(const Tag tag);
- SQLiteSaveFile &proj;
+ SQLiteSaveFile *m_proj = nullptr;
- QGraphicsPixmapItem *pix_it;
+ QGraphicsPixmapItem *pix_it = nullptr;
QPixmap pix;
QMap<long long int, TagItem*> tags;
};
diff --git a/tagview.cpp b/tagview.cpp
index b4e83dd..9a9c9a0 100644
--- a/tagview.cpp
+++ b/tagview.cpp
@@ -4,34 +4,41 @@
#include <QScrollBar>
#include <cmath>
-TagView::TagView(SQLiteSaveFile &proj)
- : scene(proj)
- , proj(proj)
- , saveCenterTimer()
+TagView::TagView(QWidget *parent)
+ : QGraphicsView(parent)
+ , saveCenterTimer(this)
{
setDragMode(QGraphicsView::ScrollHandDrag);
- setScene(&scene);
- connect(&scene, &TagScene::tagDoubleClicked, this, &TagView::tagDoubleClicked);
+ setScene(&m_scene);
+ connect(&m_scene, &TagScene::tagDoubleClicked, this, &TagView::tagDoubleClicked);
+ connect(&m_scene, &TagScene::imageLoaded, this, &TagView::zoomToFit);
saveCenterTimer.setSingleShot(true);
saveCenterTimer.setInterval(500);
- connect(&saveCenterTimer, &QTimer::timeout,
- this, &TagView::saveCenter);
+ connect(&saveCenterTimer, &QTimer::timeout, this, &TagView::saveCenter);
+}
+
+TagView::~TagView()
+{
+ were_done = true;
}
void TagView::zoomToFit()
{
QTransform tx = QTransform().rotate(-rotation);
- QRectF rect = tx.mapRect(scene.itemsBoundingRect());
+ QRectF rect = tx.mapRect(m_scene.itemsBoundingRect());
QRectF vp = viewport()->rect();
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)
{
this->zoom = zoom;
- proj.setMeta("view_zoom", zoom);
+ if (m_proj)
+ m_proj->setMeta("view_zoom", zoom);
setTransform(QTransform::fromScale(zoom, zoom).rotate(rotation));
}
@@ -47,7 +54,15 @@ void TagView::rotate(int angle)
if (tmp < 0)
tmp += 360;
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)
@@ -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()
{
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()
{
- QVariant v_rot = proj.getMeta("view_rotation");
+ if (!m_proj)
+ return;
+
+ QVariant v_rot = m_proj->getMeta("view_rotation");
if (v_rot.isValid()) {
rotation = v_rot.toInt();
} else {
rotation = 0;
}
- QVariant v_zoom = proj.getMeta("view_zoom");
+ QVariant v_zoom = m_proj->getMeta("view_zoom");
if (v_zoom.isValid()) {
zoom = v_zoom.toDouble();
setTransform(QTransform::fromScale(zoom, zoom).rotate(rotation));
@@ -86,13 +113,13 @@ void TagView::restoreViewport()
zoomToFit();
}
- QVariant v_center = proj.getMeta("view_center");
+ QVariant v_center = m_proj->getMeta("view_center");
if (v_center.isValid()) {
QJsonArray arr = QJsonDocument::fromJson(v_center.toByteArray()).toVariant().toJsonArray();
assert(arr.size() == 2);
assert(arr[0].isDouble() && arr[1].isDouble());
centerOn(QPointF(arr[0].toDouble(), arr[1].toDouble()));
} else {
- centerOn(scene.itemsBoundingRect().center());
+ centerOn(m_scene.itemsBoundingRect().center());
}
}
diff --git a/tagview.h b/tagview.h
index 71b418c..edafcf4 100644
--- a/tagview.h
+++ b/tagview.h
@@ -13,19 +13,24 @@ class TagView : public QGraphicsView
Q_OBJECT
public:
- TagView(SQLiteSaveFile &proj);
+ TagView(QWidget *parent=nullptr);
+ virtual ~TagView();
public slots:
void zoomToFit();
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 setProject(SQLiteSaveFile *proj);
+
signals:
void tagDoubleClicked(const Tag &tag);
protected:
void wheelEvent(QWheelEvent *evt) override;
+ void scrollContentsBy(int dx, int dy) override;
private slots:
void saveCenter();
@@ -33,9 +38,10 @@ private slots:
private:
void restoreViewport();
- TagScene scene;
- SQLiteSaveFile &proj;
QTimer saveCenterTimer;
+ bool were_done = false;
+ TagScene m_scene;
+ SQLiteSaveFile *m_proj = nullptr;
int rotation;
double zoom;
};