diff --git a/imagemodel.cpp b/imagemodel.cpp index b6fd272..5c58083 100644 --- a/imagemodel.cpp +++ b/imagemodel.cpp @@ -10,20 +10,25 @@ ImageModel::ImageModel(QObject *parent) { m_threadPool.setMaxThreadCount(QThread::idealThreadCount()); - // Simple gray placeholder m_placeholder = QPixmap(128, 128); m_placeholder.fill(Qt::lightGray); } 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(); QDir dir(path); QStringList filters; @@ -37,24 +42,56 @@ void ImageModel::setDirectory(const QString &path) const auto files = dir.entryInfoList(); - int row = 0; for (const QFileInfo &file : files) { m_items.append({file.absoluteFilePath(), m_placeholder, false}); + } - // Create worker - auto *worker = new ThumbnailWorker(file.absoluteFilePath(), row); + endResetModel(); +} - connect(worker, &ThumbnailWorker::finished, - this, &ImageModel::thumbnailReady, - Qt::QueuedConnection); +void ImageModel::requestThumbnails(int firstRow, int lastRow) +{ + if (m_items.isEmpty()) + return; - m_threadPool.start(worker); + firstRow = std::max(0, firstRow); + lastRow = std::min(lastRow, static_cast(m_items.size()) - 1); - row++; + for (int row = firstRow; row <= lastRow; ++row) + { + if (!m_items[row].loaded) + queueRow(row); } - endResetModel(); + // Optional: preload nearby rows + int buffer = 20; + for (int row = firstRow - buffer; row < firstRow; ++row) + if (row >= 0 && !m_items[row].loaded) + queueRow(row); + + for (int row = lastRow + 1; row <= lastRow + buffer; ++row) + if (row < m_items.size() && !m_items[row].loaded) + 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 @@ -86,13 +123,17 @@ QVariant ImageModel::data(const QModelIndex &index, int role) const return {}; } -void ImageModel::thumbnailReady(int row, const QPixmap &pixmap) +void ImageModel::thumbnailReady(int row, const QImage &image) { + if (m_cancelFlag) + return; + if (row < 0 || row >= m_items.size()) return; - m_items[row].thumbnail = pixmap; + 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}); diff --git a/imagemodel.h b/imagemodel.h index ab11e6d..c124ae6 100644 --- a/imagemodel.h +++ b/imagemodel.h @@ -4,6 +4,8 @@ #include #include #include +#include +#include class ThumbnailWorker; @@ -18,10 +20,12 @@ public: void setDirectory(const QString &path); QString filePath(const QModelIndex &index) const; + void requestThumbnails(int firstRow, int lastRow); + int rowCount(const QModelIndex &parent = QModelIndex()) const override; QVariant data(const QModelIndex &index, int role) const override; - void thumbnailReady(int row, const QPixmap &pixmap); + void thumbnailReady(int row, const QImage &image); private: struct Item @@ -31,7 +35,12 @@ private: bool loaded = false; }; + void queueRow(int row); + QVector m_items; QThreadPool m_threadPool; QPixmap m_placeholder; + + QSet m_pendingRows; + std::atomic_bool m_cancelFlag{false}; }; diff --git a/mainwindow.cpp b/mainwindow.cpp index a724b32..fedb644 100644 --- a/mainwindow.cpp +++ b/mainwindow.cpp @@ -9,6 +9,10 @@ #include #include #include +#include +#include +#include +#include MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) @@ -67,24 +71,59 @@ void MainWindow::setupConnections() { connect(m_treeView, &QTreeView::clicked, this, [this](const QModelIndex &index) - { - QString path = m_dirModel->filePath(index); - m_imageModel->setDirectory(path); - }); + { + QString path = m_dirModel->filePath(index); + m_imageModel->setDirectory(path); + // Defer thumbnail load so the view has time to lay out items + QTimer::singleShot(0, this, &MainWindow::loadVisibleThumbnails); + }); connect(m_listView, &QListView::clicked, this, [this](const QModelIndex &index) - { - QString path = m_imageModel->filePath(index); - - QImageReader reader(path); - reader.setAutoTransform(true); - QImage image = reader.read(); - - m_previewLabel->setPixmap( - QPixmap::fromImage(image).scaled( - m_previewLabel->size(), - Qt::KeepAspectRatio, - Qt::SmoothTransformation)); - }); + { + QString path = m_imageModel->filePath(index); + + QImageReader reader(path); + reader.setAutoTransform(true); + QImage image = reader.read(); + + m_previewLabel->setPixmap( + QPixmap::fromImage(image).scaled( + m_previewLabel->size(), + Qt::KeepAspectRatio, + Qt::SmoothTransformation)); + }); + + connect(m_listView->verticalScrollBar(), &QScrollBar::valueChanged, + this, [this]() + { loadVisibleThumbnails(); }); + + // Also catch resize — visible range changes when the viewport is resized + m_listView->viewport()->installEventFilter(this); +} + +bool MainWindow::eventFilter(QObject *obj, QEvent *event) +{ + if (obj == m_listView->viewport() && event->type() == QEvent::Resize) + QTimer::singleShot(0, this, &MainWindow::loadVisibleThumbnails); + + return QMainWindow::eventFilter(obj, event); +} + +void MainWindow::loadVisibleThumbnails() +{ + QModelIndex topLeft = m_listView->indexAt(QPoint(0, 0)); + QModelIndex bottomRight = m_listView->indexAt( + QPoint(m_listView->viewport()->width() - 1, + m_listView->viewport()->height() - 1)); + + int first = topLeft.isValid() ? topLeft.row() : 0; + int last = bottomRight.isValid() ? bottomRight.row() + : m_imageModel->rowCount() - 1; + + if (last < 0) + return; + + std::cout << "loadVisibleThumbnails: " << first << " - " << last << "\n"; + m_imageModel->requestThumbnails(first, last); } diff --git a/mainwindow.h b/mainwindow.h index 43ecfe2..f801f66 100644 --- a/mainwindow.h +++ b/mainwindow.h @@ -18,6 +18,9 @@ public: private: void setupUi(); void setupConnections(); + void loadVisibleThumbnails(); + + bool eventFilter(QObject *obj, QEvent *event) override; QFileSystemModel *m_dirModel; ImageModel *m_imageModel; diff --git a/thumbnailworker.cpp b/thumbnailworker.cpp index f02d710..25152d5 100644 --- a/thumbnailworker.cpp +++ b/thumbnailworker.cpp @@ -1,27 +1,34 @@ #include "thumbnailworker.h" #include -ThumbnailWorker::ThumbnailWorker(const QString &path, int row) - : m_path(path), m_row(row) +ThumbnailWorker::ThumbnailWorker(const QString &path, + int row, + std::atomic_bool *cancelFlag) + : m_path(path), + m_row(row), + m_cancelFlag(cancelFlag) { setAutoDelete(true); } void ThumbnailWorker::run() { + if (*m_cancelFlag) + return; + QImageReader reader(m_path); reader.setAutoTransform(true); QImage image = reader.read(); - QPixmap thumb; + + if (*m_cancelFlag) + return; if (!image.isNull()) { - thumb = QPixmap::fromImage( - image.scaled(128, 128, - Qt::KeepAspectRatio, - Qt::SmoothTransformation)); + QImage thumb = image.scaled(128, 128, + Qt::KeepAspectRatio, + Qt::SmoothTransformation); + emit finished(m_row, thumb); } - - emit finished(m_row, thumb); } diff --git a/thumbnailworker.h b/thumbnailworker.h index 13df897..5cdb7cd 100644 --- a/thumbnailworker.h +++ b/thumbnailworker.h @@ -2,21 +2,25 @@ #include #include -#include +#include +#include class ThumbnailWorker : public QObject, public QRunnable { Q_OBJECT public: - ThumbnailWorker(const QString &path, int row); + ThumbnailWorker(const QString &path, + int row, + std::atomic_bool *cancelFlag); void run() override; signals: - void finished(int row, const QPixmap &pixmap); + void finished(int row, const QImage &image); private: QString m_path; int m_row; + std::atomic_bool *m_cancelFlag; };