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 \