From 2deadc6cfbf71e06908963fb1bae628cfe370f9d Mon Sep 17 00:00:00 2001 From: jaseg Date: Sun, 16 Aug 2020 17:04:32 +0200 Subject: Basic model/view action works --- numberator.cpp | 14 +++- numberator.h | 2 + numberator.ui | 68 ++++++++++++++++- sqlitebackend.cpp | 200 +++++++++++++++++++++++++++++++++++++------------- sqlitebackend.h | 10 ++- tagitem.cpp | 67 ++++++++++++++++- tagitem.h | 7 +- taglistmodel.cpp | 6 +- taglistmodel.h | 2 + tagproptablemodel.cpp | 52 +++++++------ tagproptablemodel.h | 6 +- tagscene.cpp | 51 +++++++++---- tagscene.h | 12 ++- tagview.cpp | 59 +++++++++++---- tagview.h | 14 +++- 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 ¤t, 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 #include @@ -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 @@ - + @@ -69,6 +69,9 @@ View + + + @@ -85,11 +88,17 @@ Undo + + Ctrl+Z + Redo + + Ctrl+R + @@ -100,46 +109,73 @@ Cut + + Ctrl+X + Paste + + Ctrl+V + New Tag + + Ctrl+A + New Project + + Ctrl+N + Open Project + + Ctrl+O + Import Image + + Ctrl+I + Reload Image + + F5 + - Save Project + Save Project as... + + + Ctrl+Shift+S Export PDF + + Ctrl+E + @@ -150,8 +186,36 @@ Exit + + Ctrl+Q + + + + + Zoom to fit + + + Ctrl+0 + + + + + Zoom in + + + + + Zoom out + + + + TagView + QGraphicsView +
tagview.h
+
+
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 SQLiteSaveFile::getAllTags() { - qDebug() << "getAllTags"; + auto dbg = qDebug() << "getAllTags()"; QMutexLocker l(&m_dbMut); resetError(); QList rv; @@ -170,56 +171,134 @@ QList SQLiteSaveFile::getAllTags() if (!setDatabaseError(q)) return QList(); + 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> metas) { +bool SQLiteSaveFile::setMeta(std::initializer_list> 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 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> metas); + bool setMeta(const QString &key, const QVariant &value, bool setDirty=false); + bool setMeta(std::initializer_list> 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 +#include + 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 -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(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 tags; }; diff --git a/tagview.cpp b/tagview.cpp index b4e83dd..9a9c9a0 100644 --- a/tagview.cpp +++ b/tagview.cpp @@ -4,34 +4,41 @@ #include #include -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; }; -- cgit