From 71c38c8e96e6e418ca53a9153ac03d0c4c04f351 Mon Sep 17 00:00:00 2001 From: Gary Wang Date: Fri, 30 Oct 2020 13:06:46 +0800 Subject: [PATCH] feat: simple property dialog --- CMakeLists.txt | 4 + languages/PineapplePictures.ts | 67 +++++++++-- languages/PineapplePictures_zh_CN.ts | 67 +++++++++-- mainwindow.cpp | 17 +++ metadatadialog.cpp | 101 ++++++++++++++++ metadatadialog.h | 26 +++++ metadatamodel.cpp | 168 +++++++++++++++++++++++++++ metadatamodel.h | 42 +++++++ pineapple-pictures.pro | 20 ++-- 9 files changed, 481 insertions(+), 31 deletions(-) create mode 100644 metadatadialog.cpp create mode 100644 metadatadialog.h create mode 100644 metadatamodel.cpp create mode 100644 metadatamodel.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 8d76abd..5ce6017 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -23,6 +23,8 @@ set (PPIC_CPP_FILES settings.cpp settingsdialog.cpp aboutdialog.cpp + metadatamodel.cpp + metadatadialog.cpp ) set (PPIC_HEADER_FILES @@ -36,6 +38,8 @@ set (PPIC_HEADER_FILES settings.h settingsdialog.h aboutdialog.h + metadatamodel.h + metadatadialog.h ) set (PPIC_QRC_FILES diff --git a/languages/PineapplePictures.ts b/languages/PineapplePictures.ts index 9935ee1..8286787 100644 --- a/languages/PineapplePictures.ts +++ b/languages/PineapplePictures.ts @@ -193,58 +193,105 @@ MainWindow - + File url list is empty - + &Copy - + Copy P&ixmap - + Copy &File Path - + &Paste Image - + &Paste Image File + + + Properties + + - + Stay on top - + Protected mode - + Configure... - + Help + + MetadataDialog + + + Image Metadata + + + + + MetadataModel + + + General + General info about the image, section name. + + + + + File Name + + + + + File Size + + + + + Last Modified + + + + + Image Size + + + + + %1 x %2 + + + SettingsDialog diff --git a/languages/PineapplePictures_zh_CN.ts b/languages/PineapplePictures_zh_CN.ts index cc1d866..edfdf2b 100644 --- a/languages/PineapplePictures_zh_CN.ts +++ b/languages/PineapplePictures_zh_CN.ts @@ -193,58 +193,105 @@ MainWindow - + File url list is empty 文件 URL 列表为空 - + &Copy 复制(&C) - + Copy P&ixmap 复制位图(&I) - + Copy &File Path 复制文件路径(&F) - + &Paste Image 粘贴图像(&P) - + &Paste Image File 粘贴图像文件(&P) + + + Properties + + - + Stay on top 总在最前 - + Protected mode 保护模式 - + Configure... 设置... - + Help 帮助 + + MetadataDialog + + + Image Metadata + + + + + MetadataModel + + + General + General info about the image, section name. + + + + + File Name + + + + + File Size + + + + + Last Modified + + + + + Image Size + + + + + %1 x %2 + + + SettingsDialog diff --git a/mainwindow.cpp b/mainwindow.cpp index 28be67a..4b864ed 100644 --- a/mainwindow.cpp +++ b/mainwindow.cpp @@ -8,6 +8,8 @@ #include "graphicsscene.h" #include "settingsdialog.h" #include "aboutdialog.h" +#include "metadatamodel.h" +#include "metadatadialog.h" #include #include @@ -487,6 +489,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 { @@ -503,6 +516,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/metadatadialog.cpp b/metadatadialog.cpp new file mode 100644 index 0000000..4e6c3cb --- /dev/null +++ b/metadatadialog.cpp @@ -0,0 +1,101 @@ +#include "metadatadialog.h" + +#include +#include +#include +#include +#include +#include + +#include "metadatamodel.h" + +class PropertyTreeView : public QTreeView +{ +public: + explicit PropertyTreeView(QWidget* parent) : QTreeView(parent) {} + ~PropertyTreeView() {} + +protected: + void rowsInserted(const QModelIndex& parent, int start, int end) override + { + QTreeView::rowsInserted(parent, start, end); + if (!parent.isValid()) { + for (int row = start; row <= end; ++row) { + setupSection(row); + } + } + } + + void reset() override + { + QTreeView::reset(); + if (model()) { + for (int row = 0; row < model()->rowCount(); ++row) { + setupSection(row); + } + } + } + +private: + void setupSection(int row) + { + expand(model()->index(row, 0)); + setFirstColumnSpanned(row, QModelIndex(), true); + } +}; + +class PropertyTreeItemDelegate : public QStyledItemDelegate +{ +public: + PropertyTreeItemDelegate(QObject* parent) + : QStyledItemDelegate(parent) + {} + +protected: + void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const override + { + QStyleOptionViewItem opt = option; + if (!index.parent().isValid()) { + opt.font.setBold(true); + opt.features.setFlag(QStyleOptionViewItem::Alternate); + } + QStyledItemDelegate::paint(painter, opt, index); + } +}; + +MetadataDialog::MetadataDialog(QWidget *parent) + : QDialog(parent) + , m_treeView(new PropertyTreeView(this)) +{ + m_treeView->setRootIsDecorated(false); + m_treeView->setIndentation(0); + m_treeView->setItemDelegate(new PropertyTreeItemDelegate(m_treeView)); + m_treeView->header()->resizeSection(0, sizeHint().width() / 2); + + setWindowTitle(tr("Image Metadata")); + + QDialogButtonBox * buttonBox = new QDialogButtonBox(QDialogButtonBox::Close); + + setLayout(new QVBoxLayout); + layout()->addWidget(m_treeView); + layout()->addWidget(buttonBox); + + connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::close); + + setWindowFlag(Qt::WindowContextHelpButtonHint, false); +} + +MetadataDialog::~MetadataDialog() +{ + +} + +void MetadataDialog::setMetadataModel(MetadataModel * model) +{ + m_treeView->setModel(model); +} + +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..517b46a --- /dev/null +++ b/metadatamodel.cpp @@ -0,0 +1,168 @@ +#include "metadatamodel.h" + +#include +#include +#include +#include + +MetadataModel::MetadataModel(QObject *parent) + : QAbstractItemModel(parent) +{ + +} + +MetadataModel::~MetadataModel() +{ + +} + +void MetadataModel::setFile(const QString &imageFilePath) +{ + QFileInfo fileInfo(imageFilePath); + // It'll be fine if we don't re-use the image reader we pass to the graphics scene for now. + QImageReader imgReader(imageFilePath); + + const QString & sizeString = QLocale().formattedDataSize(fileInfo.size()); + const QString & timeString = QLocale().toString(fileInfo.lastModified(), QLocale::LongFormat); + const QString & imageSizeString = imageSize(imgReader.size()); + + appendSection(QStringLiteral("General"), tr("General", "General info about the image, section name.")); + + appendProperty(QStringLiteral("General"), QStringLiteral("General.Name"), + tr("File Name"), fileInfo.fileName()); + appendProperty(QStringLiteral("General"), QStringLiteral("General.Size"), + tr("File Size"), sizeString); + appendProperty(QStringLiteral("General"), QStringLiteral("General.Time"), + tr("Last Modified"), timeString); + appendProperty(QStringLiteral("General"), QStringLiteral("General.ImageSize"), + tr("Image Size"), imageSizeString); +} + +QString MetadataModel::imageSize(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('-'); + } + + return imageSize; +} + +bool MetadataModel::appendSection(const QString §ionKey, const QString §ionDisplayName) +{ + if (m_sections.contains(sectionKey)) { + return false; + } + + m_sections.append(sectionKey); + m_sectionProperties[sectionKey] = qMakePair >(sectionDisplayName, {}); + + return true; +} + +bool MetadataModel::appendProperty(const QString §ionKey, const QString &propertyKey, const QString &propertyDisplayName, const QString &propertyValue) +{ + if (!m_sections.contains(sectionKey)) { + return false; + } + + QList & propertyList = m_sectionProperties[sectionKey].second; + if (!propertyList.contains(propertyKey)) { + propertyList.append(propertyKey); + } + + m_properties[propertyKey] = qMakePair(propertyDisplayName, propertyValue); + + return true; +} + +bool MetadataModel::updateProperty(const QString &propertyKey, const QString &propertyValue) +{ + if (m_properties.contains(propertyKey)) { + m_properties[propertyKey].second = propertyValue; + return true; + } + + return false; +} + +QModelIndex MetadataModel::index(int row, int column, const QModelIndex &parent) const +{ + if (!hasIndex(row, column, parent)) { + return QModelIndex(); + } + + if (!parent.isValid()) { + return createIndex(row, column, RowType::SectionRow); + } else { + // internalid param: row means nth section it belongs to. + return createIndex(row, column, RowType::PropertyRow + parent.row()); + } +} + +QModelIndex MetadataModel::parent(const QModelIndex &child) const +{ + if (!child.isValid()) { + return QModelIndex(); + } + + if (child.internalId() == RowType::SectionRow) { + return QModelIndex(); + } else { + return createIndex(child.internalId() - RowType::PropertyRow, 0, SectionRow); + } +} + +int MetadataModel::rowCount(const QModelIndex &parent) const +{ + if (!parent.isValid()) { + return m_sections.count(); + } + + if (parent.internalId() == RowType::SectionRow) { + const QString & sectionKey = m_sections[parent.row()]; + return m_sectionProperties[sectionKey].second.count(); + } + + return 0; +} + +int MetadataModel::columnCount(const QModelIndex &) const +{ + // Always key(display name) and value. + return 2; +} + +QVariant MetadataModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid()) { + return QVariant(); + } + + if (role != Qt::DisplayRole) { + return QVariant(); + } + + if (index.internalId() == RowType::SectionRow) { + return (index.column() == 0) ? m_sectionProperties[m_sections[index.row()]].first + : QVariant(); + } else { + int sectionIndex = index.internalId() - RowType::PropertyRow; + const QString & sectionKey = m_sections[sectionIndex]; + const QList & propertyList = m_sectionProperties[sectionKey].second; + return (index.column() == 0) ? m_properties[propertyList[index.row()]].first + : m_properties[propertyList[index.row()]].second; + } +} + +QVariant MetadataModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + if (orientation == Qt::Vertical || role != Qt::DisplayRole) { + return QVariant(); + } + + return section == 0 ? tr("Property") : tr("Value"); +} diff --git a/metadatamodel.h b/metadatamodel.h new file mode 100644 index 0000000..9f1c904 --- /dev/null +++ b/metadatamodel.h @@ -0,0 +1,42 @@ +#ifndef METADATAMODEL_H +#define METADATAMODEL_H + +#include + +class MetadataModel : public QAbstractItemModel +{ + Q_OBJECT + +public: + explicit MetadataModel(QObject *parent = nullptr); + ~MetadataModel(); + + void setFile(const QString & imageFilePath); + static QString imageSize(const QSize &size); + bool appendSection(const QString & sectionKey, const QString & sectionDisplayName); + bool appendProperty(const QString & sectionKey, const QString & propertyKey, + const QString & propertyDisplayName, const QString & propertyValue = QString()); + bool updateProperty(const QString & propertyKey, const QString & propertyValue); + +private: + enum RowType : quintptr { + SectionRow, + PropertyRow, + }; + + QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override; + QModelIndex parent(const QModelIndex &child) 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; + QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; + + // [SECTION_KEY] + QList m_sections; + // {SECTION_KEY: (SECTION_DISPLAY_NAME, [PROPERTY_KEY])} + QMap > > m_sectionProperties; + // {PROPERTY_KEY: (PROPERTY_DISPLAY_NAME, PROPERTY_VALUE)} + QMap > m_properties; +}; + +#endif // METADATAMODEL_H diff --git a/pineapple-pictures.pro b/pineapple-pictures.pro index 0a6dd42..07656f7 100644 --- a/pineapple-pictures.pro +++ b/pineapple-pictures.pro @@ -1,9 +1,3 @@ -#------------------------------------------------- -# -# Project created by QtCreator 2019-09-26T23:36:07 -# -#------------------------------------------------- - QT += core widgets gui svg TARGET = ppic @@ -24,8 +18,8 @@ CONFIG += c++11 lrelease embed_translations SOURCES += \ aboutdialog.cpp \ - main.cpp \ - mainwindow.cpp \ + main.cpp \ + mainwindow.cpp \ graphicsview.cpp \ bottombuttongroup.cpp \ graphicsscene.cpp \ @@ -33,11 +27,13 @@ SOURCES += \ opacityhelper.cpp \ toolbutton.cpp \ settings.cpp \ - settingsdialog.cpp + settingsdialog.cpp \ + metadatamodel.cpp \ + metadatadialog.cpp HEADERS += \ aboutdialog.h \ - mainwindow.h \ + mainwindow.h \ graphicsview.h \ bottombuttongroup.h \ graphicsscene.h \ @@ -45,7 +41,9 @@ HEADERS += \ opacityhelper.h \ toolbutton.h \ settings.h \ - settingsdialog.h + settingsdialog.h \ + metadatamodel.h \ + metadatadialog.h TRANSLATIONS = \ languages/PineapplePictures.ts \