git repos / SagoImageBrowser

blame: src/imageviewwidget.cpp

normal view · raw

61136b77 Poul Sander 2026-02-20 22:45 1
#include "imageviewwidget.h"
83f0e0bc Poul Sander 2026-05-30 17:59 2
#include "shortcutmanager.h"
61136b77 Poul Sander 2026-02-20 22:45 3
61136b77 Poul Sander 2026-02-20 22:45 4
#include <QPainter>
c8ae2669 Poul Sander 2026-02-22 17:52 5
#include <QMouseEvent>
cdae9502 Poul Sander 2026-06-09 20:04 6
#include <QDesktopServices>
cdae9502 Poul Sander 2026-06-09 20:04 7
#include <QUrl>
80a9926d Poul Sander 2026-05-19 20:28 8
#include <QFile>
61136b77 Poul Sander 2026-02-20 22:45 9
#include <QImageReader>
f61bf886 Poul Sander 2026-02-23 19:27 10
#include <QPalette>
c58dbdb7 Poul Sander 2026-02-22 17:49 11
#include <algorithm>
58103277 Poul Sander 2026-03-01 22:53 12
#include <iostream>
58103277 Poul Sander 2026-03-01 22:53 13
80a9926d Poul Sander 2026-05-19 20:28 14
#include <fcntl.h>
80a9926d Poul Sander 2026-05-19 20:28 15
#include <unistd.h>
80a9926d Poul Sander 2026-05-19 20:28 16
58103277 Poul Sander 2026-03-01 22:53 17
// --- ImageLoadWorker ---
58103277 Poul Sander 2026-03-01 22:53 18
80a9926d Poul Sander 2026-05-19 20:28 19
ImageLoadWorker::ImageLoadWorker(const QByteArray &path, QObject *parent)
58103277 Poul Sander 2026-03-01 22:53 20
    : QThread(parent), m_path(path)
58103277 Poul Sander 2026-03-01 22:53 21
{
58103277 Poul Sander 2026-03-01 22:53 22
}
58103277 Poul Sander 2026-03-01 22:53 23
4d6b7476 Poul Sander 2026-05-30 16:39 24
// THREADING: This function runs on its own QThread, NOT the main thread.
4d6b7476 Poul Sander 2026-05-30 16:39 25
// It must not access any ImageViewWidget member state.  The only data it uses
4d6b7476 Poul Sander 2026-05-30 16:39 26
// is m_path which is immutable after construction.  The emitted imageLoaded()
4d6b7476 Poul Sander 2026-05-30 16:39 27
// signal is delivered to the main thread via Qt::AutoConnection which resolves
4d6b7476 Poul Sander 2026-05-30 16:39 28
// to QueuedConnection at emit time (worker thread != receiver's main-thread
4d6b7476 Poul Sander 2026-05-30 16:39 29
// affinity), so onImageLoaded() always executes on the main thread.
58103277 Poul Sander 2026-03-01 22:53 30
void ImageLoadWorker::run()
58103277 Poul Sander 2026-03-01 22:53 31
{
80a9926d Poul Sander 2026-05-19 20:28 32
    int fd = ::open(m_path.constData(), O_RDONLY | O_CLOEXEC);
80a9926d Poul Sander 2026-05-19 20:28 33
    if (fd < 0) {
80a9926d Poul Sander 2026-05-19 20:28 34
        emit imageLoaded(m_path, QPixmap{});
80a9926d Poul Sander 2026-05-19 20:28 35
        return;
80a9926d Poul Sander 2026-05-19 20:28 36
    }
80a9926d Poul Sander 2026-05-19 20:28 37
80a9926d Poul Sander 2026-05-19 20:28 38
    QFile file;
80a9926d Poul Sander 2026-05-19 20:28 39
    if (!file.open(fd, QIODevice::ReadOnly, QFileDevice::AutoCloseHandle)) {
80a9926d Poul Sander 2026-05-19 20:28 40
        ::close(fd);
80a9926d Poul Sander 2026-05-19 20:28 41
        emit imageLoaded(m_path, QPixmap{});
80a9926d Poul Sander 2026-05-19 20:28 42
        return;
80a9926d Poul Sander 2026-05-19 20:28 43
    }
80a9926d Poul Sander 2026-05-19 20:28 44
80a9926d Poul Sander 2026-05-19 20:28 45
    QImageReader reader(&file);
58103277 Poul Sander 2026-03-01 22:53 46
    reader.setAutoTransform(true);
58103277 Poul Sander 2026-03-01 22:53 47
    reader.setDecideFormatFromContent(true);
58103277 Poul Sander 2026-03-01 22:53 48
    reader.setAllocationLimit(0);
58103277 Poul Sander 2026-03-01 22:53 49
    QImage image = reader.read();
58103277 Poul Sander 2026-03-01 22:53 50
58103277 Poul Sander 2026-03-01 22:53 51
    QPixmap pixmap;
58103277 Poul Sander 2026-03-01 22:53 52
    if (!image.isNull()) {
58103277 Poul Sander 2026-03-01 22:53 53
        if (image.format() != QImage::Format_RGB32
58103277 Poul Sander 2026-03-01 22:53 54
            && image.format() != QImage::Format_ARGB32
58103277 Poul Sander 2026-03-01 22:53 55
            && image.format() != QImage::Format_ARGB32_Premultiplied)
58103277 Poul Sander 2026-03-01 22:53 56
            image = image.convertToFormat(QImage::Format_ARGB32);
58103277 Poul Sander 2026-03-01 22:53 57
        pixmap = QPixmap::fromImage(image);
58103277 Poul Sander 2026-03-01 22:53 58
    }
58103277 Poul Sander 2026-03-01 22:53 59
    emit imageLoaded(m_path, pixmap);
58103277 Poul Sander 2026-03-01 22:53 60
}
58103277 Poul Sander 2026-03-01 22:53 61
58103277 Poul Sander 2026-03-01 22:53 62
// --- ImageViewWidget ---
61136b77 Poul Sander 2026-02-20 22:45 63
61136b77 Poul Sander 2026-02-20 22:45 64
ImageViewWidget::ImageViewWidget(QWidget *parent)
61136b77 Poul Sander 2026-02-20 22:45 65
    : QWidget(parent)
