File indexing completed on 2024-05-19 11:43:34
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"