git repos / SagoImageBrowser

blame: src/imageviewwidget.cpp

normal view · raw

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