61136b77 Poul Sander 2026-02-20 22:45 66
{
61136b77 Poul Sander 2026-02-20 22:45 67
    setFocusPolicy(Qt::StrongFocus);
f61bf886 Poul Sander 2026-02-23 19:27 68
    setAutoFillBackground(true);
f61bf886 Poul Sander 2026-02-23 19:27 69
    setBackgroundColor("black");
cdae9502 Poul Sander 2026-06-09 20:04 70
    setMouseTracking(true);
61136b77 Poul Sander 2026-02-20 22:45 71
}
61136b77 Poul Sander 2026-02-20 22:45 72
80a9926d Poul Sander 2026-05-19 20:28 73
QPixmap ImageViewWidget::loadImageFromDisk(const QByteArray &path)
61136b77 Poul Sander 2026-02-20 22:45 74
{
80a9926d Poul Sander 2026-05-19 20:28 75
    int fd = ::open(path.constData(), O_RDONLY | O_CLOEXEC);
80a9926d Poul Sander 2026-05-19 20:28 76
    if (fd < 0)
80a9926d Poul Sander 2026-05-19 20:28 77
        return {};
80a9926d Poul Sander 2026-05-19 20:28 78
80a9926d Poul Sander 2026-05-19 20:28 79
    QFile file;
80a9926d Poul Sander 2026-05-19 20:28 80
    if (!file.open(fd, QIODevice::ReadOnly, QFileDevice::AutoCloseHandle)) {
80a9926d Poul Sander 2026-05-19 20:28 81
        ::close(fd);
80a9926d Poul Sander 2026-05-19 20:28 82
        return {};
80a9926d Poul Sander 2026-05-19 20:28 83
    }
80a9926d Poul Sander 2026-05-19 20:28 84
80a9926d Poul Sander 2026-05-19 20:28 85
    QImageReader reader(&file);
61136b77 Poul Sander 2026-02-20 22:45 86
    reader.setAutoTransform(true);
e1e7f5a6 Poul Sander 2026-02-23 19:40 87
    reader.setDecideFormatFromContent(true);
7889168c Poul Sander 2026-02-26 18:27 88
    reader.setAllocationLimit(0);
61136b77 Poul Sander 2026-02-20 22:45 89
    QImage image = reader.read();
61136b77 Poul Sander 2026-02-20 22:45 90
e1e7f5a6 Poul Sander 2026-02-23 19:40 91
    if (!image.isNull()) {
58103277 Poul Sander 2026-03-01 22:53 92
        if (image.format() != QImage::Format_RGB32
58103277 Poul Sander 2026-03-01 22:53 93
            && image.format() != QImage::Format_ARGB32
58103277 Poul Sander 2026-03-01 22:53 94
            && image.format() != QImage::Format_ARGB32_Premultiplied)
e1e7f5a6 Poul Sander 2026-02-23 19:40 95
            image = image.convertToFormat(QImage::Format_ARGB32);
58103277 Poul Sander 2026-03-01 22:53 96
        return QPixmap::fromImage(image);
58103277 Poul Sander 2026-03-01 22:53 97
    }
80a9926d Poul Sander 2026-05-19 20:28 98
    return {};
58103277 Poul Sander 2026-03-01 22:53 99
}
58103277 Poul Sander 2026-03-01 22:53 100
80a9926d Poul Sander 2026-05-19 20:28 101
void ImageViewWidget::setImage(const QByteArray &path)
58103277 Poul Sander 2026-03-01 22:53 102
{
58103277 Poul Sander 2026-03-01 22:53 103
    m_currentPath = path;
53fdab93 Poul Sander 2026-05-18 18:45 104
    m_exifData = ExifData{}; // clear stale EXIF until new data is set
58103277 Poul Sander 2026-03-01 22:53 105
58103277 Poul Sander 2026-03-01 22:53 106
    // Check cache first
58103277 Poul Sander 2026-03-01 22:53 107
    if (m_cache.contains(path)) {
58103277 Poul Sander 2026-03-01 22:53 108
        m_pixmap = m_cache[path];
58103277 Poul Sander 2026-03-01 22:53 109
    } else {
58103277 Poul Sander 2026-03-01 22:53 110
        m_pixmap = loadImageFromDisk(path);
58103277 Poul Sander 2026-03-01 22:53 111
        m_cache[path] = m_pixmap;
e1e7f5a6 Poul Sander 2026-02-23 19:40 112
    }
61136b77 Poul Sander 2026-02-20 22:45 113
c58dbdb7 Poul Sander 2026-02-22 17:49 114
    m_zoomMode = FitToScreen;
c58dbdb7 Poul Sander 2026-02-22 17:49 115
    m_zoomFactor = 1.0;
c8ae2669 Poul Sander 2026-02-22 17:52 116
    m_offset = QPoint(0, 0);
61136b77 Poul Sander 2026-02-20 22:45 117
    update();
58103277 Poul Sander 2026-03-01 22:53 118
58103277 Poul Sander 2026-03-01 22:53 119
    // Trigger prefetch of neighbors if they have been set
58103277 Poul Sander 2026-03-01 22:53 120
    if (!m_neighborPaths.isEmpty()) {
58103277 Poul Sander 2026-03-01 22:53 121
        // Evict entries that are no longer in the neighbor window
80a9926d Poul Sander 2026-05-19 20:28 122
        QSet<QByteArray> keep(m_neighborPaths.begin(), m_neighborPaths.end());
58103277 Poul Sander 2026-03-01 22:53 123
        keep.insert(m_currentPath);
58103277 Poul Sander 2026-03-01 22:53 124
        for (auto it = m_cache.begin(); it != m_cache.end(); ) {
58103277 Poul Sander 2026-03-01 22:53 125
            if (!keep.contains(it.key()))
58103277 Poul Sander 2026-03-01 22:53 126
                it = m_cache.erase(it);
58103277 Poul Sander 2026-03-01 22:53 127
            else
58103277 Poul Sander 2026-03-01 22:53 128
                ++it;
58103277 Poul Sander 2026-03-01 22:53 129
        }
58103277 Poul Sander 2026-03-01 22:53 130
58103277 Poul Sander 2026-03-01 22:53 131
        // Prefetch neighbors that are not yet cached
80a9926d Poul Sander 2026-05-19 20:28 132
        for (const QByteArray &neighbor : m_neighborPaths) {
58103277 Poul Sander 2026-03-01 22:53 133
            prefetchImage(neighbor);
58103277 Poul Sander 2026-03-01 22:53 134
        }
58103277 Poul Sander 2026-03-01 22:53 135
    }
58103277 Poul Sander 2026-03-01 22:53 136
}
58103277 Poul Sander 2026-03-01 22:53 137
80a9926d Poul Sander 2026-05-19 20:28 138
void ImageViewWidget::prefetchImage(const QByteArray &path)
58103277 Poul Sander 2026-03-01 22:53 139
{
58103277 Poul Sander 2026-03-01 22:53 140
    if (m_cache.contains(path) || m_pendingLoads.contains(path))
58103277 Poul Sander 2026-03-01 22:53 141
        return;
58103277 Poul Sander 2026-03-01 22:53 142
58103277 Poul Sander 2026-03-01 22:53 143
    m_pendingLoads.insert(path);
58103277 Poul Sander 2026-03-01 22:53 144
    auto *worker = new ImageLoadWorker(path, this);
4d6b7476 Poul Sander 2026-05-30 16:39 145
    // THREADING: Qt::AutoConnection (default) resolves to QueuedConnection at
4d6b7476 Poul Sander 2026-05-30 16:39 146
    // emit time because ImageLoadWorker::run() emits from its worker thread
4d6b7476 Poul Sander 2026-05-30 16:39 147
    // while 'this' (ImageViewWidget) has main-thread affinity.  This guarantees
4d6b7476 Poul Sander 2026-05-30 16:39 148
    // onImageLoaded() always runs on the main thread — no mutex needed.
58103277 Poul Sander 2026-03-01 22:53 149
    connect(worker, &ImageLoadWorker::imageLoaded, this, &ImageViewWidget::onImageLoaded);
58103277 Poul Sander 2026-03-01 22:53 150
    connect(worker, &QThread::finished, worker, &QObject::deleteLater);
58103277 Poul Sander 2026-03-01 22:53 151
    worker->start();
58103277 Poul Sander 2026-03-01 22:53 152
}
58103277 Poul Sander 2026-03-01 22:53 153
80a9926d Poul Sander 2026-05-19 20:28 154
void ImageViewWidget::onImageLoaded(const QByteArray &path, const QPixmap &pixmap)
58103277 Poul Sander 2026-03-01 22:53 155
{
58103277 Poul Sander 2026-03-01 22:53 156
    m_pendingLoads.remove(path);
58103277 Poul Sander 2026-03-01 22:53 157
58103277 Poul Sander 2026-03-01 22:53 158
    // Only cache if path is still relevant (current or a neighbor)
80a9926d Poul Sander 2026-05-19 20:28 159
    QSet<QByteArray> keep(m_neighborPaths.begin(), m_neighborPaths.end());
58103277 Poul Sander 2026-03-01 22:53 160
    keep.insert(m_currentPath);
58103277 Poul Sander 2026-03-01 22:53 161
    if (keep.contains(path)) {
58103277 Poul Sander 2026-03-01 22:53 162
        m_cache[path] = pixmap;
58103277 Poul Sander 2026-03-01 22:53 163
    }
58103277 Poul Sander 2026-03-01 22:53 164
}
58103277 Poul Sander 2026-03-01 22:53 165
80a9926d Poul Sander 2026-05-19 20:28 166
void ImageViewWidget::setNeighborPaths(const QList<QByteArray> &paths)
58103277 Poul Sander 2026-03-01 22:53 167
{
58103277 Poul Sander 2026-03-01 22:53 168
    m_neighborPaths = paths;
58103277 Poul Sander 2026-03-01 22:53 169
}
58103277 Poul Sander 2026-03-01 22:53 170
58103277 Poul Sander 2026-03-01 22:53 171
void ImageViewWidget::clearCache()
58103277 Poul Sander 2026-03-01 22:53 172
{
58103277 Poul Sander 2026-03-01 22:53 173
    m_cache.clear();
58103277 Poul Sander 2026-03-01 22:53 174
    m_neighborPaths.clear();
61136b77 Poul Sander 2026-02-20 22:45 175
}
61136b77 Poul Sander 2026-02-20 22:45 176
48534775 Poul Sander 2026-06-08 18:57 177
void ImageViewWidget::invalidateCache(const QByteArray &path)
48534775 Poul Sander 2026-06-08 18:57 178
{
48534775 Poul Sander 2026-06-08 18:57 179
    m_cache.remove(path);
48534775 Poul Sander 2026-06-08 18:57 180
}
48534775 Poul Sander 2026-06-08 18:57 181
f61bf886 Poul Sander 2026-02-23 19:27 182
void ImageViewWidget::setBackgroundColor(const QString &color)
f61bf886 Poul Sander 2026-02-23 19:27 183
{
f61bf886 Poul Sander 2026-02-23 19:27 184
    m_backgroundColor = color;
f61bf886 Poul Sander 2026-02-23 19:27 185
    QPalette pal = palette();
f61bf886 Poul Sander 2026-02-23 19:27 186
    pal.setColor(QPalette::Window, QColor(color));
f61bf886 Poul Sander 2026-02-23 19:27 187
    setPalette(pal);
f61bf886 Poul Sander 2026-02-23 19:27 188
    update();
f61bf886 Poul Sander 2026-02-23 19:27 189
}
f61bf886 Poul Sander 2026-02-23 19:27 190
61136b77 Poul Sander 2026-02-20 22:45 191
void ImageViewWidget::paintEvent(QPaintEvent *)
61136b77 Poul Sander 2026-02-20 22:45 192
{
61136b77 Poul Sander 2026-02-20 22:45 193
    if (m_pixmap.isNull())
61136b77 Poul Sander 2026-02-20 22:45 194
        return;
61136b77 Poul Sander 2026-02-20 22:45 195
61136b77 Poul Sander 2026-02-20 22:45 196
    QPainter painter(this);
e1e7f5a6 Poul Sander 2026-02-23 19:40 197
    painter.setRenderHint(QPainter::SmoothPixmapTransform, true);
e1e7f5a6 Poul Sander 2026-02-23 19:40 198
    painter.setRenderHint(QPainter::Antialiasing, true);
e1e7f5a6 Poul Sander 2026-02-23 19:40 199
c58dbdb7 Poul Sander 2026-02-22 17:49 200
    QPixmap drawn;
c58dbdb7 Poul Sander 2026-02-22 17:49 201
c58dbdb7 Poul Sander 2026-02-22 17:49 202
    switch (m_zoomMode) {
c58dbdb7 Poul Sander 2026-02-22 17:49 203
    case FitToScreen:
c58dbdb7 Poul Sander 2026-02-22 17:49 204
        drawn = m_pixmap.scaled(size(), Qt::KeepAspectRatio, Qt::SmoothTransformation);
c58dbdb7 Poul Sander 2026-02-22 17:49 205
        break;
c58dbdb7 Poul Sander 2026-02-22 17:49 206
    case OriginalSize:
c58dbdb7 Poul Sander 2026-02-22 17:49 207
        drawn = m_pixmap;
c58dbdb7 Poul Sander 2026-02-22 17:49 208
        break;
c58dbdb7 Poul Sander 2026-02-22 17:49 209
    case CustomZoom: {
c58dbdb7 Poul Sander 2026-02-22 17:49 210
        QSize target(static_cast<int>(m_pixmap.width() * m_zoomFactor),
c58dbdb7 Poul Sander 2026-02-22 17:49 211
                     static_cast<int>(m_pixmap.height() * m_zoomFactor));
c58dbdb7 Poul Sander 2026-02-22 17:49 212
        drawn = m_pixmap.scaled(target, Qt::KeepAspectRatio, Qt::SmoothTransformation);
c58dbdb7 Poul Sander 2026-02-22 17:49 213
        break;
c58dbdb7 Poul Sander 2026-02-22 17:49 214
    }
c58dbdb7 Poul Sander 2026-02-22 17:49 215
    }
c58dbdb7 Poul Sander 2026-02-22 17:49 216
c8ae2669 Poul Sander 2026-02-22 17:52 217
    clampOffset(drawn.width(), drawn.height());
c8ae2669 Poul Sander 2026-02-22 17:52 218
c8ae2669 Poul Sander 2026-02-22 17:52 219
    int x = (width()  - drawn.width())  / 2 + m_offset.x();
c8ae2669 Poul Sander 2026-02-22 17:52 220
    int y = (height() - drawn.height()) / 2 + m_offset.y();
c58dbdb7 Poul Sander 2026-02-22 17:49 221
    painter.drawPixmap(x, y, drawn);
53fdab93 Poul Sander 2026-05-18 18:45 222
53fdab93 Poul Sander 2026-05-18 18:45 223
    // EXIF overlay — top-right corner, toggled with 'i'
53fdab93 Poul Sander 2026-05-18 18:45 224
    if (m_showExifOverlay) {
53fdab93 Poul Sander 2026-05-18 18:45 225
        const auto fields = m_exifData.toList();
53fdab93 Poul Sander 2026-05-18 18:45 226
        if (!fields.isEmpty()) {
53fdab93 Poul Sander 2026-05-18 18:45 227
            const int padding = 10;
53fdab93 Poul Sander 2026-05-18 18:45 228
            const int lineH = 18;
53fdab93 Poul Sander 2026-05-18 18:45 229
53fdab93 Poul Sander 2026-05-18 18:45 230
            QFont font = painter.font();
53fdab93 Poul Sander 2026-05-18 18:45 231
            font.setPointSize(9);
53fdab93 Poul Sander 2026-05-18 18:45 232
            painter.setFont(font);
53fdab93 Poul Sander 2026-05-18 18:45 233
            QFontMetrics fm(font);
53fdab93 Poul Sander 2026-05-18 18:45 234
53fdab93 Poul Sander 2026-05-18 18:45 235
            int maxW = 0;
53fdab93 Poul Sander 2026-05-18 18:45 236
            for (const auto &f : fields)
53fdab93 Poul Sander 2026-05-18 18:45 237
                maxW = qMax(maxW, fm.horizontalAdvance(f.first + QLatin1String(": ") + f.second));
53fdab93 Poul Sander 2026-05-18 18:45 238
53fdab93 Poul Sander 2026-05-18 18:45 239
            const int boxW = maxW + padding * 2;
53fdab93 Poul Sander 2026-05-18 18:45 240
            const int boxH = fields.size() * lineH + padding * 2;
53fdab93 Poul Sander 2026-05-18 18:45 241
            const int boxX = width() - boxW - padding;
53fdab93 Poul Sander 2026-05-18 18:45 242
            const int boxY = padding;
53fdab93 Poul Sander 2026-05-18 18:45 243
53fdab93 Poul Sander 2026-05-18 18:45 244
            painter.save();
53fdab93 Poul Sander 2026-05-18 18:45 245
            painter.setPen(Qt::NoPen);
53fdab93 Poul Sander 2026-05-18 18:45 246
            painter.setBrush(QColor(0, 0, 0, 160));
53fdab93 Poul Sander 2026-05-18 18:45 247
            painter.drawRoundedRect(boxX, boxY, boxW, boxH, 8, 8);
53fdab93 Poul Sander 2026-05-18 18:45 248
53fdab93 Poul Sander 2026-05-18 18:45 249
            painter.setPen(Qt::white);
cdae9502 Poul Sander 2026-06-09 20:04 250
            m_osmLinkRect = QRect();
53fdab93 Poul Sander 2026-05-18 18:45 251
            for (int i = 0; i < fields.size(); ++i) {
53fdab93 Poul Sander 2026-05-18 18:45 252
                const QString line = fields[i].first + QLatin1String(": ") + fields[i].second;
cdae9502 Poul Sander 2026-06-09 20:04 253
                if (fields[i].first == "OpenStreetMap") {
cdae9502 Poul Sander 2026-06-09 20:04 254
                    painter.setPen(Qt::cyan);
cdae9502 Poul Sander 2026-06-09 20:04 255
                    m_osmLinkRect = QRect(boxX + padding,
cdae9502 Poul Sander 2026-06-09 20:04 256
                                          boxY + padding + i * lineH,
cdae9502 Poul Sander 2026-06-09 20:04 257
                                          fm.horizontalAdvance(line),
cdae9502 Poul Sander 2026-06-09 20:04 258
                                          lineH);
cdae9502 Poul Sander 2026-06-09 20:04 259
                } else {
cdae9502 Poul Sander 2026-06-09 20:04 260
                    painter.setPen(Qt::white);
cdae9502 Poul Sander 2026-06-09 20:04 261
                }
53fdab93 Poul Sander 2026-05-18 18:45 262
                painter.drawText(boxX + padding,
53fdab93 Poul Sander 2026-05-18 18:45 263
                                 boxY + padding + fm.ascent() + i * lineH,
53fdab93 Poul Sander 2026-05-18 18:45 264
                                 line);
53fdab93 Poul Sander 2026-05-18 18:45 265
            }
53fdab93 Poul Sander 2026-05-18 18:45 266
            painter.restore();
53fdab93 Poul Sander 2026-05-18 18:45 267
        }
53fdab93 Poul Sander 2026-05-18 18:45 268
    }
beb4a823 Poul Sander 2026-05-19 21:53 269
beb4a823 Poul Sander 2026-05-19 21:53 270
    // Caption-Abstract overlay — bottom-centre, always shown when non-empty
beb4a823 Poul Sander 2026-05-19 21:53 271
    if (!m_exifData.captionAbstract.isEmpty()) {
beb4a823 Poul Sander 2026-05-19 21:53 272
        const int padding  = 10;
beb4a823 Poul Sander 2026-05-19 21:53 273
        const int margin   = 20;  // distance from bottom edge
beb4a823 Poul Sander 2026-05-19 21:53 274
        const int maxWidth = width() * 3 / 4;
beb4a823 Poul Sander 2026-05-19 21:53 275
beb4a823 Poul Sander 2026-05-19 21:53 276
        QFont captionFont = painter.font();
beb4a823 Poul Sander 2026-05-19 21:53 277
        captionFont.setPointSize(11);
beb4a823 Poul Sander 2026-05-19 21:53 278
        painter.setFont(captionFont);
beb4a823 Poul Sander 2026-05-19 21:53 279
        QFontMetrics cfm(captionFont);
beb4a823 Poul Sander 2026-05-19 21:53 280
beb4a823 Poul Sander 2026-05-19 21:53 281
        QRect textBounds = cfm.boundingRect(
beb4a823 Poul Sander 2026-05-19 21:53 282
            QRect(0, 0, maxWidth, 0),
beb4a823 Poul Sander 2026-05-19 21:53 283
            Qt::AlignHCenter | Qt::TextWordWrap,
beb4a823 Poul Sander 2026-05-19 21:53 284
            m_exifData.captionAbstract);
beb4a823 Poul Sander 2026-05-19 21:53 285
beb4a823 Poul Sander 2026-05-19 21:53 286
        const int boxW = textBounds.width()  + padding * 2;
beb4a823 Poul Sander 2026-05-19 21:53 287
        const int boxH = textBounds.height() + padding * 2;
beb4a823 Poul Sander 2026-05-19 21:53 288
        const int boxX = (width() - boxW) / 2;
beb4a823 Poul Sander 2026-05-19 21:53 289
        const int boxY = height() - boxH - margin;
beb4a823 Poul Sander 2026-05-19 21:53 290
beb4a823 Poul Sander 2026-05-19 21:53 291
        painter.save();
beb4a823 Poul Sander 2026-05-19 21:53 292
        painter.setPen(Qt::NoPen);
beb4a823 Poul Sander 2026-05-19 21:53 293
        painter.setBrush(QColor(0, 0, 0, 180));
beb4a823 Poul Sander 2026-05-19 21:53 294
        painter.drawRoundedRect(boxX, boxY, boxW, boxH, 8, 8);
beb4a823 Poul Sander 2026-05-19 21:53 295
beb4a823 Poul Sander 2026-05-19 21:53 296
        painter.setPen(Qt::white);
beb4a823 Poul Sander 2026-05-19 21:53 297
        painter.drawText(QRect(boxX + padding, boxY + padding, textBounds.width(), textBounds.height()),
beb4a823 Poul Sander 2026-05-19 21:53 298
                         Qt::AlignHCenter | Qt::TextWordWrap,
beb4a823 Poul Sander 2026-05-19 21:53 299
                         m_exifData.captionAbstract);
beb4a823 Poul Sander 2026-05-19 21:53 300
        painter.restore();
beb4a823 Poul Sander 2026-05-19 21:53 301
    }
4db3041a Poul Sander 2026-05-26 22:01 302
4db3041a Poul Sander 2026-05-26 22:01 303
    // Help overlay — centered, toggled with F1
4db3041a Poul Sander 2026-05-26 22:01 304
    if (m_showHelpOverlay) {
83f0e0bc Poul Sander 2026-05-30 17:59 305
        const ShortcutManager &sc = ShortcutManager::instance();
83f0e0bc Poul Sander 2026-05-30 17:59 306
83f0e0bc Poul Sander 2026-05-30 17:59 307
        // Build the shortcut table dynamically from current bindings.
83f0e0bc Poul Sander 2026-05-30 17:59 308
        // Some actions are grouped together for display (e.g. pan directions).
83f0e0bc Poul Sander 2026-05-30 17:59 309
        struct HelpEntry { QString key; QString desc; };
83f0e0bc Poul Sander 2026-05-30 17:59 310
        QList<HelpEntry> entries;
83f0e0bc Poul Sander 2026-05-30 17:59 311
83f0e0bc Poul Sander 2026-05-30 17:59 312
        entries.append({sc.displayKey(ShortcutManager::ToggleHelp),
83f0e0bc Poul Sander 2026-05-30 17:59 313
                        sc.description(ShortcutManager::ToggleHelp)});
83f0e0bc Poul Sander 2026-05-30 17:59 314
        entries.append({sc.displayKey(ShortcutManager::CloseViewer)
83f0e0bc Poul Sander 2026-05-30 17:59 315
                        + " / " + sc.displayKey(ShortcutManager::CloseViewerAlt),
4c41f5ff Poul Sander 2026-05-31 00:01 316
                        tr("Close viewer")});
83f0e0bc Poul Sander 2026-05-30 17:59 317
        entries.append({sc.displayKey(ShortcutManager::NextImage)
83f0e0bc Poul Sander 2026-05-30 17:59 318
                        + " / " + sc.displayKey(ShortcutManager::PrevImage),
4c41f5ff Poul Sander 2026-05-31 00:01 319
                        tr("Next / previous image")});
83f0e0bc Poul Sander 2026-05-30 17:59 320
        entries.append({sc.displayKey(ShortcutManager::PanLeft)
83f0e0bc Poul Sander 2026-05-30 17:59 321
                        + " " + sc.displayKey(ShortcutManager::PanRight)
83f0e0bc Poul Sander 2026-05-30 17:59 322
                        + " " + sc.displayKey(ShortcutManager::PanUp)
83f0e0bc Poul Sander 2026-05-30 17:59 323
                        + " " + sc.displayKey(ShortcutManager::PanDown),
4c41f5ff Poul Sander 2026-05-31 00:01 324
                        tr("Pan image")});
83f0e0bc Poul Sander 2026-05-30 17:59 325
        entries.append({sc.displayKey(ShortcutManager::ZoomOriginal),
83f0e0bc Poul Sander 2026-05-30 17:59 326
                        sc.description(ShortcutManager::ZoomOriginal)});
83f0e0bc Poul Sander 2026-05-30 17:59 327
        entries.append({sc.displayKey(ShortcutManager::ZoomFit),
83f0e0bc Poul Sander 2026-05-30 17:59 328
                        sc.description(ShortcutManager::ZoomFit)});
83f0e0bc Poul Sander 2026-05-30 17:59 329
        entries.append({sc.displayKey(ShortcutManager::ZoomIn)
83f0e0bc Poul Sander 2026-05-30 17:59 330
                        + " / " + sc.displayKey(ShortcutManager::ZoomOut),
4c41f5ff Poul Sander 2026-05-31 00:01 331
                        tr("Zoom in / out")});
83f0e0bc Poul Sander 2026-05-30 17:59 332
        entries.append({sc.displayKey(ShortcutManager::ToggleExif),
83f0e0bc Poul Sander 2026-05-30 17:59 333
                        sc.description(ShortcutManager::ToggleExif)});
83f0e0bc Poul Sander 2026-05-30 17:59 334
        entries.append({sc.displayKey(ShortcutManager::EditCaption),
83f0e0bc Poul Sander 2026-05-30 17:59 335
                        sc.description(ShortcutManager::EditCaption)});
83f0e0bc Poul Sander 2026-05-30 17:59 336
        entries.append({sc.displayKey(ShortcutManager::ToggleFullscreen),
83f0e0bc Poul Sander 2026-05-30 17:59 337
                        sc.description(ShortcutManager::ToggleFullscreen)});
83f0e0bc Poul Sander 2026-05-30 17:59 338
83f0e0bc Poul Sander 2026-05-30 17:59 339
        const int count = entries.size();
4db3041a Poul Sander 2026-05-26 22:01 340
4db3041a Poul Sander 2026-05-26 22:01 341
        const int padding = 16;
4db3041a Poul Sander 2026-05-26 22:01 342
        const int lineH   = 22;
4db3041a Poul Sander 2026-05-26 22:01 343
        const int colGap  = 20;
4db3041a Poul Sander 2026-05-26 22:01 344
4db3041a Poul Sander 2026-05-26 22:01 345
        QFont font = painter.font();
4db3041a Poul Sander 2026-05-26 22:01 346
        font.setPointSize(10);
4db3041a Poul Sander 2026-05-26 22:01 347
        painter.setFont(font);
4db3041a Poul Sander 2026-05-26 22:01 348
        QFontMetrics fm(font);
4db3041a Poul Sander 2026-05-26 22:01 349
4db3041a Poul Sander 2026-05-26 22:01 350
        int keyColW  = 0;
4db3041a Poul Sander 2026-05-26 22:01 351
        int descColW = 0;
83f0e0bc Poul Sander 2026-05-30 17:59 352
        for (const auto &e : entries) {
83f0e0bc Poul Sander 2026-05-30 17:59 353
            keyColW  = qMax(keyColW,  fm.horizontalAdvance(e.key));
83f0e0bc Poul Sander 2026-05-30 17:59 354
            descColW = qMax(descColW, fm.horizontalAdvance(e.desc));
4db3041a Poul Sander 2026-05-26 22:01 355
        }
4db3041a Poul Sander 2026-05-26 22:01 356
4db3041a Poul Sander 2026-05-26 22:01 357
        const int boxW = padding + keyColW + colGap + descColW + padding;
4db3041a Poul Sander 2026-05-26 22:01 358
        const int boxH = count * lineH + padding * 2;
4db3041a Poul Sander 2026-05-26 22:01 359
        const int boxX = (width()  - boxW) / 2;
4db3041a Poul Sander 2026-05-26 22:01 360
        const int boxY = (height() - boxH) / 2;
4db3041a Poul Sander 2026-05-26 22:01 361
4db3041a Poul Sander 2026-05-26 22:01 362
        painter.save();
4db3041a Poul Sander 2026-05-26 22:01 363
        painter.setPen(Qt::NoPen);
4db3041a Poul Sander 2026-05-26 22:01 364
        painter.setBrush(QColor(0, 0, 0, 200));
4db3041a Poul Sander 2026-05-26 22:01 365
        painter.drawRoundedRect(boxX, boxY, boxW, boxH, 10, 10);
4db3041a Poul Sander 2026-05-26 22:01 366
4db3041a Poul Sander 2026-05-26 22:01 367
        for (int i = 0; i < count; ++i) {
4db3041a Poul Sander 2026-05-26 22:01 368
            const int textY = boxY + padding + fm.ascent() + i * lineH;
4db3041a Poul Sander 2026-05-26 22:01 369
            // Key column — right-aligned, light lavender
4db3041a Poul Sander 2026-05-26 22:01 370
            painter.setPen(QColor(180, 180, 255));
4db3041a Poul Sander 2026-05-26 22:01 371
            painter.drawText(QRect(boxX + padding, textY - fm.ascent(), keyColW, lineH),
4db3041a Poul Sander 2026-05-26 22:01 372
                             Qt::AlignRight | Qt::AlignVCenter,
83f0e0bc Poul Sander 2026-05-30 17:59 373
                             entries[i].key);
4db3041a Poul Sander 2026-05-26 22:01 374
            // Description column — left-aligned, white
4db3041a Poul Sander 2026-05-26 22:01 375
            painter.setPen(Qt::white);
4db3041a Poul Sander 2026-05-26 22:01 376
            painter.drawText(QRect(boxX + padding + keyColW + colGap,
4db3041a Poul Sander 2026-05-26 22:01 377
                                   textY - fm.ascent(), descColW, lineH),
4db3041a Poul Sander 2026-05-26 22:01 378
                             Qt::AlignLeft | Qt::AlignVCenter,
83f0e0bc Poul Sander 2026-05-30 17:59 379
                             entries[i].desc);
4db3041a Poul Sander 2026-05-26 22:01 380
        }
4db3041a Poul Sander 2026-05-26 22:01 381
        painter.restore();
4db3041a Poul Sander 2026-05-26 22:01 382
    }
61136b77 Poul Sander 2026-02-20 22:45 383
}
61136b77 Poul Sander 2026-02-20 22:45 384
61136b77 Poul Sander 2026-02-20 22:45 385
void ImageViewWidget::keyPressEvent(QKeyEvent *event)
61136b77 Poul Sander 2026-02-20 22:45 386
{
83f0e0bc Poul Sander 2026-05-30 17:59 387
    const ShortcutManager &sc = ShortcutManager::instance();
83f0e0bc Poul Sander 2026-05-30 17:59 388
83f0e0bc Poul Sander 2026-05-30 17:59 389
    if (sc.matches(event, ShortcutManager::ToggleHelp))
4db3041a Poul Sander 2026-05-26 22:01 390
    {
4db3041a Poul Sander 2026-05-26 22:01 391
        m_showHelpOverlay = !m_showHelpOverlay;
4db3041a Poul Sander 2026-05-26 22:01 392
        update();
4db3041a Poul Sander 2026-05-26 22:01 393
        event->accept();
4db3041a Poul Sander 2026-05-26 22:01 394
        return;
4db3041a Poul Sander 2026-05-26 22:01 395
    }
4db3041a Poul Sander 2026-05-26 22:01 396
83f0e0bc Poul Sander 2026-05-30 17:59 397
    if (sc.matches(event, ShortcutManager::CloseViewer)
83f0e0bc Poul Sander 2026-05-30 17:59 398
        || sc.matches(event, ShortcutManager::CloseViewerAlt))
61136b77 Poul Sander 2026-02-20 22:45 399
    {
61136b77 Poul Sander 2026-02-20 22:45 400
        emit closeRequested();
61136b77 Poul Sander 2026-02-20 22:45 401
        event->accept();
61136b77 Poul Sander 2026-02-20 22:45 402
        return;
61136b77 Poul Sander 2026-02-20 22:45 403
    }
61136b77 Poul Sander 2026-02-20 22:45 404
83f0e0bc Poul Sander 2026-05-30 17:59 405
    if (sc.matches(event, ShortcutManager::NextImage))
1ffc1962 Poul Sander 2026-02-20 23:41 406
    {
1ffc1962 Poul Sander 2026-02-20 23:41 407
        emit nextRequested();
1ffc1962 Poul Sander 2026-02-20 23:41 408
        event->accept();
1ffc1962 Poul Sander 2026-02-20 23:41 409
        return;
1ffc1962 Poul Sander 2026-02-20 23:41 410
    }
1ffc1962 Poul Sander 2026-02-20 23:41 411
83f0e0bc Poul Sander 2026-05-30 17:59 412
    if (sc.matches(event, ShortcutManager::PrevImage))
1ffc1962 Poul Sander 2026-02-20 23:41 413
    {
1ffc1962 Poul Sander 2026-02-20 23:41 414
        emit previousRequested();
1ffc1962 Poul Sander 2026-02-20 23:41 415
        event->accept();
1ffc1962 Poul Sander 2026-02-20 23:41 416
        return;
1ffc1962 Poul Sander 2026-02-20 23:41 417
    }
1ffc1962 Poul Sander 2026-02-20 23:41 418
c8ae2669 Poul Sander 2026-02-22 17:52 419
    // Arrow keys for panning
c8ae2669 Poul Sander 2026-02-22 17:52 420
    constexpr int panStep = 50;
83f0e0bc Poul Sander 2026-05-30 17:59 421
    if (sc.matches(event, ShortcutManager::PanLeft))
c8ae2669 Poul Sander 2026-02-22 17:52 422
    {
c8ae2669 Poul Sander 2026-02-22 17:52 423
        m_offset.rx() += panStep;
c8ae2669 Poul Sander 2026-02-22 17:52 424
        update();
c8ae2669 Poul Sander 2026-02-22 17:52 425
        event->accept();
c8ae2669 Poul Sander 2026-02-22 17:52 426
        return;
c8ae2669 Poul Sander 2026-02-22 17:52 427
    }
83f0e0bc Poul Sander 2026-05-30 17:59 428
    if (sc.matches(event, ShortcutManager::PanRight))
c8ae2669 Poul Sander 2026-02-22 17:52 429
    {
c8ae2669 Poul Sander 2026-02-22 17:52 430
        m_offset.rx() -= panStep;
c8ae2669 Poul Sander 2026-02-22 17:52 431
        update();
c8ae2669 Poul Sander 2026-02-22 17:52 432
        event->accept();
c8ae2669 Poul Sander 2026-02-22 17:52 433
        return;
c8ae2669 Poul Sander 2026-02-22 17:52 434
    }
83f0e0bc Poul Sander 2026-05-30 17:59 435
    if (sc.matches(event, ShortcutManager::PanUp))
c8ae2669 Poul Sander 2026-02-22 17:52 436
    {
c8ae2669 Poul Sander 2026-02-22 17:52 437
        m_offset.ry() += panStep;
c8ae2669 Poul Sander 2026-02-22 17:52 438
        update();
c8ae2669 Poul Sander 2026-02-22 17:52 439
        event->accept();
c8ae2669 Poul Sander 2026-02-22 17:52 440
        return;
c8ae2669 Poul Sander 2026-02-22 17:52 441
    }
83f0e0bc Poul Sander 2026-05-30 17:59 442
    if (sc.matches(event, ShortcutManager::PanDown))
c8ae2669 Poul Sander 2026-02-22 17:52 443
    {
c8ae2669 Poul Sander 2026-02-22 17:52 444
        m_offset.ry() -= panStep;
c8ae2669 Poul Sander 2026-02-22 17:52 445
        update();
c8ae2669 Poul Sander 2026-02-22 17:52 446
        event->accept();
c8ae2669 Poul Sander 2026-02-22 17:52 447
        return;
c8ae2669 Poul Sander 2026-02-22 17:52 448
    }
c8ae2669 Poul Sander 2026-02-22 17:52 449
83f0e0bc Poul Sander 2026-05-30 17:59 450
    if (sc.matches(event, ShortcutManager::ZoomOriginal))
c58dbdb7 Poul Sander 2026-02-22 17:49 451
    {
c58dbdb7 Poul Sander 2026-02-22 17:49 452
        zoomOriginal();
c58dbdb7 Poul Sander 2026-02-22 17:49 453
        event->accept();
c58dbdb7 Poul Sander 2026-02-22 17:49 454
        return;
c58dbdb7 Poul Sander 2026-02-22 17:49 455
    }
c58dbdb7 Poul Sander 2026-02-22 17:49 456
83f0e0bc Poul Sander 2026-05-30 17:59 457
    if (sc.matches(event, ShortcutManager::ZoomFit))
c58dbdb7 Poul Sander 2026-02-22 17:49 458
    {
c58dbdb7 Poul Sander 2026-02-22 17:49 459
        zoomFitToScreen();
c58dbdb7 Poul Sander 2026-02-22 17:49 460
        event->accept();
c58dbdb7 Poul Sander 2026-02-22 17:49 461
        return;
c58dbdb7 Poul Sander 2026-02-22 17:49 462
    }
c58dbdb7 Poul Sander 2026-02-22 17:49 463
83f0e0bc Poul Sander 2026-05-30 17:59 464
    if (sc.matches(event, ShortcutManager::ZoomIn))
c58dbdb7 Poul Sander 2026-02-22 17:49 465
    {
c58dbdb7 Poul Sander 2026-02-22 17:49 466
        zoomIn();
c58dbdb7 Poul Sander 2026-02-22 17:49 467
        event->accept();
c58dbdb7 Poul Sander 2026-02-22 17:49 468
        return;
c58dbdb7 Poul Sander 2026-02-22 17:49 469
    }
c58dbdb7 Poul Sander 2026-02-22 17:49 470
83f0e0bc Poul Sander 2026-05-30 17:59 471
    if (sc.matches(event, ShortcutManager::ZoomOut))
c58dbdb7 Poul Sander 2026-02-22 17:49 472
    {
c58dbdb7 Poul Sander 2026-02-22 17:49 473
        zoomOut();
c58dbdb7 Poul Sander 2026-02-22 17:49 474
        event->accept();
c58dbdb7 Poul Sander 2026-02-22 17:49 475
        return;
c58dbdb7 Poul Sander 2026-02-22 17:49 476
    }
c58dbdb7 Poul Sander 2026-02-22 17:49 477
83f0e0bc Poul Sander 2026-05-30 17:59 478
    if (sc.matches(event, ShortcutManager::ToggleExif))
53fdab93 Poul Sander 2026-05-18 18:45 479
    {
53fdab93 Poul Sander 2026-05-18 18:45 480
        toggleExifOverlay();
53fdab93 Poul Sander 2026-05-18 18:45 481
        event->accept();
53fdab93 Poul Sander 2026-05-18 18:45 482
        return;
53fdab93 Poul Sander 2026-05-18 18:45 483
    }
53fdab93 Poul Sander 2026-05-18 18:45 484
83f0e0bc Poul Sander 2026-05-30 17:59 485
    if (sc.matches(event, ShortcutManager::EditCaption))
beb4a823 Poul Sander 2026-05-19 21:53 486
    {
beb4a823 Poul Sander 2026-05-19 21:53 487
        emit editCaptionRequested();
beb4a823 Poul Sander 2026-05-19 21:53 488
        event->accept();
beb4a823 Poul Sander 2026-05-19 21:53 489
        return;
beb4a823 Poul Sander 2026-05-19 21:53 490
    }
beb4a823 Poul Sander 2026-05-19 21:53 491
61136b77 Poul Sander 2026-02-20 22:45 492
    QWidget::keyPressEvent(event);
61136b77 Poul Sander 2026-02-20 22:45 493
}
c58dbdb7 Poul Sander 2026-02-22 17:49 494
53fdab93 Poul Sander 2026-05-18 18:45 495
void ImageViewWidget::setExifData(const ExifData &data)
53fdab93 Poul Sander 2026-05-18 18:45 496
{
53fdab93 Poul Sander 2026-05-18 18:45 497
    m_exifData = data;
53fdab93 Poul Sander 2026-05-18 18:45 498
    if (m_showExifOverlay)
53fdab93 Poul Sander 2026-05-18 18:45 499
        update();
53fdab93 Poul Sander 2026-05-18 18:45 500
}
53fdab93 Poul Sander 2026-05-18 18:45 501
53fdab93 Poul Sander 2026-05-18 18:45 502
void ImageViewWidget::toggleExifOverlay()
53fdab93 Poul Sander 2026-05-18 18:45 503
{
53fdab93 Poul Sander 2026-05-18 18:45 504
    m_showExifOverlay = !m_showExifOverlay;
53fdab93 Poul Sander 2026-05-18 18:45 505
    update();
53fdab93 Poul Sander 2026-05-18 18:45 506
}
53fdab93 Poul Sander 2026-05-18 18:45 507
beb4a823 Poul Sander 2026-05-19 21:53 508
QByteArray ImageViewWidget::currentPath() const
beb4a823 Poul Sander 2026-05-19 21:53 509
{
beb4a823 Poul Sander 2026-05-19 21:53 510
    return m_currentPath;
beb4a823 Poul Sander 2026-05-19 21:53 511
}
beb4a823 Poul Sander 2026-05-19 21:53 512
c58dbdb7 Poul Sander 2026-02-22 17:49 513
void ImageViewWidget::zoomIn()
c58dbdb7 Poul Sander 2026-02-22 17:49 514
{
c58dbdb7 Poul Sander 2026-02-22 17:49 515
    if (m_zoomMode == FitToScreen) {
c58dbdb7 Poul Sander 2026-02-22 17:49 516
        // Calculate the current effective scale so zooming feels continuous
c58dbdb7 Poul Sander 2026-02-22 17:49 517
        double sx = static_cast<double>(width()) / m_pixmap.width();
c58dbdb7 Poul Sander 2026-02-22 17:49 518
        double sy = static_cast<double>(height()) / m_pixmap.height();
c58dbdb7 Poul Sander 2026-02-22 17:49 519
        m_zoomFactor = std::min(sx, sy);
c58dbdb7 Poul Sander 2026-02-22 17:49 520
    }
c58dbdb7 Poul Sander 2026-02-22 17:49 521
    m_zoomFactor *= 1.25;
c58dbdb7 Poul Sander 2026-02-22 17:49 522
    m_zoomMode = CustomZoom;
c58dbdb7 Poul Sander 2026-02-22 17:49 523
    update();
c58dbdb7 Poul Sander 2026-02-22 17:49 524
}
c58dbdb7 Poul Sander 2026-02-22 17:49 525
c58dbdb7 Poul Sander 2026-02-22 17:49 526
void ImageViewWidget::zoomOut()
c58dbdb7 Poul Sander 2026-02-22 17:49 527
{
c58dbdb7 Poul Sander 2026-02-22 17:49 528
    if (m_zoomMode == FitToScreen) {
c58dbdb7 Poul Sander 2026-02-22 17:49 529
        double sx = static_cast<double>(width()) / m_pixmap.width();
c58dbdb7 Poul Sander 2026-02-22 17:49 530
        double sy = static_cast<double>(height()) / m_pixmap.height();
c58dbdb7 Poul Sander 2026-02-22 17:49 531
        m_zoomFactor = std::min(sx, sy);
c58dbdb7 Poul Sander 2026-02-22 17:49 532
    }
c58dbdb7 Poul Sander 2026-02-22 17:49 533
    m_zoomFactor /= 1.25;
c58dbdb7 Poul Sander 2026-02-22 17:49 534
    if (m_zoomFactor < 0.01)
c58dbdb7 Poul Sander 2026-02-22 17:49 535
        m_zoomFactor = 0.01;
c58dbdb7 Poul Sander 2026-02-22 17:49 536
    m_zoomMode = CustomZoom;
c58dbdb7 Poul Sander 2026-02-22 17:49 537
    update();
c58dbdb7 Poul Sander 2026-02-22 17:49 538
}
c58dbdb7 Poul Sander 2026-02-22 17:49 539
c58dbdb7 Poul Sander 2026-02-22 17:49 540
void ImageViewWidget::zoomOriginal()
c58dbdb7 Poul Sander 2026-02-22 17:49 541
{
c58dbdb7 Poul Sander 2026-02-22 17:49 542
    m_zoomMode = OriginalSize;
c58dbdb7 Poul Sander 2026-02-22 17:49 543
    m_zoomFactor = 1.0;
c58dbdb7 Poul Sander 2026-02-22 17:49 544
    update();
c58dbdb7 Poul Sander 2026-02-22 17:49 545
}
c58dbdb7 Poul Sander 2026-02-22 17:49 546
c58dbdb7 Poul Sander 2026-02-22 17:49 547
void ImageViewWidget::zoomFitToScreen()
c58dbdb7 Poul Sander 2026-02-22 17:49 548
{
c58dbdb7 Poul Sander 2026-02-22 17:49 549
    m_zoomMode = FitToScreen;
c8ae2669 Poul Sander 2026-02-22 17:52 550
    m_offset = QPoint(0, 0);
c58dbdb7 Poul Sander 2026-02-22 17:49 551
    update();
c58dbdb7 Poul Sander 2026-02-22 17:49 552
}
c8ae2669 Poul Sander 2026-02-22 17:52 553
c8ae2669 Poul Sander 2026-02-22 17:52 554
void ImageViewWidget::mousePressEvent(QMouseEvent *event)
c8ae2669 Poul Sander 2026-02-22 17:52 555
{
c8ae2669 Poul Sander 2026-02-22 17:52 556
    if (event->button() == Qt::LeftButton) {
cdae9502 Poul Sander 2026-06-09 20:04 557
        if (m_showExifOverlay && m_osmLinkRect.contains(event->pos())) {
cdae9502 Poul Sander 2026-06-09 20:04 558
            QDesktopServices::openUrl(QUrl(m_exifData.osmLink));
cdae9502 Poul Sander 2026-06-09 20:04 559
            event->accept();
cdae9502 Poul Sander 2026-06-09 20:04 560
            return;
cdae9502 Poul Sander 2026-06-09 20:04 561
        }
c8ae2669 Poul Sander 2026-02-22 17:52 562
        m_dragging = true;
c8ae2669 Poul Sander 2026-02-22 17:52 563
        m_dragStart = event->pos();
c8ae2669 Poul Sander 2026-02-22 17:52 564
        m_offsetAtDragStart = m_offset;
c8ae2669 Poul Sander 2026-02-22 17:52 565
        setCursor(Qt::ClosedHandCursor);
c8ae2669 Poul Sander 2026-02-22 17:52 566
        event->accept();
c8ae2669 Poul Sander 2026-02-22 17:52 567
    }
c8ae2669 Poul Sander 2026-02-22 17:52 568
}
c8ae2669 Poul Sander 2026-02-22 17:52 569
c8ae2669 Poul Sander 2026-02-22 17:52 570
void ImageViewWidget::mouseMoveEvent(QMouseEvent *event)
c8ae2669 Poul Sander 2026-02-22 17:52 571
{
c8ae2669 Poul Sander 2026-02-22 17:52 572
    if (m_dragging) {
c8ae2669 Poul Sander 2026-02-22 17:52 573
        m_offset = m_offsetAtDragStart + (event->pos() - m_dragStart);
c8ae2669 Poul Sander 2026-02-22 17:52 574
        update();
c8ae2669 Poul Sander 2026-02-22 17:52 575
        event->accept();
cdae9502 Poul Sander 2026-06-09 20:04 576
    } else {
cdae9502 Poul Sander 2026-06-09 20:04 577
        if (m_showExifOverlay && m_osmLinkRect.contains(event->pos())) {
cdae9502 Poul Sander 2026-06-09 20:04 578
            setCursor(Qt::PointingHandCursor);
cdae9502 Poul Sander 2026-06-09 20:04 579
        } else {
cdae9502 Poul Sander 2026-06-09 20:04 580
            setCursor(Qt::ArrowCursor);
cdae9502 Poul Sander 2026-06-09 20:04 581
        }
c8ae2669 Poul Sander 2026-02-22 17:52 582
    }
c8ae2669 Poul Sander 2026-02-22 17:52 583
}
c8ae2669 Poul Sander 2026-02-22 17:52 584
c8ae2669 Poul Sander 2026-02-22 17:52 585
void ImageViewWidget::mouseReleaseEvent(QMouseEvent *event)
c8ae2669 Poul Sander 2026-02-22 17:52 586
{
c8ae2669 Poul Sander 2026-02-22 17:52 587
    if (event->button() == Qt::LeftButton && m_dragging) {
c8ae2669 Poul Sander 2026-02-22 17:52 588
        m_dragging = false;
c8ae2669 Poul Sander 2026-02-22 17:52 589
        setCursor(Qt::ArrowCursor);
c8ae2669 Poul Sander 2026-02-22 17:52 590
        event->accept();
c8ae2669 Poul Sander 2026-02-22 17:52 591
    }
c8ae2669 Poul Sander 2026-02-22 17:52 592
}
c8ae2669 Poul Sander 2026-02-22 17:52 593
1035a476 Poul Sander 2026-02-23 19:30 594
void ImageViewWidget::mouseDoubleClickEvent(QMouseEvent *event)
1035a476 Poul Sander 2026-02-23 19:30 595
{
1035a476 Poul Sander 2026-02-23 19:30 596
    if (event->button() == Qt::LeftButton) {
1035a476 Poul Sander 2026-02-23 19:30 597
        emit closeRequested();
1035a476 Poul Sander 2026-02-23 19:30 598
        event->accept();
1035a476 Poul Sander 2026-02-23 19:30 599
    }
1035a476 Poul Sander 2026-02-23 19:30 600
}
1035a476 Poul Sander 2026-02-23 19:30 601
c8ae2669 Poul Sander 2026-02-22 17:52 602
void ImageViewWidget::clampOffset(int imgW, int imgH)
c8ae2669 Poul Sander 2026-02-22 17:52 603
{
c8ae2669 Poul Sander 2026-02-22 17:52 604
    // Only allow panning when the drawn image exceeds the widget
c8ae2669 Poul Sander 2026-02-22 17:52 605
    int overflowX = imgW - width();
c8ae2669 Poul Sander 2026-02-22 17:52 606
    int overflowY = imgH - height();
c8ae2669 Poul Sander 2026-02-22 17:52 607
c8ae2669 Poul Sander 2026-02-22 17:52 608
    if (overflowX <= 0)
c8ae2669 Poul Sander 2026-02-22 17:52 609
        m_offset.rx() = 0;
c8ae2669 Poul Sander 2026-02-22 17:52 610
    else {
c8ae2669 Poul Sander 2026-02-22 17:52 611
        int limit = overflowX / 2;
c8ae2669 Poul Sander 2026-02-22 17:52 612
        m_offset.rx() = std::clamp(m_offset.x(), -limit, limit);
c8ae2669 Poul Sander 2026-02-22 17:52 613
    }
c8ae2669 Poul Sander 2026-02-22 17:52 614
c8ae2669 Poul Sander 2026-02-22 17:52 615
    if (overflowY <= 0)
c8ae2669 Poul Sander 2026-02-22 17:52 616
        m_offset.ry() = 0;
c8ae2669 Poul Sander 2026-02-22 17:52 617
    else {
c8ae2669 Poul Sander 2026-02-22 17:52 618
        int limit = overflowY / 2;
c8ae2669 Poul Sander 2026-02-22 17:52 619
        m_offset.ry() = std::clamp(m_offset.y(), -limit, limit);
c8ae2669 Poul Sander 2026-02-22 17:52 620
    }
c8ae2669 Poul Sander 2026-02-22 17:52 621
}
1970-01-01 00:00 622