Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions src/adaptors/UBThumbnailAdaptor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,27 @@ void UBThumbnailAdaptor::generateMissingThumbnails(std::shared_ptr<UBDocumentPro
}
}

QPixmap UBThumbnailAdaptor::generateMissingThumbnail(std::shared_ptr<UBDocumentProxy> proxy, int pageIndex)
{
QString thumbFileName = proxy->persistencePath() + UBFileSystemUtils::digitFileFormat("/page%1.thumbnail.jpg", pageIndex);

QFile thumbFile(thumbFileName);

if (!thumbFile.exists())
{
std::shared_ptr<UBGraphicsScene> scene = UBSvgSubsetAdaptor::loadScene(proxy, pageIndex);

if (scene)
{
persistScene(proxy, scene, pageIndex);
}
}

QPixmap pix;
pix.load(thumbFileName);
return pix;
}

QPixmap UBThumbnailAdaptor::get(std::shared_ptr<UBDocumentProxy> proxy, int pageIndex)
{
QString fileName = proxy->persistencePath() + UBFileSystemUtils::digitFileFormat("/page%1.thumbnail.jpg", pageIndex);
Expand Down
1 change: 1 addition & 0 deletions src/adaptors/UBThumbnailAdaptor.h
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ class UBThumbnailAdaptor //static class

static QPixmap get(std::shared_ptr<UBDocumentProxy> proxy, int index);
static void load(std::shared_ptr<UBDocumentProxy> proxy, QList<std::shared_ptr<QPixmap>>& list);
static QPixmap generateMissingThumbnail(std::shared_ptr<UBDocumentProxy> proxy, int pageIndex);

private:
static void generateMissingThumbnails(std::shared_ptr<UBDocumentProxy> proxy);
Expand Down
2 changes: 2 additions & 0 deletions src/frameworks/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
target_sources(openboard PRIVATE
UBBackgroundLoader.cpp
UBBackgroundLoader.h
UBBase32.cpp
UBBase32.h
UBCoreGraphicsScene.cpp
Expand Down
121 changes: 121 additions & 0 deletions src/frameworks/UBBackgroundLoader.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
/*
* Copyright (C) 2015-2025 Département de l'Instruction Publique (DIP-SEM)
*
* This file is part of OpenBoard.
*
* OpenBoard is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3 of the License,
* with a specific linking exception for the OpenSSL project's
* "OpenSSL" library (or with modified versions of it that use the
* same license as the "OpenSSL" library).
*
* OpenBoard is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with OpenBoard. If not, see <http://www.gnu.org/licenses/>.
*/


#include "UBBackgroundLoader.h"

#include <QFile>

#include "core/UBApplication.h"

UBBackgroundLoader::UBBackgroundLoader(QObject* parent)
: QThread{parent}
{
}

UBBackgroundLoader::UBBackgroundLoader(QList<std::pair<int, QString>> paths, QObject* parent)
: QThread{parent}
{
mPaths.insert(mPaths.cend(), paths.constBegin(), paths.constEnd());
mPathCounter.release(paths.size());
}

UBBackgroundLoader::~UBBackgroundLoader()
{
abort();
wait();
}

bool UBBackgroundLoader::isResultAvailable()
{
QMutexLocker lock{&mMutex};
return !mResults.empty();
}

std::pair<int, QByteArray> UBBackgroundLoader::takeResult()
{
QMutexLocker lock{&mMutex};

if (mResults.empty())
{
return {};
}

const auto result = mResults.front();
mResults.pop_front();
return result;
}

void UBBackgroundLoader::start()
{
mRunning = true;
QThread::start();
}

void UBBackgroundLoader::addPaths(QList<std::pair<int, QString>> paths)
{
QMutexLocker lock{&mMutex};
mPaths.insert(mPaths.cend(), paths.constBegin(), paths.constEnd());
mPathCounter.release(paths.size());
}

void UBBackgroundLoader::abort()
{
mRunning = false;
mPathCounter.release();
}

void UBBackgroundLoader::run()
{
while (mRunning && !UBApplication::isClosing)
{
mPathCounter.acquire();

if (mRunning && !UBApplication::isClosing)
{
std::pair<int, QString> path;

{
QMutexLocker lock{&mMutex};
path = mPaths.front();
mPaths.pop_front();
}

QFile file{path.second};
QByteArray result;

if (file.open(QFile::ReadOnly))
{
result = file.readAll();
file.close();
}

{
QMutexLocker lock{&mMutex};
mResults.push_back({path.first, result});
}

emit resultAvailable(path.first, result);
}
}

quit();
}
60 changes: 60 additions & 0 deletions src/frameworks/UBBackgroundLoader.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/*
* Copyright (C) 2015-2025 Département de l'Instruction Publique (DIP-SEM)
*
* This file is part of OpenBoard.
*
* OpenBoard is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, version 3 of the License,
* with a specific linking exception for the OpenSSL project's
* "OpenSSL" library (or with modified versions of it that use the
* same license as the "OpenSSL" library).
*
* OpenBoard is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with OpenBoard. If not, see <http://www.gnu.org/licenses/>.
*/


#pragma once

#include <QMutex>
#include <QObject>
#include <QSemaphore>
#include <QThread>
#include <deque>

class UBBackgroundLoader : public QThread
{
Q_OBJECT

public:
explicit UBBackgroundLoader(QObject* parent = nullptr);
UBBackgroundLoader(QList<std::pair<int, QString>> paths, QObject* parent = nullptr);
virtual ~UBBackgroundLoader();

bool isResultAvailable();
std::pair<int, QByteArray> takeResult();

public slots:
void start();
void addPaths(QList<std::pair<int, QString>> paths);
void abort();

signals:
void resultAvailable(int index, QByteArray data);

protected:
void run() override;

private:
std::deque<std::pair<int, QString>> mPaths{};
std::deque<std::pair<int, QByteArray>> mResults{};
QMutex mMutex{};
QSemaphore mPathCounter{};
bool mRunning{false};
};
2 changes: 2 additions & 0 deletions src/frameworks/frameworks.pri
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ HEADERS += src/frameworks/UBGeometryUtils.h \
src/frameworks/UBVersion.h \
src/frameworks/UBCoreGraphicsScene.h \
src/frameworks/UBCryptoUtils.h \
src/frameworks/UBBackgroundLoader.h \
src/frameworks/UBBase32.h

SOURCES += src/frameworks/UBGeometryUtils.cpp \
Expand All @@ -15,6 +16,7 @@ SOURCES += src/frameworks/UBGeometryUtils.cpp \
src/frameworks/UBVersion.cpp \
src/frameworks/UBCoreGraphicsScene.cpp \
src/frameworks/UBCryptoUtils.cpp \
src/frameworks/UBBackgroundLoader.cpp \
src/frameworks/UBBase32.cpp


Expand Down
76 changes: 69 additions & 7 deletions src/gui/UBThumbnailScene.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
#include "core/UBApplication.h"
#include "document/UBDocument.h"
#include "document/UBDocumentProxy.h"
#include "frameworks/UBBackgroundLoader.h"
#include "gui/UBThumbnail.h"
#include "gui/UBThumbnailArranger.h"
#include "gui/UBThumbnailsView.h"
Expand Down Expand Up @@ -172,7 +173,26 @@ void UBThumbnailScene::createThumbnails(int startIndex)
delete item;
}

// now create all missing thumbnails for document
// create the list of all thumbnail paths
QList<std::pair<int,QString>> paths;

for (int index = startIndex; index < mDocument->proxy()->pageCount(); ++index)
{
paths << std::pair<int,QString>{index, UBThumbnailAdaptor::thumbnailUrl(mDocument->proxy(), index).toLocalFile()};
}

// start background loading of files
if (mLoader)
{
// abort a running loader
mLoader->abort();
delete mLoader;
}

mLoader = new UBBackgroundLoader{paths, this};
mLoader->start();

// now create all missing thumbnails for document as they arrive from the loader
loadNextThumbnail();
}

