Large update. Added Video, Image and Live item views. Added Live search

This commit is contained in:
Digital Artifex
2026-08-05 19:58:24 -04:00
parent 5b2500c841
commit e5627742d2
46 changed files with 3416 additions and 578 deletions
+40 -33
View File
@@ -25,45 +25,52 @@ qt_add_qml_module(
common/logging.h
common/fetchresult.h
paginators/newestpackspaginator.h
SOURCES paginators/featuredpackspaginator.h
SOURCES paginators/featuredimagespaginator.h
SOURCES paginators/wallpaperpaginator.h
SOURCES paginators/packsearchpaginator.h
SOURCES paginators/imagesearchpaginator.h
SOURCES paginators/newestshaderspaginator.h
SOURCES paginators/newestcubemapspaginator.h
SOURCES paginators/featuredshaderspaginator.h
SOURCES paginators/featuredcubemapspaginator.h
SOURCES paginators/searchshaderspaginator.h
SOURCES paginators/searchcubemapspaginator.h
SOURCES paginators/videosearchpaginator.h
SOURCES featuredpacksmodel.h
SOURCES featuredpacksmodel.cpp
SOURCES featuredimagesmodel.h
SOURCES featuredimagesmodel.cpp
SOURCES featuredvideosmodel.h
SOURCES featuredvideosmodel.cpp
SOURCES paginators/featuredvideospaginator.h
SOURCES common/imagecache.h
SOURCES paginators/imagepaginator.h
SOURCES common/videocache.h
SOURCES paginators/videopaginator.h
SOURCES imagesearchmodel.cpp
SOURCES imagesearchmodel.h
SOURCES videosearchmodel.h
SOURCES videosearchmodel.cpp
SOURCES
SOURCES
SOURCES packsearchmodel.cpp
SOURCES packsearchmodel.h
paginators/featuredpackspaginator.h
paginators/featuredimagespaginator.h
paginators/wallpaperpaginator.h
paginators/packsearchpaginator.h
paginators/imagesearchpaginator.h
paginators/newestshaderspaginator.h
paginators/newestcubemapspaginator.h
paginators/featuredshaderspaginator.h
paginators/featuredcubemapspaginator.h
paginators/searchshaderspaginator.h
paginators/searchcubemapspaginator.h
paginators/videosearchpaginator.h
featuredpacksmodel.h
featuredpacksmodel.cpp
featuredimagesmodel.h
featuredimagesmodel.cpp
featuredvideosmodel.h
featuredvideosmodel.cpp
paginators/featuredvideospaginator.h
common/imagecache.h
paginators/imagepaginator.h
common/videocache.h
paginators/videopaginator.h
imagesearchmodel.cpp
imagesearchmodel.h
videosearchmodel.h
videosearchmodel.cpp
packsearchmodel.cpp
packsearchmodel.h
videoitemmodel.h
videoitemmodel.cpp
downloadmanager.h downloadmanager.cpp
common/exceptions.h
common/shadertoymetadata.h
common/shaderpackmetadata.h
)
target_compile_definitions(
KomplexHubPlugin
PUBLIC
KOMPLEX_PLUGIN
KOMPLEX_LOCAL_DEV
)
target_link_libraries(KomplexHubPlugin PRIVATE
Qt${QT_VERSION_MAJOR}::Core
Qt${QT_VERSION_MAJOR}::Gui
)
if(ENABLE_ASAN)
+12
View File
@@ -50,3 +50,15 @@ auto CoreServices::sessionToken() -> QByteArray
{}
);
}
auto CoreServices::screenSize() -> QSize
{
QScreen *screen = QGuiApplication::primaryScreen();
if(screen == nullptr)
{
return QSize(0,0);
}
return screen->size();
}
+3
View File
@@ -24,6 +24,7 @@
#include <QNetworkReply>
#include <QSharedPointer>
#include <QWeakPointer>
#include <QScreen>
#include <QMutex>
#include <QMutexLocker>
@@ -45,6 +46,8 @@ public:
*/
static auto sessionToken() -> QByteArray;
static auto screenSize() -> QSize;
private:
CoreServices() = default;
+28
View File
@@ -0,0 +1,28 @@
#ifndef EXCEPTIONS_H
#define EXCEPTIONS_H
#include <QString>
#include <exception>
#include "komplex_global.h"
struct KOMPLEX_EXPORT Exception : std::exception
{
explicit Exception(const QString &message, quint16 errorCode = 0)
: std::exception(), message(message), errorCode(errorCode){}
const QString message;
const quint16 errorCode;
};
struct NetworkException : Exception
{
NetworkException(const QString &message, qsizetype errorCode = 0) : Exception(message, errorCode) {}
};
struct FileException : Exception
{
FileException(const QString &message, qsizetype errorCode = 0) : Exception(message, errorCode) {}
};
struct SqlException : Exception
{
SqlException(const QString &message, qsizetype errorCode = 0) : Exception(message, errorCode) {}
};
#endif // EXCEPTIONS_H
+2 -1
View File
@@ -31,11 +31,12 @@
#define SLIDING_CACHE_PREFETCH_THRESHOLD 20
#define NETWORK_TIMEOUT 10000
#define NETWORK_RETRY_LIMIT 3
#ifdef KOMPLEX_LOCAL_DEV
#define KOMPLEX_API_HOST "https://komplexapi.hlab"
#else
#define KOMPLEX_API_HOST "https://komplex.digitalartifex.dev"
#define KOMPLEX_API_HOST "https://api.komplex.dev"
#endif
#define KOMPLEX_API_VERSION "v2"
+63
View File
@@ -23,6 +23,9 @@
#include <QMutex>
#include <QMutexLocker>
#include <qdebug.h>
#include <QNetworkReply>
#include <QJsonDocument>
#include <QJsonParseError>
#include "komplex_global.h"
#include "slidingcachecontroller.h"
@@ -47,6 +50,7 @@ signals:
auto pageChanged() -> void;
auto countChanged() -> void;
auto queryChanged() -> void;
auto uriChanged() -> void;
};
/**
@@ -168,6 +172,34 @@ public:
setOffset(0);
}
/**
* @brief uri
* URI of the API Endpoint. This is set by the child class
* @return
*/
[[nodiscard]]
auto uri() const -> QString
{
return m_uri;
}
/**
* @brief setUri
* Sets the API Endpoint URI to fetch from
* @param uri
*/
auto setUri(const QString &uri) -> void
{
if(uri == m_uri)
{
return;
}
m_uri = uri;
Q_EMIT uriChanged();
}
/**
* @brief data
* @return
@@ -321,6 +353,31 @@ protected:
controller()->reset();
}
/**
* @brief getDocument
* Extracts the JSON document from the server reply and deletes the reply
* object.
* @param reply
* @return
*/
[[nodiscard]]
auto getDocument(QNetworkReply *reply) const -> QJsonDocument
{
QByteArray data = reply->readAll();
QJsonParseError documentError;
QJsonDocument document = QJsonDocument::fromJson(data, &documentError);
reply->deleteLater();
LOG_ERROR_X(documentError.error != QJsonParseError::NoError,
"NewestPacksPaginator::getDocument",
documentError.errorString().toStdString().c_str(),
{}
);
return document;
}
private:
/**
@@ -377,6 +434,12 @@ private:
* Controls the queryable state of the paginator
*/
bool m_queryable = false;
/**
* @brief m_uri
* API Endpoint URI
*/
QString m_uri;
};
#endif // PAGINATOR_H
@@ -0,0 +1,138 @@
#ifndef SHADERPACKMETADATA
#define SHADERPACKMETADATA
#include <QObject>
#include <QString>
#include <QFile>
#include <QFileInfo>
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonArray>
#include <QJsonParseError>
#include <QStandardPaths>
#include <QDir>
#include <QMap>
#include <QProcess>
#include <QEventLoop>
#include <QtQml/qqmlregistration.h>
#include "komplex_global.h"
class KOMPLEX_EXPORT ShaderPackMetadata : public QObject
{
Q_OBJECT
QML_ELEMENT
QString m_author;
QString m_description;
QString m_engine;
QString m_file;
QString m_id;
QString m_license;
QString m_name;
QString m_version;
Q_PROPERTY(QString author READ author WRITE setAuthor NOTIFY authorChanged)
Q_PROPERTY(QString description READ description WRITE setDescription NOTIFY descriptionChanged)
Q_PROPERTY(QString engine READ engine WRITE setEngine NOTIFY engineChanged)
Q_PROPERTY(QString file READ file WRITE setFile NOTIFY fileChanged)
Q_PROPERTY(QString id READ id WRITE setId NOTIFY idChanged)
Q_PROPERTY(QString license READ license WRITE setLicense NOTIFY licenseChanged)
Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged)
Q_PROPERTY(QString version READ version WRITE setVersion NOTIFY versionChanged)
Q_SIGNALS:
void authorChanged();
void descriptionChanged();
void engineChanged();
void idChanged();
void licenseChanged();
void nameChanged();
void versionChanged();
void fileChanged();
public:
QString author() const { return m_author; }
void setAuthor(const QString& author)
{
if(author != m_author)
{
m_author = author;
Q_EMIT authorChanged();
}
}
QString description() const { return m_description; }
void setDescription(const QString& description)
{
if(description != m_description)
{
m_description = description;
Q_EMIT descriptionChanged();
}
}
QString engine() const { return m_engine; }
void setEngine(const QString& engine)
{
if(engine != m_engine)
{
m_engine = engine;
Q_EMIT engineChanged();
}
}
QString file() const { return m_file; }
void setFile(const QString& file)
{
if(file != m_file)
{
m_file = file;
Q_EMIT fileChanged();
}
}
QString id() const { return m_id; }
void setId(const QString& id)
{
if(id != m_id)
{
m_id = id;
Q_EMIT idChanged();
}
}
QString license() const { return m_license; }
void setLicense(const QString& license)
{
if(license != m_license)
{
m_license = license;
Q_EMIT licenseChanged();
}
}
QString name() const { return m_name; }
void setName(const QString& name)
{
if(name != m_name)
{
m_name = name;
Q_EMIT nameChanged();
}
}
QString version() const { return m_version; }
void setVersion(const QString& version)
{
if(version != m_version)
{
m_version = version;
Q_EMIT versionChanged();
}
}
};
Q_DECLARE_METATYPE(ShaderPackMetadata)
#endif
+108
View File
@@ -0,0 +1,108 @@
#ifndef SHADERTOYMETADATA_H
#define SHADERTOYMETADATA_H
#include <QObject>
#include <QDateTime>
#include <QNetworkReply>
#include <QNetworkRequest>
#include <QStandardPaths>
#include <QUuid>
#include <QProcess>
#include <QTimer>
#include <QMutex>
#include <QDir>
#include <QEventLoop>
#include <QJsonDocument>
#include <QJsonParseError>
#include <QJsonObject>
#include <QJsonArray>
#include <QJsonValue>
#include <QNetworkAccessManager>
#include <QPixmap>
#include <qqmlintegration.h>
#include "komplex_global.h"
struct KOMPLEX_EXPORT ShaderToyMetadata
{
QDateTime date;
QString description;
quint64 flags = 0;
bool hasLiked = false;
QString id;
quint64 likes = 0;
QString name;
quint64 published = 0;
QStringList tags;
bool usePreview = false;
QString username;
QString version;
quint64 views = 0;
};
struct KOMPLEX_EXPORT ShaderToyRenderInput
{
quint8 channel = 0;
QString ctype;
QString filter;
quint64 id = 0;
QString internal;
bool published = false;
QString source;
bool srgb = false;
bool verticalFlip = false;
QString wrap;
};
struct KOMPLEX_EXPORT ShaderToyRenderOutput
{
const quint8 channel = 0;
const quint64 id = 0;
};
struct KOMPLEX_EXPORT ShaderToyRenderPass
{
QByteArray code;
QString description;
QList<ShaderToyRenderInput> inputs;
QString name;
QList<ShaderToyRenderOutput> outputs;
QString type;
};
enum KOMPLEX_EXPORT ShaderToyErrorCode
{
NoError = 0,
NetworkError = (0x10 << 24),
MediaError = (0x20 << 24),
DiskError = (0x30 << 24),
CompileError = (0x40 << 24)
};
struct KOMPLEX_EXPORT ShaderToyError
{
QString message;
ShaderToyErrorCode code;
};
struct KOMPLEX_EXPORT ShaderToyEntry
{
enum Status
{
Idle,
Loading,
Compiling,
Compiled,
Error
};
ShaderToyMetadata metadata;
QList<ShaderToyRenderPass> renderPasses;
QByteArray data;
ShaderToyError error;
QStringList videoSelections;
Status status = Idle;
};
Q_DECLARE_METATYPE(ShaderToyEntry)
Q_DECLARE_METATYPE(ShaderToyError)
#endif // SHADERTOYMETADATA_H
@@ -96,7 +96,6 @@ public:
auto at(qsizetype index) const -> const T&
{
QMutexLocker cacheLocker(&m_cacheMutex);
T defaultValue;
if(!externalBoundaryCheck(index))
{
@@ -203,7 +202,7 @@ public:
return;
}
if(!externalBoundaryCheck(index + count))
if(externalBoundaryCheck(index + count))
{
slideWindow(index);
}
@@ -589,6 +588,8 @@ private:
* Override for window size
*/
qsizetype m_windowSize = nullsize;
T defaultValue;
};
#endif // SLIDINGCACHECONTROLLER_H
+819
View File
@@ -0,0 +1,819 @@
#include "downloadmanager.h"
#include "common/coreservices.h"
#include "common/exceptions.h"
#include "common/logging.h"
#include "common/shaderpackmetadata.h"
DownloadManager::DownloadManager(QObject *parent)
: QObject{parent}
{
}
auto DownloadManager::downloadImage(const QString &author, const QString &authorId, const QString &description, const QUrl &url) noexcept(false) -> void
{
QFuture future = QtConcurrent::run
(
[this, url, author, authorId, description]()
{
QNetworkRequest request(url);
QUrl localUri;
QString id = QUuid::createUuidV7().toString(QUuid::WithoutBraces);
try
{
QUrl downloadUri = download(request, id);
ShaderPackMetadata metadata;
metadata.setAuthor(author);
metadata.setDescription(description);
metadata.setName(QStringLiteral("Pexels Image (%1)").arg(id));
setState(Complete);
Q_EMIT downloadComplete(downloadUri.toString());
}
catch (FileException e)
{
setError(QStringLiteral("File Exception"), e.message);
localUri.clear();
}
catch (NetworkException e)
{
setError(QStringLiteral("Network Exception"), e.message);
localUri.clear();
}
}
);
}
auto DownloadManager::downloadPack(const QString &id) noexcept(false) -> void
{
QFuture future = QtConcurrent::run
(
[this, id]()
{
QUrl uri = QUrl
(
QStringLiteral("%1/%2/%3").arg
(
KOMPLEX_API_HOST,
KOMPLEX_API_VERSION,
KOMPLEX_ENDPOINT_PACKS_ITEM,
id
)
);
QNetworkRequest request(uri);
request.setRawHeader(QByteArray("uuid"), id.toUtf8());
QUrl localUri;
try
{
QUrl tempUri = download(request, id);
QUrl decompressedUri = decompress(tempUri);
ShaderToyEntry entry = readShaderToyEntry(decompressedUri);
//scan and replace media via user choice. resave with decompressedUri
QUrl compiledUri = compile(decompressedUri);
QUrl installedUri = install(compiledUri);
setState(Complete);
Q_EMIT downloadComplete(installedUri.toString());
}
catch (FileException e)
{
setError(QStringLiteral("File Exception"), e.message);
localUri.clear();
}
catch (NetworkException e)
{
setError(QStringLiteral("Network Exception"), e.message);
localUri.clear();
}
}
);
}
auto DownloadManager::reset() -> void
{
setError(QString(), QString());
setState(Idle);
}
auto DownloadManager::setError(const QString &title, const QString &message) -> void
{
if(title == m_errorTitle && message == m_errorMessage)
{
return;
}
m_errorMessage = std::move(message);
m_errorTitle = std::move(title);
Q_EMIT errorChanged();
}
auto DownloadManager::compile(const QUrl &uri) noexcept(false) -> QUrl
{
if(!uri.isLocalFile())
{
throw FileException(QStringLiteral("Uri needs to be a local file"), 0);
}
setState(Compiling);
QDir localToolsDirectory
(
QStringLiteral("%1/.local/share/komplex/tools").arg
(
QStandardPaths::writableLocation(QStandardPaths::HomeLocation)
)
);
QDir outputDirectory = QStringLiteral("%1/komplex/build").arg
(
QStandardPaths::writableLocation(QStandardPaths::TempLocation)
);
QStringList arguments =
{
localToolsDirectory.absoluteFilePath(QStringLiteral("stc.py")),
QStringLiteral("-i"),
uri.toLocalFile(),
QStringLiteral("-o"),
outputDirectory.absolutePath()
};
if(!QFile::exists(localToolsDirectory.absoluteFilePath(QStringLiteral("stc.py"))))
{
throw FileException
(
QStringLiteral("Shader Compiler is not installed at %1").arg
(
localToolsDirectory.absoluteFilePath(QStringLiteral("stc.py"))
)
);
}
QProcess *process = new QProcess(this);
QObject::connect
(
process,
&QProcess::readyReadStandardOutput,
this,
[this, process]()
{
QByteArray processData = process->readAllStandardOutput();
setCompilerOutput(m_compilerOutput + processData);
}
);
QObject::connect
(
process,
&QProcess::readyReadStandardError,
this,
[this, process]()
{
QByteArray processData = process->readAllStandardError();
if(!processData.isValidUtf8())
{
qWarning() << QStringLiteral("Process output not valid UTF8 data");
return;
}
setCompilerOutput(m_compilerOutput + processData);
}
);
process->start(QStringLiteral("python3"), arguments);
if(!process->waitForStarted(3000))
{
process->deleteLater();
throw FileException(QStringLiteral("Could not start shader compiler"));
}
if(!process->waitForFinished())
{
process->deleteLater();
throw FileException(QStringLiteral("Shader compiler timeout"));
}
if(process->exitCode() != 0)
{
process->deleteLater();
throw FileException(QStringLiteral("Shader compiler error"));
}
process->deleteLater();
QUrl outputUri = outputDirectory.absolutePath();
outputUri.setScheme(QStringLiteral("file://"));
return outputUri;
}
void DownloadManager::setCompilerOutput(const QString &compilerOutput)
{
if (m_compilerOutput == compilerOutput)
{
return;
}
m_compilerOutput = compilerOutput;
emit compilerOutputChanged();
}
auto DownloadManager::setState(State state) -> void
{
if(state == m_state)
{
return;
}
m_state = state;
Q_EMIT stateChanged();
return;
}
auto DownloadManager::setDownloadProgress(qreal progress) -> void
{
if(qFuzzyCompare(progress, m_downloadProgress))
{
return;
}
m_downloadProgress = progress;
Q_EMIT downloadProgressChanged();
}
auto DownloadManager::decompress(const QUrl &uri) noexcept(false) -> QUrl
{
return {};
}
// void DownloadManager::save(ShaderToyEntry entry)
// {
// QString directoryLocation = QStringLiteral("%1/komplex/src/%2").arg(QStandardPaths::writableLocation(QStandardPaths::TempLocation), entry.metadata.id);
// QDir directory(directoryLocation);
// if(!directory.exists())
// {
// directory.mkpath(directoryLocation + QStringLiteral("/shaders"));
// directory.mkpath(directoryLocation + QStringLiteral("/images"));
// directory.mkpath(directoryLocation + QStringLiteral("/videos"));
// }
// QDir shaderDirectory(directoryLocation + QStringLiteral("/shaders"));
// QDir imageDirectory(directoryLocation + QStringLiteral("/images"));
// // QDir videoDirectory(directoryLocation + QStringLiteral("/videos"));
// QJsonObject rootObject;
// rootObject[QStringLiteral("author")] = entry.metadata.username;
// rootObject[QStringLiteral("name")] = entry.metadata.name;
// rootObject[QStringLiteral("version")] = entry.metadata.version;
// rootObject[QStringLiteral("engine")] = QStringLiteral("shadertoy");
// rootObject[QStringLiteral("description")] = entry.metadata.description;
// rootObject[QStringLiteral("id")] = entry.metadata.id;
// rootObject[QStringLiteral("tags")] = QJsonArray::fromStringList(entry.metadata.tags);
// QMap<QString,QString> externalMedia;
// externalMedia.insert
// (
// directory.absoluteFilePath(QStringLiteral("thumbnail.jpg")),
// QStringLiteral("/media/shaders/%1.jpg").arg(entry.metadata.id)
// );
// for(const ShaderToyRenderPass &pass : std::as_const(entry.renderPasses))
// {
// // skip tone generators
// if(pass.type == QStringLiteral("sound"))
// continue;
// QString passName = pass.name;
// if(passName.contains(QStringLiteral("Buf")) && !passName.contains(QStringLiteral("Buffer")))
// passName.replace(QStringLiteral("Buf"), QStringLiteral("Buffer"));
// QFile shaderFile(shaderDirectory.absoluteFilePath(passName + QStringLiteral(".frag")));
// if(!shaderFile.open(QFile::WriteOnly))
// {
// qWarning() << QStringLiteral("Could not open shader file for saving");
// return;
// }
// if(shaderFile.write(pass.code) != pass.code.length())
// {
// qWarning() << QStringLiteral("Could not write shader file data");
// shaderFile.close();
// return;
// }
// shaderFile.close();
// //this is the common file
// if(pass.type == QStringLiteral("common"))
// continue; // wont have any inputs
// const ShaderToyRenderOutput *channelOutput = nullptr;
// for(const ShaderToyRenderOutput &output : std::as_const(pass.outputs))
// {
// if(output.channel == 0)
// {
// channelOutput = &output;
// break;
// }
// }
// QList<QJsonObject> channels(4);
// QJsonObject *passObject = nullptr;
// //this is the root shader
// if(pass.type == QStringLiteral("image"))
// {
// rootObject[QStringLiteral("source")] = QStringLiteral("./shaders/%1.frag.qsb").arg(pass.name);
// passObject = &rootObject;
// }
// else
// passObject = new QJsonObject;
// for(const ShaderToyRenderInput &input : std::as_const(pass.inputs))
// {
// /*
// * Only recursive buffers, images, videos and shader buffers are currently supported.
// * audio will default to audio capture
// */
// if(!m_supportedChannelTypes.contains(input.ctype))
// {
// qWarning() << input.ctype << QStringLiteral(" is not a valid channel type");
// continue;
// }
// // recursive buffer reference
// if(channelOutput && input.id == channelOutput->id)
// {
// passObject->insert(QStringLiteral("frame_buffer_channel"), input.channel);
// continue;
// }
// if(input.ctype == QStringLiteral("buffer"))
// {
// // get input reference by id
// const ShaderToyRenderPass *inputPass = nullptr;
// for(const ShaderToyRenderPass &passSubScan : std::as_const(entry.renderPasses))
// {
// for(const ShaderToyRenderOutput &output : std::as_const(passSubScan.outputs))
// {
// if(output.id == input.id && output.channel == 0)
// {
// inputPass = &passSubScan;
// break;
// }
// if(inputPass)
// break;
// }
// }
// //whoopsie
// if(!inputPass)
// continue;
// QString name = inputPass->name.toCaseFolded();
// name.replace(name.length() - 1, 1, name.right(1).toUpper());
// name.remove(QLatin1Char(' '));
// name.replace(QStringLiteral("buf"), QStringLiteral("buffer"));
// channels[input.channel][QStringLiteral("source")] = QStringLiteral("{%1}").arg(name);
// }
// else if(input.ctype == QStringLiteral("audio"))
// channels[input.channel][QStringLiteral("type")] = 4;
// else if(input.ctype == QStringLiteral("texture"))
// {
// QString filename = input.source;
// filename = filename.mid(filename.lastIndexOf(QLatin1Char('/')) + 1);
// channels[input.channel][QStringLiteral("type")] = 0;
// channels[input.channel][QStringLiteral("source")] = QStringLiteral("./images/%1").arg(filename);
// externalMedia.insert(imageDirectory.absoluteFilePath(filename), input.source);
// }
// //select video file after compilation
// else if(input.ctype == QStringLiteral("video"))
// {
// //set the channel source to a uuid then add that uuid to the video
// // selection stringlist
// QString sourceName = QUuid::createUuidV7().toString();
// channels[input.channel][QStringLiteral("type")] = 1;
// channels[input.channel][QStringLiteral("source")] = sourceName;
// QStringList newSelections = m_videoSelections;
// newSelections += sourceName;
// setVideoSelections(newSelections);
// }
// channels[input.channel][QStringLiteral("filter")] = input.filter;
// channels[input.channel][QStringLiteral("wrap")] = input.wrap;
// channels[input.channel][QStringLiteral("invert")] = input.verticalFlip;
// channels[input.channel][QStringLiteral("srgb")] = input.srgb;
// channels[input.channel][QStringLiteral("internal")] = input.internal;
// }
// for(int i = 0; i < 4; ++i)
// {
// if(channels[i].isEmpty())
// continue;
// passObject->insert(QStringLiteral("channel%1").arg(i), channels[i]);
// }
// //this is a buffer
// if(pass.type == QStringLiteral("buffer"))
// {
// QString name = pass.name.toCaseFolded();
// name.replace(name.length() - 1, 1, name.right(1).toUpper());
// name.remove(QLatin1Char(' '));
// name.replace(QStringLiteral("buf"), QStringLiteral("buffer"));
// passObject->insert(QStringLiteral("source"), QStringLiteral("./shaders/%1.frag.qsb").arg(passName));
// rootObject[name] = *passObject;
// }
// if(*passObject != rootObject)
// delete passObject;
// }
// QFile shaderPackFile(directory.absoluteFilePath(QStringLiteral("pack.json")));
// if(!shaderPackFile.open(QFile::WriteOnly))
// {
// qWarning() << QStringLiteral("Could not open pack file");
// return;
// }
// QJsonDocument packDocument;
// packDocument.setObject(rootObject);
// QByteArray jsonData = packDocument.toJson(QJsonDocument::Indented);
// if(shaderPackFile.write(jsonData) != jsonData.length())
// {
// qWarning() << QStringLiteral("Could not write pack data");
// return;
// }
// const QStringList keys = externalMedia.keys();
// // qWarning() << QStringLiteral("Downloading %1 Images").arg(externalMedia.count());
// // setStatus(Compiling, QStringLiteral("Downloading images"));
// setTotalDownloads(externalMedia.count());
// for(const QString &key : keys)
// downloadMedia(key, externalMedia[key]);
// }
auto DownloadManager::install(const QUrl &uri) noexcept(false) -> QUrl
{
if(!uri.isLocalFile())
{
throw FileException(QStringLiteral("URI is not a local file"));
}
QDir tempDirectory(uri.toLocalFile());
QString installLocation = QStringLiteral("%1/.local/share/komplex/packs/%2").arg
(
QStandardPaths::writableLocation(QStandardPaths::HomeLocation),
tempDirectory.dirName()
);
QDir installDirectory(installLocation);
if(installDirectory.exists())
installDirectory.removeRecursively();
QProcess process;
QObject::connect
(
&process,
&QProcess::readyReadStandardOutput,
this,
[this, &process]()
{
QByteArray processData = process.readAllStandardOutput();
if(!processData.isValidUtf8())
{
qWarning() << QStringLiteral("Process output not valid UTF8 data");
return;
}
setCompilerOutput(m_compilerOutput + processData);
}
);
QObject::connect
(
&process,
&QProcess::readyReadStandardError,
this,
[this, &process]()
{
QByteArray processData = process.readAllStandardError();
if(!processData.isValidUtf8())
{
qWarning() << QStringLiteral("Process output not valid UTF8 data");
return;
}
setCompilerOutput(m_compilerOutput + processData);
}
);
QStringList arguments = QStringList
{
QStringLiteral("-R"),
uri.toLocalFile(),
installLocation
};
process.start(QStringLiteral("cp"), arguments);
if(!process.waitForStarted(3000))
{
qWarning() << QStringLiteral("Could not start copy process: %1").arg(process.readAllStandardError());
throw FileException(QStringLiteral("Could not start install process"));
}
if(!process.waitForFinished())
{
qWarning() << QStringLiteral("Copy process took longer than expected (>30s)");
throw FileException(QStringLiteral("Install process took longer than expected (>30s)"));
}
QUrl installUri(installLocation);
installUri.setScheme(QStringLiteral("file://"));
return installUri;
}
auto DownloadManager::download(const QNetworkRequest &request, const QString &id) noexcept(false) -> QUrl
{
QEventLoop loop;
QUrl downloadUri = QStringLiteral("%1/%2").arg
(
QStandardPaths::writableLocation(QStandardPaths::TempLocation),
request.url().path().split('/', Qt::SkipEmptyParts).last()
);
QWeakPointer<QNetworkAccessManager> reference = CoreServices::networkAccessManager();
QSharedPointer<QNetworkAccessManager> manager = reference.toStrongRef();
if(manager == nullptr)
{
throw NetworkException
(
QStringLiteral("Network Manager reference has already been deleted")
);
}
QFile downloadFile(downloadUri.toLocalFile());
if(!downloadFile.open(QFile::ReadWrite))
{
throw FileException(QStringLiteral("Could not open temp file location"));
}
QNetworkReply *reply = manager->post(request, nullptr);
QObject::connect
(
reply,
&QNetworkReply::finished,
&loop,
&QEventLoop::quit
);
QObject::connect
(
reply,
&QNetworkReply::errorOccurred,
&loop,
[&loop](QNetworkReply::NetworkError error) -> void
{
loop.quit();
throw NetworkException
(
QStringLiteral("Network Error %1").arg
(
QString::number(static_cast<qint64>(error))
)
);
}
);
QObject::connect
(
reply,
&QNetworkReply::downloadProgress,
this,
[this](qint64 bytesDownloaded, qint64 bytesTotal)
{
setDownloadProgress(static_cast<qreal>(bytesDownloaded) / bytesTotal);
}
);
QObject::connect
(
reply,
&QNetworkReply::readyRead,
this,
[this, &downloadFile, &reply]()
{
if(!downloadFile.isOpen())
{
return;
}
quint64 bytes = reply->bytesAvailable();
quint64 bytesWritten = downloadFile.write(reply->read(bytes));
if(bytesWritten != bytes)
{
throw FileException(QStringLiteral("Could not write temp file data"));
}
}
);
if(!reply->isFinished())
{
loop.exec();
}
if(reply->bytesAvailable() > 0)
{
quint64 bytes = reply->bytesAvailable();
quint64 bytesWritten = downloadFile.write(reply->read(bytes));
if(bytesWritten != bytes)
{
throw FileException(QStringLiteral("Could not write temp file data"));
}
}
downloadFile.close();
manager.clear();
return downloadUri;
}
auto DownloadManager::readShaderToyEntry(const QUrl &uri) noexcept(false) -> ShaderToyEntry
{
if(!uri.isLocalFile())
{
throw FileException(QStringLiteral("URI is not a local file"));
}
QDir packDir(uri.toLocalFile());
if(!packDir.exists(QStringLiteral("pack.json")))
{
throw FileException(QStringLiteral("Pack file not found"));
}
QFile packFile(packDir.absoluteFilePath(QStringLiteral("pack.json")));
if(!packFile.open(QFile::ReadOnly))
{
throw FileException
(
QStringLiteral("Could not open pack file: %1").arg
(
packFile.errorString()
)
);
}
QByteArray data = packFile.readAll();
QJsonParseError jsonError;
QJsonDocument document = QJsonDocument::fromJson(data, &jsonError);
if(jsonError.error != QJsonParseError::NoError)
{
throw FileException
(
QStringLiteral("Could parse pack file: %1").arg
(
jsonError.errorString()
)
);
}
QJsonObject documentObject = document.object();
QJsonObject rootObject = documentObject[QStringLiteral("Shader")].toObject();
QJsonObject infoObject = rootObject[QStringLiteral("info")].toObject();
QJsonArray tagsArray = infoObject[QStringLiteral("tags")].toArray();
QStringList tags;
for(const QJsonValue &tag : std::as_const(tagsArray))
tags += tag.toString();
ShaderToyEntry entry;
entry.metadata = ShaderToyMetadata
{
QDateTime::fromSecsSinceEpoch(infoObject[QStringLiteral("date")].toInt()),
infoObject[QStringLiteral("description")].toString(),
static_cast<quint64>(infoObject[QStringLiteral("flags")].toInt()),
static_cast<bool>(infoObject[QStringLiteral("hasLiked")].toInt()),
infoObject[QStringLiteral("id")].toString(),
static_cast<quint64>(infoObject[QStringLiteral("likes")].toInt()),
infoObject[QStringLiteral("name")].toString(),
static_cast<quint64>(infoObject[QStringLiteral("published")].toInt()),
tags,
static_cast<bool>(infoObject[QStringLiteral("usePreview")].toInt()),
infoObject[QStringLiteral("username")].toString(),
rootObject[QStringLiteral("ver")].toString(),
static_cast<quint64>(infoObject[QStringLiteral("views")].toInt())
};
QJsonArray ShaderToyRenderPassArray = rootObject[QStringLiteral("renderpass")].toArray();
for(const QJsonValue &ShaderToyRenderPassValue : std::as_const(ShaderToyRenderPassArray))
{
QJsonArray inputArray = ShaderToyRenderPassValue[QStringLiteral("inputs")].toArray();
QJsonArray outputArray = ShaderToyRenderPassValue[QStringLiteral("outputs")].toArray();
QList<ShaderToyRenderInput> inputs;
QList<ShaderToyRenderOutput> outputs;
for(const QJsonValue &inputValue : std::as_const(inputArray))
{
QJsonObject samplerObject = inputValue[QStringLiteral("sampler")].toObject();
inputs.append
(
ShaderToyRenderInput
{
static_cast<quint8>(inputValue[QStringLiteral("channel")].toInt()),
inputValue[QStringLiteral("ctype")].toString(),
samplerObject[QStringLiteral("filter")].toString(),
static_cast<quint64>(inputValue[QStringLiteral("id")].toInt()),
samplerObject[QStringLiteral("internal")].toString(),
static_cast<bool>(inputValue[QStringLiteral("published")].toInt()),
inputValue[QStringLiteral("src")].toString(),
samplerObject[QStringLiteral("srgb")].toBool(),
samplerObject[QStringLiteral("verticalFlip")].toBool(),
samplerObject[QStringLiteral("wrap")].toString()
}
);
}
for(const QJsonValue &outputValue : std::as_const(outputArray))
{
outputs.append
(
ShaderToyRenderOutput
{
static_cast<quint8>(outputValue[QStringLiteral("channel")].toInt()),
static_cast<quint64>(outputValue[QStringLiteral("id")].toInt())
}
);
}
entry.renderPasses.append
(
ShaderToyRenderPass
{
ShaderToyRenderPassValue[QStringLiteral("code")].toVariant().toByteArray(),
ShaderToyRenderPassValue[QStringLiteral("description")].toString(),
inputs,
ShaderToyRenderPassValue[QStringLiteral("name")].toString(),
outputs,
ShaderToyRenderPassValue[QStringLiteral("type")].toString()
}
);
}
return std::move(entry);
}
+104
View File
@@ -0,0 +1,104 @@
#ifndef DOWNLOADMANAGER_H
#define DOWNLOADMANAGER_H
#include <QObject>
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QNetworkRequest>
#include <QFileInfo>
#include <QUuid>
#include <QQmlEngine>
#include <QProcess>
#include <QDir>
#include <QStandardPaths>
#include <QUuid>
#include <QtConcurrent/QtConcurrentRun>
#include "common/komplex_global.h"
#include "common/shadertoymetadata.h"
#include "common/shaderpackmetadata.h"
class KOMPLEX_EXPORT DownloadManager : public QObject
{
Q_OBJECT
QML_ELEMENT
public:
enum State
{
Idle,
Downloading,
Compiling,
Installing,
Complete,
Error
};
Q_ENUM(State)
explicit DownloadManager(QObject *parent = nullptr);
/**
* @brief downloadImage
* Downloads the image to tmp from the given url
* @param url
*/
auto downloadImage(const QString &author, const QString &authorId, const QString &description, const QUrl &url) noexcept(false) -> void;
/**
* @brief downloadPack
* Attempts to download, compile and install the wallpaper
* pack from the komplex endpoint.
* @param id
* @return File info of the installed pack
*/
auto downloadPack(const QString &id) noexcept(false) -> void;
auto compilerOutput() const -> const QString & { return m_compilerOutput; }
auto errorTitle() const -> const QString & { return m_errorTitle; }
auto errorMessage() const -> const QString & { return m_errorMessage; }
auto state() const -> State { return m_state; }
auto reset() -> void;
auto downloadProgress() -> qreal { return m_downloadProgress; }
auto compileProgress() -> qreal { return m_compileProgress; }
protected:
auto setError(const QString &title, const QString &message) -> void;
auto setCompilerOutput(const QString &compilerOutput) -> void;
auto setState(State state) -> void;
auto setDownloadProgress(qreal progress) -> void;
signals:
auto compilerOutputChanged() -> void;
auto errorChanged() -> void;
auto stateChanged() -> void;
auto downloadProgressChanged() -> void;
auto compileProgressChanged() -> void;
auto downloadComplete(const QString &uri) -> void;
private:
auto decompress(const QUrl &uri) noexcept(false) -> QUrl;
auto compile(const QUrl &uri) noexcept(false) -> QUrl;
auto install(const QUrl &uri) noexcept(false) -> QUrl;
auto download(const QNetworkRequest &request, const QString &id) noexcept(false) -> QUrl;
auto readShaderToyEntry(const QUrl &uri) noexcept(false) -> ShaderToyEntry;
QString m_compilerOutput;
QString m_errorTitle;
QString m_errorMessage;
qreal m_downloadProgress = 0;
qreal m_compileProgress = 0;
State m_state;
QMutex m_downloadMutex;
Q_PROPERTY(QString compilerOutput READ compilerOutput WRITE setCompilerOutput NOTIFY compilerOutputChanged FINAL)
Q_PROPERTY(QString errorTitle READ errorTitle NOTIFY errorChanged FINAL)
Q_PROPERTY(QString errorMessage READ errorMessage NOTIFY errorChanged FINAL)
Q_PROPERTY(qreal downloadProgress READ downloadProgress NOTIFY downloadProgressChanged FINAL)
Q_PROPERTY(qreal compileProgress READ compileProgress NOTIFY compileProgressChanged FINAL)
Q_PROPERTY(State state READ state NOTIFY stateChanged FINAL)
};
Q_DECLARE_METATYPE(DownloadManager)
#endif // DOWNLOADMANAGER_H
+55 -30
View File
@@ -55,6 +55,14 @@ FeaturedImagesModel::FeaturedImagesModel(QObject *parent) : QAbstractListModel{p
);
}
FeaturedImagesModel::~FeaturedImagesModel()
{
if(m_paginator)
{
delete m_paginator;
}
}
auto FeaturedImagesModel::rowCount(const QModelIndex &) const -> int
{
if(m_paginator != nullptr)
@@ -94,49 +102,36 @@ auto FeaturedImagesModel::data(const QModelIndex &index, int role) const -> QVar
data = dataPoint.altText;
break;
case ThumbnailRole:
data = dataPoint.sources.value(QString::fromUtf8("tiny"));
data = dataPoint.sources.value(QString::fromUtf8("thumbnail"));
break;
case LargeThumbnailRole:
data = dataPoint.sources.value(QString::fromUtf8("largeThumbnail"));
break;
case PortraitUrlRole:
data = dataPoint.sources.value(QString::fromUtf8("portrait"));
break;
case LandscapeUrlRole:
data = dataPoint.sources.value(QString::fromUtf8("landscape"));
case PortraitSizeRole:
data = dataPoint.sourceSizes.value(QString::fromUtf8("portrait"));
break;
case SmallUrlRole:
data = dataPoint.sources.value(QString::fromUtf8("small"));
case ScreenUrlRole:
data = dataPoint.sources.value(QString::fromUtf8("screen"));
break;
case ScreenSizeRole:
data = dataPoint.sourceSizes.value(QString::fromUtf8("screen"));
break;
case OriginalUrlRole:
data = dataPoint.sources.value(QString::fromUtf8("original"));
break;
case MediumUrlRole:
data = dataPoint.sources.value(QString::fromUtf8("medium"));
break;
case LargeUrlRole:
data = dataPoint.sources.value(QString::fromUtf8("large"));
break;
case ExtraLargeUrlRole:
data = dataPoint.sources.value(QString::fromUtf8("large2x"));
break;
case PortraitSizeRole:
data = dataPoint.sourceSizes.value(QString::fromUtf8("portrait"));
case LandscapeUrlRole:
data = dataPoint.sources.value(QString::fromUtf8("landscape"));
break;
case LandscapeSizeRole:
data = dataPoint.sourceSizes.value(QString::fromUtf8("landscape"));
break;
case SmallSizeRole:
data = dataPoint.sourceSizes.value(QString::fromUtf8("small"));
break;
case OriginalSizeRole:
data = dataPoint.sourceSizes.value(QString::fromUtf8("original"));
break;
case MediumSizeRole:
data = dataPoint.sourceSizes.value(QString::fromUtf8("medium"));
break;
case LargeSizeRole:
data = dataPoint.sourceSizes.value(QString::fromUtf8("large"));
break;
case ExtraLargeSizeRole:
data = dataPoint.sourceSizes.value(QString::fromUtf8("large2x"));
case BackgroundPortraitRole:
data = dataPoint.sources.value(QString::fromUtf8("backgroundPortrait"));
case BackgroundLandscapeRole:
data = dataPoint.sources.value(QString::fromUtf8("backgroundLandscape"));
break;
}
@@ -250,13 +245,43 @@ auto FeaturedImagesModel::resultsPerPage() const -> qsizetype
auto FeaturedImagesModel::setResultsPerPage(qsizetype resultsPerPage) -> void
{
if(m_paginator != nullptr)
{
QFuture<void> future = QtConcurrent::run
(
[this, resultsPerPage]
{
qsizetype difference = resultsPerPage - m_paginator->resultsPerPage();
m_paginator->setResultsPerPage(resultsPerPage);
if(difference > 0)
{
qsizetype firstIndex = m_paginator->resultsPerPage() - difference;
beginInsertRows
(
QModelIndex(),
firstIndex,
m_paginator->count() - 1
);
endInsertRows();
}
else if(difference < 0)
{
qsizetype firstIndex = m_paginator->resultsPerPage();
beginRemoveRows
(
QModelIndex(),
firstIndex,
firstIndex - difference
);
endRemoveRows();
}
}
);
}
+23 -42
View File
@@ -69,24 +69,21 @@ public:
AuthorUrlRole,
DescriptionRole,
ThumbnailRole,
LargeThumbnailRole,
OriginalUrlRole,
ScreenUrlRole,
PortraitUrlRole,
LandscapeUrlRole,
SmallUrlRole,
OriginalUrlRole,
MediumUrlRole,
LargeUrlRole,
ExtraLargeUrlRole,
ScreenSizeRole,
PortraitSizeRole,
LandscapeSizeRole,
SmallSizeRole,
OriginalSizeRole,
MediumSizeRole,
LargeSizeRole,
ExtraLargeSizeRole
BackgroundPortraitRole,
BackgroundLandscapeRole
};
Q_ENUM(DataRole)
explicit FeaturedImagesModel(QObject *parent = nullptr);
~FeaturedImagesModel();
auto rowCount(const QModelIndex &parent = QModelIndex()) const -> int override;
@@ -295,6 +292,14 @@ private:
static_cast<int>(ThumbnailRole),
QByteArray("thumbnail")
},
{
static_cast<int>(LargeThumbnailRole),
QByteArray("largeThumbnail")
},
{
static_cast<int>(OriginalUrlRole),
QByteArray("original")
},
{
static_cast<int>(PortraitUrlRole),
QByteArray("portrait")
@@ -304,24 +309,20 @@ private:
QByteArray("landscape")
},
{
static_cast<int>(SmallUrlRole),
QByteArray("small")
static_cast<int>(BackgroundPortraitRole),
QByteArray("backgroundPortrait")
},
{
static_cast<int>(OriginalUrlRole),
QByteArray("original")
static_cast<int>(BackgroundLandscapeRole),
QByteArray("backgroundLandscape")
},
{
static_cast<int>(MediumUrlRole),
QByteArray("medium")
static_cast<int>(ScreenUrlRole),
QByteArray("fullScreen")
},
{
static_cast<int>(LargeUrlRole),
QByteArray("large")
},
{
static_cast<int>(ExtraLargeUrlRole),
QByteArray("extraLarge")
static_cast<int>(ScreenSizeRole),
QByteArray("fullScreenSize")
},
{
static_cast<int>(PortraitSizeRole),
@@ -330,26 +331,6 @@ private:
{
static_cast<int>(LandscapeSizeRole),
QByteArray("landscapeSize")
},
{
static_cast<int>(SmallSizeRole),
QByteArray("smallSize")
},
{
static_cast<int>(OriginalSizeRole),
QByteArray("originalSize")
},
{
static_cast<int>(MediumSizeRole),
QByteArray("mediumSize")
},
{
static_cast<int>(LargeSizeRole),
QByteArray("largeSize")
},
{
static_cast<int>(ExtraLargeSizeRole),
QByteArray("extraLargeSize")
}
};
State m_state = Idle;
+47
View File
@@ -56,6 +56,14 @@ FeaturedPacksModel::FeaturedPacksModel(QObject *parent) : QAbstractListModel{par
);
}
FeaturedPacksModel::~FeaturedPacksModel()
{
if(m_paginator)
{
delete m_paginator;
}
}
auto FeaturedPacksModel::rowCount(const QModelIndex &) const -> int
{
if(m_paginator != nullptr)
@@ -181,6 +189,16 @@ auto FeaturedPacksModel::onPaginatorFetching() -> void
setState(Loading);
}
auto FeaturedPacksModel::boundaryCheck(qsizetype index) const -> bool
{
if(index < 0 || m_paginator == nullptr || index >= m_paginator->count())
{
return false;
}
return true;
}
auto FeaturedPacksModel::roleNames() const -> QHash<int, QByteArray>
{
return m_dataRoles;
@@ -220,7 +238,36 @@ auto FeaturedPacksModel::setResultsPerPage(qsizetype resultsPerPage) -> void
(
[this, resultsPerPage]
{
qsizetype difference = resultsPerPage - m_paginator->resultsPerPage();
m_paginator->setResultsPerPage(resultsPerPage);
if(difference > 0)
{
qsizetype firstIndex = m_paginator->resultsPerPage() - difference;
beginInsertRows
(
QModelIndex(),
firstIndex,
m_paginator->count() - 1
);
endInsertRows();
}
else if(difference < 0)
{
qsizetype firstIndex = m_paginator->resultsPerPage();
beginRemoveRows
(
QModelIndex(),
firstIndex,
firstIndex - difference
);
endRemoveRows();
}
}
);
}
+9
View File
@@ -78,6 +78,7 @@ public:
Q_ENUM(DataRole)
explicit FeaturedPacksModel(QObject *parent = nullptr);
~FeaturedPacksModel();
auto rowCount(const QModelIndex &parent = QModelIndex()) const -> int override;
@@ -239,6 +240,14 @@ signals:
auto totalPagesChanged() -> void;
private:
/**
* @brief boundaryCheck
* Helper function to check the requested index boundary
* @param index
* @return
*/
auto boundaryCheck(qsizetype index) const -> bool;
/**
* @brief m_dataRoles
* Data role map that connects ImageSearchModel::DataRole to it's QML accessor name
+72 -7
View File
@@ -3,6 +3,8 @@
FeaturedVideosModel::FeaturedVideosModel(QObject *parent) : QAbstractListModel{parent}
{
m_itemModel = new VideoItemModel(this);
m_paginator = new FeaturedVideosPaginator(this);
PaginationNotifier *notifier = static_cast<PaginationNotifier*>(m_paginator);
@@ -56,6 +58,19 @@ FeaturedVideosModel::FeaturedVideosModel(QObject *parent) : QAbstractListModel{p
);
}
FeaturedVideosModel::~FeaturedVideosModel()
{
if(m_paginator)
{
delete m_paginator;
}
if(m_itemModel)
{
delete m_itemModel;
}
}
auto FeaturedVideosModel::rowCount(const QModelIndex &) const -> int
{
if(m_paginator != nullptr)
@@ -68,7 +83,7 @@ auto FeaturedVideosModel::rowCount(const QModelIndex &) const -> int
auto FeaturedVideosModel::data(const QModelIndex &index, int role) const -> QVariant
{
if(index.row() < 0 || m_paginator == nullptr || index.row() >= m_paginator->count())
if(!boundaryCheck(index.row()))
{
return {};
}
@@ -115,12 +130,7 @@ auto FeaturedVideosModel::index(int row, int column, const QModelIndex &parent)
{
Q_UNUSED(parent)
if(m_paginator == nullptr)
{
return {};
}
if(row < 0 || row >= m_paginator->count())
if(!boundaryCheck(row))
{
return {};
}
@@ -180,6 +190,21 @@ auto FeaturedVideosModel::onPaginatorFetching() -> void
setState(Loading);
}
auto FeaturedVideosModel::boundaryCheck(qsizetype index) const -> bool
{
if(index < 0 || m_paginator == nullptr || index >= m_paginator->count())
{
return false;
}
return true;
}
auto FeaturedVideosModel::itemModel() const -> VideoItemModel*
{
return m_itemModel;
}
auto FeaturedVideosModel::roleNames() const -> QHash<int, QByteArray>
{
return m_dataRoles;
@@ -219,7 +244,36 @@ auto FeaturedVideosModel::setResultsPerPage(qsizetype resultsPerPage) -> void
(
[this, resultsPerPage]
{
qsizetype difference = resultsPerPage - m_paginator->resultsPerPage();
m_paginator->setResultsPerPage(resultsPerPage);
if(difference > 0)
{
qsizetype firstIndex = m_paginator->resultsPerPage() - difference;
beginInsertRows
(
QModelIndex(),
firstIndex,
m_paginator->count() - 1
);
endInsertRows();
}
else if(difference < 0)
{
qsizetype firstIndex = m_paginator->resultsPerPage();
beginRemoveRows
(
QModelIndex(),
firstIndex,
firstIndex - difference
);
endRemoveRows();
}
}
);
}
@@ -263,6 +317,17 @@ auto FeaturedVideosModel::previousPage() -> void
}
}
auto FeaturedVideosModel::setItem(qint64 index) -> bool
{
if(!boundaryCheck(index))
{
return false;
}
m_itemModel->setDataEntry(m_paginator->at(index));
return true;
}
qsizetype FeaturedVideosModel::page() const
{
if(m_paginator != nullptr)
+22
View File
@@ -38,6 +38,7 @@
#include <QtConcurrent/QtConcurrentRun>
#include "paginators/featuredvideospaginator.h"
#include "videoitemmodel.h"
/**
* @brief The FeaturedVideosModel class
@@ -76,6 +77,7 @@ public:
Q_ENUM(DataRole)
explicit FeaturedVideosModel(QObject *parent = nullptr);
~FeaturedVideosModel();
auto rowCount(const QModelIndex &parent = QModelIndex()) const -> int override;
@@ -205,6 +207,16 @@ public:
*/
Q_INVOKABLE auto previousPage() -> void;
/**
* @brief setItem
* Sets the current itemModel to the item at the specified index
* @param index
* @return
*/
Q_INVOKABLE auto setItem(qint64 index) -> bool;
VideoItemModel *itemModel() const;
protected:
/**
* @brief setErrorString
@@ -237,6 +249,14 @@ signals:
auto totalPagesChanged() -> void;
private:
/**
* @brief boundaryCheck
* Helper function to check the requested index boundary
* @param index
* @return
*/
auto boundaryCheck(qsizetype index) const -> bool;
/**
* @brief m_dataRoles
* Data role map that connects ImageSearchModel::DataRole to it's QML accessor name
@@ -285,6 +305,7 @@ private:
QString m_errorString = QString();
FeaturedVideosPaginator *m_paginator = nullptr;
VideoItemModel *m_itemModel = nullptr;
Q_PROPERTY(State state READ state WRITE setState RESET resetState NOTIFY stateChanged FINAL)
Q_PROPERTY(QString errorString READ errorString WRITE setErrorString NOTIFY errorStringChanged FINAL)
@@ -292,6 +313,7 @@ private:
Q_PROPERTY(qsizetype totalResults READ totalResults NOTIFY totalResultsChanged FINAL)
Q_PROPERTY(qsizetype totalPages READ totalPages NOTIFY totalPagesChanged FINAL)
Q_PROPERTY(qsizetype page READ page NOTIFY pageChanged FINAL)
Q_PROPERTY(VideoItemModel *itemModel READ itemModel CONSTANT FINAL)
};
Q_DECLARE_METATYPE(FeaturedVideosModel)
+27 -30
View File
@@ -109,49 +109,36 @@ auto ImageSearchModel::data(const QModelIndex &index, int role) const -> QVarian
data = dataPoint.altText;
break;
case ThumbnailRole:
data = dataPoint.sources.value(QString::fromUtf8("tiny"));
data = dataPoint.sources.value(QString::fromUtf8("thumbnail"));
break;
case LargeThumbnailRole:
data = dataPoint.sources.value(QString::fromUtf8("largeThumbnail"));
break;
case PortraitUrlRole:
data = dataPoint.sources.value(QString::fromUtf8("portrait"));
break;
case LandscapeUrlRole:
data = dataPoint.sources.value(QString::fromUtf8("landscape"));
case PortraitSizeRole:
data = dataPoint.sourceSizes.value(QString::fromUtf8("portrait"));
break;
case SmallUrlRole:
data = dataPoint.sources.value(QString::fromUtf8("small"));
case ScreenUrlRole:
data = dataPoint.sources.value(QString::fromUtf8("screen"));
break;
case ScreenSizeRole:
data = dataPoint.sourceSizes.value(QString::fromUtf8("screen"));
break;
case OriginalUrlRole:
data = dataPoint.sources.value(QString::fromUtf8("original"));
break;
case MediumUrlRole:
data = dataPoint.sources.value(QString::fromUtf8("medium"));
break;
case LargeUrlRole:
data = dataPoint.sources.value(QString::fromUtf8("large"));
break;
case ExtraLargeUrlRole:
data = dataPoint.sources.value(QString::fromUtf8("large2x"));
break;
case PortraitSizeRole:
data = dataPoint.sourceSizes.value(QString::fromUtf8("portrait"));
case LandscapeUrlRole:
data = dataPoint.sources.value(QString::fromUtf8("landscape"));
break;
case LandscapeSizeRole:
data = dataPoint.sourceSizes.value(QString::fromUtf8("landscape"));
break;
case SmallSizeRole:
data = dataPoint.sourceSizes.value(QString::fromUtf8("small"));
break;
case OriginalSizeRole:
data = dataPoint.sourceSizes.value(QString::fromUtf8("original"));
break;
case MediumSizeRole:
data = dataPoint.sourceSizes.value(QString::fromUtf8("medium"));
break;
case LargeSizeRole:
data = dataPoint.sourceSizes.value(QString::fromUtf8("large"));
break;
case ExtraLargeSizeRole:
data = dataPoint.sourceSizes.value(QString::fromUtf8("large2x"));
case BackgroundPortraitRole:
data = dataPoint.sources.value(QString::fromUtf8("backgroundPortrait"));
case BackgroundLandscapeRole:
data = dataPoint.sources.value(QString::fromUtf8("backgroundLandscape"));
break;
}
@@ -222,6 +209,16 @@ auto ImageSearchModel::onPaginatorFetching() -> void
setState(Loading);
}
auto ImageSearchModel::boundaryCheck(qsizetype index) const -> bool
{
if(index < 0 || m_paginator == nullptr || index >= m_paginator->count())
{
return false;
}
return true;
}
auto ImageSearchModel::hasNextPage() const -> bool
{
return m_paginator->page() < m_paginator->totalPages();
+79 -91
View File
@@ -69,20 +69,16 @@ public:
AuthorUrlRole,
DescriptionRole,
ThumbnailRole,
LargeThumbnailRole,
OriginalUrlRole,
ScreenUrlRole,
PortraitUrlRole,
LandscapeUrlRole,
SmallUrlRole,
OriginalUrlRole,
MediumUrlRole,
LargeUrlRole,
ExtraLargeUrlRole,
ScreenSizeRole,
PortraitSizeRole,
LandscapeSizeRole,
SmallSizeRole,
OriginalSizeRole,
MediumSizeRole,
LargeSizeRole,
ExtraLargeSizeRole
BackgroundPortraitRole,
BackgroundLandscapeRole
};
Q_ENUM(DataRole)
@@ -322,93 +318,85 @@ signals:
auto queryChanged() -> void;
private:
/**
* @brief boundaryCheck
* Helper function to check the requested index boundary
* @param index
* @return
*/
auto boundaryCheck(qsizetype index) const -> bool;
/**
* @brief m_dataRoles
* Data role map that connects ImageSearchModel::DataRole to it's QML accessor name
*/
static inline const QHash<int, QByteArray> m_dataRoles =
{
{
static_cast<int>(UuidRole),
QByteArray("uuid")
},
{
static_cast<int>(AuthorRole),
QByteArray("author")
},
{
static_cast<int>(AuthorIdRole),
QByteArray("authorId")
},
{
static_cast<int>(AuthorUrlRole),
QByteArray("authorUrl")
},
{
static_cast<int>(DescriptionRole),
QByteArray("description")
},
{
static_cast<int>(ThumbnailRole),
QByteArray("thumbnail")
},
{
static_cast<int>(PortraitUrlRole),
QByteArray("portrait")
},
{
static_cast<int>(LandscapeUrlRole),
QByteArray("landscape")
},
{
static_cast<int>(SmallUrlRole),
QByteArray("small")
},
{
static_cast<int>(OriginalUrlRole),
QByteArray("original")
},
{
static_cast<int>(MediumUrlRole),
QByteArray("medium")
},
{
static_cast<int>(LargeUrlRole),
QByteArray("large")
},
{
static_cast<int>(ExtraLargeUrlRole),
QByteArray("extraLarge")
},
{
static_cast<int>(PortraitSizeRole),
QByteArray("portraitSize")
},
{
static_cast<int>(LandscapeSizeRole),
QByteArray("landscapeSize")
},
{
static_cast<int>(SmallSizeRole),
QByteArray("smallSize")
},
{
static_cast<int>(OriginalSizeRole),
QByteArray("originalSize")
},
{
static_cast<int>(MediumSizeRole),
QByteArray("mediumSize")
},
{
static_cast<int>(LargeSizeRole),
QByteArray("largeSize")
},
{
static_cast<int>(ExtraLargeSizeRole),
QByteArray("extraLargeSize")
}
};
{
static_cast<int>(UuidRole),
QByteArray("uuid")
},
{
static_cast<int>(AuthorRole),
QByteArray("author")
},
{
static_cast<int>(AuthorIdRole),
QByteArray("authorId")
},
{
static_cast<int>(AuthorUrlRole),
QByteArray("authorUrl")
},
{
static_cast<int>(DescriptionRole),
QByteArray("description")
},
{
static_cast<int>(ThumbnailRole),
QByteArray("thumbnail")
},
{
static_cast<int>(LargeThumbnailRole),
QByteArray("largeThumbnail")
},
{
static_cast<int>(OriginalUrlRole),
QByteArray("original")
},
{
static_cast<int>(PortraitUrlRole),
QByteArray("portrait")
},
{
static_cast<int>(LandscapeUrlRole),
QByteArray("landscape")
},
{
static_cast<int>(BackgroundPortraitRole),
QByteArray("backgroundPortrait")
},
{
static_cast<int>(BackgroundLandscapeRole),
QByteArray("backgroundLandscape")
},
{
static_cast<int>(ScreenUrlRole),
QByteArray("fullScreen")
},
{
static_cast<int>(ScreenSizeRole),
QByteArray("fullScreenSize")
},
{
static_cast<int>(PortraitSizeRole),
QByteArray("portraitSize")
},
{
static_cast<int>(LandscapeSizeRole),
QByteArray("landscapeSize")
}
};
State m_state = Idle;
QString m_errorString = QString();
+44 -1
View File
@@ -58,6 +58,14 @@ NewestPacksModel::NewestPacksModel(QObject *parent) : QAbstractListModel{parent}
);
}
NewestPacksModel::~NewestPacksModel()
{
if(m_paginator)
{
delete m_paginator;
}
}
auto NewestPacksModel::rowCount(const QModelIndex &) const -> int
{
if(m_paginator != nullptr)
@@ -216,7 +224,42 @@ auto NewestPacksModel::setResultsPerPage(qsizetype resultsPerPage) -> void
{
if(m_paginator != nullptr)
{
m_paginator->setResultsPerPage(resultsPerPage);
QFuture<void> future = QtConcurrent::run
(
[this, resultsPerPage]
{
qsizetype difference = resultsPerPage - m_paginator->resultsPerPage();
m_paginator->setResultsPerPage(resultsPerPage);
if(difference > 0)
{
qsizetype firstIndex = m_paginator->resultsPerPage() - difference;
beginInsertRows
(
QModelIndex(),
firstIndex,
m_paginator->count() - 1
);
endInsertRows();
}
else if(difference < 0)
{
qsizetype firstIndex = m_paginator->resultsPerPage();
beginRemoveRows
(
QModelIndex(),
firstIndex,
firstIndex - difference
);
endRemoveRows();
}
}
);
}
}
+13
View File
@@ -78,6 +78,7 @@ public:
Q_ENUM(DataRole)
explicit NewestPacksModel(QObject *parent = nullptr);
~NewestPacksModel();
/**
* @brief rowCount
@@ -249,6 +250,18 @@ signals:
auto totalPagesChanged() -> void;
private:
/**
* @brief boundaryCheck
* Helper function to check the requested index boundary
* @param index
* @return
*/
auto boundaryCheck(qsizetype index) const -> bool;
/**
* @brief m_dataRoles
* Data role map that connects ImageSearchModel::DataRole to it's QML accessor name
*/
static inline const QHash<int, QByteArray> m_dataRoles =
{
{
+47
View File
@@ -62,6 +62,14 @@ PackSearchModel::PackSearchModel(QObject *parent) : QAbstractListModel{parent}
);
}
PackSearchModel::~PackSearchModel()
{
if(m_paginator)
{
delete m_paginator;
}
}
auto PackSearchModel::rowCount(const QModelIndex &) const -> int
{
if(m_paginator != nullptr)
@@ -185,6 +193,16 @@ auto PackSearchModel::resetDataModel() -> void
endInsertRows();
}
auto PackSearchModel::boundaryCheck(qsizetype index) const -> bool
{
if(index < 0 || m_paginator == nullptr || index >= m_paginator->count())
{
return false;
}
return true;
}
auto PackSearchModel::hasNextPage() const -> bool
{
return m_paginator->page() < m_paginator->totalPages();
@@ -239,7 +257,36 @@ auto PackSearchModel::setResultsPerPage(qsizetype resultsPerPage) -> void
(
[this, resultsPerPage]
{
qsizetype difference = resultsPerPage - m_paginator->resultsPerPage();
m_paginator->setResultsPerPage(resultsPerPage);
if(difference > 0)
{
qsizetype firstIndex = m_paginator->resultsPerPage() - difference;
beginInsertRows
(
QModelIndex(),
firstIndex,
m_paginator->count() - 1
);
endInsertRows();
}
else if(difference < 0)
{
qsizetype firstIndex = m_paginator->resultsPerPage();
beginRemoveRows
(
QModelIndex(),
firstIndex,
firstIndex - difference
);
endRemoveRows();
}
}
);
}
+9
View File
@@ -78,6 +78,7 @@ public:
Q_ENUM(DataRole)
explicit PackSearchModel(QObject *parent = nullptr);
~PackSearchModel();
auto rowCount(const QModelIndex &parent = QModelIndex()) const -> int override;
@@ -311,6 +312,14 @@ signals:
auto queryChanged() -> void;
private:
/**
* @brief boundaryCheck
* Helper function to check the requested index boundary
* @param index
* @return
*/
auto boundaryCheck(qsizetype index) const -> bool;
/**
* Data role map that connects PackSearchModel::DataRole to it's QML accessor name
*/
@@ -40,7 +40,7 @@ public:
QString::fromUtf8(KOMPLEX_ENDPOINT_PACKS_FEATURED)
);
controller()->setWindowSize(20);
controller()->setWindowSize(32);
}
};
+107 -36
View File
@@ -152,30 +152,121 @@ protected:
const QVariantMap sources = resultObject.value(QString::fromUtf8("src")).toObject().toVariantMap();
QMap<QString, QString> sourceMap;
QMap<QString, QString> sizeMap;
QMapIterator<QString,QVariant> sourceIterator(sources);
QString original;
while(sourceIterator.hasNext())
{
sourceIterator.next();
QString url = sourceIterator.value().toString();
sourceMap.insert(sourceIterator.key(), url);
auto matches = s_sizeExpression.match(url);
if(matches.hasMatch())
if(sourceIterator.key().toLower() == QString::fromUtf8("original"))
{
QString size = matches.captured();
size.remove(QString::fromUtf8("w="));
size.remove(QString::fromUtf8("h="));
size.replace(QLatin1Char('&'), QLatin1Char('x'));
sizeMap.insert(sourceIterator.key(), std::move(size));
original = sourceIterator.value().toString();
break;
}
}
QSize screenSize = CoreServices::screenSize();
QSize portraitSize(screenSize.height(),screenSize.width());
QSize landscapeSize(screenSize.width(),screenSize.height());
if(screenSize.height() > screenSize.width())
{
portraitSize = screenSize;
landscapeSize = QSize(screenSize.height(),screenSize.width());
}
sourceMap.clear();
sourceMap = QMap<QString,QString>
{
{
QString::fromUtf8("original"),
original
},
{
QString::fromUtf8("screen"),
QString::fromUtf8("%1?auto=compress&cs=tinysrgb&fit=crop&h=%2&w=%3").arg(
original,
QString::number(screenSize.height()),
QString::number(screenSize.width())
)
},
{
QString::fromUtf8("portrait"),
QString::fromUtf8("%1?auto=compress&cs=tinysrgb&fit=crop&h=%2&w=%3").arg(
original,
QString::number(portraitSize.height()),
QString::number(portraitSize.width())
)
},
{
QString::fromUtf8("landscape"),
QString::fromUtf8("%1?auto=compress&cs=tinysrgb&fit=crop&h=%2&w=%3").arg(
original,
QString::number(landscapeSize.height()),
QString::number(landscapeSize.width())
)
},
{
QString::fromUtf8("backgroundPortrait"),
QString::fromUtf8("%1?auto=compress&cs=tinysrgb&fit=crop&h=%2&w=%3").arg(
original,
QString::number(portraitSize.height() / 2),
QString::number(portraitSize.width() / 2)
)
},
{
QString::fromUtf8("backgroundLandscape"),
QString::fromUtf8("%1?auto=compress&cs=tinysrgb&fit=crop&h=%2&w=%3").arg(
original,
QString::number(landscapeSize.height() / 2),
QString::number(landscapeSize.width() / 2)
)
},
{
QString::fromUtf8("thumbnail"),
QString::fromUtf8("%1?auto=compress&cs=tinysrgb&fit=crop&h=144&w=256%3").arg(
original
)
},
{
QString::fromUtf8("largeThumbnail"),
QString::fromUtf8("%1?auto=compress&cs=tinysrgb&fit=crop&h=450&w=800%3").arg(
original
)
}
};
QMap<QString, QString> sizeMap =
{
{
QString::fromUtf8("original"),
QString()
},
{
QString::fromUtf8("screen"),
QString::fromUtf8("%1x%2").arg(
QString::number(screenSize.width()),
QString::number(screenSize.height())
)
},
{
QString::fromUtf8("portrait"),
QString::fromUtf8("%1x%2").arg(
QString::number(portraitSize.width()),
QString::number(portraitSize.height())
)
},
{
QString::fromUtf8("landscape"),
QString::fromUtf8("%1x%2").arg(
QString::number(landscapeSize.width()),
QString::number(landscapeSize.height())
)
}
};
fetchedResults.append(
{
resultObject.value(QString::fromUtf8("alt")).toString(),
@@ -214,8 +305,8 @@ protected:
auto getNetworkReply(const QNetworkRequest &request) const -> QNetworkReply*
{
QEventLoop loop;
QWeakPointer<QNetworkAccessManager> managerReference = CoreServices::networkAccessManager();
QWeakPointer<QNetworkAccessManager> managerReference = CoreServices::networkAccessManager();
QSharedPointer<QNetworkAccessManager> manager = managerReference.toStrongRef();
LOG_ERROR_X(!manager,
@@ -238,6 +329,8 @@ protected:
loop.exec();
}
manager.clear();
LOG_ERROR_X(reply->error() != QNetworkReply::NoError,
"NewestPacksPaginator::getNetworkReply",
reply->errorString().toStdString().c_str(),
@@ -247,28 +340,6 @@ protected:
return reply;
}
/**
* @brief getDocument
* Extracts the JSON document from the server reply
* @param reply
* @return
*/
[[nodiscard]]
auto getDocument(QNetworkReply *reply) const -> QJsonDocument
{
QByteArray data = reply->readAll();
QJsonParseError documentError;
QJsonDocument document = QJsonDocument::fromJson(data, &documentError);
LOG_ERROR_X(documentError.error != QJsonParseError::NoError,
"NewestPacksPaginator::getDocument",
documentError.errorString().toStdString().c_str(),
{}
);
return document;
}
/**
* @brief setUri
* Sets the API Endpoint URI to fetch from
@@ -254,28 +254,6 @@ protected:
return reply;
}
/**
* @brief getDocument
* Extracts the JSON document from the server reply
* @param reply
* @return
*/
[[nodiscard]]
auto getDocument(QNetworkReply *reply) const -> QJsonDocument
{
QByteArray data = reply->readAll();
QJsonParseError documentError;
QJsonDocument document = QJsonDocument::fromJson(data, &documentError);
LOG_ERROR_X(documentError.error != QJsonParseError::NoError,
"NewestPacksPaginator::getDocument",
documentError.errorString().toStdString().c_str(),
{}
);
return document;
}
/**
* @brief setUri
* Sets the API Endpoint URI to fetch from
@@ -62,35 +62,35 @@ public:
return m_uri;
}
/**
* @brief query
* Current query string, if the API Endpoint supports queries
* @return
*/
[[nodiscard]]
auto query() const -> QString
{
return m_query;
}
// /**
// * @brief query
// * Current query string, if the API Endpoint supports queries
// * @return
// */
// [[nodiscard]]
// auto query() const -> QString
// {
// return m_query;
// }
/**
* @brief setQuery
* Sets the new query string and updates the controller
* @param query
*/
auto setQuery(const QString &query) -> void
{
if(query == m_query)
{
return;
}
// /**
// * @brief setQuery
// * Sets the new query string and updates the controller
// * @param query
// */
// auto setQuery(const QString &query) -> void
// {
// if(query == m_query)
// {
// return;
// }
m_query = query;
Q_EMIT queryChanged();
// m_query = query;
// Q_EMIT queryChanged();
reset();
setOffset(0);
}
// reset();
// setOffset(0);
// }
signals:
/**
@@ -120,7 +120,7 @@ protected:
{}
);
if(queryable() && query().isEmpty())
if(queryable() && query().isEmpty())
{
return {};
}
@@ -139,7 +139,7 @@ protected:
{
{
QByteArray("query"),
m_query.toUtf8()
query().toUtf8()
},
{
QByteArray("offset"),
@@ -303,11 +303,11 @@ private:
*/
QString m_uri;
/**
* @brief m_query
* The query string used in fetch operations
*/
QString m_query;
// /**
// * @brief m_query
// * The query string used in fetch operations
// */
// QString m_query;
};
#endif // WALLPAPERPAGINATOR_H
+203
View File
@@ -0,0 +1,203 @@
#include "videoitemmodel.h"
VideoItemModel::VideoItemModel(QObject *parent) : QAbstractListModel{parent}
{
QObject::connect(
this,
&VideoItemModel::dataEntryChanged,
this,
&VideoItemModel::resetDataModel
);
}
auto VideoItemModel::rowCount(const QModelIndex &) const -> int
{
return m_dataEntry.files.count();
}
auto VideoItemModel::data(const QModelIndex &index, int role) const -> QVariant
{
if(!boundaryCheck(index.row()))
{
return {};
}
VideoEntry dataPoint = m_dataEntry.files.at(index.row());
QVariant data;
switch(static_cast<DataRole>(role))
{
case UuidRole:
data = dataPoint.id;
break;
case UrlRole:
data = dataPoint.url;
break;
case HeightRole:
data = dataPoint.height;
break;
case WidthRole:
data = dataPoint.width;
break;
case FileTypeRole:
data = dataPoint.type;
break;
case FpsRole:
data = dataPoint.fps;
break;
case SizeRole:
data = dataPoint.size;
break;
case QualityRole:
data = dataPoint.quality;
break;
case TextRole:
data = friendlyText(dataPoint);
break;
}
return data;
}
auto VideoItemModel::index(int row, int column, const QModelIndex &parent) const -> QModelIndex
{
Q_UNUSED(parent)
if(!boundaryCheck(row))
{
return {};
}
return createIndex(row, column, &m_dataEntry.files[row]);
}
auto VideoItemModel::columnCount(const QModelIndex &) const -> int
{
return 0;
}
auto VideoItemModel::parent(const QModelIndex &) const -> QModelIndex
{
return {};
}
auto VideoItemModel::setData(const QModelIndex &, const QVariant &, int) -> bool
{
return false;
}
auto VideoItemModel::state() const -> VideoItemModel::State
{
return m_state;
}
auto VideoItemModel::setState(State state) -> void
{
if (m_state == state)
{
return;
}
m_state = state;
emit stateChanged();
}
auto VideoItemModel::resetState() -> void
{
setState(Idle);
}
auto VideoItemModel::resetDataModel() -> void
{
if(m_lastCount > 0)
{
//invalidate previous model data
beginRemoveRows(QModelIndex(), 0, m_lastCount - 1);
endRemoveRows();
}
m_lastCount = m_dataEntry.files.count();
//signal new data
beginInsertRows(QModelIndex(), 0, m_dataEntry.files.count() - 1);
endInsertRows();
}
auto VideoItemModel::boundaryCheck(qsizetype index) const -> bool
{
if(index < 0 || index >= m_dataEntry.files.count())
{
return false;
}
return true;
}
auto VideoItemModel::setDataEntry(const VideoCache &entry) -> void
{
if(entry == m_dataEntry)
{
return;
}
m_dataEntry = entry;
Q_EMIT dataEntryChanged();
}
auto VideoItemModel::friendlyText(const VideoEntry &video) const -> QString
{
qint64 size = video.size;
qint64 sizeIndex = 0;
while(size >= 1000)
{
size = static_cast<qint64>(std::ceil(static_cast<qreal>(size) / 1000));
++sizeIndex;
}
if(!video.quality.isEmpty())
{
return QString("%1/%2 [%3x%4 %5FPS] (%6%7)").arg
(
video.quality.toUpper(),
video.type.toUpper(),
QString::number(video.width),
QString::number(video.height),
QString::number(video.fps),
QString::number(size),
m_sizeTexts[sizeIndex]
);
}
return QString("%1 [%2x%3 %4FPS] (%5%6)").arg
(
video.type.toUpper(),
QString::number(video.width),
QString::number(video.height),
QString::number(video.fps),
QString::number(size),
m_sizeTexts[sizeIndex]
);
}
auto VideoItemModel::roleNames() const -> QHash<int, QByteArray>
{
return m_dataRoles;
}
auto VideoItemModel::errorString() const -> QString
{
return m_errorString;
}
auto VideoItemModel::setErrorString(const QString &errorString) -> void
{
if (m_errorString == errorString)
{
return;
}
m_errorString = errorString;
emit errorStringChanged();
}
+294
View File
@@ -0,0 +1,294 @@
/*
* Komplex Wallpaper Engine
* Copyright (C) 2026 @DigitalArtifex
* https://digitalartifex.dev - https://github.com/DigitalArtifex
*
* This program 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, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 this program. If not, see <https://www.gnu.org/licenses/>
*/
#ifndef VideoItemModel_H
#define VideoItemModel_H
#include <QObject>
#include <QQmlEngine>
#include <QList>
#include <QStandardPaths>
#include <QDir>
#include <QFile>
#include <QFileInfo>
#include <QJsonParseError>
#include <QJsonDocument>
#include <QJsonObject>
#include <QJsonArray>
#include <QAbstractListModel>
#include <QProcess>
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QNetworkRequest>
#include <QEventLoop>
#include <QtConcurrent/QtConcurrentRun>
#include "common/komplex_global.h"
#include "common/videocache.h"
/**
* @brief The VideoItemModel class
*/
class KOMPLEX_EXPORT VideoItemModel : public QAbstractListModel
{
Q_OBJECT
QML_ELEMENT
public:
/**
* @brief Used to control the UI state
*/
enum State
{
Idle,
Loading,
Error
};
Q_ENUM(State)
/**
* @brief Data roles used by the view to request data
*/
enum DataRole {
UuidRole = Qt::UserRole + 1,
FileTypeRole,
FpsRole,
HeightRole,
WidthRole,
SizeRole,
UrlRole,
QualityRole,
TextRole
};
Q_ENUM(DataRole)
explicit VideoItemModel(QObject *parent = nullptr);
auto rowCount(const QModelIndex &parent = QModelIndex()) const -> int override;
/**
* @brief data
* Used by the view to pull data from the model.
* Required by QAbstractListModel
* @param index
* @param role
* @return
*/
auto data(
const QModelIndex &index,
int role = Qt::DisplayRole
) const -> QVariant override;
/**
* @brief index
* Used by the view to create an index for the data point.
* Required by QAbstractListModel
* @param row
* @param column
* @param parent
* @return
*/
auto index(
int row,
int column,
const QModelIndex &parent = QModelIndex()
) const -> QModelIndex override;
/**
* @brief columnCount
* Required by QAbstractListModel, but just returns 0
* since we dont use cols
* @param parent
* @return
*/
auto columnCount(
const QModelIndex &parent = QModelIndex()
) const -> int override;
/**
* @brief parent
* @param index
* @return
*/
auto parent(const QModelIndex &index) const -> QModelIndex override;
/**
* @brief setData
* Required by QAbstractListModel but not used
* @param index
* @param value
* @param role
* @return true always
*/
auto setData(
const QModelIndex &index,
const QVariant &value,
int role = Qt::EditRole
) -> bool override;
/**
* @brief state
* Current Model State
* @return
*/
auto state() const -> State;
/**
* @brief roleNames
* Used to tell the view what data roles are available
* and their QML friendly names
* @return
*/
auto roleNames() const -> QHash<int, QByteArray> override;
/**
* @brief errorString
* User-friendly error string to be reported to the user
* @return
*/
auto errorString() const -> QString;
/**
* @brief setDataEntry
* Set the dataEntry from the Search or List model
* @param entry
*/
auto setDataEntry(const VideoCache &entry) -> void;
/**
* @brief friendlyText
* Generates the friendly text for comboboxes and the like
* @param video
* @return
*/
auto friendlyText(const VideoEntry &video) const -> QString;
protected:
/**
* @brief setErrorString
* @param errorString
*/
auto setErrorString(const QString &errorString) -> void;
/**
* @brief setState
* @param state
*/
auto setState(State state) -> void;
/**
* @brief resetState
*/
auto resetState() -> void;
/**
* @brief resetDataModel
* Clears the data model and creates a new one if data exists
*/
auto resetDataModel() -> void;
signals:
auto dataEntryChanged() -> void;
/**
* @brief stateChanged
* Signaled when reported state changes
*/
auto stateChanged() -> void;
/**
* @brief errorStringChanged
* Signaled when reported error message changes
*/
auto errorStringChanged() -> void;
private:
/**
* @brief boundaryCheck
* Helper function to check the requested index boundary
* @param index
* @return
*/
auto boundaryCheck(qsizetype index) const -> bool;
/**
* Size texts used in friendlyText()
*/
QStringList m_sizeTexts
{
QString::fromUtf8("B"),
QString::fromUtf8("KB"),
QString::fromUtf8("MB"),
QString::fromUtf8("GB")
};
/**
* Data role map that connects VideoItemModel::DataRole to it's QML accessor name
*/
static inline const QHash<int, QByteArray> m_dataRoles =
{
{
static_cast<int>(UuidRole),
QByteArray("uuid")
},
{
static_cast<int>(FileTypeRole),
QByteArray("fileType")
},
{
static_cast<int>(FpsRole),
QByteArray("fps")
},
{
static_cast<int>(HeightRole),
QByteArray("height")
},
{
static_cast<int>(WidthRole),
QByteArray("width")
},
{
static_cast<int>(SizeRole),
QByteArray("size")
},
{
static_cast<int>(UrlRole),
QByteArray("url")
},
{
static_cast<int>(QualityRole),
QByteArray("quality")
},
{
static_cast<int>(TextRole),
QByteArray("text")
}
};
State m_state = Idle;
QString m_errorString = QString();
VideoCache m_dataEntry;
qint64 m_lastCount = 0;
Q_PROPERTY(State state READ state WRITE setState RESET resetState NOTIFY stateChanged FINAL)
Q_PROPERTY(QString errorString READ errorString WRITE setErrorString NOTIFY errorStringChanged FINAL)
};
Q_DECLARE_METATYPE(VideoItemModel)
#endif // VideoItemModel_H
+81 -10
View File
@@ -2,6 +2,7 @@
VideoSearchModel::VideoSearchModel(QObject *parent) : QAbstractListModel{parent}
{
m_itemModel = new VideoItemModel(this);
m_paginator = new VideoSearchPaginator(this);
PaginationNotifier *notifier = static_cast<PaginationNotifier*>(m_paginator);
@@ -62,6 +63,19 @@ VideoSearchModel::VideoSearchModel(QObject *parent) : QAbstractListModel{parent}
);
}
VideoSearchModel::~VideoSearchModel()
{
if(m_paginator)
{
delete m_paginator;
}
if(m_itemModel)
{
delete m_itemModel;
}
}
auto VideoSearchModel::rowCount(const QModelIndex &) const -> int
{
if(m_paginator != nullptr)
@@ -74,18 +88,12 @@ auto VideoSearchModel::rowCount(const QModelIndex &) const -> int
auto VideoSearchModel::data(const QModelIndex &index, int role) const -> QVariant
{
if(m_paginator == nullptr)
{
return {};
}
if(index.row() < 0 || index.row() >= m_paginator->count() )
if(!boundaryCheck(index.row()))
{
return {};
}
VideoCache dataPoint = m_paginator->at(index.row());
QVariant data;
switch(static_cast<DataRole>(role))
@@ -126,8 +134,10 @@ auto VideoSearchModel::index(int row, int column, const QModelIndex &parent) con
{
Q_UNUSED(parent)
if(row < 0 || row >= m_paginator->count())
if(!boundaryCheck(row))
{
return {};
}
return createIndex(row, column, &m_paginator[row]);
}
@@ -171,14 +181,45 @@ auto VideoSearchModel::resetState() -> void
auto VideoSearchModel::resetDataModel() -> void
{
//invalidate previous model data
beginRemoveRows(QModelIndex(), 0, m_paginator->count());
endRemoveRows();
if(m_lastCount > 0)
{
beginRemoveRows(QModelIndex(), 0, m_lastCount - 1);
endRemoveRows();
}
m_lastCount = m_paginator->count();
//signal new data
beginInsertRows(QModelIndex(), 0, m_paginator->count() - 1);
endInsertRows();
}
auto VideoSearchModel::setItem(qint64 index) -> bool
{
if(!boundaryCheck(index))
{
return false;
}
m_itemModel->setDataEntry(m_paginator->at(index));
return true;
}
auto VideoSearchModel::boundaryCheck(qsizetype index) const -> bool
{
if(index < 0 || m_paginator == nullptr || index >= m_paginator->count())
{
return false;
}
return true;
}
auto VideoSearchModel::itemModel() const -> VideoItemModel*
{
return m_itemModel;
}
auto VideoSearchModel::hasNextPage() const -> bool
{
return m_paginator->page() < m_paginator->totalPages();
@@ -233,7 +274,37 @@ auto VideoSearchModel::setResultsPerPage(qsizetype resultsPerPage) -> void
(
[this, resultsPerPage]
{
qsizetype difference = resultsPerPage - m_paginator->resultsPerPage();
m_paginator->setResultsPerPage(resultsPerPage);
m_lastCount = m_paginator->resultsPerPage();
if(difference > 0)
{
qsizetype firstIndex = m_paginator->resultsPerPage() - difference;
beginInsertRows
(
QModelIndex(),
firstIndex,
m_paginator->count() - 1
);
endInsertRows();
}
else if(difference < 0)
{
qsizetype firstIndex = m_paginator->resultsPerPage();
beginRemoveRows
(
QModelIndex(),
firstIndex,
firstIndex - difference
);
endRemoveRows();
}
}
);
}
+24
View File
@@ -38,6 +38,7 @@
#include <QtConcurrent/QtConcurrentRun>
#include "paginators/videosearchpaginator.h"
#include "videoitemmodel.h"
/**
* @brief The VideoSearchModel class
@@ -76,6 +77,7 @@ public:
Q_ENUM(DataRole)
explicit VideoSearchModel(QObject *parent = nullptr);
~VideoSearchModel();
auto rowCount(const QModelIndex &parent = QModelIndex()) const -> int override;
@@ -233,6 +235,16 @@ public:
*/
auto hasPreviousPage() const -> bool;
/**
* @brief setItem
* Sets the current itemModel to the item at the specified index
* @param index
* @return
*/
Q_INVOKABLE auto setItem(qint64 index) -> bool;
VideoItemModel *itemModel() const;
protected:
/**
@@ -309,6 +321,14 @@ signals:
auto queryChanged() -> void;
private:
/**
* @brief boundaryCheck
* Helper function to check the requested index boundary
* @param index
* @return
*/
auto boundaryCheck(qsizetype index) const -> bool;
/**
* Data role map that connects VideoSearchModel::DataRole to it's QML accessor name
*/
@@ -356,6 +376,9 @@ private:
QString m_errorString = QString();
VideoSearchPaginator *m_paginator = nullptr;
VideoItemModel *m_itemModel = nullptr;
qint64 m_lastCount = 0;
Q_PROPERTY(State state READ state WRITE setState RESET resetState NOTIFY stateChanged FINAL)
Q_PROPERTY(QString errorString READ errorString WRITE setErrorString NOTIFY errorStringChanged FINAL)
@@ -366,6 +389,7 @@ private:
Q_PROPERTY(bool hasNextPage READ hasNextPage NOTIFY pageChanged FINAL)
Q_PROPERTY(bool hasPreviousPage READ hasPreviousPage NOTIFY pageChanged FINAL)
Q_PROPERTY(QString query READ query WRITE setQuery NOTIFY queryChanged FINAL)
Q_PROPERTY(VideoItemModel *itemModel READ itemModel CONSTANT FINAL)
};
Q_DECLARE_METATYPE(VideoSearchModel)