commit beb4a823
Show Caption. Edit does not work at the moment.
Changed files
| M | src/exifreader.cpp before |
| M | src/exifreader.h before |
| M | src/imageviewwidget.cpp before |
| M | src/imageviewwidget.h before |
| M | src/mainwindow.cpp before |
| M | src/mainwindow.h before |
diff --git a/src/exifreader.cpp b/src/exifreader.cpp
index 0a9ddb7..3f94eb8 100644
--- a/src/exifreader.cpp
+++ b/src/exifreader.cpp
@@ -35,7 +35,8 @@ static void addField(QList<QPair<QString, QString>> &result, const char *label,
bool ExifData::isEmpty() const
{
- return dateTime.isEmpty() && make.isEmpty() && model.isEmpty()
+ return captionAbstract.isEmpty() && description.isEmpty()
+ && dateTime.isEmpty() && make.isEmpty() && model.isEmpty()
&& exposureTime.isEmpty() && fNumber.isEmpty() && iso.isEmpty()
&& focalLength.isEmpty() && flash.isEmpty()
&& dimensions.isEmpty() && fileSize.isEmpty();
@@ -44,6 +45,8 @@ bool ExifData::isEmpty() const
QList<QPair<QString, QString>> ExifData::toList() const
{
QList<QPair<QString, QString>> result;
+ addField(result, "Caption", captionAbstract);
+ addField(result, "Description", description);
addField(result, "Date/Time", dateTime);
addField(result, "Camera Make", make);
addField(result, "Camera Model", model);
@@ -111,9 +114,51 @@ ExifData ExifReader::read(const QByteArray &path)
data.iso = findTag(exif, "Exif.Photo.ISOSpeedRatings");
data.focalLength = findTag(exif, "Exif.Photo.FocalLength");
data.flash = findTag(exif, "Exif.Photo.Flash");
+ data.description = findTag(exif, "Exif.Image.ImageDescription");
+
+ // IPTC Caption-Abstract
+ const Exiv2::IptcData &iptc = image->iptcData();
+ auto iptcIt = iptc.findKey(Exiv2::IptcKey("Iptc.Application2.Caption"));
+ if (iptcIt != iptc.end())
+ data.captionAbstract = QString::fromStdString(iptcIt->toString()).trimmed();
} catch (const Exiv2::Error &) {
// No EXIF or unsupported format — leave tags empty
}
return data;
}
+
+bool ExifReader::saveCaption(const QByteArray &path,
+ const QString &caption,
+ const ExifData &oldData)
+{
+ try {
+ std::unique_ptr<Exiv2::Image> image =
+ Exiv2::ImageFactory::open(path.toStdString());
+ image->readMetadata();
+
+ // Erase all existing Caption-Abstract entries then add the new one
+ Exiv2::IptcData &iptc = image->iptcData();
+ {
+ Exiv2::IptcKey key("Iptc.Application2.Caption");
+ auto it = iptc.findKey(key);
+ while (it != iptc.end()) {
+ it = iptc.erase(it);
+ it = iptc.findKey(key);
+ }
+ }
+ Exiv2::Iptcdatum datum(Exiv2::IptcKey("Iptc.Application2.Caption"));
+ datum.setValue(caption.toStdString());
+ iptc.add(datum);
+
+ // If Caption-Abstract and Description were previously in sync, keep them in sync
+ if (oldData.captionAbstract == oldData.description) {
+ image->exifData()["Exif.Image.ImageDescription"] = caption.toStdString();
+ }
+
+ image->writeMetadata();
+ return true;
+ } catch (const Exiv2::Error &) {
+ return false;
+ }
+}
diff --git a/src/exifreader.h b/src/exifreader.h
index 6d2957a..450d5cd 100644
--- a/src/exifreader.h
+++ b/src/exifreader.h
@@ -6,6 +6,8 @@
#include <QPair>
struct ExifData {
+ QString captionAbstract; // IPTC Caption-Abstract (Iptc.Application2.Caption)
+ QString description; // Exif.Image.ImageDescription
QString dateTime;
QString make;
QString model;
@@ -23,4 +25,12 @@ struct ExifData {
namespace ExifReader {
ExifData read(const QByteArray &path);
+
+// Write caption to IPTC Caption-Abstract.
+// If oldData.captionAbstract == oldData.description they were in sync;
+// in that case also update Exif.Image.ImageDescription to match.
+// Returns true on success.
+bool saveCaption(const QByteArray &path,
+ const QString &caption,
+ const ExifData &oldData);
} // namespace ExifReader
diff --git a/src/imageviewwidget.cpp b/src/imageviewwidget.cpp
index 44802b2..0556a77 100644
--- a/src/imageviewwidget.cpp
+++ b/src/imageviewwidget.cpp
@@ -238,6 +238,39 @@ void ImageViewWidget::paintEvent(QPaintEvent *)
painter.restore();
}
}
+
+ // Caption-Abstract overlay — bottom-centre, always shown when non-empty
+ if (!m_exifData.captionAbstract.isEmpty()) {
+ const int padding = 10;
+ const int margin = 20; // distance from bottom edge
+ const int maxWidth = width() * 3 / 4;
+
+ QFont captionFont = painter.font();
+ captionFont.setPointSize(11);
+ painter.setFont(captionFont);
+ QFontMetrics cfm(captionFont);
+
+ QRect textBounds = cfm.boundingRect(
+ QRect(0, 0, maxWidth, 0),
+ Qt::AlignHCenter | Qt::TextWordWrap,
+ m_exifData.captionAbstract);
+
+ const int boxW = textBounds.width() + padding * 2;
+ const int boxH = textBounds.height() + padding * 2;
+ const int boxX = (width() - boxW) / 2;
+ const int boxY = height() - boxH - margin;
+
+ painter.save();
+ painter.setPen(Qt::NoPen);
+ painter.setBrush(QColor(0, 0, 0, 180));
+ painter.drawRoundedRect(boxX, boxY, boxW, boxH, 8, 8);
+
+ painter.setPen(Qt::white);
+ painter.drawText(QRect(boxX + padding, boxY + padding, textBounds.width(), textBounds.height()),
+ Qt::AlignHCenter | Qt::TextWordWrap,
+ m_exifData.captionAbstract);
+ painter.restore();
+ }
}
void ImageViewWidget::keyPressEvent(QKeyEvent *event)
@@ -329,6 +362,13 @@ void ImageViewWidget::keyPressEvent(QKeyEvent *event)
return;
}
+ if (event->key() == Qt::Key_E)
+ {
+ emit editCaptionRequested();
+ event->accept();
+ return;
+ }
+
QWidget::keyPressEvent(event);
}
@@ -345,6 +385,11 @@ void ImageViewWidget::toggleExifOverlay()
update();
}
+QByteArray ImageViewWidget::currentPath() const
+{
+ return m_currentPath;
+}
+
void ImageViewWidget::zoomIn()
{
if (m_zoomMode == FitToScreen) {
diff --git a/src/imageviewwidget.h b/src/imageviewwidget.h
index 3895eca..7ee4223 100644
--- a/src/imageviewwidget.h
+++ b/src/imageviewwidget.h
@@ -48,11 +48,13 @@ public:
void setExifData(const ExifData &data);
void toggleExifOverlay();
+ QByteArray currentPath() const;
signals:
void closeRequested();
void nextRequested();
void previousRequested();
+ void editCaptionRequested();
protected:
void paintEvent(QPaintEvent *event) override;
diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp
index 6559d9d..7b2cce9 100644
--- a/src/mainwindow.cpp
+++ b/src/mainwindow.cpp
@@ -29,6 +29,10 @@
#include <QAction>
#include <QSettings>
#include <QTableWidget>
+#include <QDialog>
+#include <QVBoxLayout>
+#include <QDialogButtonBox>
+#include <QPlainTextEdit>
#include <iostream>
#include <fcntl.h>
@@ -266,7 +270,8 @@ void MainWindow::setupConnections()
connect(m_imageView, &ImageViewWidget::closeRequested,
this, &MainWindow::leaveSingleImageMode);
-
+ connect(m_imageView, &ImageViewWidget::editCaptionRequested,
+ this, &MainWindow::onEditCaption);
connect(m_imageView, &ImageViewWidget::nextRequested, this, [this]() {
QModelIndex cur = m_listView->currentIndex();
int next = cur.isValid() ? cur.row() + 1 : 0;
@@ -408,6 +413,39 @@ void MainWindow::enterSingleImageMode(const QModelIndex &index)
}
}
+void MainWindow::onEditCaption()
+{
+ const QByteArray path = m_imageView->currentPath();
+ if (path.isEmpty())
+ return;
+
+ ExifData data = ExifReader::read(path);
+
+ QDialog dlg(this);
+ dlg.setWindowTitle(tr("Edit Caption"));
+ auto *layout = new QVBoxLayout(&dlg);
+ layout->addWidget(new QLabel(tr("Caption-Abstract:"), &dlg));
+ auto *edit = new QPlainTextEdit(&dlg);
+ edit->setPlainText(data.captionAbstract);
+ edit->setMinimumWidth(400);
+ layout->addWidget(edit);
+ auto *buttons = new QDialogButtonBox(
+ QDialogButtonBox::Ok | QDialogButtonBox::Cancel, &dlg);
+ layout->addWidget(buttons);
+ connect(buttons, &QDialogButtonBox::accepted, &dlg, &QDialog::accept);
+ connect(buttons, &QDialogButtonBox::rejected, &dlg, &QDialog::reject);
+
+ if (dlg.exec() != QDialog::Accepted)
+ return;
+
+ const QString newCaption = edit->toPlainText();
+ if (ExifReader::saveCaption(path, newCaption, data)) {
+ ExifData updated = ExifReader::read(path);
+ m_imageView->setExifData(updated);
+ updateExifInfo(path);
+ }
+}
+
void MainWindow::leaveSingleImageMode()
{
if (isFullScreen()) {
diff --git a/src/mainwindow.h b/src/mainwindow.h
index 52b4511..944db13 100644
--- a/src/mainwindow.h
+++ b/src/mainwindow.h
@@ -41,6 +41,7 @@ private:
void updateExifInfo(const QByteArray &path);
void enterSingleImageMode(const QModelIndex &index);
void leaveSingleImageMode();
+ void onEditCaption();
QList<QByteArray> computeNeighborPaths(const QModelIndex &index) const;
void keyPressEvent(QKeyEvent *event) override;