Expand Down Expand Up @@ -212,6 +232,12 @@ void UBThumbnailScene::insertThumbnail(int pageIndex, std::shared_ptr<UBGraphics
renumberThumbnails(pageIndex);
arrangeThumbnails(pageIndex);
}

if (mLoader)
{
// restart loading remaining thumbnails
createThumbnails(mThumbnailItems.size());
}
}

void UBThumbnailScene::deleteThumbnail(int pageIndex, bool rearrange)
Expand All @@ -230,6 +256,12 @@ void UBThumbnailScene::deleteThumbnail(int pageIndex, bool rearrange)
}
}

if (mLoader)
{
// restart loading remaining thumbnails
createThumbnails(mThumbnailItems.size());
}

if (mThumbnailItems.size() == 1)
{
mThumbnailItems.first()->setDeletable(false);
Expand All @@ -252,6 +284,12 @@ void UBThumbnailScene::moveThumbnail(int fromIndex, int toIndex)
renumberThumbnails(fromIndex, toIndex + 1);
arrangeThumbnails(fromIndex, toIndex + 1);
}

if (mLoader)
{
// restart loading remaining thumbnails
createThumbnails(mThumbnailItems.size());
}
}

void UBThumbnailScene::reloadThumbnail(int pageIndex)
Expand Down Expand Up @@ -293,7 +331,7 @@ UBThumbnailArranger* UBThumbnailScene::currentThumbnailArranger()

