diff --git a/src/attica/atticaprovider.cpp b/src/attica/atticaprovider.cpp --- a/src/attica/atticaprovider.cpp +++ b/src/attica/atticaprovider.cpp @@ -55,6 +55,8 @@ SLOT(authenticationCredentialsMissing(Provider))); connect(this, &Provider::loadComments, this, &AtticaProvider::loadComments); connect(this, &Provider::loadPerson, this, &AtticaProvider::loadPerson); + connect(this, &Provider::postComment, this, &AtticaProvider::postComment); + connect(this, &Provider::editCredentials, this, &AtticaProvider::credentialsEditorRequested); } AtticaProvider::AtticaProvider(const Attica::Provider &provider, const QStringList &categories, const QString& additionalAgentInformation) @@ -423,6 +425,22 @@ emit personLoaded(author); } +void AtticaProvider::postComment(const EntryInternal &entry, const QString& title, const QString& text, const QString& replyTo) +{ + ItemPostJob* job = m_provider.addNewComment(Attica::Comment::ContentComment, entry.uniqueId(), QStringLiteral(""), replyTo, title, text); + connect(job, &BaseJob::finished, this, &AtticaProvider::postedComment); + job->start(); +} + +void AtticaProvider::postedComment(Attica::BaseJob *baseJob) +{ + if (!jobSuccess(baseJob)) { + return; + } + + emit commentPosted(baseJob->metadata().statusCode()); +} + void AtticaProvider::accountBalanceLoaded(Attica::BaseJob *baseJob) { if (!jobSuccess(baseJob)) { @@ -484,6 +502,16 @@ return entries; } +void AtticaProvider::credentialsEditorRequested() +{ + // TODO KF6: Again a note about the future - this will need replacing with the future editCredentials call in attica's platformdependent + m_provider.saveCredentials(QString{}, QString{}); +} + +bool AtticaProvider::userCanVote() { + return m_provider.hasCredentials(); +} + void AtticaProvider::vote(const EntryInternal &entry, uint rating) { PostJob *job = m_provider.voteForContent(entry.uniqueId(), rating); diff --git a/src/attica/atticaprovider_p.h b/src/attica/atticaprovider_p.h --- a/src/attica/atticaprovider_p.h +++ b/src/attica/atticaprovider_p.h @@ -75,11 +75,16 @@ * @see Provider::loadPerson(const QString &username) */ Q_SLOT void loadPerson(const QString &username); + /** + * The slot which causes the Attica provider to attempt to post a comment + * @see Provider::postComment(const EntryInternal &entry, const QString& title, const QString& text, const QString& replyTo); + */ + Q_SLOT void postComment(const EntryInternal &entry, const QString& title, const QString& text, const QString& replyTo); - bool userCanVote() override - { - return true; - } + // TODO KF6: This wants to become an overload for the when-we-can-virtualize Provider::editCredentials + void credentialsEditorRequested(); + + bool userCanVote() override; void vote(const EntryInternal &entry, uint rating) override; bool userCanBecomeFan() override @@ -100,6 +105,7 @@ void detailsLoaded(Attica::BaseJob *job); void loadedComments(Attica::BaseJob *job); void loadedPerson(Attica::BaseJob *job); + void postedComment(Attica::BaseJob *job); private: void checkForUpdates(); diff --git a/src/core/commentsmodel.h b/src/core/commentsmodel.h --- a/src/core/commentsmodel.h +++ b/src/core/commentsmodel.h @@ -93,6 +93,33 @@ void setEntry(const KNSCore::EntryInternal &newEntry); Q_SIGNAL void entryChanged(); + /** + * @brief Add a new comment to an entry + * + * Creates a comment with a title and text on the entry with the requested ID + * + * @param title the title of the comment + * @param text the main text of the comment + * @param id the ID of the parent comment, if this comment is a reply + */ + Q_INVOKABLE void createComment(QString title, QString text, QString id); + + /** + * @brief Convenience function to send a comment and a vote immediately after each other + * + * Setting score on an entry immediately following creating a comment will be perceived + * as marking that comment as a review, and set the score of the review accordingly. + * This function is a convenienct way of ensuring this will definitely happen in the + * correct order and with the expected timing. + * + * @note there is no way to call this with a parent comment, since reviews should always be a top level comment + * + * @param title The title of the review + * @param text The main text of the review + * @param rating The rating you wish to set for the review (0 means remove this user's vote, 1-100 is the score the user wishes to give) + * @see KNSCore::CommmentsModel::createComment(QString title, QString text) + */ + Q_INVOKABLE void createReview(QString title, QString text, int rating); private: class Private; Private *d; diff --git a/src/core/commentsmodel.cpp b/src/core/commentsmodel.cpp --- a/src/core/commentsmodel.cpp +++ b/src/core/commentsmodel.cpp @@ -223,3 +223,27 @@ d->fetch(Private::ClearModel); emit entryChanged(); } + +void KNSCore::CommentsModel::createComment(QString title, QString text, QString id) +{ + if (d->engine && d->entry.isValid() && d->engine->userCanVote(d->entry)) { + QSharedPointer provider = d->engine->provider(d->entry.providerId()); + provider->postComment(d->entry, title, text, id); + } +} + +void KNSCore::CommentsModel::createReview(QString title, QString text, int rating) +{ + if (d->engine && d->entry.isValid() && d->engine->userCanVote(d->entry)) { + QSharedPointer provider = d->engine->provider(d->entry.providerId()); + QMetaObject::Connection * const connection = new QMetaObject::Connection; + *connection = connect(provider.data(), &Provider::commentPosted, [this, connection, provider, rating](int status){ + QObject::disconnect(*connection); + if (status == 100) { + provider->vote(d->entry, rating); + } + delete connection; + }); + provider->postComment(d->entry, title, text, QStringLiteral("")); + } +} diff --git a/src/core/engine.h b/src/core/engine.h --- a/src/core/engine.h +++ b/src/core/engine.h @@ -465,6 +465,13 @@ */ QSharedPointer defaultProvider() const; + /** + * Request that the provider with the passed ID show an editor for user credentials + * @param providerID The ID of the provider you wish to show a credentials editor for + * @since 5.67 + */ + void editCredentials(const QString& providerID); + /** * This function will return an instance of a model which contains comments for * the entry passed to it. The model may be empty (if there are no comments for diff --git a/src/core/engine.cpp b/src/core/engine.cpp --- a/src/core/engine.cpp +++ b/src/core/engine.cpp @@ -817,6 +817,14 @@ return nullptr; } +void KNSCore::Engine::editCredentials(const QString& providerID) +{ + QSharedPointer provider = m_providers.value(providerID); + if (provider) { + provider->editCredentials(); + } +} + KNSCore::CommentsModel *KNSCore::Engine::commentsForEntry(const KNSCore::EntryInternal &entry) { CommentsModel *model = d->commentsModels[entry]; diff --git a/src/core/provider.h b/src/core/provider.h --- a/src/core/provider.h +++ b/src/core/provider.h @@ -171,6 +171,40 @@ * @since 5.63 */ Q_SIGNAL void loadPerson(const QString &username); + /** + * Request posting of a comment on an entry, optionally as a reply to another comment + * The engine listens to the commentPosted() signal for the result + * + * @note Implementation detail: All subclasses should connect to this signal + * and point it at a slot which does the actual work, if they support comments. + * + * TODO: KF6 This should be a virtual function, but can't do it now because BIC + * @param entry The entry to comment on + * @param title The title of the comment + * @param text The main text of the comment + * @param replyTo Some identifier for a comment this is intended as a reply to. If this is an empty string, consider it not a reply + * @since 5.67 + */ + Q_SIGNAL void postComment(const EntryInternal &entry, const QString& title, const QString& text, const QString& replyTo); + + /** + * Request that the provider backend shows a way to edit a user's credentials. + * + * This should be a fire-and-forget function, and the provider should handle + * the (non)availability of credentials internally. + * + * @note A touch of background information to follow: + * Historically, this would be done using a username/password combo directly from KNS, + * however the world has moved on somewhat from sending passwords across the internet + * a lot, and so now we pass the responsibility for credentials management to the + * specific backend (staticxml will likely not support such a thing, and the attica + * backend will use the attica-kde plugin from Plasma to perform this function, and + * if not available will not support logging in). + * + * TODO: KF6 This should be a virtual function, but can't do it now because BIC + * @since 5.67 + */ + Q_SIGNAL void editCredentials(); virtual bool userCanVote() { @@ -239,6 +273,16 @@ * @since 5.63 */ void personLoaded(const std::shared_ptr author); + /** + * Fired when an attempt to post a comment has completed. The result of the attempt can + * be gleaned from the status parameter, and should be interpreted as follows: + * 100 - successful + * 101 - content must not be empty + * 102 - message or subject must not be empty + * 103 - no permission to add a comment + * @param status Whether or not the comment was posted successfully (see above for possible values) + */ + void commentPosted(int status); void signalInformation(const QString &) const; void signalError(const QString &) const; diff --git a/src/qtquick/qml/EntryDetails.qml b/src/qtquick/qml/EntryDetails.qml --- a/src/qtquick/qml/EntryDetails.qml +++ b/src/qtquick/qml/EntryDetails.qml @@ -55,6 +55,7 @@ property int downloadCount property var downloadLinks property string providerId + property bool userCanVote NewStuff.DownloadItemsSheet { id: downloadItemsSheet @@ -173,14 +174,33 @@ QtLayouts.Layout.fillWidth: true Kirigami.LinkButton { Kirigami.FormData.label: i18n("Comments and Reviews:") - enabled: component.commentsCount > 0 - text: i18nc("A link which, when clicked, opens a new sub page with comments (comments with or without ratings) for this entry", "%1 Reviews and Comments", component.commentsCount) - onClicked: pageStack.push(commentsPage) + text: component.commentsCount > 0 + ? i18nc("A link which, when clicked, opens a new sub page with comments (comments with or without ratings) for this entry", "%1 Reviews and Comments", component.commentsCount) + : i18nc("A link which, when clicked, opens a sheet which allows the user to create a new review or comment for this entry when none exist already", "None - create one and be the first!") + onClicked: { + if (component.commentsCount > 0) { + pageStack.push(commentsPage); + } else { + if (newCommentSheet.commentsModel === null) { + newCommentSheet.commentsModel = newStuffModel.data(newStuffModel.index(component.index, 0), NewStuff.ItemsModel.CommentsModelRole) + } + newCommentSheet.newComment(""); + } + } } Private.Rating { id: ratingsItem Kirigami.FormData.label: i18n("Rating:") rating: Math.floor(component.rating / 10) + votable: true + onVoteRequested: { + if (component.userCanVote) { + // do a vote + newStuffModel.voteForItem(component.index, votedRating * 10); + } else { + newStuffModel.engine.editCredentials(component.providerId); + } + } } Kirigami.LinkButton { Kirigami.FormData.label: i18n("Homepage:") @@ -209,6 +229,11 @@ entryName: component.name entryAuthorId: component.author.name entryProviderId: component.providerId + userCanVote: component.userCanVote } } + Private.NewCommentSheet { + id: newCommentSheet + visualParent: component + } } diff --git a/src/qtquick/qml/private/EntryCommentDelegate.qml b/src/qtquick/qml/private/EntryCommentDelegate.qml --- a/src/qtquick/qml/private/EntryCommentDelegate.qml +++ b/src/qtquick/qml/private/EntryCommentDelegate.qml @@ -69,6 +69,19 @@ * The depth of the comment (in essence, how many parents the comment has) */ property int depth + /** + * Whether or not the user is able to do voting + */ + property bool userCanVote + /** + * Whether or not a user is able to create comments + * TODO KF6: Add the commenting capability to KNSCore::Provider, which can't be done easily now due to BIC issues + */ + property bool userCanComment: userCanVote + /** + * Fired when the user clicks on the reply control + */ + signal replyRequested(); spacing: 0 @@ -117,7 +130,6 @@ } QtLayouts.RowLayout { - visible: (component.title !== "" || component.score !== 0) QtLayouts.Layout.fillWidth: true QtLayouts.Layout.leftMargin: Kirigami.Units.largeSpacing Kirigami.Heading { @@ -128,8 +140,13 @@ } Rating { id: ratingStars + visible: rating > 0 rating: Math.floor(component.score / 10) } + Kirigami.LinkButton { + text: i18nc("The title for a control which allows the user to reply to a specific comment", "Reply") + onClicked: component.replyRequested() + } Item { QtLayouts.Layout.minimumWidth: Kirigami.Units.largeSpacing QtLayouts.Layout.maximumWidth: Kirigami.Units.largeSpacing diff --git a/src/qtquick/qml/private/EntryCommentsPage.qml b/src/qtquick/qml/private/EntryCommentsPage.qml --- a/src/qtquick/qml/private/EntryCommentsPage.qml +++ b/src/qtquick/qml/private/EntryCommentsPage.qml @@ -36,10 +36,22 @@ property string entryName property string entryAuthorId property string entryProviderId + property bool userCanVote property alias entryIndex: commentsModel.entryIndex property alias itemsModel: commentsModel.itemsModel title: i18nc("Title for the page containing a view of the comments for the entry", "Comments and Reviews for %1", component.entryName) actions { + main: Kirigami.Action { + text: i18nc("An action which show a sheet which allows the user to create a new comment", "Create Comment...") + icon.name: "comment-symbolic" + onTriggered: { + if (component.userCanVote) { + newCommentSheet.newComment("") + } else { + commentsModel.itemsModel.engine.editCredentials(component.entryProviderId); + } + } + } contextualActions: [ Kirigami.Action { text: i18nc("Title for the item which is checked when all comments should be shown", "Show All Comments") @@ -83,6 +95,13 @@ title: model.subject reviewText: model.text depth: model.depth + userCanVote: component.userCanVote + onReplyRequested: newCommentSheet.newComment(model.id) } } + NewCommentSheet { + id: newCommentSheet + visualParent: commentsView + commentsModel: commentsModel + } } diff --git a/src/qtquick/qml/private/NewCommentSheet.qml b/src/qtquick/qml/private/NewCommentSheet.qml new file mode 100644 --- /dev/null +++ b/src/qtquick/qml/private/NewCommentSheet.qml @@ -0,0 +1,124 @@ +/* + * Copyright (C) 2020 Dan Leinir Turthra Jensen + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) version 3, or any + * later version accepted by the membership of KDE e.V. (or its + * successor approved by the membership of KDE e.V.), which shall + * act as a proxy defined in Section 6 of version 3 of the license. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + */ + +/** + * @brief A Kirigami.OverlaySheet based component used for creating new comments + */ + +import QtQuick 2.11 +import QtQuick.Controls 2.11 as QtControls +import QtQuick.Layouts 1.11 as QtLayouts + +import org.kde.kirigami 2.7 as Kirigami + +Kirigami.OverlaySheet { + id: component; + property QtObject commentsModel; + property Item visualParent; + function newComment(replyTo) { + if (replyTo == "") { + component.isReply = false; + component.replyTo = ""; + } else { + component.isReply = true; + component.replyTo = replyTo; + } + starRating.rating = 0; + component.open(); + // Explicitly not clearing the existing comment until it gets sent off + // (to avoid accidental deletion if the user closes the sheet for any reason) + } + showCloseButton: true + property bool isReply: false + property string replyTo: "" + header: QtLayouts.ColumnLayout { + spacing: Kirigami.Units.largeSpacing + Kirigami.Heading { + QtLayouts.Layout.fillWidth: true + text: component.isReply ? i18nc("The title for a dialog which lets you write a reply to some comment", "Reply To Comment") : i18nc("The title for a dialog which lets you enter a new comment or review", "Create New Comment") + elide: Text.ElideRight + } + QtControls.Label { + QtLayouts.Layout.fillWidth: true + QtLayouts.Layout.margins: Kirigami.Units.largeSpacing + text: component.isReply + ? i18n("Enter the title and text of your reply in the fields below") + : i18n("Enter the title and text of your comment in the fields below. If you wish for this comment to be a review, also select a rating by selecting a star rating from one through five.") + wrapMode: Text.Wrap + } + } + QtLayouts.ColumnLayout { + QtLayouts.Layout.preferredWidth: component.visualParent.width - Kirigami.Units.largeSpacing * 4 + Kirigami.FormLayout { + wideMode: false + QtLayouts.Layout.fillWidth: true + Kirigami.ActionTextField { + id: newCommentTitle + QtLayouts.Layout.fillWidth: true + Kirigami.FormData.label: i18nc("Label for a text field for entering a subject or title line for a comment", "Title:") + rightActions: [ + Kirigami.Action { + iconName: "edit-clear" + visible: newCommentTitle.text !== "" + onTriggered: newCommentTitle.text = "" + } + ] + } + Rating { + id: starRating + visible: !component.isReply + QtLayouts.Layout.fillWidth: true + editable: true + Kirigami.FormData.label: i18nc("Label for a star rating selector for a review", "Rating:") + } + } + QtControls.TextArea { + id: newCommentText + placeholderText: i18nc("A placeholder text for the text area into which the user should type their comment", "Type in your comment here") + QtLayouts.Layout.fillWidth: true + QtLayouts.Layout.minimumHeight: newCommentTitle.height * 4 + } + QtLayouts.RowLayout { + Item { + QtLayouts.Layout.fillWidth: true + height: Kirigami.Units.largeSpacing + } + QtControls.Button { + text: starRating.rating > 0 ? i18nc("Label for a button which will post a new review", "Post Review") : i18nc("Label for a button which will post a new comment", "Post Comment") + enabled: newCommentTitle.text.length > 0 && newCommentText.text.length > 0 + onClicked: { + if (starRating.rating > 0) { + // Then this is a review + commentsModel.sourceModel.createReview(newCommentTitle.text, newCommentText.text, starRating.rating * 10); + } else { + // Otherwise it's a comment + commentsModel.sourceModel.createComment(newCommentTitle.text, newCommentText.text, component.replyTo); + } + // once submitted, close and clear + component.close(); + newCommentTitle.text = ""; + newCommentText.text = ""; + starRating.rating = 0; + } + } + } + } +} diff --git a/src/qtquick/qml/private/Rating.qml b/src/qtquick/qml/private/Rating.qml --- a/src/qtquick/qml/private/Rating.qml +++ b/src/qtquick/qml/private/Rating.qml @@ -19,16 +19,19 @@ import QtQuick 2.11 import QtQuick.Layouts 1.11 +import QtQuick.Controls 2.11 import org.kde.kirigami 2.0 as Kirigami RowLayout { id: view property bool editable: false + property bool votable: false property int max: 10 property int rating: 0 property real starSize: Kirigami.Units.gridUnit + signal voteRequested(int votedRating); clip: true spacing: 0 @@ -46,21 +49,33 @@ width: height source: "rating" - opacity: (view.editable && mouse.item.containsMouse ? 0.7 + opacity: ((view.editable || view.votable) && mouse.item.containsMouse ? 0.7 : index>=view.ratingIndex ? 0.2 : 1) ConditionalLoader { id: mouse anchors.fill: parent - condition: view.editable + condition: view.editable || view.votable componentTrue: MouseArea { hoverEnabled: true - onClicked: rating = (max/theRepeater.model*(index+1)) + onClicked: { + if (view.editable) { + rating = (max/theRepeater.model*(index+1)); + } else if (view.votable) { + view.voteRequested((max/theRepeater.model*(index+1))); + } + } } componentFalse: null } } } + ToolButton { + icon.name: "edit-clear" + onClicked: view.rating = 0 + enabled: view.editable && view.rating > 0 + visible: view.editable; + } } diff --git a/src/qtquick/qml/private/entrygriddelegates/BigPreviewDelegate.qml b/src/qtquick/qml/private/entrygriddelegates/BigPreviewDelegate.qml --- a/src/qtquick/qml/private/entrygriddelegates/BigPreviewDelegate.qml +++ b/src/qtquick/qml/private/entrygriddelegates/BigPreviewDelegate.qml @@ -49,7 +49,8 @@ rating: model.rating, downloadCount: model.downloadCount, downloadLinks: model.downloadLinks, - providerId: model.providerId + providerId: model.providerId, + userCanVote: model.userCanVote }); } actions: [ diff --git a/src/qtquick/qml/private/entrygriddelegates/ThumbDelegate.qml b/src/qtquick/qml/private/entrygriddelegates/ThumbDelegate.qml --- a/src/qtquick/qml/private/entrygriddelegates/ThumbDelegate.qml +++ b/src/qtquick/qml/private/entrygriddelegates/ThumbDelegate.qml @@ -140,7 +140,8 @@ rating: model.rating, downloadCount: model.downloadCount, downloadLinks: model.downloadLinks, - providerId: model.providerId + providerId: model.providerId, + userCanVote: model.userCanVote }); } } diff --git a/src/qtquick/qml/private/entrygriddelegates/TileDelegate.qml b/src/qtquick/qml/private/entrygriddelegates/TileDelegate.qml --- a/src/qtquick/qml/private/entrygriddelegates/TileDelegate.qml +++ b/src/qtquick/qml/private/entrygriddelegates/TileDelegate.qml @@ -50,7 +50,8 @@ rating: model.rating, downloadCount: model.downloadCount, downloadLinks: model.downloadLinks, - providerId: model.providerId + providerId: model.providerId, + userCanVote: model.userCanVote }); } actions: [ diff --git a/src/qtquick/quickengine.h b/src/qtquick/quickengine.h --- a/src/qtquick/quickengine.h +++ b/src/qtquick/quickengine.h @@ -108,6 +108,13 @@ Q_INVOKABLE void resetChangedEntries(); Q_SIGNAL void changedEntriesChanged(); int changedEntriesCount() const; + + /** + * Request that the provider with the passed ID show an editor for user credentials + * @param providerID The ID of the provider you wish to show a credentials editor for + * @since 5.67 + */ + Q_INVOKABLE void editCredentials(const QString& providerID); Q_SIGNALS: void message(const QString &message); void idleMessage(const QString &message); diff --git a/src/qtquick/quickengine.cpp b/src/qtquick/quickengine.cpp --- a/src/qtquick/quickengine.cpp +++ b/src/qtquick/quickengine.cpp @@ -272,3 +272,10 @@ d->changedEntries.clear(); emit changedEntriesChanged(); } + +void Engine::editCredentials(const QString& providerID) +{ + if (d->engine) { + d->engine->editCredentials(providerID); + } +} diff --git a/src/qtquick/quickitemsmodel.h b/src/qtquick/quickitemsmodel.h --- a/src/qtquick/quickitemsmodel.h +++ b/src/qtquick/quickitemsmodel.h @@ -98,6 +98,7 @@ InstalledFilesRole, UnInstalledFilesRole, RatingRole, + UserCanVoteRole, NumberOfCommentsRole, DownloadCountRole, NumberFansRole, @@ -169,10 +170,21 @@ * * @note This will simply fail quietly if the item is not installed * - * @param index The intex of the item to be adopted + * @param index The index of the item to be adopted */ Q_INVOKABLE void adoptItem(int index); + /** + * @brief Vote for an item + * + * If the user is able, this will rate the item + * + * @param index The index of the item to vote for + * @param rating The rating you wish to set for the item (0 means remove this user's vote, 1-100 is the score the user wishes to give) + * @see KNSCore::CommentsModel::createReview( + */ + Q_INVOKABLE void voteForItem(int index, int rating); + /** * @brief Fired when an entry's data changes * diff --git a/src/qtquick/quickitemsmodel.cpp b/src/qtquick/quickitemsmodel.cpp --- a/src/qtquick/quickitemsmodel.cpp +++ b/src/qtquick/quickitemsmodel.cpp @@ -122,6 +122,7 @@ {InstalledFilesRole, "installedFiles"}, {UnInstalledFilesRole, "uninstalledFiles"}, {RatingRole, "rating"}, + {UserCanVoteRole, "userCanVote"}, ///<@ While it seems strange to hold this here when core has it on Engine, this information is conceptually per-Entry {NumberOfCommentsRole, "numberOfComments"}, {DownloadCountRole, "downloadCount"}, {NumberFansRole, "numberFans"}, @@ -243,6 +244,9 @@ case RatingRole: data.setValue(entry.rating()); break; + case UserCanVoteRole: + data.setValue(d->coreEngine->userCanVote(entry)); + break; case NumberOfCommentsRole: data.setValue(entry.numberOfComments()); break; @@ -429,3 +433,13 @@ } } } + +void ItemsModel::voteForItem(int index, int rating) +{ + if (d->coreEngine) { + KNSCore::EntryInternal entry = d->model->data(d->model->index(index), Qt::UserRole).value(); + if (d->coreEngine->userCanVote(entry)) { + d->coreEngine->vote(entry, rating); + } + } +} diff --git a/src/ui/entrydetailsdialog.cpp b/src/ui/entrydetailsdialog.cpp --- a/src/ui/entrydetailsdialog.cpp +++ b/src/ui/entrydetailsdialog.cpp @@ -127,8 +127,10 @@ // Most of the voting is 20 - 80, so rate 20 as 0 stars and 80 as 5 stars int rating = qMax(0, qMin(10, (m_entry.rating() - 20) / 6)); ui->ratingWidget->setRating(rating); - connect(ui->ratingWidget, static_cast(&KRatingWidget::ratingChanged), - this, &EntryDetails::ratingChanged); + if (m_engine->userCanVote(entry)) { + connect(ui->ratingWidget, static_cast(&KRatingWidget::ratingChanged), + this, &EntryDetails::ratingChanged); + } } else { ui->ratingWidget->setVisible(false); } diff --git a/src/ui/itemsviewdelegate.cpp b/src/ui/itemsviewdelegate.cpp --- a/src/ui/itemsviewdelegate.cpp +++ b/src/ui/itemsviewdelegate.cpp @@ -81,9 +81,7 @@ rating->setMaxRating(10); rating->setHalfStepsEnabled(true); list << rating; - const KNSCore::EntryInternal entry = index.data(Qt::UserRole).value(); - connect(rating, static_cast(&KRatingWidget::ratingChanged), - this, [this, entry](unsigned int newRating){m_engine->vote(entry, newRating * 10);}); + // Not supposed to be able to actually vote from the listview, so we're not connecting the widget to anything return list; }