diff --git a/client/thumbnailprovider.cpp b/client/thumbnailprovider.cpp index bcf5545e..16dbe78f 100644 --- a/client/thumbnailprovider.cpp +++ b/client/thumbnailprovider.cpp @@ -85,13 +85,13 @@ class AbstractThumbnailResponse : public QQuickImageResponse { QQuickTextureFactory* textureFactory() const override { QReadLocker _(&lock); - return QQuickTextureFactory::textureFactoryForImage(image); + return QQuickTextureFactory::textureFactoryForImage(result.value_or(QImage())); } QString errorString() const override { QReadLocker _(&lock); - return errorStr; + return result.has_value() ? QString() : result.error(); } void cancel() override @@ -119,93 +119,42 @@ private slots: const auto* currentRoom = timeline->currentRoom(); if (!currentRoom) { - finish({}, NoConnectionError); + finish(NoConnectionError); return; } - job = currentRoom->connection()->getThumbnail(mediaId, requestedSize); - - // Connect to any possible outcome including abandonment - // to make sure the QML thread is not left stuck forever. - connect(job, &BaseJob::finished, this, [this] { - Q_ASSERT(job->error() != BaseJob::Pending); - if (job->error() == BaseJob::Success) { - qCDebug(THUMBNAILS).noquote() - << "Thumbnail for" << mediaId - << "ready, actual size:" << job->thumbnail().size(); - finish(job->thumbnail()); - } else if (job->error() == BaseJob::Abandoned) { + // Save the future so that we could cancel it + futureResult = + Quotient::JobHandle(currentRoom->connection()->getThumbnail(mediaId, requestedSize)) + .then(this, + [this](const QImage& thumbnail) { + qCDebug(THUMBNAILS).noquote() + << "Thumbnail for" << mediaId + << "ready, actual size:" << thumbnail.size(); + return result_type { thumbnail }; + }, + [this](const Quotient::MediaThumbnailJob* job) { + qCWarning(THUMBNAILS).nospace() + << "No valid thumbnail for" << mediaId << ": " << job->errorString(); + return result_type { job->errorString() }; + }); + // NB: Make sure to connect to any possible outcome including cancellation so that + // the QML thread is not left stuck forever. + futureResult + .onCanceled([this] { qCDebug(THUMBNAILS) << "Request cancelled for" << mediaId; - finish({}, tr("Image request has been cancelled")); - } else { - qCWarning(THUMBNAILS).nospace() - << "No valid thumbnail for" << mediaId << ": " - << job->errorString(); - finish({}, job->errorString()); - } - job = nullptr; - }); + return tr("Image request has been cancelled"); // Turn it to an error + }) + .then([this] (const result_type& r) { finish(r); }); } void doCancel() override { - if (job) { - Q_ASSERT(QThread::currentThread() == job->thread()); - job->abandon(); - } + futureResult.cancel(); } private: - QPointer job = nullptr; -}; - -class AvatarResponse : public AbstractThumbnailResponse { - Q_OBJECT -public: - using AbstractThumbnailResponse::AbstractThumbnailResponse; - -private: - void startRequest() override - { - Q_ASSERT(QThread::currentThread() == qApp->thread()); - - Quotient::Room* currentRoom = timeline->currentRoom(); - if (!currentRoom) { - finish({}, NoConnectionError); - return; - } - - // NB: both Room:avatar() and User::avatar() invocations return an image - // available right now and, if needed, request one with the better - // resolution asynchronously. To get this better resolution image, - // Avatar elements in QML should call Avatar.reload() in response to - // Room::avatarChanged() and Room::memberAvatarChanged() (sic!) - // respectively. - const auto& w = requestedSize.width(); - const auto& h = requestedSize.height(); - if (mediaId.startsWith(u'!')) { - if (mediaId != currentRoom->id()) { - currentRoom = currentRoom->connection()->room(mediaId); - Q_ASSERT(currentRoom != nullptr); - } - // As of libQuotient 0.8, Room::avatar() is the only call in the - // Room::avatar*() family that substitutes the counterpart's - // avatar for a direct chat avatar. - prepareResult(currentRoom->avatar(w, h)); - return; - } - - auto* user = currentRoom->user(mediaId); - Q_ASSERT(user != nullptr); - prepareResult(user->avatar(w, h, currentRoom)); - } - - void prepareResult(const QImage& avatar) - { - qCDebug(THUMBNAILS).noquote() << "Returning avatar for" << mediaId - << "with size:" << avatar.size(); - finish(avatar); - } + QFuture> futureResult; }; #include "thumbnailprovider.moc" // Because we define a Q_OBJECT in the cpp file @@ -226,11 +175,6 @@ class ImageProviderTemplate : public QQuickAsyncImageProvider { Q_DISABLE_COPY(ImageProviderTemplate) }; -QQuickAsyncImageProvider* makeAvatarProvider(TimelineWidget* parent) -{ - return new ImageProviderTemplate(parent); -} - QQuickAsyncImageProvider* makeThumbnailProvider(TimelineWidget* parent) { return new ImageProviderTemplate(parent); diff --git a/client/thumbnailprovider.h b/client/thumbnailprovider.h index e2797f27..e12cc5ac 100644 --- a/client/thumbnailprovider.h +++ b/client/thumbnailprovider.h @@ -12,5 +12,4 @@ class TimelineWidget; -QQuickAsyncImageProvider* makeAvatarProvider(TimelineWidget* parent); QQuickAsyncImageProvider* makeThumbnailProvider(TimelineWidget* parent); diff --git a/client/timelinewidget.cpp b/client/timelinewidget.cpp index 7feba508..d6de0507 100644 --- a/client/timelinewidget.cpp +++ b/client/timelinewidget.cpp @@ -44,7 +44,6 @@ TimelineWidget::TimelineWidget(ChatRoomWidget* chatRoomWidget) setResizeMode(SizeRootObjectToView); - engine()->addImageProvider("avatar"_ls, makeAvatarProvider(this)); engine()->addImageProvider("thumbnail"_ls, makeThumbnailProvider(this)); auto* ctxt = rootContext(); @@ -179,13 +178,7 @@ void TimelineWidget::showMenu(int index, const QString& hoveredLink, auto menu = new QMenu(this); menu->setAttribute(Qt::WA_DeleteOnClose); - const auto* plEvt = - currentRoom()->currentState().get(); - const auto localUserId = currentRoom()->localUser()->id(); - const int userPl = plEvt ? plEvt->powerLevelForUser(localUserId) : 0; - const auto* modelUser = - modelIndex.data(MessageEventModel::AuthorRole).value(); - if (!plEvt || userPl >= plEvt->redact() || localUserId == modelUser->id()) + if (currentRoom()->canRedact(eventId)) menu->addAction(QIcon::fromTheme("edit-delete"), tr("Redact"), this, [this, eventId] { currentRoom()->redactEvent(eventId); });