git repos / SagoImageBrowser

imagemodel.cpp

browsing at commit = 09130ab9bc9b07df0d254786902c0d94fdc1b1d2

raw · blame · history

#include "imagemodel.h"
#include "thumbnailworker.h"

#include <QDir>
#include <QImageReader>
#include <QFileInfo>
#include <QFileIconProvider>

ImageModel::ImageModel(QObject *parent)
    : QAbstractListModel(parent)
{
    m_threadPool.setMaxThreadCount(QThread::idealThreadCount());

    m_placeholder = QPixmap(128, 128);
    m_placeholder.fill(Qt::lightGray);

    QFileIconProvider iconProvider;
    m_folderIcon = iconProvider.icon(QFileIconProvider::Folder);
}

ImageModel::~ImageModel()
{
    m_cancelFlag = true;
    m_threadPool.waitForDone();
}

void ImageModel::setDirectory(const QString &path)
{
    m_cancelFlag = true;
    m_threadPool.waitForDone();
    m_cancelFlag = false;

    beginResetModel();
    m_items.clear();
    m_pendingRows.clear();
    m_currentDir = path;

    QDir dir(path);

    // Add subdirectories first
    dir.setFilter(QDir::AllDirs | QDir::NoDot);
    dir.setSorting(QDir::Name);
    const auto dirs = dir.entryInfoList();
    QPixmap folderPixmap = m_folderIcon.pixmap(128, 128);
    for (const QFileInfo &d : dirs)
    {
        QString name = d.fileName();  // preserves ".." for the parent entry
        m_items.append({d.absoluteFilePath(), name, folderPixmap, true, true});
    }

    // Then add image files
    QStringList filters;

    const auto formats = QImageReader::supportedImageFormats();
    for (const QByteArray &fmt : formats)
        filters << "*." + fmt;

    dir.setNameFilters(filters);
    dir.setFilter(QDir::Files);
    dir.setSorting(QDir::Name);

    const auto files = dir.entryInfoList();

    for (const QFileInfo &file : files)
    {
        m_items.append({file.absoluteFilePath(), file.fileName(), m_placeholder, false, false});
    }

    endResetModel();
}

void ImageModel::requestThumbnails(int firstRow, int lastRow)
{
    if (m_items.isEmpty())
        return;

    firstRow = std::max(0, firstRow);
    lastRow = std::min(lastRow, static_cast<int>(m_items.size()) - 1);

    for (int row = firstRow; row <= lastRow; ++row)
    {
        if (!m_items[row].loaded && !m_items[row].isFolder)
            queueRow(row);
    }

    // Optional: preload nearby rows
    int buffer = 20;
    for (int row = firstRow - buffer; row < firstRow; ++row)
        if (row >= 0 && !m_items[row].loaded && !m_items[row].isFolder)
            queueRow(row);

    for (int row = lastRow + 1; row <= lastRow + buffer; ++row)
        if (row < m_items.size() && !m_items[row].loaded && !m_items[row].isFolder)
            queueRow(row);
}

void ImageModel::queueRow(int row)
{
    if (m_pendingRows.contains(row))
        return;

    m_pendingRows.insert(row);

    auto *worker = new ThumbnailWorker(
        m_items[row].path,
        row,
        &m_cancelFlag);

    connect(worker, &ThumbnailWorker::finished,
            this, &ImageModel::thumbnailReady,
            Qt::QueuedConnection);

    m_threadPool.start(worker);
}

QString ImageModel::filePath(const QModelIndex &index) const
{
    if (!index.isValid())
        return {};

    return m_items[index.row()].path;
}

bool ImageModel::isFolder(const QModelIndex &index) const
{
    if (!index.isValid() || index.row() >= m_items.size())
        return false;
    return m_items[index.row()].isFolder;
}

QString ImageModel::currentDirectory() const
{
    return m_currentDir;
}

int ImageModel::rowCount(const QModelIndex &) const
{
    return m_items.size();
}

QVariant ImageModel::data(const QModelIndex &index, int role) const
{
    if (!index.isValid())
        return {};

    const Item &item = m_items[index.row()];

    if (role == Qt::DecorationRole)
        return item.thumbnail;

    if (role == Qt::DisplayRole)
        return item.displayName;

    return {};
}

void ImageModel::thumbnailReady(int row, const QImage &image)
{
    if (m_cancelFlag)
        return;

    if (row < 0 || row >= m_items.size())
        return;

    m_items[row].thumbnail = QPixmap::fromImage(image);
    m_items[row].loaded = true;
    m_pendingRows.remove(row);

    QModelIndex idx = index(row);
    emit dataChanged(idx, idx, {Qt::DecorationRole});
}