File indexing completed on 2024-04-28 15:29:05

0001 /*
0002     This file is part of KNewStuff2.
0003     SPDX-FileCopyrightText: 2008 Jeremy Whiting <jpwhiting@kde.org>
0004     SPDX-FileCopyrightText: 2010 Reza Fatahilah Shah <rshah0385@kireihana.com>
0005     SPDX-FileCopyrightText: 2010 Frederik Gladhorn <gladhorn@kde.org>
0006 
0007     SPDX-License-Identifier: LGPL-2.1-or-later
0008 */
0009 
0010 #include "itemsviewdelegate_p.h"
0011 
0012 #include <QApplication>
0013 #include <QMenu>
0014 #include <QPainter>
0015 #include <QProcess>
0016 #include <QToolButton>
0017 #include <knewstuff_debug.h>
0018 
0019 #include <KFormat>
0020 #include <KLocalizedString>
0021 #include <KRatingWidget>
0022 #include <KShell>
0023 
0024 #include "core/itemsmodel.h"
0025 
0026 #include "entrydetailsdialog_p.h"
0027 
0028 namespace KNS3
0029 {
0030 enum { DelegateLabel, DelegateInstallButton, DelegateDetailsButton, DelegateRatingWidget };
0031 
0032 ItemsViewDelegate::ItemsViewDelegate(QAbstractItemView *itemView, KNSCore::Engine *engine, QObject *parent)
0033     : ItemsViewBaseDelegate(itemView, engine, parent)
0034 {
0035 }
0036 
0037 ItemsViewDelegate::~ItemsViewDelegate()
0038 {
0039 }
0040 
0041 QList<QWidget *> ItemsViewDelegate::createItemWidgets(const QModelIndex &index) const
0042 {
0043     Q_UNUSED(index);
0044     QList<QWidget *> list;
0045 
0046     QLabel *infoLabel = new QLabel();
0047     infoLabel->setOpenExternalLinks(true);
0048     // not so nice - work around constness to install the event filter
0049     ItemsViewDelegate *delegate = const_cast<ItemsViewDelegate *>(this);
0050     infoLabel->installEventFilter(delegate);
0051     list << infoLabel;
0052 
0053     QToolButton *installButton = new QToolButton();
0054     installButton->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
0055     list << installButton;
0056     setBlockedEventTypes(installButton, QList<QEvent::Type>() << QEvent::MouseButtonPress << QEvent::MouseButtonRelease << QEvent::MouseButtonDblClick);
0057     connect(installButton, &QAbstractButton::clicked, this, &ItemsViewDelegate::slotInstallClicked);
0058     connect(installButton, &QToolButton::triggered, this, &ItemsViewDelegate::slotInstallActionTriggered);
0059 
0060     QToolButton *detailsButton = new QToolButton();
0061     detailsButton->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
0062     list << detailsButton;
0063     setBlockedEventTypes(detailsButton, QList<QEvent::Type>() << QEvent::MouseButtonPress << QEvent::MouseButtonRelease << QEvent::MouseButtonDblClick);
0064     connect(detailsButton, &QToolButton::clicked, this, static_cast<void (ItemsViewDelegate::*)()>(&ItemsViewDelegate::slotDetailsClicked));
0065 
0066     KRatingWidget *rating = new KRatingWidget();
0067     rating->setMaxRating(10);
0068     rating->setHalfStepsEnabled(true);
0069     list << rating;
0070     const KNSCore::EntryInternal entry = index.data(Qt::UserRole).value<KNSCore::EntryInternal>();
0071     connect(rating, &KRatingWidget::ratingChanged, this, [this, entry](int newRating) {
0072         m_engine->vote(entry, newRating * 10);
0073     });
0074 
0075     return list;
0076 }
0077 
0078 void ItemsViewDelegate::updateItemWidgets(const QList<QWidget *> widgets, const QStyleOptionViewItem &option, const QPersistentModelIndex &index) const
0079 {
0080     const KNSCore::ItemsModel *model = qobject_cast<const KNSCore::ItemsModel *>(index.model());
0081     if (!model) {
0082         qCDebug(KNEWSTUFF) << "WARNING - INVALID MODEL!";
0083         return;
0084     }
0085 
0086     const KNSCore::EntryInternal entry = index.data(Qt::UserRole).value<KNSCore::EntryInternal>();
0087 
0088     // setup the install button
0089     int margin = option.fontMetrics.height() / 2;
0090     int right = option.rect.width();
0091 
0092     QToolButton *installButton = qobject_cast<QToolButton *>(widgets.at(DelegateInstallButton));
0093     if (installButton) {
0094         if (installButton->menu()) {
0095             QMenu *buttonMenu = installButton->menu();
0096             buttonMenu->clear();
0097             installButton->setMenu(nullptr);
0098             buttonMenu->deleteLater();
0099         }
0100 
0101         bool installable = false;
0102         bool enabled = true;
0103         QString text;
0104         QIcon icon;
0105 
0106         switch (entry.status()) {
0107         case Entry::Installed:
0108             text = i18n("Uninstall");
0109             icon = m_iconDelete;
0110             break;
0111         case Entry::Updateable:
0112             text = i18n("Update");
0113             icon = m_iconUpdate;
0114             installable = true;
0115             break;
0116         case Entry::Installing:
0117             text = i18n("Installing");
0118             enabled = false;
0119             icon = m_iconUpdate;
0120             break;
0121         case Entry::Updating:
0122             text = i18n("Updating");
0123             enabled = false;
0124             icon = m_iconUpdate;
0125             break;
0126         case Entry::Downloadable:
0127             text = i18n("Install");
0128             icon = m_iconInstall;
0129             installable = true;
0130             break;
0131         case Entry::Deleted:
0132             text = i18n("Install Again");
0133             icon = m_iconInstall;
0134             installable = true;
0135             break;
0136         default:
0137             text = i18n("Install");
0138         }
0139         installButton->setText(text);
0140         installButton->setEnabled(enabled);
0141         installButton->setIcon(icon);
0142         installButton->setPopupMode(QToolButton::InstantPopup);
0143 
0144         // If there are multiple files we want to show a dropdown, but not if it is just an update
0145         if (installable && entry.downloadLinkCount() > 1 && entry.status() != Entry::Updateable) {
0146             QMenu *installMenu = new QMenu(installButton);
0147             const auto lst = entry.downloadLinkInformationList();
0148             for (const KNSCore::EntryInternal::DownloadLinkInformation &info : lst) {
0149                 QString text = info.name;
0150                 if (!info.distributionType.trimmed().isEmpty()) {
0151                     text += QLatin1String(" (") + info.distributionType.trimmed() + QLatin1Char(')');
0152                 }
0153                 QAction *installAction = installMenu->addAction(m_iconInstall, text);
0154                 installAction->setData(QPoint(index.row(), info.id));
0155             }
0156             installButton->setMenu(installMenu);
0157         } else if (entry.status() == Entry::Installed && m_engine->hasAdoptionCommand()) {
0158             QMenu *m = new QMenu(installButton);
0159             // Add icon to use dropdown, see also BUG: 385858
0160             QAction *action = m->addAction(QIcon::fromTheme(QStringLiteral("checkmark")), m_engine->useLabel());
0161             connect(action, &QAction::triggered, m, [this, entry](bool) {
0162                 m_engine->adoptEntry(entry);
0163             });
0164             installButton->setPopupMode(QToolButton::MenuButtonPopup);
0165             installButton->setMenu(m);
0166         }
0167         // Add uninstall option for updatable entries, BUG: 422047
0168         if (entry.status() == Entry::Updateable) {
0169             QMenu *m = installButton->menu();
0170             if (!m) {
0171                 m = new QMenu(installButton);
0172             }
0173             QAction *action = m->addAction(m_iconDelete, i18n("Uninstall"));
0174             connect(action, &QAction::triggered, m, [this, entry](bool) {
0175                 m_engine->uninstall(entry);
0176             });
0177             installButton->setPopupMode(QToolButton::MenuButtonPopup);
0178             installButton->setMenu(m);
0179         }
0180     }
0181 
0182     QToolButton *detailsButton = qobject_cast<QToolButton *>(widgets.at(DelegateDetailsButton));
0183     if (detailsButton) {
0184         detailsButton->setText(i18n("Details"));
0185         detailsButton->setIcon(QIcon::fromTheme(QStringLiteral("documentinfo")));
0186     }
0187 
0188     if (installButton && detailsButton) {
0189         if (m_buttonSize.width() < installButton->sizeHint().width()) {
0190             const_cast<QSize &>(m_buttonSize) =
0191                 QSize(qMax(option.fontMetrics.height() * 7, qMax(installButton->sizeHint().width(), detailsButton->sizeHint().width())),
0192                       installButton->sizeHint().height());
0193         }
0194         installButton->resize(m_buttonSize);
0195         installButton->move(right - installButton->width() - margin, option.rect.height() / 2 - installButton->height() * 1.5);
0196         detailsButton->resize(m_buttonSize);
0197         detailsButton->move(right - installButton->width() - margin, option.rect.height() / 2 - installButton->height() / 2);
0198     }
0199 
0200     QLabel *infoLabel = qobject_cast<QLabel *>(widgets.at(DelegateLabel));
0201     if (infoLabel != nullptr) {
0202         infoLabel->setWordWrap(true);
0203         if (model->hasPreviewImages()) {
0204             // move the text right by kPreviewWidth + margin pixels to fit the preview
0205             infoLabel->move(KNSCore::PreviewWidth + margin * 2, 0);
0206             infoLabel->resize(QSize(option.rect.width() - KNSCore::PreviewWidth - (margin * 6) - m_buttonSize.width(), option.fontMetrics.height() * 7));
0207 
0208         } else {
0209             infoLabel->move(margin, 0);
0210             infoLabel->resize(QSize(option.rect.width() - (margin * 4) - m_buttonSize.width(), option.fontMetrics.height() * 7));
0211         }
0212 
0213         QString text = QStringLiteral(
0214             "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0//EN\" \"http://www.w3.org/TR/REC-html40/strict.dtd\">\n"
0215             "<html><head><meta name=\"qrichtext\" content=\"1\" /><style type=\"text/css\">p, li { white-space: pre-wrap; margin:0 0 0 0;}\n"
0216             "</style></head><body><p><b>");
0217 
0218         QUrl link = qvariant_cast<QUrl>(entry.homepage());
0219         if (!link.isEmpty()) {
0220             text += QLatin1String("<p><a href=\"") + link.url() + QLatin1String("\">") + entry.name() + QLatin1String("</a></p>\n");
0221         } else {
0222             text += entry.name();
0223         }
0224 
0225         const auto downloadInfo = entry.downloadLinkInformationList();
0226         if (!downloadInfo.isEmpty() && downloadInfo.at(0).size > 0) {
0227             QString sizeString = KFormat().formatByteSize(downloadInfo.at(0).size * 1000);
0228             text += i18nc("Show the size of the file in a list", "<p>Size: %1</p>", sizeString);
0229         }
0230 
0231         text += QLatin1String("</b></p>\n");
0232 
0233         QString authorName = entry.author().name();
0234         QString email = entry.author().email();
0235         QString authorPage = entry.author().homepage();
0236 
0237         if (!authorName.isEmpty()) {
0238             if (!authorPage.isEmpty()) {
0239                 text += QLatin1String("<p>")
0240                     + i18nc("Show the author of this item in a list",
0241                             "By <i>%1</i>",
0242                             QLatin1String(" <a href=\"") + authorPage + QLatin1String("\">") + authorName + QLatin1String("</a>"))
0243                     + QLatin1String("</p>\n");
0244             } else if (!email.isEmpty()) {
0245                 text += QLatin1String("<p>") + i18nc("Show the author of this item in a list", "By <i>%1</i>", authorName) + QLatin1String(" <a href=\"mailto:")
0246                     + email + QLatin1String("\">") + email + QLatin1String("</a></p>\n");
0247             } else {
0248                 text += QLatin1String("<p>") + i18nc("Show the author of this item in a list", "By <i>%1</i>", authorName) + QLatin1String("</p>\n");
0249             }
0250         }
0251 
0252         QString summary =
0253             QLatin1String("<p>") + option.fontMetrics.elidedText(entry.summary(), Qt::ElideRight, infoLabel->width() * 3) + QStringLiteral("</p>\n");
0254         text += summary;
0255 
0256         unsigned int fans = entry.numberFans();
0257         unsigned int downloads = entry.downloadCount();
0258 
0259         QString fanString;
0260         QString downloadString;
0261         if (fans > 0) {
0262             fanString = i18ncp("fan as in supporter", "1 fan", "%1 fans", fans);
0263         }
0264         if (downloads > 0) {
0265             downloadString = i18np("1 download", "%1 downloads", downloads);
0266         }
0267         if (downloads > 0 || fans > 0) {
0268             text += QLatin1String("<p>") + downloadString;
0269             if (downloads > 0 && fans > 0) {
0270                 text += QLatin1String(", ");
0271             }
0272             text += fanString + QLatin1String("</p>\n");
0273         }
0274 
0275         text += QLatin1String("</body></html>");
0276         // use simplified to get rid of newlines etc
0277         text = KNSCore::replaceBBCode(text).simplified();
0278         infoLabel->setText(text);
0279     }
0280 
0281     KRatingWidget *rating = qobject_cast<KRatingWidget *>(widgets.at(DelegateRatingWidget));
0282     if (rating) {
0283         if (entry.rating() > 0) {
0284             rating->setToolTip(i18n("Rating: %1%", entry.rating()));
0285             // Don't attempt to send a rating to the server if we're just updating the UI
0286             rating->blockSignals(true);
0287             // assume all entries come with rating 0..100 but most are in the range 20 - 80, so 20 is 0 stars, 80 is 5 stars
0288             rating->setRating((entry.rating() - 20) * 10 / 60);
0289             rating->blockSignals(false);
0290             // put the rating label below the install button
0291             rating->move(right - installButton->width() - margin, option.rect.height() / 2 + installButton->height() / 2);
0292             rating->resize(m_buttonSize);
0293         } else {
0294             rating->setVisible(false);
0295         }
0296     }
0297 }
0298 
0299 // draws the preview
0300 void ItemsViewDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
0301 {
0302     int margin = option.fontMetrics.height() / 2;
0303 
0304     QStyle *style = QApplication::style();
0305     style->drawPrimitive(QStyle::PE_PanelItemViewItem, &option, painter, nullptr);
0306 
0307     painter->save();
0308 
0309     if (option.state & QStyle::State_Selected) {
0310         painter->setPen(QPen(option.palette.highlightedText().color()));
0311     } else {
0312         painter->setPen(QPen(option.palette.text().color()));
0313     }
0314 
0315     const KNSCore::ItemsModel *realmodel = qobject_cast<const KNSCore::ItemsModel *>(index.model());
0316 
0317     if (realmodel->hasPreviewImages()) {
0318         int height = option.rect.height();
0319         QPoint point(option.rect.left() + margin, option.rect.top() + ((height - KNSCore::PreviewHeight) / 2));
0320 
0321         KNSCore::EntryInternal entry = index.data(Qt::UserRole).value<KNSCore::EntryInternal>();
0322         if (entry.previewUrl(KNSCore::EntryInternal::PreviewSmall1).isEmpty()) {
0323             // paint the no preview icon
0324             // point.setX((PreviewWidth - m_noImage.width())/2 + 5);
0325             // point.setY(option.rect.top() + ((height - m_noImage.height()) / 2));
0326             // painter->drawPixmap(point, m_noImage);
0327         } else {
0328             QImage image = entry.previewImage(KNSCore::EntryInternal::PreviewSmall1);
0329             if (!image.isNull()) {
0330                 point.setX((KNSCore::PreviewWidth - image.width()) / 2 + 5);
0331                 point.setY(option.rect.top() + ((height - image.height()) / 2));
0332                 painter->drawImage(point, image);
0333 
0334                 QPoint framePoint(point.x() - 5, point.y() - 5);
0335                 if (m_frameImage.isNull()) {
0336                     painter->drawPixmap(framePoint, m_frameImage);
0337                 } else {
0338                     painter->drawPixmap(framePoint, m_frameImage.scaled(image.width() + 10, image.height() + 10));
0339                 }
0340             } else {
0341                 QRect rect(point, QSize(KNSCore::PreviewWidth, KNSCore::PreviewHeight));
0342                 painter->drawText(rect, Qt::AlignCenter | Qt::TextWordWrap, i18n("Loading Preview"));
0343             }
0344         }
0345     }
0346     painter->restore();
0347 }
0348 
0349 QSize ItemsViewDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const
0350 {
0351     Q_UNUSED(option);
0352     Q_UNUSED(index);
0353 
0354     QSize size;
0355 
0356     size.setWidth(option.fontMetrics.height() * 4);
0357     size.setHeight(qMax(option.fontMetrics.height() * 7, KNSCore::PreviewHeight)); // up to 6 lines of text, and two margins
0358     return size;
0359 }
0360 
0361 } // namespace
0362 
0363 #include "moc_itemsviewdelegate_p.cpp"