WIP: properties dialog
This commit is contained in:
parent
54c54e2adf
commit
83246f04ae
|
@ -8,6 +8,8 @@
|
|||
#include "graphicsscene.h"
|
||||
#include "settingsdialog.h"
|
||||
#include "aboutdialog.h"
|
||||
#include "metadatadialog.h"
|
||||
#include "metadatamodel.h"
|
||||
|
||||
#include <QMouseEvent>
|
||||
#include <QMovie>
|
||||
|
@ -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();
|
||||
|
||||
|
|
81
metadata.cpp
Normal file
81
metadata.cpp
Normal file
|
@ -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);
|
||||
}
|
||||
|
66
metadata.h
Normal file
66
metadata.h
Normal file
|
@ -0,0 +1,66 @@
|
|||
#ifndef METADATA_H
|
||||
#define METADATA_H
|
||||
|
||||
#include <QMap>
|
||||
#include <QPair>
|
||||
#include <QCoreApplication>
|
||||
|
||||
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<PropertyType> m_propertyIndexes;
|
||||
QMap<PropertyType, QString> m_propertiesValueMap;
|
||||
QMap<PropertyType, QString> m_propertiesOverrideDisplayNameMap;
|
||||
|
||||
const QMap<PropertyType, QString> 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<SectionType> m_sectionIndexes;
|
||||
QMap<SectionType, Section> m_sections;
|
||||
|
||||
void setSectionName(SectionType type, const QString & displayName);
|
||||
void setPropertyValue(SectionType type, Section::PropertyType propType, QString value);
|
||||
|
||||
};
|
||||
|
||||
#endif // METADATA_H
|
41
metadatadialog.cpp
Normal file
41
metadatadialog.cpp
Normal file
|
@ -0,0 +1,41 @@
|
|||
#include "metadatadialog.h"
|
||||
|
||||
#include <QDialogButtonBox>
|
||||
#include <QTreeView>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
#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);
|
||||
}
|
26
metadatadialog.h
Normal file
26
metadatadialog.h
Normal file
|
@ -0,0 +1,26 @@
|
|||
#ifndef METADATADIALOG_H
|
||||
#define METADATADIALOG_H
|
||||
|
||||
#include <QDialog>
|
||||
|
||||
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
|
267
metadatamodel.cpp
Normal file
267
metadatamodel.cpp
Normal file
|
@ -0,0 +1,267 @@
|
|||
#include "metadatamodel.h"
|
||||
|
||||
#include <QFileInfo>
|
||||
#include <QDateTime>
|
||||
#include <QLocale>
|
||||
#include <QSize>
|
||||
#include <QImageReader>
|
||||
|
||||
// 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<Entry*> m_entries;
|
||||
QHash<QString, int> 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<quintptr>(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<int>(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());
|
||||
}
|
||||
}
|
36
metadatamodel.h
Normal file
36
metadatamodel.h
Normal file
|
@ -0,0 +1,36 @@
|
|||
#ifndef METADATAMODEL_H
|
||||
#define METADATAMODEL_H
|
||||
|
||||
#include <QVector>
|
||||
#include <QAbstractItemModel>
|
||||
|
||||
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<MetadataSection *> m_sections; // associated pointers free at destructor function.
|
||||
QMap<Sections, MetadataSection *> 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
|
|
@ -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 \
|
||||
|
|
Loading…
Reference in New Issue
Block a user