git repos / SagoImageBrowser

blame: src/imagemodel.cpp

normal view · raw

032aa1e1 Poul Sander 2026-02-19 18:55 1
#include "imagemodel.h"
cec3b5da Poul Sander 2026-02-19 19:17 2
#include "thumbnailworker.h"
032aa1e1 Poul Sander 2026-02-19 18:55 3
032aa1e1 Poul Sander 2026-02-19 18:55 4
#include <QDir>
032aa1e1 Poul Sander 2026-02-19 18:55 5
#include <QImageReader>
032aa1e1 Poul Sander 2026-02-19 18:55 6
#include <QFileInfo>
09130ab9 Poul Sander 2026-02-22 22:19 7
#include <QFileIconProvider>
032aa1e1 Poul Sander 2026-02-19 18:55 8
032aa1e1 Poul Sander 2026-02-19 18:55 9
ImageModel::ImageModel(QObject *parent)
032aa1e1 Poul Sander 2026-02-19 18:55 10
    : QAbstractListModel(parent)
032aa1e1 Poul Sander 2026-02-19 18:55 11
{
459bad0f Poul Sander 2026-05-17 14:11 12
    // Limit 1 thread for thumbnail loading to avoid excessive disk I/O on harddrives. SSDs are so fast the extra threads don't help much anyway.
459bad0f Poul Sander 2026-05-17 14:11 13
    m_threadPool.setMaxThreadCount(1);  //QThread::idealThreadCount()
cec3b5da Poul Sander 2026-02-19 19:17 14
cec3b5da Poul Sander 2026-02-19 19:17 15
    m_placeholder = QPixmap(128, 128);
cec3b5da Poul Sander 2026-02-19 19:17 16
    m_placeholder.fill(Qt::lightGray);
09130ab9 Poul Sander 2026-02-22 22:19 17
09130ab9 Poul Sander 2026-02-22 22:19 18
    QFileIconProvider iconProvider;
09130ab9 Poul Sander 2026-02-22 22:19 19
    m_folderIcon = iconProvider.icon(QFileIconProvider::Folder);
cec3b5da Poul Sander 2026-02-19 19:17 20
}
cec3b5da Poul Sander 2026-02-19 19:17 21
cec3b5da Poul Sander 2026-02-19 19:17 22
ImageModel::~ImageModel()
cec3b5da Poul Sander 2026-02-19 19:17 23
{
6fcd6c90 Poul Sander 2026-02-19 21:12 24
    m_cancelFlag = true;
cec3b5da Poul Sander 2026-02-19 19:17 25
    m_threadPool.waitForDone();
032aa1e1 Poul Sander 2026-02-19 18:55 26
}
032aa1e1 Poul Sander 2026-02-19 18:55 27
459bad0f Poul Sander 2026-05-17 14:11 28
void ImageModel::setCacheMaxSize(int n)
459bad0f Poul Sander 2026-05-17 14:11 29
{
459bad0f Poul Sander 2026-05-17 14:11 30
    m_cacheMaxSize = std::max(0, n);
459bad0f Poul Sander 2026-05-17 14:11 31
    while (m_cacheOrder.size() > m_cacheMaxSize)
459bad0f Poul Sander 2026-05-17 14:11 32
        m_folderThumbnailCache.remove(m_cacheOrder.takeFirst());
459bad0f Poul Sander 2026-05-17 14:11 33
}
459bad0f Poul Sander 2026-05-17 14:11 34
ac476ca8 Poul Sander 2026-05-18 16:47 35
void ImageModel::setThumbnailSize(ThumbnailCache::Size size)
ac476ca8 Poul Sander 2026-05-18 16:47 36
{
ac476ca8 Poul Sander 2026-05-18 16:47 37
    if (size == m_thumbnailSize)
ac476ca8 Poul Sander 2026-05-18 16:47 38
        return;
ac476ca8 Poul Sander 2026-05-18 16:47 39
ac476ca8 Poul Sander 2026-05-18 16:47 40
    m_thumbnailSize = size;
ac476ca8 Poul Sander 2026-05-18 16:47 41
ac476ca8 Poul Sander 2026-05-18 16:47 42
    // Cancel any in-flight workers (they were started with the previous size).
ac476ca8 Poul Sander 2026-05-18 16:47 43
    m_cancelFlag = true;
ac476ca8 Poul Sander 2026-05-18 16:47 44
    m_threadPool.waitForDone();
ac476ca8 Poul Sander 2026-05-18 16:47 45
    m_cancelFlag = false;
ac476ca8 Poul Sander 2026-05-18 16:47 46
ac476ca8 Poul Sander 2026-05-18 16:47 47
    m_pendingRows.clear();
ac476ca8 Poul Sander 2026-05-18 16:47 48
ac476ca8 Poul Sander 2026-05-18 16:47 49
    // The folder cache is keyed by directory and size-agnostic; drop it so the
ac476ca8 Poul Sander 2026-05-18 16:47 50
    // user actually sees the new size.
ac476ca8 Poul Sander 2026-05-18 16:47 51
    m_folderThumbnailCache.clear();
ac476ca8 Poul Sander 2026-05-18 16:47 52
    m_cacheOrder.clear();
ac476ca8 Poul Sander 2026-05-18 16:47 53
ac476ca8 Poul Sander 2026-05-18 16:47 54
    // Mark existing image entries unloaded so they get re-fetched at the new size.
ac476ca8 Poul Sander 2026-05-18 16:47 55
    if (!m_items.isEmpty())
ac476ca8 Poul Sander 2026-05-18 16:47 56
    {
ac476ca8 Poul Sander 2026-05-18 16:47 57
        for (int row = 0; row < m_items.size(); ++row)
ac476ca8 Poul Sander 2026-05-18 16:47 58
        {
ac476ca8 Poul Sander 2026-05-18 16:47 59
            Item &item = m_items[row];
ac476ca8 Poul Sander 2026-05-18 16:47 60
            if (item.isFolder)
ac476ca8 Poul Sander 2026-05-18 16:47 61
                continue;
ac476ca8 Poul Sander 2026-05-18 16:47 62
            item.thumbnail = m_placeholder;
ac476ca8 Poul Sander 2026-05-18 16:47 63
            item.loaded = false;
ac476ca8 Poul Sander 2026-05-18 16:47 64
        }
ac476ca8 Poul Sander 2026-05-18 16:47 65
        emit dataChanged(index(0), index(m_items.size() - 1),
ac476ca8 Poul Sander 2026-05-18 16:47 66
                         {Qt::DecorationRole});
ac476ca8 Poul Sander 2026-05-18 16:47 67
    }
ac476ca8 Poul Sander 2026-05-18 16:47 68
}
ac476ca8 Poul Sander 2026-05-18 16:47 69
032aa1e1 Poul Sander 2026-02-19 18:55 70
void ImageModel::setDirectory(const QString &path)
032aa1e1 Poul Sander 2026-02-19 18:55 71
{
6fcd6c90 Poul Sander 2026-02-19 21:12 72
    m_cancelFlag = true;
6fcd6c90 Poul Sander 2026-02-19 21:12 73
    m_threadPool.waitForDone();
6fcd6c90 Poul Sander 2026-02-19 21:12 74
    m_cancelFlag = false;
6fcd6c90 Poul Sander 2026-02-19 21:12 75
459bad0f Poul Sander 2026-05-17 14:11 76
    // Save current folder's thumbnails to cache before discarding them
459bad0f Poul Sander 2026-05-17 14:11 77
    if (!m_currentDir.isEmpty()) {
459bad0f Poul Sander 2026-05-17 14:11 78
        ThumbnailMap map;
459bad0f Poul Sander 2026-05-17 14:11 79
        for (const Item &item : m_items)
459bad0f Poul Sander 2026-05-17 14:11 80
            if (item.loaded && !item.isFolder)
459bad0f Poul Sander 2026-05-17 14:11 81
                map[item.path] = item.thumbnail;
459bad0f Poul Sander 2026-05-17 14:11 82
        if (!map.isEmpty()) {
459bad0f Poul Sander 2026-05-17 14:11 83
            if (!m_cacheOrder.contains(m_currentDir)) {
459bad0f Poul Sander 2026-05-17 14:11 84
                m_cacheOrder.append(m_currentDir);
459bad0f Poul Sander 2026-05-17 14:11 85
                if (m_cacheOrder.size() > m_cacheMaxSize)
459bad0f Poul Sander 2026-05-17 14:11 86
                    m_folderThumbnailCache.remove(m_cacheOrder.takeFirst());
459bad0f Poul Sander 2026-05-17 14:11 87
            }
459bad0f Poul Sander 2026-05-17 14:11 88
            m_folderThumbnailCache[m_currentDir] = std::move(map);
459bad0f Poul Sander 2026-05-17 14:11 89
        }
459bad0f Poul Sander 2026-05-17 14:11 90
    }
459bad0f Poul Sander 2026-05-17 14:11 91
032aa1e1 Poul Sander 2026-02-19 18:55 92
    beginResetModel();
032aa1e1 Poul Sander 2026-02-19 18:55 93
    m_items.clear();
6fcd6c90 Poul Sander 2026-02-19 21:12 94
    m_pendingRows.clear();
09130ab9 Poul Sander 2026-02-22 22:19 95
    m_currentDir = path;
032aa1e1 Poul Sander 2026-02-19 18:55 96
032aa1e1 Poul Sander 2026-02-19 18:55 97
    QDir dir(path);
09130ab9 Poul Sander 2026-02-22 22:19 98
09130ab9 Poul Sander 2026-02-22 22:19 99
    // Add subdirectories first
09130ab9 Poul Sander 2026-02-22 22:19 100
    dir.setFilter(QDir::AllDirs | QDir::NoDot);
09130ab9 Poul Sander 2026-02-22 22:19 101
    dir.setSorting(QDir::Name);
09130ab9 Poul Sander 2026-02-22 22:19 102
    const auto dirs = dir.entryInfoList();
09130ab9 Poul Sander 2026-02-22 22:19 103
    QPixmap folderPixmap = m_folderIcon.pixmap(128, 128);
09130ab9 Poul Sander 2026-02-22 22:19 104
    for (const QFileInfo &d : dirs)
09130ab9 Poul Sander 2026-02-22 22:19 105
    {
09130ab9 Poul Sander 2026-02-22 22:19 106
        QString name = d.fileName();  // preserves ".." for the parent entry
09130ab9 Poul Sander 2026-02-22 22:19 107
        m_items.append({d.absoluteFilePath(), name, folderPixmap, true, true});
09130ab9 Poul Sander 2026-02-22 22:19 108
    }
09130ab9 Poul Sander 2026-02-22 22:19 109
09130ab9 Poul Sander 2026-02-22 22:19 110
    // Then add image files
032aa1e1 Poul Sander 2026-02-19 18:55 111
    QStringList filters;
032aa1e1 Poul Sander 2026-02-19 18:55 112
032aa1e1 Poul Sander 2026-02-19 18:55 113
    const auto formats = QImageReader::supportedImageFormats();
032aa1e1 Poul Sander 2026-02-19 18:55 114
    for (const QByteArray &fmt : formats)
032aa1e1 Poul Sander 2026-02-19 18:55 115
        filters << "*." + fmt;
032aa1e1 Poul Sander 2026-02-19 18:55 116
032aa1e1 Poul Sander 2026-02-19 18:55 117
    dir.setNameFilters(filters);
032aa1e1 Poul Sander 2026-02-19 18:55 118
    dir.setFilter(QDir::Files);
09130ab9 Poul Sander 2026-02-22 22:19 119
    dir.setSorting(QDir::Name);
032aa1e1 Poul Sander 2026-02-19 18:55 120
cec3b5da Poul Sander 2026-02-19 19:17 121
    const auto files = dir.entryInfoList();
cec3b5da Poul Sander 2026-02-19 19:17 122
459bad0f Poul Sander 2026-05-17 14:11 123
    // Restore cached thumbnails for this folder if available
459bad0f Poul Sander 2026-05-17 14:11 124
    const ThumbnailMap *cached = m_folderThumbnailCache.contains(path)
459bad0f Poul Sander 2026-05-17 14:11 125
        ? &m_folderThumbnailCache[path]
459bad0f Poul Sander 2026-05-17 14:11 126
        : nullptr;
459bad0f Poul Sander 2026-05-17 14:11 127
cec3b5da Poul Sander 2026-02-19 19:17 128
    for (const QFileInfo &file : files)
032aa1e1 Poul Sander 2026-02-19 18:55 129
    {
459bad0f Poul Sander 2026-05-17 14:11 130
        QPixmap thumb = m_placeholder;
459bad0f Poul Sander 2026-05-17 14:11 131
        bool loaded = false;
459bad0f Poul Sander 2026-05-17 14:11 132
        if (cached) {
459bad0f Poul Sander 2026-05-17 14:11 133
            auto it = cached->find(file.absoluteFilePath());
459bad0f Poul Sander 2026-05-17 14:11 134
            if (it != cached->end()) {
459bad0f Poul Sander 2026-05-17 14:11 135
                thumb = it.value();
459bad0f Poul Sander 2026-05-17 14:11 136
                loaded = true;
459bad0f Poul Sander 2026-05-17 14:11 137
            }
459bad0f Poul Sander 2026-05-17 14:11 138
        }
459bad0f Poul Sander 2026-05-17 14:11 139
        m_items.append({file.absoluteFilePath(), file.fileName(), thumb, loaded, false});
6fcd6c90 Poul Sander 2026-02-19 21:12 140
    }
cec3b5da Poul Sander 2026-02-19 19:17 141
6fcd6c90 Poul Sander 2026-02-19 21:12 142
    endResetModel();
6fcd6c90 Poul Sander 2026-02-19 21:12 143
}
032aa1e1 Poul Sander 2026-02-19 18:55 144
6fcd6c90 Poul Sander 2026-02-19 21:12 145
void ImageModel::requestThumbnails(int firstRow, int lastRow)
6fcd6c90 Poul Sander 2026-02-19 21:12 146
{
6fcd6c90 Poul Sander 2026-02-19 21:12 147
    if (m_items.isEmpty())
6fcd6c90 Poul Sander 2026-02-19 21:12 148
        return;
032aa1e1 Poul Sander 2026-02-19 18:55 149
6fcd6c90 Poul Sander 2026-02-19 21:12 150
    firstRow = std::max(0, firstRow);
6fcd6c90 Poul Sander 2026-02-19 21:12 151
    lastRow = std::min(lastRow, static_cast<int>(m_items.size()) - 1);
cec3b5da Poul Sander 2026-02-19 19:17 152
6fcd6c90 Poul Sander 2026-02-19 21:12 153
    for (int row = firstRow; row <= lastRow; ++row)
6fcd6c90 Poul Sander 2026-02-19 21:12 154
    {
09130ab9 Poul Sander 2026-02-22 22:19 155
        if (!m_items[row].loaded && !m_items[row].isFolder)
6fcd6c90 Poul Sander 2026-02-19 21:12 156
            queueRow(row);
032aa1e1 Poul Sander 2026-02-19 18:55 157
    }
032aa1e1 Poul Sander 2026-02-19 18:55 158
6fcd6c90 Poul Sander 2026-02-19 21:12 159
    // Optional: preload nearby rows
6fcd6c90 Poul Sander 2026-02-19 21:12 160
    int buffer = 20;
6fcd6c90 Poul Sander 2026-02-19 21:12 161
    for (int row = firstRow - buffer; row < firstRow; ++row)
09130ab9 Poul Sander 2026-02-22 22:19 162
        if (row >= 0 && !m_items[row].loaded && !m_items[row].isFolder)
6fcd6c90 Poul Sander 2026-02-19 21:12 163
            queueRow(row);
6fcd6c90 Poul Sander 2026-02-19 21:12 164
6fcd6c90 Poul Sander 2026-02-19 21:12 165
    for (int row = lastRow + 1; row <= lastRow + buffer; ++row)
09130ab9 Poul Sander 2026-02-22 22:19 166
        if (row < m_items.size() && !m_items[row].loaded && !m_items[row].isFolder)
6fcd6c90 Poul Sander 2026-02-19 21:12 167
            queueRow(row);
6fcd6c90 Poul Sander 2026-02-19 21:12 168
}
6fcd6c90 Poul Sander 2026-02-19 21:12 169
6fcd6c90 Poul Sander 2026-02-19 21:12 170
void ImageModel::queueRow(int row)
6fcd6c90 Poul Sander 2026-02-19 21:12 171
{
6fcd6c90 Poul Sander 2026-02-19 21:12 172
    if (m_pendingRows.contains(row))
6fcd6c90 Poul Sander 2026-02-19 21:12 173
        return;
6fcd6c90 Poul Sander 2026-02-19 21:12 174
6fcd6c90 Poul Sander 2026-02-19 21:12 175
    m_pendingRows.insert(row);
6fcd6c90 Poul Sander 2026-02-19 21:12 176
6fcd6c90 Poul Sander 2026-02-19 21:12 177
    auto *worker = new ThumbnailWorker(
6fcd6c90 Poul Sander 2026-02-19 21:12 178
        m_items[row].path,
6fcd6c90 Poul Sander 2026-02-19 21:12 179
        row,
ac476ca8 Poul Sander 2026-05-18 16:47 180
        m_thumbnailSize,
6fcd6c90 Poul Sander 2026-02-19 21:12 181
        &m_cancelFlag);
6fcd6c90 Poul Sander 2026-02-19 21:12 182
6fcd6c90 Poul Sander 2026-02-19 21:12 183
    connect(worker, &ThumbnailWorker::finished,
6fcd6c90 Poul Sander 2026-02-19 21:12 184
            this, &ImageModel::thumbnailReady,
6fcd6c90 Poul Sander 2026-02-19 21:12 185
            Qt::QueuedConnection);
6fcd6c90 Poul Sander 2026-02-19 21:12 186
6fcd6c90 Poul Sander 2026-02-19 21:12 187
    m_threadPool.start(worker);
032aa1e1 Poul Sander 2026-02-19 18:55 188
}
032aa1e1 Poul Sander 2026-02-19 18:55 189
032aa1e1 Poul Sander 2026-02-19 18:55 190
QString ImageModel::filePath(const QModelIndex &index) const
032aa1e1 Poul Sander 2026-02-19 18:55 191
{
032aa1e1 Poul Sander 2026-02-19 18:55 192
    if (!index.isValid())
032aa1e1 Poul Sander 2026-02-19 18:55 193
        return {};
032aa1e1 Poul Sander 2026-02-19 18:55 194
032aa1e1 Poul Sander 2026-02-19 18:55 195
    return m_items[index.row()].path;
032aa1e1 Poul Sander 2026-02-19 18:55 196
}
032aa1e1 Poul Sander 2026-02-19 18:55 197
09130ab9 Poul Sander 2026-02-22 22:19 198
bool ImageModel::isFolder(const QModelIndex &index) const
09130ab9 Poul Sander 2026-02-22 22:19 199
{
09130ab9 Poul Sander 2026-02-22 22:19 200
    if (!index.isValid() || index.row() >= m_items.size())
09130ab9 Poul Sander 2026-02-22 22:19 201
        return false;
09130ab9 Poul Sander 2026-02-22 22:19 202
    return m_items[index.row()].isFolder;
09130ab9 Poul Sander 2026-02-22 22:19 203
}
09130ab9 Poul Sander 2026-02-22 22:19 204
09130ab9 Poul Sander 2026-02-22 22:19 205
QString ImageModel::currentDirectory() const
09130ab9 Poul Sander 2026-02-22 22:19 206
{
09130ab9 Poul Sander 2026-02-22 22:19 207
    return m_currentDir;
09130ab9 Poul Sander 2026-02-22 22:19 208
}
09130ab9 Poul Sander 2026-02-22 22:19 209
032aa1e1 Poul Sander 2026-02-19 18:55 210
int ImageModel::rowCount(const QModelIndex &) const
032aa1e1 Poul Sander 2026-02-19 18:55 211
{
032aa1e1 Poul Sander 2026-02-19 18:55 212
    return m_items.size();
032aa1e1 Poul Sander 2026-02-19 18:55 213
}
032aa1e1 Poul Sander 2026-02-19 18:55 214
032aa1e1 Poul Sander 2026-02-19 18:55 215
QVariant ImageModel::data(const QModelIndex &index, int role) const
032aa1e1 Poul Sander 2026-02-19 18:55 216
{
032aa1e1 Poul Sander 2026-02-19 18:55 217
    if (!index.isValid())
032aa1e1 Poul Sander 2026-02-19 18:55 218
        return {};
032aa1e1 Poul Sander 2026-02-19 18:55 219
032aa1e1 Poul Sander 2026-02-19 18:55 220
    const Item &item = m_items[index.row()];
032aa1e1 Poul Sander 2026-02-19 18:55 221
032aa1e1 Poul Sander 2026-02-19 18:55 222
    if (role == Qt::DecorationRole)
032aa1e1 Poul Sander 2026-02-19 18:55 223
        return item.thumbnail;
032aa1e1 Poul Sander 2026-02-19 18:55 224
032aa1e1 Poul Sander 2026-02-19 18:55 225
    if (role == Qt::DisplayRole)
09130ab9 Poul Sander 2026-02-22 22:19 226
        return item.displayName;
032aa1e1 Poul Sander 2026-02-19 18:55 227
032aa1e1 Poul Sander 2026-02-19 18:55 228
    return {};
032aa1e1 Poul Sander 2026-02-19 18:55 229
}
cec3b5da Poul Sander 2026-02-19 19:17 230
6fcd6c90 Poul Sander 2026-02-19 21:12 231
void ImageModel::thumbnailReady(int row, const QImage &image)
cec3b5da Poul Sander 2026-02-19 19:17 232
{
6fcd6c90 Poul Sander 2026-02-19 21:12 233
    if (m_cancelFlag)
6fcd6c90 Poul Sander 2026-02-19 21:12 234
        return;
6fcd6c90 Poul Sander 2026-02-19 21:12 235
cec3b5da Poul Sander 2026-02-19 19:17 236
    if (row < 0 || row >= m_items.size())
cec3b5da Poul Sander 2026-02-19 19:17 237
        return;
cec3b5da Poul Sander 2026-02-19 19:17 238
6fcd6c90 Poul Sander 2026-02-19 21:12 239
    m_items[row].thumbnail = QPixmap::fromImage(image);
cec3b5da Poul Sander 2026-02-19 19:17 240
    m_items[row].loaded = true;
6fcd6c90 Poul Sander 2026-02-19 21:12 241
    m_pendingRows.remove(row);
cec3b5da Poul Sander 2026-02-19 19:17 242
cec3b5da Poul Sander 2026-02-19 19:17 243
    QModelIndex idx = index(row);
cec3b5da Poul Sander 2026-02-19 19:17 244
    emit dataChanged(idx, idx, {Qt::DecorationRole});
cec3b5da Poul Sander 2026-02-19 19:17 245
}
1970-01-01 00:00 246