From 83246f04ae68c68be1b9ba4e0adb217a7697b537 Mon Sep 17 00:00:00 2001 From: Gary Wang Date: Mon, 12 Oct 2020 22:35:37 +0800 Subject: [PATCH] WIP: properties dialog --- mainwindow.cpp | 17 +++ metadata.cpp | 81 +++++++++++++ metadata.h | 66 ++++++++++ metadatadialog.cpp | 41 +++++++ metadatadialog.h | 26 ++++ metadatamodel.cpp | 267 +++++++++++++++++++++++++++++++++++++++++ metadatamodel.h | 36 ++++++ pineapple-pictures.pro | 6 + 8 files changed, 540 insertions(+) create mode 100644 metadata.cpp create mode 100644 metadata.h create mode 100644 metadatadialog.cpp create mode 100644 metadatadialog.h create mode 100644 metadatamodel.cpp create mode 100644 metadatamodel.h diff --git a/mainwindow.cpp b/mainwindow.cpp index 8479c1c..0a40d6c 100644 --- a/mainwindow.cpp +++ b/mainwindow.cpp @@ -8,6 +8,8 @@ #include "graphicsscene.h" #include "settingsdialog.h" #include "aboutdialog.h" +#include "metadatadialog.h" +#include "metadatamodel.h" #include #include @@ -480,6 +482,17 @@ void MainWindow::contextMenuEvent(QContextMenuEvent *event) ad->deleteLater(); }); + QAction * propertiesAction = new QAction(tr("Properties")); + connect(propertiesAction, &QAction::triggered, this, [ = ](){ + MetadataModel * md = new MetadataModel(); + md->setFile(currentFileUrl.toLocalFile()); + + MetadataDialog * ad = new MetadataDialog(this); + ad->setMetadataModel(md); + ad->exec(); + ad->deleteLater(); + }); + if (copyMenu->actions().count() == 1) { menu->addActions(copyMenu->actions()); } else { @@ -496,6 +509,10 @@ void MainWindow::contextMenuEvent(QContextMenuEvent *event) menu->addSeparator(); menu->addAction(toggleSettings); menu->addAction(helpAction); + if (currentFileUrl.isValid()) { + menu->addSeparator(); + menu->addAction(propertiesAction); + } menu->exec(mapToGlobal(event->pos())); menu->deleteLater(); diff --git a/metadata.cpp b/metadata.cpp new file mode 100644 index 0000000..6b18ecc --- /dev/null +++ b/metadata.cpp @@ -0,0 +1,81 @@ +#include "metadata.h" + +Section::Section() +{ + +} + +Section::~Section() +{ + +} + +// Warning! this method won't copy anything from the given argument! +// we actually doesn't care about this, but since QList and QMap are not movable +// so there won't be a (not ill-formed) default ctor to use and we won't get a +// proper copy ctor, thus the operator= will be deleted. +// When accessing QMap value if not exist, we just use this to create a new Section. +Section & Section::operator=(const Section &) +{ + return *this; +} + +int Section::count() const +{ + return m_propertiesValueMap.count(); +} + +void Section::setSectionName(const QString &displayName) +{ + m_displayName = displayName; +} + +void Section::setValue(PropertyType type, const QString &value) +{ + if (!m_propertiesValueMap.contains(type)) { + m_propertyIndexes.append(type); + } + + m_propertiesValueMap[type] = value; +} + +QString Section::valueAt(int index) const +{ + Q_ASSERT(index >= 0 && index < m_propertyIndexes.count()); + + return m_propertiesValueMap[m_propertyIndexes[index]]; +} + +QString Section::value(Section::PropertyType type) const +{ + return m_propertiesValueMap.value(type, tr("Unknown")); +} + +QString Section::propertyName(PropertyType type) const +{ + return m_propertiesOverrideDisplayNameMap.value( + type, + m_builtinPropDisplayNameMap.value(type, tr("Unknown")) + ); +} + +Metadata::Metadata() +{ + +} + +Metadata::~Metadata() +{ + +} + +void Metadata::setSectionName(SectionType type, const QString &displayName) +{ + m_sections[type].setSectionName(displayName); +} + +void Metadata::setPropertyValue(Metadata::SectionType type, Section::PropertyType propType, QString value) +{ + m_sections[type].setValue(propType, value); +} + diff --git a/metadata.h b/metadata.h new file mode 100644 index 0000000..9ea9b63 --- /dev/null +++ b/metadata.h @@ -0,0 +1,66 @@ +#ifndef METADATA_H +#define METADATA_H + +#include +#include +#include + +class Section +{ + Q_DECLARE_TR_FUNCTIONS(Section) +public: + Section(); + ~Section(); + Section & operator=(const Section& other); + + // TODO: maybe use QString instead of a enum? different section won't share a same + // enum value in any way... + enum PropertyType : int { + NameProp, + FileSizeProp, + LastModifiedProp, + ImageSizeProp, + }; + + int count() const; + void setSectionName(const QString & displayName); + void setValue(PropertyType type, const QString & value); + QString value(PropertyType type) const; + QString valueAt(int index) const; + QString propertyName(PropertyType type) const; + +private: + QString m_displayName; + QList m_propertyIndexes; + QMap m_propertiesValueMap; + QMap m_propertiesOverrideDisplayNameMap; + + const QMap m_builtinPropDisplayNameMap { + {NameProp, tr("Name")}, + {FileSizeProp, tr("File Size")}, + {LastModifiedProp, tr("Last Modified")}, + {ImageSizeProp, tr("Image Size")}, + }; +}; + +class Metadata +{ +public: + enum SectionType : int { + GeneralSection, + ExifSection, + }; + + Metadata(); + ~Metadata(); + +private: + QList m_sectionIndexes; + QMap m_sections; + + void setSectionName(SectionType type, const QString & displayName); + void setPropertyValue(SectionType type, Section::PropertyType propType, QString value); + +}; + +#endif // METADATA_H diff --git a/metadatadialog.cpp b/metadatadialog.cpp new file mode 100644 index 0000000..e76b033 --- /dev/null +++ b/metadatadialog.cpp @@ -0,0 +1,41 @@ +#include "metadatadialog.h" + +#include +#include +#include + +#include "metadatamodel.h" + +MetadataDialog::MetadataDialog(QWidget *parent) + : QDialog(parent) + , m_treeView(new QTreeView(this)) +{ +// m_treeView->setRootIsDecorated(false); + m_treeView->setIndentation(0); + + setWindowTitle(tr("Image Metadata")); + + QDialogButtonBox * buttonBox = new QDialogButtonBox(QDialogButtonBox::Close); + + setLayout(new QVBoxLayout); + layout()->addWidget(m_treeView); + layout()->addWidget(buttonBox); + + connect(buttonBox, &QDialogButtonBox::close, this, &QDialog::close); +} + +MetadataDialog::~MetadataDialog() +{ + +} + +void MetadataDialog::setMetadataModel(MetadataModel * model) +{ + m_treeView->setModel(model); + m_treeView->expandAll(); +} + +QSize MetadataDialog::sizeHint() const +{ + return QSize(520, 350); +} diff --git a/metadatadialog.h b/metadatadialog.h new file mode 100644 index 0000000..8459c95 --- /dev/null +++ b/metadatadialog.h @@ -0,0 +1,26 @@ +#ifndef METADATADIALOG_H +#define METADATADIALOG_H + +#include + +QT_BEGIN_NAMESPACE +class QTreeView; +QT_END_NAMESPACE + +class MetadataModel; +class MetadataDialog : public QDialog +{ + Q_OBJECT +public: + explicit MetadataDialog(QWidget * parent); + ~MetadataDialog() override; + + void setMetadataModel(MetadataModel * model); + + QSize sizeHint() const override; + +private: + QTreeView * m_treeView = nullptr; +}; + +#endif // METADATADIALOG_H diff --git a/metadatamodel.cpp b/metadatamodel.cpp new file mode 100644 index 0000000..c1c96cd --- /dev/null +++ b/metadatamodel.cpp @@ -0,0 +1,267 @@ +#include "metadatamodel.h" + +#include +#include +#include +#include +#include + +// This model is very similar to imagemetainfomodel.cpp in +// Gwenview, since We don't care about ABI here and we won't +// have any KDE lib as dept (KIO is a tier 3 framework from +// KDE for example) which might cause this project no longer +// tiny and easy to maintain, I'll just create a simpler +// implementation than do a simple copy-paste... + +enum Sections : int { + GeneralSection, + ExifSection, + CustomSection = 61, +}; + +class MetadataSection +{ +public: + class Entry + { + public: + // key: for internal indexing. + // label: display name of the key. + Entry(const QString & key, const QString & label, const QString & value) + : m_key(key) + , m_label(label.trimmed()) + , m_value(value.trimmed()) + {} + + QString m_key; + QString m_label; + QString m_value; + }; + + MetadataSection(const QString & sectionName) + : m_sectionName(sectionName) + {} + + ~MetadataSection() + { + qDeleteAll(m_entries); + } + + void clear() + { + qDeleteAll(m_entries); + m_entries.clear(); + } + + void addEnrty(const QString & key, const QString & label, const QString & value = QString()) + { + Entry * e = new Entry(key, label, value); + m_entries << e; + m_keyEntryMap[e->m_key] = m_entries.size() - 1; + } + + QString keyAt(int index) const + { + Q_ASSERT(index < m_entries.size()); + return m_entries[index]->m_key; + } + + QString valueAt(int index) const + { + Q_ASSERT(index < m_entries.size()); + return m_entries[index]->m_value; + } + + void setValueAt(int index, const QString & value) + { + Q_ASSERT(index < m_entries.size()); + m_entries[index]->m_value = value; + } + + QString labelAt(int index) const + { + Q_ASSERT(index < m_entries.size()); + return m_entries[index]->m_label; + } + + int keyIndex(const QString & key) { + return m_keyEntryMap.value(key, -1); + } + + QString sectionName() const + { + return m_sectionName; + } + + int size() const + { + return m_entries.size(); + } + +private: + QList m_entries; + QHash m_keyEntryMap; + QString m_sectionName; +}; + +MetadataModel::MetadataModel() +{ + sectionRegister(GeneralSection, new MetadataSection(tr("General", "General info about the image, section name in metadata dialog"))); + + initGeneralSection(); +} + +MetadataModel::~MetadataModel() +{ + qDeleteAll(m_sections); +} + +void MetadataModel::setFile(const QString &path) +{ + QFileInfo fileInfo(path); + const QString sizeString = QLocale().formattedDataSize(fileInfo.size()); + const QString timeString = QLocale().toString(fileInfo.lastModified(), QLocale::LongFormat); + + // FIXME: this implementation is very dirty! + QImageReader imgReader(path); + setImageSize(imgReader.size()); + + setSectionEntryValue(GeneralSection, QStringLiteral("General.Name"), fileInfo.fileName()); + setSectionEntryValue(GeneralSection, QStringLiteral("General.Size"), sizeString); + setSectionEntryValue(GeneralSection, QStringLiteral("General.Time"), timeString); +} + +void MetadataModel::setImageSize(const QSize &size) +{ + QString imageSize; + + if (size.isValid()) { + imageSize = tr("%1 x %2").arg(QString::number(size.width()), QString::number(size.height())); + } else { + imageSize = QLatin1Char('-'); + } + + setSectionEntryValue(GeneralSection, QStringLiteral("General.ImageSize"), imageSize); +} + +QModelIndex MetadataModel::index(int row, int col, const QModelIndex &parent) const +{ + if (col < 0 || col > 1) { + return QModelIndex(); + } + if (!parent.isValid()) { + // This is a group + if (row < 0 || row >= m_sections.size()) { + return QModelIndex(); + } + return createIndex(row, col, col == 0 ? 2 : 3); // ???????? + } else { + // This is an entry + int group = parent.row(); + if (row < 0 || row >= m_sections[group]->size()) { + return QModelIndex(); + } + return createIndex(row, col, static_cast(group)); + } +} + +QModelIndex MetadataModel::parent(const QModelIndex & index) const +{ + if (!index.isValid()) { + return QModelIndex(); + } + if (index.internalId() == 2 || index.internalId() == 3) { + return QModelIndex(); + } else { + return createIndex(static_cast(index.internalId()), 0, 2); + } +} + +int MetadataModel::rowCount(const QModelIndex & parent) const +{ + if (!parent.isValid()) { + return m_sections.size(); + } else if (parent.internalId() == 2) { + return m_sections[parent.row()]->size(); + } else { + return 0; + } +} + +int MetadataModel::columnCount(const QModelIndex &) const +{ + return 2; +} + +QVariant MetadataModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid()) { + return QVariant(); + } + + switch (role) { + case Qt::DisplayRole: + return displayData(index); + default: + return QVariant(); + } +} + +void MetadataModel::initGeneralSection() +{ + MetadataSection * s = section(GeneralSection); + + s->addEnrty(QStringLiteral("General.Name"), tr("File Name")); + s->addEnrty(QStringLiteral("General.Size"), tr("File Size")); + s->addEnrty(QStringLiteral("General.Time"), tr("Last Modified")); + s->addEnrty(QStringLiteral("General.ImageSize"), tr("Image Size")); +} + +bool MetadataModel::sectionRegister(Sections sectionType, MetadataSection *section) +{ + if (m_sectionEnumIndexMap.contains(sectionType)) { + return false; + } + m_sections.append(section); + m_sectionEnumIndexMap[sectionType] = section; + + return true; +} + +void MetadataModel::setSectionEntryValue(Sections sectionType, const QString &key, const QString &value) +{ + MetadataSection * s = section(sectionType); + int entryIndex = s->keyIndex(key); + if (entryIndex < 0) { + // no such entry + return; + } + s->setValueAt(entryIndex, value); +} + +MetadataSection *MetadataModel::section(Sections sectionType) +{ + return m_sectionEnumIndexMap[sectionType]; +} + +QVariant MetadataModel::displayData(const QModelIndex &index) const +{ + if (index.internalId() == 2) { + if (index.column() != 0) { + return QVariant(); + } + QString label = m_sections[index.row()]->sectionName(); + return QVariant(label); + } + + if (index.internalId() == 3) { + return QString(); + } + + MetadataSection* group = m_sections[index.internalId()]; + if (index.column() == 0) { + return group->labelAt(index.row()); + } else { + return group->valueAt(index.row()); + } +} diff --git a/metadatamodel.h b/metadatamodel.h new file mode 100644 index 0000000..074f787 --- /dev/null +++ b/metadatamodel.h @@ -0,0 +1,36 @@ +#ifndef METADATAMODEL_H +#define METADATAMODEL_H + +#include +#include + +enum Sections : int; +class MetadataSection; +class MetadataModel : public QAbstractItemModel +{ + Q_OBJECT +public: + MetadataModel(); + ~MetadataModel() override; + + void setFile(const QString & url); + void setImageSize(const QSize & size); + + QModelIndex index(int row, int col, const QModelIndex& parent = QModelIndex()) const override; + QModelIndex parent(const QModelIndex & index) const override; + int rowCount(const QModelIndex & parent = QModelIndex()) const override; + int columnCount(const QModelIndex & = QModelIndex()) const override; + QVariant data(const QModelIndex & index, int role = Qt::DisplayRole) const override; + +private: + QList m_sections; // associated pointers free at destructor function. + QMap m_sectionEnumIndexMap; // pointer shared with m_sections + + void initGeneralSection(); + bool sectionRegister(Sections sectionType, MetadataSection * section); + void setSectionEntryValue(Sections sectionType, const QString & key, const QString & value); + MetadataSection * section(Sections sectionType); + QVariant displayData(const QModelIndex & index) const; +}; + +#endif // METADATAMODEL_H diff --git a/pineapple-pictures.pro b/pineapple-pictures.pro index 0a6dd42..778640c 100644 --- a/pineapple-pictures.pro +++ b/pineapple-pictures.pro @@ -29,6 +29,9 @@ SOURCES += \ graphicsview.cpp \ bottombuttongroup.cpp \ graphicsscene.cpp \ + metadata.cpp \ + metadatadialog.cpp \ + metadatamodel.cpp \ navigatorview.cpp \ opacityhelper.cpp \ toolbutton.cpp \ @@ -41,6 +44,9 @@ HEADERS += \ graphicsview.h \ bottombuttongroup.h \ graphicsscene.h \ + metadata.h \ + metadatadialog.h \ + metadatamodel.h \ navigatorview.h \ opacityhelper.h \ toolbutton.h \