void UBThumbnailScene::loadNextThumbnail()
{
// number of thumbnails to load in one pass
// max number of thumbnails to load in one pass
constexpr int bulkSize{10};

if (mThumbnailItems.size() < mDocument->proxy()->pageCount())
Expand All @@ -303,20 +341,40 @@ void UBThumbnailScene::loadNextThumbnail()
return;
}

if (!mLoader->isResultAvailable())
{
// no data available, defer next execution
QTimer::singleShot(50, mLoader, [this]() { loadNextThumbnail(); });
return;
}

const auto firstIndex = mThumbnailItems.size();

for (int i = 0; i < bulkSize; ++i)
{
int nextIndex = mThumbnailItems.size();

if (nextIndex == mDocument->proxy()->pageCount())
if (!mLoader->isResultAvailable())
{
break;
}

// take next result and determine index from current number of thumbnails and
// not from result, because pages may have been added or removed in the meantime
const auto result = mLoader->takeResult();
const auto nextIndex = mThumbnailItems.size();
QPixmap pixmap;

if (result.second.isEmpty())
{
pixmap = UBThumbnailAdaptor::generateMissingThumbnail(mDocument->proxy(), nextIndex);
}
else
{
pixmap.loadFromData(result.second);
}

auto thumbnailItem = new UBThumbnail;

thumbnailItem->setPixmap(UBThumbnailAdaptor::get(mDocument->proxy(), nextIndex));
thumbnailItem->setPixmap(pixmap);
thumbnailItem->setSceneIndex(nextIndex);

mThumbnailItems << thumbnailItem;
Expand All @@ -326,7 +384,7 @@ void UBThumbnailScene::loadNextThumbnail()
arrangeThumbnails(firstIndex);

// load next thumbnails in a deferred task executed on the main thread when it is idle.
QTimer::singleShot(0, this, [this]() { loadNextThumbnail(); });
QTimer::singleShot(1, mLoader, [this]() { loadNextThumbnail(); });
}
else
{
Expand All @@ -335,6 +393,10 @@ void UBThumbnailScene::loadNextThumbnail()
{
mThumbnailItems.first()->setDeletable(false);
}

// delete background loader
delete mLoader;
mLoader = nullptr;
}
}

Expand Down
Loading