From c6713d0876ce2d99f912151c9884477606909681 Mon Sep 17 00:00:00 2001 From: jaseg Date: Mon, 17 Aug 2020 00:56:17 +0200 Subject: More UI work --- TagEditDialog.ui | 134 ++++++++++++++++++++++++++++++++++++++++++++++++++ numberator.cpp | 75 ++++++++++++++++++++++++---- numberator.h | 6 ++- numberator.pro | 1 + sqlitebackend.cpp | 1 - sqlitebackend.h | 5 +- tagitem.cpp | 53 ++++++++++++++++---- tagitem.h | 10 +++- taglistmodel.cpp | 19 +++++++ taglistmodel.h | 1 + tagproptablemodel.cpp | 99 ++++++++++++++++++++----------------- tagproptablemodel.h | 10 ++-- tagscene.cpp | 9 ++++ tagscene.h | 2 + tagview.h | 2 + 15 files changed, 352 insertions(+), 75 deletions(-) create mode 100644 TagEditDialog.ui diff --git a/TagEditDialog.ui b/TagEditDialog.ui new file mode 100644 index 0000000..f25c59f --- /dev/null +++ b/TagEditDialog.ui @@ -0,0 +1,134 @@ + + + TagEditDialog + + + + 0 + 0 + 400 + 300 + + + + Dialog + + + true + + + true + + + + + + QFormLayout::AllNonFixedFieldsGrow + + + + + Name + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + + Tag ID + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + + + + + + + + + Properties + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + Location + + + + + + + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + buttonBox + accepted() + TagEditDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + TagEditDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/numberator.cpp b/numberator.cpp index 29c0d6b..3161ee8 100644 --- a/numberator.cpp +++ b/numberator.cpp @@ -8,11 +8,13 @@ Numberator::Numberator(QWidget *parent) : QMainWindow(parent) , ui(new Ui::Numberator) , tagsDockUi(new Ui::TagListDock) + , tagEditUi(new Ui::TagEditDialog) , settings("jaseg.de", "Numberator") , loadImageDialog(this) , proj() , tagListModel(proj) - , tagPropTableModel(proj) + , tagPropTableModel(&proj) + , dialogTagPropTableModel(nullptr, false) { ui->setupUi(this); @@ -22,16 +24,75 @@ Numberator::Numberator(QWidget *parent) QMessageBox::critical(this, errorName, description); }); + QDialog *tagEditDialog = new QDialog(this); + tagEditUi->setupUi(tagEditDialog); + tagEditUi->tagPropertiesView->setModel(&dialogTagPropTableModel); + connect(tagEditUi->buttonBox, &QDialogButtonBox::accepted, [=]() { + Tag newTag(m_tagBeingEdited); + newTag.name = tagEditUi->tagNameEdit->text(); + newTag.metadata = dialogTagPropTableModel.metadata(); + proj.updateTag(newTag); + tagEditDialog->hide(); + }); + + connect(tagEditUi->buttonBox, &QDialogButtonBox::rejected, tagEditDialog, &QDialog::hide); + + connect(ui->graphicsView->scene(), &TagScene::selectionChanged, [=](){ + auto dbg = qDebug() << "tag view selection changed"; + auto items = ui->graphicsView->scene()->selectedItems(); + if (items.isEmpty()) { + dbg << ""; + return; + } + QGraphicsItem *first = items.first(); + TagItem *it = qgraphicsitem_cast(first); + dbg << first << it << first->type() << TagItem::Type; + if (!it) { + dbg << ""; + return; + } + dbg << it->tag().id << it->tag().name; + QSignalBlocker(tagsDockUi->tagList->selectionModel()); + tagsDockUi->tagList->selectionModel()->select(tagListModel.indexOf(it->tag()), QItemSelectionModel::ClearAndSelect); + tagPropTableModel.showTag(it->tag()); + }); + + /* + m_tagBeingEdited = it->tag(); + tagEditUi->tagNameEdit->setText(m_tagBeingEdited.name); + tagEditUi->tagIdLabel->setText(QString::number(m_tagBeingEdited.id)); + tagEditUi->tagLocationLabel->setText(m_tagBeingEdited.humanReadableAnchor()); + dialogTagPropTableModel.showTag(m_tagBeingEdited); + */ + QDockWidget *dock = new QDockWidget(this); tagsDockUi->setupUi(dock); addDockWidget(Qt::LeftDockWidgetArea, dock); + tagsDockUi->tagList->setModel(&tagListModel); + qDebug() << "connecting up model" << tagsDockUi->tagList->selectionModel(); + connect(tagsDockUi->tagList->selectionModel(), &QItemSelectionModel::selectionChanged, + [=](const QItemSelection &selected, const QItemSelection &deselected) { + Q_UNUSED(deselected); + auto dbg = qDebug() << "tagList...selectionChanged"; + if (selected.indexes().isEmpty()) { + dbg << ""; + ui->graphicsView->scene()->clearSelection(); + tagPropTableModel.showTag(Tag()); + + } else { + Tag tag(tagListModel.getTag(selected.indexes().first())); + qDebug() << tag.id << tag.name; + tagPropTableModel.showTag(tag); + QSignalBlocker(ui->graphicsView->scene()); + ui->graphicsView->scene()->selectTag(tag); + } + }); + ui->menuView->addAction(dock->toggleViewAction()); - connect(ui->actionReload_Image, &QAction::triggered, - &proj, &SQLiteSaveFile::reloadImageFromDisk); + connect(ui->actionReload_Image, &QAction::triggered, &proj, &SQLiteSaveFile::reloadImageFromDisk); ui->graphicsView->setProject(&proj); - tagsDockUi->tagList->setModel(&tagListModel); tagsDockUi->propertyTable->setModel(&tagPropTableModel); loadImageDialog.setWindowModality(Qt::ApplicationModal); @@ -80,12 +141,6 @@ Numberator::Numberator(QWidget *parent) }); connect(ui->actionAbout, &QAction::triggered, &aboutDialog, &AboutDialog::open); - 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); diff --git a/numberator.h b/numberator.h index 4192196..f81c44b 100644 --- a/numberator.h +++ b/numberator.h @@ -9,6 +9,7 @@ #include #include +#include QT_BEGIN_NAMESPACE namespace Ui { @@ -33,6 +34,7 @@ public slots: private: Ui::Numberator *ui; Ui::TagListDock *tagsDockUi; + Ui::TagEditDialog *tagEditUi; QSettings settings; QFileDialog loadImageDialog, @@ -41,7 +43,7 @@ private: SQLiteSaveFile proj; TagListModel tagListModel; - TagPropTableModel tagPropTableModel; - TagView m_tagView; + TagPropTableModel tagPropTableModel, dialogTagPropTableModel; + Tag m_tagBeingEdited; }; #endif // NUMBERATOR_H diff --git a/numberator.pro b/numberator.pro index 1b2dd1f..71b9fa3 100644 --- a/numberator.pro +++ b/numberator.pro @@ -37,6 +37,7 @@ HEADERS += \ tagscene.h FORMS += \ + TagEditDialog.ui \ TagEditor.ui \ TagListDock.ui \ aboutdialog.ui \ diff --git a/sqlitebackend.cpp b/sqlitebackend.cpp index 4a9f8b9..8fc5b9d 100644 --- a/sqlitebackend.cpp +++ b/sqlitebackend.cpp @@ -258,7 +258,6 @@ QString SQLiteSaveFile::getNextAutoTagName() bool ok = false; int numericSuffix = res.captured(2).toInt(&ok); QString stringPrefix = res.captured(1); - qDebug() << "Name has match" << stringPrefix << numericSuffix; if (ok) { do { diff --git a/sqlitebackend.h b/sqlitebackend.h index 0e7d339..baa3b19 100644 --- a/sqlitebackend.h +++ b/sqlitebackend.h @@ -21,7 +21,10 @@ public: Tag(long long int id, const Tag &other); Tag(QString name, const QPointF &anchor, const QVariantMap metadata=QVariantMap()); - bool isValid() { return valid; } + bool operator==(const Tag &t2) const { return id == t2.id; } + + bool isValid() const { return valid; } + QString humanReadableAnchor() { return QString("%1, %2").arg(anchor.x()).arg(anchor.y()); } long long int id; QString name; diff --git a/tagitem.cpp b/tagitem.cpp index e493b81..01b26fb 100644 --- a/tagitem.cpp +++ b/tagitem.cpp @@ -1,18 +1,22 @@ #include "tagitem.h" +#include "tagscene.h" #include #include +#include +#include TagItem::TagItem(const Tag &tag) : valid(true) + , m_margins(2, 2, 2, 2) { - setFlags(QGraphicsItem::ItemIsMovable - | QGraphicsItem::ItemIsSelectable + setFlags(QGraphicsItem::ItemIsSelectable + | QGraphicsItem::ItemIsMovable | QGraphicsItem::ItemIsFocusable | QGraphicsItem::ItemIgnoresTransformations | QGraphicsItem::ItemSendsGeometryChanges); QFont font(QGuiApplication::font()); - font.setPointSize(18); + font.setPointSize(12); setFont(font); tagUpdated(tag); } @@ -29,7 +33,7 @@ QRectF TagItem::boundingRect() const { QRectF parentRect(QGraphicsSimpleTextItem::boundingRect()); parentRect.translate(-(parentRect.bottomRight() - parentRect.topLeft()) * 0.5); - return parentRect.marginsAdded(QMargins(5, 5, 5, 5)); + return parentRect.marginsAdded(m_margins); } /* For some reason this is not exposed through the public API so we have to copy-paste it here m( */ @@ -56,7 +60,11 @@ static void paintSelectionHighlightBorder(QPainter *painter, const QStyleOptionG void TagItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) { - painter->setBrush(Qt::white); + bool selectionHighlight = option->state & (QStyle::State_Selected | QStyle::State_HasFocus); + if (selectionHighlight) + painter->setBrush(QColor("#80aaaaff")); + else + painter->setBrush(QColor("#80ffffff")); painter->drawRect(boundingRect()); QRectF parentRect(QGraphicsSimpleTextItem::boundingRect()); @@ -68,7 +76,7 @@ void TagItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, Q QGraphicsSimpleTextItem::paint(painter, &newOption, widget); painter->translate(pos); - if (option->state & (QStyle::State_Selected | QStyle::State_HasFocus)) + if (selectionHighlight) paintSelectionHighlightBorder(painter, option, this); } @@ -80,13 +88,40 @@ QPainterPath TagItem::shape() const QVariant TagItem::itemChange(QGraphicsItem::GraphicsItemChange change, const QVariant &value) { - qDebug() << "itemChange" << m_tag.name << this->boundingRect() << change; - if (change == ItemPositionChange) { + //qDebug() << "itemChange" << m_tag.name << this->boundingRect() << change; + if (change == ItemSceneChange) { + assert(qvariant_cast(value)); /* TagItems must only be used in TagScene. */ + } else if (change == ItemPositionChange) { + /* https://gist.github.com/csukuangfj/c2a06416062bec9ed99eddd705c21275#file-qgraphicsscenetest-cpp-L90 * */ /* FIXME */ - } return QGraphicsItem::itemChange(change, value); } + +void TagItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event) +{ + if (event->buttons() & Qt::LeftButton) { + QPoint delta = event->screenPos() - event->buttonDownScreenPos(Qt::LeftButton); + int pixelThreshold = 10; + if (QPoint::dotProduct(delta, delta) > pixelThreshold*pixelThreshold) + dragAboveThreshold = true; + + if (!dragAboveThreshold) { + // patch up event to prevent small movements + event->setPos(event->buttonDownPos(Qt::LeftButton)); + event->setScenePos(event->buttonDownScenePos(Qt::LeftButton)); + event->setScreenPos(event->buttonDownScreenPos(Qt::LeftButton)); + } + } + QGraphicsItem::mouseMoveEvent(event); +} + +void TagItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) +{ + Q_UNUSED(event); + dragAboveThreshold = false; + QGraphicsItem::mouseReleaseEvent(event); +} diff --git a/tagitem.h b/tagitem.h index 3863d40..908f441 100644 --- a/tagitem.h +++ b/tagitem.h @@ -12,12 +12,13 @@ public: TagItem(const Tag &tag); TagItem() : valid(false) {}; - enum { TagItemType = UserType + 1 }; - int type() const override { return TagItemType; } + enum { Type = UserType + 1 }; + int type() const override { return Type; } 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; @@ -25,9 +26,14 @@ public: protected: QVariant itemChange(QGraphicsItem::GraphicsItemChange change, const QVariant &value) override; + virtual void mouseMoveEvent(QGraphicsSceneMouseEvent *event) override; + virtual void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) override; + private: Tag m_tag; bool valid; + QMargins m_margins; + bool dragAboveThreshold = false; }; #endif // TAGITEM_H diff --git a/taglistmodel.cpp b/taglistmodel.cpp index d68fc57..435f5f1 100644 --- a/taglistmodel.cpp +++ b/taglistmodel.cpp @@ -17,10 +17,24 @@ int TagListModel::rowCount(const QModelIndex &parent) const return cached_tags.size(); } +bool collateTagNames(QString a, QString b) { + QRegularExpression name_re("^(\\d*)(.*?)(\\d*)$"); + auto res_a = name_re.match(a), res_b = name_re.match(b); + + if (res_a.captured(1).isEmpty() && !res_b.captured(1).isEmpty()) + return true; + + if (res_a.captured().toInt() < res_b.captured().toInt()) + return true; + /* FIXME TODO */ +} + void TagListModel::reloadTags() { + qDebug() << "TagListModel::reloadTags()"; beginResetModel(); cached_tags = backend.getAllTags(); + std::sort(cached_tags.begin(), cached_tags.end(), [](const Tag &a, const Tag &b) { return collateTagNames(a.name, b.name); }); endResetModel(); } @@ -47,6 +61,11 @@ QVariant TagListModel::headerData(int section, Qt::Orientation orientation, int return QString("Tag"); } +QModelIndex TagListModel::indexOf(const Tag &t) const +{ + return index(cached_tags.indexOf(t)); +} + Qt::ItemFlags TagListModel::flags(const QModelIndex &index) const { Q_UNUSED(index); diff --git a/taglistmodel.h b/taglistmodel.h index d4bd5df..d7e102f 100644 --- a/taglistmodel.h +++ b/taglistmodel.h @@ -15,6 +15,7 @@ public: int rowCount(const QModelIndex &parent=QModelIndex()) const override; QVariant data(const QModelIndex &index, int role=Qt::DisplayRole) const override; QVariant headerData(int section, Qt::Orientation orientation, int role=Qt::DisplayRole) const override; + QModelIndex indexOf(const Tag &t) const; Qt::ItemFlags flags(const QModelIndex &index) const override; bool setData(const QModelIndex &index, const QVariant &value, int role=Qt::EditRole) override; diff --git a/tagproptablemodel.cpp b/tagproptablemodel.cpp index 4dc5e6b..e015987 100644 --- a/tagproptablemodel.cpp +++ b/tagproptablemodel.cpp @@ -1,32 +1,28 @@ #include "tagproptablemodel.h" -TagPropTableModel::TagPropTableModel(SQLiteSaveFile &backend, const Tag tag) - : TagPropTableModel(backend, true) -{ - tag_cached = tag; - tag_keys = tag_cached.metadata.keys(); - tag_keys.sort(); - qDebug() << "connecting TagPropTableModel" << &backend; -} -TagPropTableModel::TagPropTableModel(SQLiteSaveFile &backend, bool tagIsValid) +TagPropTableModel::TagPropTableModel(SQLiteSaveFile *backend, bool showBaseData) : backend(backend) - , tagIsValid(tagIsValid) + , showBaseData(showBaseData) { - connect(&backend, &SQLiteSaveFile::tagChange, this, &TagPropTableModel::tagChange); - connect(&backend, &SQLiteSaveFile::fileReload, [=]() { - beginResetModel(); - this->tagIsValid = false; - endResetModel(); - }); + if (backend) { + connect(backend, &SQLiteSaveFile::tagChange, this, &TagPropTableModel::tagChange); + connect(backend, &SQLiteSaveFile::fileReload, [=]() { + showTag(Tag()); + }); + } } int TagPropTableModel::rowCount(const QModelIndex &parent) const { Q_UNUSED(parent); - if (!tagIsValid) + if (!tag_cached.isValid()) return 0; - return 3 + tag_cached.metadata.size(); + + if (showBaseData) + return 3 + tag_cached.metadata.size(); + else + return tag_cached.metadata.size(); } int TagPropTableModel::columnCount(const QModelIndex &parent) const @@ -43,16 +39,20 @@ QVariant TagPropTableModel::data(const QModelIndex &index, int role) const if (role != Qt::DisplayRole && role != Qt::EditRole) return QVariant(); + int row = index.row(); bool label = index.column() == 0; + if (showBaseData) { + + switch (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") : QString("%1, %2").arg(tag_cached.anchor.x()).arg(tag_cached.anchor.y()); break; + } - 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") : QString("%1, %2").arg(tag_cached.anchor.x()).arg(tag_cached.anchor.y()); break; + row -= 3; } - int idx = index.row() - 3; - return label ? tag_keys[idx] : tag_cached.metadata[tag_keys[idx]]; + return label ? tag_keys[row] : tag_cached.metadata[tag_keys[row]]; } QVariant TagPropTableModel::headerData(int section, Qt::Orientation orientation, int role) const @@ -75,17 +75,19 @@ Qt::ItemFlags TagPropTableModel::flags(const QModelIndex &index) const return Qt::NoItemFlags; if (index.column() == 0) { - if (index.row() < 3) + if (showBaseData && index.row() < 3) return Qt::ItemIsEnabled; return Qt::ItemIsEnabled | Qt::ItemIsEditable; } - if (index.row() == 1) - return Qt::ItemIsEnabled | Qt::ItemIsEditable; + if (showBaseData) { + if (index.row() == 1) + return Qt::ItemIsEnabled | Qt::ItemIsEditable; - if (index.row() == 2) /* anchor */ - return Qt::ItemIsEnabled; + if (index.row() == 2) /* anchor */ + return Qt::ItemIsEnabled; + } return Qt::ItemIsEditable | Qt::ItemIsSelectable | Qt::ItemIsEnabled; } @@ -98,22 +100,32 @@ bool TagPropTableModel::setData(const QModelIndex &index, const QVariant &value, if (role != Qt::EditRole) return false; - if (index.row() == 1) { - tag_cached.name = value.toString(); - } else if (index.row() < 3) { - return false; - } else { - 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]); + int row = index.row(); + if (showBaseData) { + if (row == 1) { + tag_cached.name = value.toString(); + + if (backend) + backend->updateTag(tag_cached); + return true; + + } else if (row < 3) { + return false; } else { - tag_cached.metadata[tag_keys[idx]] = value.toString(); + row -= 3; } } - backend.updateTag(tag_cached); + 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[row]]; + tag_cached.metadata.remove(tag_keys[row]); + } else { + tag_cached.metadata[tag_keys[row]] = value.toString(); + } + + if (backend) + backend->updateTag(tag_cached); return true; } @@ -130,9 +142,7 @@ void TagPropTableModel::tagChange(TagChange change, const Tag &tag) } else if (change == TagChange::DELETED) { dbg << "deleted"; - beginResetModel(); - tagIsValid = false; - endResetModel(); + showTag(Tag()); } } @@ -143,6 +153,5 @@ 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 09ab8b4..4ba0b3a 100644 --- a/tagproptablemodel.h +++ b/tagproptablemodel.h @@ -12,8 +12,7 @@ class TagPropTableModel : public QAbstractTableModel Q_OBJECT public: - TagPropTableModel(SQLiteSaveFile &backend) : TagPropTableModel(backend, false) {}; - TagPropTableModel(SQLiteSaveFile &backend, const Tag tag); + TagPropTableModel(SQLiteSaveFile *backend, bool showBaseData=true); int rowCount(const QModelIndex &parent=QModelIndex()) const override; int columnCount(const QModelIndex &parent=QModelIndex()) const override; @@ -25,16 +24,17 @@ public: void showTag(const Tag &tag); + QVariantMap metadata() { return tag_cached.metadata; } + private slots: void tagChange(TagChange change, const Tag &tag); private: - TagPropTableModel(SQLiteSaveFile &backend, bool tagIsValid); - SQLiteSaveFile &backend; + SQLiteSaveFile *backend = nullptr; Tag tag_cached; QStringList tag_keys; - bool tagIsValid; + bool showBaseData; }; #endif // TAGPROPTABLEMODEL_H diff --git a/tagscene.cpp b/tagscene.cpp index a6efd17..c5777e0 100644 --- a/tagscene.cpp +++ b/tagscene.cpp @@ -81,6 +81,15 @@ void TagScene::reloadScene() reloadPicture(); /* calls invalidate() for us */ } +void TagScene::selectTag(const Tag &tag) +{ + clearSelection(); + TagItem *it = tags[tag.id]; + if (!it) + return; + it->setSelected(true); +} + void TagScene::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event) { QGraphicsItem *it = itemAt(event->scenePos(), QTransform()); diff --git a/tagscene.h b/tagscene.h index 4c81fe5..18f9f54 100644 --- a/tagscene.h +++ b/tagscene.h @@ -15,11 +15,13 @@ class TagScene : public QGraphicsScene public: TagScene() {} + ~TagScene() { this->blockSignals(true); } const QGraphicsPixmapItem *backgroundPixmapItem() const { return pix_it; } public slots: void reloadPicture(); void reloadScene(); + void selectTag(const Tag &tag); void setProject(SQLiteSaveFile *proj); diff --git a/tagview.h b/tagview.h index edafcf4..12d1088 100644 --- a/tagview.h +++ b/tagview.h @@ -16,6 +16,8 @@ public: TagView(QWidget *parent=nullptr); virtual ~TagView(); + TagScene *scene() { return &m_scene; } + public slots: void zoomToFit(); void setZoom(qreal zoom); -- cgit