#include "imagemodel.h" #include "thumbnailworker.h" #include #include #include #include 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(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}); }