8 Commits

38 changed files with 2516 additions and 1371 deletions

1
.gitignore vendored
View File

@ -1,5 +1,6 @@
# User files
*.user
*.user.*
# Translation files
*.qm

View File

@ -12,30 +12,34 @@ set (QT_MINIMUM_VERSION "5.10")
find_package(Qt5 ${QT_MINIMUM_VERSION} CONFIG REQUIRED Widgets Svg LinguistTools)
set (PPIC_CPP_FILES
main.cpp
mainwindow.cpp
graphicsview.cpp
graphicsscene.cpp
bottombuttongroup.cpp
navigatorview.cpp
opacityhelper.cpp
toolbutton.cpp
settings.cpp
settingsdialog.cpp
aboutdialog.cpp
app/main.cpp
app/mainwindow.cpp
app/graphicsview.cpp
app/graphicsscene.cpp
app/bottombuttongroup.cpp
app/navigatorview.cpp
app/opacityhelper.cpp
app/toolbutton.cpp
app/settings.cpp
app/settingsdialog.cpp
app/aboutdialog.cpp
app/metadatamodel.cpp
app/metadatadialog.cpp
)
set (PPIC_HEADER_FILES
mainwindow.h
graphicsview.h
graphicsscene.h
bottombuttongroup.h
navigatorview.h
opacityhelper.h
toolbutton.h
settings.h
settingsdialog.h
aboutdialog.h
app/mainwindow.h
app/graphicsview.h
app/graphicsscene.h
app/bottombuttongroup.h
app/navigatorview.h
app/opacityhelper.h
app/toolbutton.h
app/settings.h
app/settingsdialog.h
app/aboutdialog.h
app/metadatamodel.h
app/metadatadialog.h
)
set (PPIC_QRC_FILES
@ -49,7 +53,7 @@ set (PPIC_RC_FILES
set (EXE_NAME ppic)
# Translation
file (GLOB PPIC_TS_FILES languages/*.ts)
file (GLOB PPIC_TS_FILES translations/*.ts)
set (PPIC_CPP_FILES_FOR_I18N ${PPIC_CPP_FILES})
qt5_create_translation(PPIC_QM_FILES ${PPIC_CPP_FILES_FOR_I18N} ${PPIC_TS_FILES})

View File

@ -33,7 +33,7 @@ BottomButtonGroup::BottomButtonGroup(QWidget *parent)
QPushButton * btn = new QPushButton(QIcon(QStringLiteral(":/icons/") + text), "");
btn->setIconSize(QSize(40, 40));
btn->setFixedSize(40, 40);
connect(btn, &QAbstractButton::clicked, btn, func);
QObject::connect(btn, &QAbstractButton::clicked, btn, func);
return btn;
};
addButton(newBtn("zoom-original", [this]() {

View File

@ -51,8 +51,15 @@ void GraphicsView::showFileFromUrl(const QUrl &url, bool doRequestGallery)
// QImage::Format imageFormat = imageReader.imageFormat();
if (imageReader.format().isEmpty()) {
showText(tr("File is not a valid image"));
} else if (!imageReader.supportsAnimation() && !imageReader.canRead()) {
showText(tr("Image data is invalid or currently unsupported"));
} else {
showImage(QPixmap::fromImageReader(&imageReader));
const QPixmap & pixmap = QPixmap::fromImageReader(&imageReader);
if (pixmap.isNull()) {
showText(tr("Image data is invalid or currently unsupported"));
} else {
showImage(pixmap);
}
}
}

View File

@ -8,8 +8,8 @@
#include "graphicsscene.h"
#include "settingsdialog.h"
#include "aboutdialog.h"
#include "metadatadialog.h"
#include "metadatamodel.h"
#include "metadatadialog.h"
#include <QMouseEvent>
#include <QMovie>
@ -24,6 +24,7 @@
#include <QCollator>
#include <QClipboard>
#include <QMimeData>
#include <QWindow>
#ifdef _WIN32
#include <windows.h>
@ -341,7 +342,13 @@ void MainWindow::mousePressEvent(QMouseEvent *event)
void MainWindow::mouseMoveEvent(QMouseEvent *event)
{
if (event->buttons() & Qt::LeftButton && m_clickedOnWindow && !isMaximized()) {
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
if (!window()->windowHandle()->startSystemMove()) {
move(event->globalPos() - m_oldMousePos);
}
#else
move(event->globalPos() - m_oldMousePos);
#endif // QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
event->accept();
}

101
app/metadatadialog.cpp Normal file
View File

@ -0,0 +1,101 @@
#include "metadatadialog.h"
#include <QDialogButtonBox>
#include <QPainter>
#include <QStyledItemDelegate>
#include <QTreeView>
#include <QVBoxLayout>
#include <QHeaderView>
#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);
}

205
app/metadatamodel.cpp Normal file
View File

@ -0,0 +1,205 @@
#include "metadatamodel.h"
#include <QDebug>
#include <QDateTime>
#include <QFileInfo>
#include <QImageReader>
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);
imgReader.setAutoTransform(true);
imgReader.setDecideFormatFromContent(true);
const QString & itemTypeString = tr("%1 File").arg(QString(imgReader.format().toUpper()));
const QString & sizeString = QLocale().formattedDataSize(fileInfo.size());
const QString & birthTimeString = QLocale().toString(fileInfo.birthTime(), QLocale::LongFormat);
const QString & lastModifiedTimeString = QLocale().toString(fileInfo.lastModified(), QLocale::LongFormat);
const QString & imageDimensionsString = imageSize(imgReader.size());
const QString & imageRatioString = imageSizeRatio(imgReader.size());
#if 0
appendSection(QStringLiteral("Description"), tr("Description", "Section name."));
appendSection(QStringLiteral("Origin"), tr("Origin", "Section name."));
#endif // 0
appendSection(QStringLiteral("Image"), tr("Image", "Section name."));
#if 0
appendSection(QStringLiteral("Camera"), tr("Camera", "Section name."));
appendSection(QStringLiteral("AdvancedPhoto"), tr("Advanced photo", "Section name."));
appendSection(QStringLiteral("GPS"), tr("GPS", "Section name."));
#endif // 0
appendSection(QStringLiteral("File"), tr("File", "Section name."));
appendProperty(QStringLiteral("Image"), QStringLiteral("Image.Dimensions"),
tr("Dimensions"), imageDimensionsString);
appendProperty(QStringLiteral("Image"), QStringLiteral("Image.SizeRatio"),
tr("Aspect Ratio"), imageRatioString);
appendProperty(QStringLiteral("File"), QStringLiteral("File.Name"),
tr("Name"), fileInfo.fileName());
appendProperty(QStringLiteral("File"), QStringLiteral("File.ItemType"),
tr("Item type"), itemTypeString);
appendProperty(QStringLiteral("File"), QStringLiteral("File.Path"),
tr("Folder path"), fileInfo.path());
appendProperty(QStringLiteral("File"), QStringLiteral("File.Size"),
tr("Size"), sizeString);
appendProperty(QStringLiteral("File"), QStringLiteral("File.CreatedTime"),
tr("Date Created"), birthTimeString);
appendProperty(QStringLiteral("File"), QStringLiteral("File.LastModified"),
tr("Date Modified"), lastModifiedTimeString);
}
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;
}
int simplegcd(int a, int b) {
return b == 0 ? a : simplegcd(b, a % b);
}
QString MetadataModel::imageSizeRatio(const QSize &size)
{
if (!size.isValid()) {
return QStringLiteral("-");
}
int gcd = simplegcd(size.width(), size.height());
return tr("%1 : %2").arg(QString::number(size.width() / gcd), QString::number(size.height() / gcd));
}
bool MetadataModel::appendSection(const QString &sectionKey, const QString &sectionDisplayName)
{
if (m_sections.contains(sectionKey)) {
return false;
}
m_sections.append(sectionKey);
m_sectionProperties[sectionKey] = qMakePair<QString, QList<QString> >(sectionDisplayName, {});
return true;
}
bool MetadataModel::appendProperty(const QString &sectionKey, const QString &propertyKey, const QString &propertyDisplayName, const QString &propertyValue)
{
if (!m_sections.contains(sectionKey)) {
return false;
}
QList<QString> & propertyList = m_sectionProperties[sectionKey].second;
if (!propertyList.contains(propertyKey)) {
propertyList.append(propertyKey);
}
m_properties[propertyKey] = qMakePair<QString, QString>(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<QString> & 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");
}

43
app/metadatamodel.h Normal file
View File

@ -0,0 +1,43 @@
#ifndef METADATAMODEL_H
#define METADATAMODEL_H
#include <QAbstractItemModel>
class MetadataModel : public QAbstractItemModel
{
Q_OBJECT
public:
explicit MetadataModel(QObject *parent = nullptr);
~MetadataModel();
void setFile(const QString & imageFilePath);
static QString imageSize(const QSize &size);
static QString imageSizeRatio(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<QString> m_sections;
// {SECTION_KEY: (SECTION_DISPLAY_NAME, [PROPERTY_KEY])}
QMap<QString, QPair<QString, QList<QString> > > m_sectionProperties;
// {PROPERTY_KEY: (PROPERTY_DISPLAY_NAME, PROPERTY_VALUE)}
QMap<QString, QPair<QString, QString> > m_properties;
};
#endif // METADATAMODEL_H

View File

@ -2,7 +2,7 @@
<component type="desktop-application">
<id>net.blumia.pineapple-pictures</id>
<metadata_license>CC0-1.0</metadata_license>
<project_license>GPL-3.0+</project_license>
<project_license>MIT</project_license>
<name>Pineapple Pictures</name>
<name xml:lang="zh-CN">菠萝看图</name>
<summary>Image Viewer</summary>

File diff suppressed because it is too large Load Diff

Before

Width:  |  Height:  |  Size: 40 KiB

After

Width:  |  Height:  |  Size: 44 KiB

View File

@ -1,294 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS>
<TS version="2.1">
<context>
<name>AboutDialog</name>
<message>
<location filename="../aboutdialog.cpp" line="22"/>
<source>About</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../aboutdialog.cpp" line="25"/>
<source>Launch application with image file path as argument to load the file.</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../aboutdialog.cpp" line="26"/>
<source>Drag and drop image file onto the window is also supported.</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../aboutdialog.cpp" line="27"/>
<source>Context menu option explanation:</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../aboutdialog.cpp" line="31"/>
<source>Make window stay on top of all other windows.</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../aboutdialog.cpp" line="35"/>
<source>Avoid close window accidentally. (eg. by double clicking the window)</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../aboutdialog.cpp" line="44"/>
<source>Version: %1</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../aboutdialog.cpp" line="47"/>
<source>Copyright (c) 2020 %1</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../aboutdialog.cpp" line="49"/>
<source>Logo designed by %1</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../aboutdialog.cpp" line="51"/>
<source>Built with Qt %1 (%2)</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../aboutdialog.cpp" line="52"/>
<source>Source code</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../aboutdialog.cpp" line="58"/>
<source>Contributors</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../aboutdialog.cpp" line="60"/>
<source>List of contributors on GitHub</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../aboutdialog.cpp" line="61"/>
<source>Thanks to all people who contributed to this project.</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../aboutdialog.cpp" line="65"/>
<source>Translators</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../aboutdialog.cpp" line="66"/>
<source>I would like to thank the following people who volunteered to translate this application.</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../aboutdialog.cpp" line="131"/>
<source>&amp;Special Thanks</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../aboutdialog.cpp" line="133"/>
<source>&amp;Third-party Libraries</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../aboutdialog.cpp" line="72"/>
<source>Your Rights</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../aboutdialog.cpp" line="74"/>
<source>%1 is released under the MIT License.</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../aboutdialog.cpp" line="75"/>
<source>This license grants people a number of freedoms:</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../aboutdialog.cpp" line="76"/>
<source>You are free to use %1, for any purpose</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../aboutdialog.cpp" line="77"/>
<source>You are free to distribute %1</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../aboutdialog.cpp" line="78"/>
<source>You can study how %1 works and change it</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../aboutdialog.cpp" line="79"/>
<source>You can distribute changed versions of %1</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../aboutdialog.cpp" line="81"/>
<source>The MIT license guarantees you this freedom. Nobody is ever permitted to take it away.</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../aboutdialog.cpp" line="109"/>
<source>Third-party Libraries used by %1</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../aboutdialog.cpp" line="110"/>
<source>%1 is built on the following free software libraries:</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../aboutdialog.cpp" line="129"/>
<source>&amp;Help</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../aboutdialog.cpp" line="130"/>
<source>&amp;About</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../aboutdialog.cpp" line="132"/>
<source>&amp;License</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>GraphicsScene</name>
<message>
<location filename="../graphicsscene.cpp" line="16"/>
<source>Drag image here</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>GraphicsView</name>
<message>
<location filename="../graphicsview.cpp" line="254"/>
<source>File url list is empty</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../graphicsview.cpp" line="53"/>
<source>File is not a valid image</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../graphicsview.cpp" line="262"/>
<source>Image data is invalid</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../graphicsview.cpp" line="269"/>
<source>Not supported mimedata: %1</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>MainWindow</name>
<message>
<location filename="../mainwindow.cpp" line="175"/>
<source>File url list is empty</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../mainwindow.cpp" line="408"/>
<source>&amp;Copy</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../mainwindow.cpp" line="429"/>
<source>Copy P&amp;ixmap</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../mainwindow.cpp" line="434"/>
<source>Copy &amp;File Path</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../mainwindow.cpp" line="444"/>
<source>&amp;Paste Image</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../mainwindow.cpp" line="450"/>
<source>&amp;Paste Image File</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../aboutdialog.cpp" line="30"/>
<location filename="../mainwindow.cpp" line="455"/>
<source>Stay on top</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../aboutdialog.cpp" line="34"/>
<location filename="../mainwindow.cpp" line="462"/>
<source>Protected mode</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../mainwindow.cpp" line="469"/>
<source>Configure...</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../mainwindow.cpp" line="476"/>
<source>Help</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>SettingsDialog</name>
<message>
<location filename="../settingsdialog.cpp" line="15"/>
<source>Settings</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../settingsdialog.cpp" line="20"/>
<source>Do nothing</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../settingsdialog.cpp" line="21"/>
<source>Close the window</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../settingsdialog.cpp" line="22"/>
<source>Toggle maximize</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../settingsdialog.cpp" line="30"/>
<source>Stay on top when start-up</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../settingsdialog.cpp" line="31"/>
<source>Double-click behavior</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>main</name>
<message>
<location filename="../main.cpp" line="27"/>
<source>Pineapple Pictures</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../main.cpp" line="31"/>
<source>File list.</source>
<translation type="unfinished"></translation>
</message>
</context>
</TS>

View File

@ -1,81 +0,0 @@
#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);
}

View File

@ -1,66 +0,0 @@
#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

View File

@ -1,41 +0,0 @@
#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);
}

View File

@ -1,267 +0,0 @@
#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());
}
}

View File

@ -1,36 +0,0 @@
#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

View File

@ -1,9 +1,3 @@
#-------------------------------------------------
#
# Project created by QtCreator 2019-09-26T23:36:07
#
#-------------------------------------------------
QT += core widgets gui svg
TARGET = ppic
@ -23,39 +17,37 @@ DEFINES += QT_DEPRECATED_WARNINGS
CONFIG += c++11 lrelease embed_translations
SOURCES += \
aboutdialog.cpp \
main.cpp \
mainwindow.cpp \
graphicsview.cpp \
bottombuttongroup.cpp \
graphicsscene.cpp \
metadata.cpp \
metadatadialog.cpp \
metadatamodel.cpp \
navigatorview.cpp \
opacityhelper.cpp \
toolbutton.cpp \
settings.cpp \
settingsdialog.cpp
app/aboutdialog.cpp \
app/main.cpp \
app/mainwindow.cpp \
app/graphicsview.cpp \
app/bottombuttongroup.cpp \
app/graphicsscene.cpp \
app/navigatorview.cpp \
app/opacityhelper.cpp \
app/toolbutton.cpp \
app/settings.cpp \
app/settingsdialog.cpp \
app/metadatamodel.cpp \
app/metadatadialog.cpp
HEADERS += \
aboutdialog.h \
mainwindow.h \
graphicsview.h \
bottombuttongroup.h \
graphicsscene.h \
metadata.h \
metadatadialog.h \
metadatamodel.h \
navigatorview.h \
opacityhelper.h \
toolbutton.h \
settings.h \
settingsdialog.h
app/aboutdialog.h \
app/mainwindow.h \
app/graphicsview.h \
app/bottombuttongroup.h \
app/graphicsscene.h \
app/navigatorview.h \
app/opacityhelper.h \
app/toolbutton.h \
app/settings.h \
app/settingsdialog.h \
app/metadatamodel.h \
app/metadatadialog.h
TRANSLATIONS = \
languages/PineapplePictures.ts \
languages/PineapplePictures_zh_CN.ts
translations/PineapplePictures.ts \
translations/PineapplePictures_zh_CN.ts
# Default rules for deployment.
qnx: target.path = /tmp/$${TARGET}/bin

View File

@ -0,0 +1,423 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS>
<TS version="2.1">
<context>
<name>AboutDialog</name>
<message>
<location filename="../app/aboutdialog.cpp" line="22"/>
<source>About</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../app/aboutdialog.cpp" line="25"/>
<source>Launch application with image file path as argument to load the file.</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../app/aboutdialog.cpp" line="26"/>
<source>Drag and drop image file onto the window is also supported.</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../app/aboutdialog.cpp" line="27"/>
<source>Context menu option explanation:</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../app/aboutdialog.cpp" line="31"/>
<source>Make window stay on top of all other windows.</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../app/aboutdialog.cpp" line="35"/>
<source>Avoid close window accidentally. (eg. by double clicking the window)</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../app/aboutdialog.cpp" line="44"/>
<source>Version: %1</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../app/aboutdialog.cpp" line="47"/>
<source>Copyright (c) 2020 %1</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../app/aboutdialog.cpp" line="49"/>
<source>Logo designed by %1</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../app/aboutdialog.cpp" line="51"/>
<source>Built with Qt %1 (%2)</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../app/aboutdialog.cpp" line="52"/>
<source>Source code</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../app/aboutdialog.cpp" line="58"/>
<source>Contributors</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../app/aboutdialog.cpp" line="60"/>
<source>List of contributors on GitHub</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../app/aboutdialog.cpp" line="61"/>
<source>Thanks to all people who contributed to this project.</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../app/aboutdialog.cpp" line="65"/>
<source>Translators</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../app/aboutdialog.cpp" line="66"/>
<source>I would like to thank the following people who volunteered to translate this application.</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../app/aboutdialog.cpp" line="131"/>
<source>&amp;Special Thanks</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../app/aboutdialog.cpp" line="133"/>
<source>&amp;Third-party Libraries</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../app/aboutdialog.cpp" line="72"/>
<source>Your Rights</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../app/aboutdialog.cpp" line="74"/>
<source>%1 is released under the MIT License.</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../app/aboutdialog.cpp" line="75"/>
<source>This license grants people a number of freedoms:</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../app/aboutdialog.cpp" line="76"/>
<source>You are free to use %1, for any purpose</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../app/aboutdialog.cpp" line="77"/>
<source>You are free to distribute %1</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../app/aboutdialog.cpp" line="78"/>
<source>You can study how %1 works and change it</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../app/aboutdialog.cpp" line="79"/>
<source>You can distribute changed versions of %1</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../app/aboutdialog.cpp" line="81"/>
<source>The MIT license guarantees you this freedom. Nobody is ever permitted to take it away.</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../app/aboutdialog.cpp" line="109"/>
<source>Third-party Libraries used by %1</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../app/aboutdialog.cpp" line="110"/>
<source>%1 is built on the following free software libraries:</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../app/aboutdialog.cpp" line="129"/>
<source>&amp;Help</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../app/aboutdialog.cpp" line="130"/>
<source>&amp;About</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../app/aboutdialog.cpp" line="132"/>
<source>&amp;License</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>GraphicsScene</name>
<message>
<location filename="../app/graphicsscene.cpp" line="16"/>
<source>Drag image here</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>GraphicsView</name>
<message>
<location filename="../app/graphicsview.cpp" line="261"/>
<source>File url list is empty</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../app/graphicsview.cpp" line="53"/>
<source>File is not a valid image</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../app/graphicsview.cpp" line="55"/>
<location filename="../app/graphicsview.cpp" line="59"/>
<source>Image data is invalid or currently unsupported</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../app/graphicsview.cpp" line="269"/>
<source>Image data is invalid</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../app/graphicsview.cpp" line="276"/>
<source>Not supported mimedata: %1</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>MainWindow</name>
<message>
<location filename="../app/mainwindow.cpp" line="178"/>
<source>File url list is empty</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../app/mainwindow.cpp" line="417"/>
<source>&amp;Copy</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../app/mainwindow.cpp" line="438"/>
<source>Copy P&amp;ixmap</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../app/mainwindow.cpp" line="443"/>
<source>Copy &amp;File Path</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../app/mainwindow.cpp" line="453"/>
<source>&amp;Paste Image</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../app/mainwindow.cpp" line="459"/>
<source>&amp;Paste Image File</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../app/mainwindow.cpp" line="492"/>
<source>Properties</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../app/aboutdialog.cpp" line="30"/>
<location filename="../app/mainwindow.cpp" line="464"/>
<source>Stay on top</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../app/aboutdialog.cpp" line="34"/>
<location filename="../app/mainwindow.cpp" line="471"/>
<source>Protected mode</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../app/mainwindow.cpp" line="478"/>
<source>Configure...</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../app/mainwindow.cpp" line="485"/>
<source>Help</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>MetadataDialog</name>
<message>
<location filename="../app/metadatadialog.cpp" line="75"/>
<source>Image Metadata</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>MetadataModel</name>
<message>
<location filename="../app/metadatamodel.cpp" line="36"/>
<source>Origin</source>
<comment>Section name.</comment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../app/metadatamodel.cpp" line="38"/>
<source>Image</source>
<comment>Section name.</comment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../app/metadatamodel.cpp" line="44"/>
<source>File</source>
<comment>Section name.</comment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../app/metadatamodel.cpp" line="40"/>
<source>Camera</source>
<comment>Section name.</comment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../app/metadatamodel.cpp" line="27"/>
<source>%1 File</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../app/metadatamodel.cpp" line="35"/>
<source>Description</source>
<comment>Section name.</comment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../app/metadatamodel.cpp" line="41"/>
<source>Advanced photo</source>
<comment>Section name.</comment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../app/metadatamodel.cpp" line="42"/>
<source>GPS</source>
<comment>Section name.</comment>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../app/metadatamodel.cpp" line="47"/>
<source>Dimensions</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../app/metadatamodel.cpp" line="49"/>
<source>Aspect Ratio</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../app/metadatamodel.cpp" line="52"/>
<source>Name</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../app/metadatamodel.cpp" line="54"/>
<source>Item type</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../app/metadatamodel.cpp" line="56"/>
<source>Folder path</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../app/metadatamodel.cpp" line="58"/>
<source>Size</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../app/metadatamodel.cpp" line="60"/>
<source>Date Created</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../app/metadatamodel.cpp" line="62"/>
<source>Date Modified</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../app/metadatamodel.cpp" line="70"/>
<source>%1 x %2</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../app/metadatamodel.cpp" line="88"/>
<source>%1 : %2</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../app/metadatamodel.cpp" line="204"/>
<source>Property</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../app/metadatamodel.cpp" line="204"/>
<source>Value</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>SettingsDialog</name>
<message>
<location filename="../app/settingsdialog.cpp" line="15"/>
<source>Settings</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../app/settingsdialog.cpp" line="20"/>
<source>Do nothing</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../app/settingsdialog.cpp" line="21"/>
<source>Close the window</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../app/settingsdialog.cpp" line="22"/>
<source>Toggle maximize</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../app/settingsdialog.cpp" line="30"/>
<source>Stay on top when start-up</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../app/settingsdialog.cpp" line="31"/>
<source>Double-click behavior</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>main</name>
<message>
<location filename="../app/main.cpp" line="27"/>
<source>Pineapple Pictures</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../app/main.cpp" line="31"/>
<source>File list.</source>
<translation type="unfinished"></translation>
</message>
</context>
</TS>

View File

@ -4,157 +4,157 @@
<context>
<name>AboutDialog</name>
<message>
<location filename="../aboutdialog.cpp" line="22"/>
<location filename="../app/aboutdialog.cpp" line="22"/>
<source>About</source>
<translation></translation>
</message>
<message>
<location filename="../aboutdialog.cpp" line="25"/>
<location filename="../app/aboutdialog.cpp" line="25"/>
<source>Launch application with image file path as argument to load the file.</source>
<translation></translation>
</message>
<message>
<location filename="../aboutdialog.cpp" line="26"/>
<location filename="../app/aboutdialog.cpp" line="26"/>
<source>Drag and drop image file onto the window is also supported.</source>
<translation></translation>
</message>
<message>
<location filename="../aboutdialog.cpp" line="27"/>
<location filename="../app/aboutdialog.cpp" line="27"/>
<source>Context menu option explanation:</source>
<translation></translation>
</message>
<message>
<location filename="../aboutdialog.cpp" line="31"/>
<location filename="../app/aboutdialog.cpp" line="31"/>
<source>Make window stay on top of all other windows.</source>
<translation>使</translation>
</message>
<message>
<location filename="../aboutdialog.cpp" line="35"/>
<location filename="../app/aboutdialog.cpp" line="35"/>
<source>Avoid close window accidentally. (eg. by double clicking the window)</source>
<translation></translation>
</message>
<message>
<location filename="../aboutdialog.cpp" line="44"/>
<location filename="../app/aboutdialog.cpp" line="44"/>
<source>Version: %1</source>
<translation>: %1</translation>
</message>
<message>
<location filename="../aboutdialog.cpp" line="47"/>
<location filename="../app/aboutdialog.cpp" line="47"/>
<source>Copyright (c) 2020 %1</source>
<translation> (c) 2020 %1</translation>
</message>
<message>
<location filename="../aboutdialog.cpp" line="49"/>
<location filename="../app/aboutdialog.cpp" line="49"/>
<source>Logo designed by %1</source>
<translation>Logo %1 </translation>
</message>
<message>
<location filename="../aboutdialog.cpp" line="51"/>
<location filename="../app/aboutdialog.cpp" line="51"/>
<source>Built with Qt %1 (%2)</source>
<translation>使 Qt %1 (%2) </translation>
</message>
<message>
<location filename="../aboutdialog.cpp" line="52"/>
<location filename="../app/aboutdialog.cpp" line="52"/>
<source>Source code</source>
<translation></translation>
</message>
<message>
<location filename="../aboutdialog.cpp" line="58"/>
<location filename="../app/aboutdialog.cpp" line="58"/>
<source>Contributors</source>
<translation></translation>
</message>
<message>
<location filename="../aboutdialog.cpp" line="60"/>
<location filename="../app/aboutdialog.cpp" line="60"/>
<source>List of contributors on GitHub</source>
<translation>GitHub </translation>
</message>
<message>
<location filename="../aboutdialog.cpp" line="61"/>
<location filename="../app/aboutdialog.cpp" line="61"/>
<source>Thanks to all people who contributed to this project.</source>
<translation></translation>
</message>
<message>
<location filename="../aboutdialog.cpp" line="65"/>
<location filename="../app/aboutdialog.cpp" line="65"/>
<source>Translators</source>
<translation></translation>
</message>
<message>
<location filename="../aboutdialog.cpp" line="66"/>
<location filename="../app/aboutdialog.cpp" line="66"/>
<source>I would like to thank the following people who volunteered to translate this application.</source>
<translation></translation>
</message>
<message>
<location filename="../aboutdialog.cpp" line="131"/>
<location filename="../app/aboutdialog.cpp" line="131"/>
<source>&amp;Special Thanks</source>
<translation>(&amp;S)</translation>
</message>
<message>
<location filename="../aboutdialog.cpp" line="133"/>
<location filename="../app/aboutdialog.cpp" line="133"/>
<source>&amp;Third-party Libraries</source>
<translation>(&amp;T)</translation>
</message>
<message>
<location filename="../aboutdialog.cpp" line="72"/>
<location filename="../app/aboutdialog.cpp" line="72"/>
<source>Your Rights</source>
<translation></translation>
</message>
<message>
<location filename="../aboutdialog.cpp" line="74"/>
<location filename="../app/aboutdialog.cpp" line="74"/>
<source>%1 is released under the MIT License.</source>
<translation>%1 MIT </translation>
</message>
<message>
<location filename="../aboutdialog.cpp" line="75"/>
<location filename="../app/aboutdialog.cpp" line="75"/>
<source>This license grants people a number of freedoms:</source>
<translation></translation>
</message>
<message>
<location filename="../aboutdialog.cpp" line="76"/>
<location filename="../app/aboutdialog.cpp" line="76"/>
<source>You are free to use %1, for any purpose</source>
<translation>使 %1</translation>
</message>
<message>
<location filename="../aboutdialog.cpp" line="77"/>
<location filename="../app/aboutdialog.cpp" line="77"/>
<source>You are free to distribute %1</source>
<translation> %1</translation>
</message>
<message>
<location filename="../aboutdialog.cpp" line="78"/>
<location filename="../app/aboutdialog.cpp" line="78"/>
<source>You can study how %1 works and change it</source>
<translation> %1 </translation>
</message>
<message>
<location filename="../aboutdialog.cpp" line="79"/>
<location filename="../app/aboutdialog.cpp" line="79"/>
<source>You can distribute changed versions of %1</source>
<translation> %1 </translation>
</message>
<message>
<location filename="../aboutdialog.cpp" line="81"/>
<location filename="../app/aboutdialog.cpp" line="81"/>
<source>The MIT license guarantees you this freedom. Nobody is ever permitted to take it away.</source>
<translation> MIT </translation>
</message>
<message>
<location filename="../aboutdialog.cpp" line="109"/>
<location filename="../app/aboutdialog.cpp" line="109"/>
<source>Third-party Libraries used by %1</source>
<translation>%1 使</translation>
</message>
<message>
<location filename="../aboutdialog.cpp" line="110"/>
<location filename="../app/aboutdialog.cpp" line="110"/>
<source>%1 is built on the following free software libraries:</source>
<translation>%1 </translation>
</message>
<message>
<location filename="../aboutdialog.cpp" line="129"/>
<location filename="../app/aboutdialog.cpp" line="129"/>
<source>&amp;Help</source>
<translation>(&amp;H)</translation>
</message>
<message>
<location filename="../aboutdialog.cpp" line="130"/>
<location filename="../app/aboutdialog.cpp" line="130"/>
<source>&amp;About</source>
<translation>(&amp;A)</translation>
</message>
<message>
<location filename="../aboutdialog.cpp" line="132"/>
<location filename="../app/aboutdialog.cpp" line="132"/>
<source>&amp;License</source>
<translation>(&amp;L)</translation>
</message>
@ -162,7 +162,7 @@
<context>
<name>GraphicsScene</name>
<message>
<location filename="../graphicsscene.cpp" line="16"/>
<location filename="../app/graphicsscene.cpp" line="16"/>
<source>Drag image here</source>
<translation></translation>
</message>
@ -170,22 +170,28 @@
<context>
<name>GraphicsView</name>
<message>
<location filename="../graphicsview.cpp" line="254"/>
<location filename="../app/graphicsview.cpp" line="261"/>
<source>File url list is empty</source>
<translation> URL </translation>
</message>
<message>
<location filename="../graphicsview.cpp" line="53"/>
<location filename="../app/graphicsview.cpp" line="53"/>
<source>File is not a valid image</source>
<translation></translation>
</message>
<message>
<location filename="../graphicsview.cpp" line="262"/>
<location filename="../app/graphicsview.cpp" line="55"/>
<location filename="../app/graphicsview.cpp" line="59"/>
<source>Image data is invalid or currently unsupported</source>
<translation></translation>
</message>
<message>
<location filename="../app/graphicsview.cpp" line="269"/>
<source>Image data is invalid</source>
<translation></translation>
</message>
<message>
<location filename="../graphicsview.cpp" line="269"/>
<location filename="../app/graphicsview.cpp" line="276"/>
<source>Not supported mimedata: %1</source>
<translation> MimeData %1</translation>
</message>
@ -193,87 +199,210 @@
<context>
<name>MainWindow</name>
<message>
<location filename="../mainwindow.cpp" line="175"/>
<location filename="../app/mainwindow.cpp" line="178"/>
<source>File url list is empty</source>
<translation> URL </translation>
</message>
<message>
<location filename="../mainwindow.cpp" line="408"/>
<location filename="../app/mainwindow.cpp" line="417"/>
<source>&amp;Copy</source>
<translation>(&amp;C)</translation>
</message>
<message>
<location filename="../mainwindow.cpp" line="429"/>
<location filename="../app/mainwindow.cpp" line="438"/>
<source>Copy P&amp;ixmap</source>
<translation>(&amp;I)</translation>
</message>
<message>
<location filename="../mainwindow.cpp" line="434"/>
<location filename="../app/mainwindow.cpp" line="443"/>
<source>Copy &amp;File Path</source>
<translation>(&amp;F)</translation>
</message>
<message>
<location filename="../mainwindow.cpp" line="444"/>
<location filename="../app/mainwindow.cpp" line="453"/>
<source>&amp;Paste Image</source>
<translation>(&amp;P)</translation>
</message>
<message>
<location filename="../mainwindow.cpp" line="450"/>
<location filename="../app/mainwindow.cpp" line="459"/>
<source>&amp;Paste Image File</source>
<translation>(&amp;P)</translation>
</message>
<message>
<location filename="../aboutdialog.cpp" line="30"/>
<location filename="../mainwindow.cpp" line="455"/>
<location filename="../app/mainwindow.cpp" line="492"/>
<source>Properties</source>
<translation></translation>
</message>
<message>
<location filename="../app/aboutdialog.cpp" line="30"/>
<location filename="../app/mainwindow.cpp" line="464"/>
<source>Stay on top</source>
<translation></translation>
</message>
<message>
<location filename="../aboutdialog.cpp" line="34"/>
<location filename="../mainwindow.cpp" line="462"/>
<location filename="../app/aboutdialog.cpp" line="34"/>
<location filename="../app/mainwindow.cpp" line="471"/>
<source>Protected mode</source>
<translation></translation>
</message>
<message>
<location filename="../mainwindow.cpp" line="469"/>
<location filename="../app/mainwindow.cpp" line="478"/>
<source>Configure...</source>
<translation>...</translation>
</message>
<message>
<location filename="../mainwindow.cpp" line="476"/>
<location filename="../app/mainwindow.cpp" line="485"/>
<source>Help</source>
<translation></translation>
</message>
</context>
<context>
<name>MetadataDialog</name>
<message>
<location filename="../app/metadatadialog.cpp" line="75"/>
<source>Image Metadata</source>
<translation></translation>
</message>
</context>
<context>
<name>MetadataModel</name>
<message>
<location filename="../app/metadatamodel.cpp" line="36"/>
<source>Origin</source>
<comment>Section name.</comment>
<translation></translation>
</message>
<message>
<location filename="../app/metadatamodel.cpp" line="38"/>
<source>Image</source>
<comment>Section name.</comment>
<translation></translation>
</message>
<message>
<location filename="../app/metadatamodel.cpp" line="44"/>
<source>File</source>
<comment>Section name.</comment>
<translation></translation>
</message>
<message>
<location filename="../app/metadatamodel.cpp" line="40"/>
<source>Camera</source>
<comment>Section name.</comment>
<translation></translation>
</message>
<message>
<location filename="../app/metadatamodel.cpp" line="27"/>
<source>%1 File</source>
<translation>%1 </translation>
</message>
<message>
<location filename="../app/metadatamodel.cpp" line="35"/>
<source>Description</source>
<comment>Section name.</comment>
<translation></translation>
</message>
<message>
<location filename="../app/metadatamodel.cpp" line="41"/>
<source>Advanced photo</source>
<comment>Section name.</comment>
<translation></translation>
</message>
<message>
<location filename="../app/metadatamodel.cpp" line="42"/>
<source>GPS</source>
<comment>Section name.</comment>
<translation>GPS</translation>
</message>
<message>
<location filename="../app/metadatamodel.cpp" line="47"/>
<source>Dimensions</source>
<translation></translation>
</message>
<message>
<location filename="../app/metadatamodel.cpp" line="49"/>
<source>Aspect Ratio</source>
<translation></translation>
</message>
<message>
<location filename="../app/metadatamodel.cpp" line="52"/>
<source>Name</source>
<translation></translation>
</message>
<message>
<location filename="../app/metadatamodel.cpp" line="54"/>
<source>Item type</source>
<translation></translation>
</message>
<message>
<location filename="../app/metadatamodel.cpp" line="56"/>
<source>Folder path</source>
<translation></translation>
</message>
<message>
<location filename="../app/metadatamodel.cpp" line="58"/>
<source>Size</source>
<translation></translation>
</message>
<message>
<location filename="../app/metadatamodel.cpp" line="60"/>
<source>Date Created</source>
<translation></translation>
</message>
<message>
<location filename="../app/metadatamodel.cpp" line="62"/>
<source>Date Modified</source>
<translation></translation>
</message>
<message>
<location filename="../app/metadatamodel.cpp" line="70"/>
<source>%1 x %2</source>
<translation>%1 x %2</translation>
</message>
<message>
<location filename="../app/metadatamodel.cpp" line="88"/>
<source>%1 : %2</source>
<translation>%1 : %2</translation>
</message>
<message>
<location filename="../app/metadatamodel.cpp" line="204"/>
<source>Property</source>
<translation></translation>
</message>
<message>
<location filename="../app/metadatamodel.cpp" line="204"/>
<source>Value</source>
<translation></translation>
</message>
</context>
<context>
<name>SettingsDialog</name>
<message>
<location filename="../settingsdialog.cpp" line="15"/>
<location filename="../app/settingsdialog.cpp" line="15"/>
<source>Settings</source>
<translation></translation>
</message>
<message>
<location filename="../settingsdialog.cpp" line="20"/>
<location filename="../app/settingsdialog.cpp" line="20"/>
<source>Do nothing</source>
<translation></translation>
</message>
<message>
<location filename="../settingsdialog.cpp" line="21"/>
<location filename="../app/settingsdialog.cpp" line="21"/>
<source>Close the window</source>
<translation></translation>
</message>
<message>
<location filename="../settingsdialog.cpp" line="22"/>
<location filename="../app/settingsdialog.cpp" line="22"/>
<source>Toggle maximize</source>
<translation></translation>
</message>
<message>
<location filename="../settingsdialog.cpp" line="30"/>
<location filename="../app/settingsdialog.cpp" line="30"/>
<source>Stay on top when start-up</source>
<translation></translation>
</message>
<message>
<location filename="../settingsdialog.cpp" line="31"/>
<location filename="../app/settingsdialog.cpp" line="31"/>
<source>Double-click behavior</source>
<translation></translation>
</message>
@ -281,12 +410,12 @@
<context>
<name>main</name>
<message>
<location filename="../main.cpp" line="27"/>
<location filename="../app/main.cpp" line="27"/>
<source>Pineapple Pictures</source>
<translation></translation>
</message>
<message>
<location filename="../main.cpp" line="31"/>
<location filename="../app/main.cpp" line="31"/>
<source>File list.</source>
<translation></translation>
</message>