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

0001 /*
0002     SPDX-FileCopyrightText: 2008 Jeremy Whiting <jpwhiting@kde.org>
0003     SPDX-FileCopyrightText: 2010 Reza Fatahilah Shah <rshah0385@kireihana.com>
0004     SPDX-FileCopyrightText: 2010 Frederik Gladhorn <gladhorn@kde.org>
0005 
0006     SPDX-License-Identifier: LGPL-2.1-or-later
0007 */
0008 
0009 #include "itemsgridviewdelegate_p.h"
0010 
0011 #include <QAbstractItemView>
0012 #include <QApplication>
0013 #include <QHBoxLayout>
0014 #include <QLabel>
0015 #include <QMenu>
0016 #include <QPainter>
0017 #include <QToolButton>
0018 
0019 #include <KFormat>
0020 #include <KLocalizedString>
0021 #include <KRatingWidget>
0022 #include <KSqueezedTextLabel>
0023 #include <knewstuff_debug.h>
0024 
0025 #include "core/itemsmodel.h"
0026 
0027 namespace KNS3
0028 {
0029 enum { DelegateTitleLabel, DelegateAuthorLabel, DelegateDownloadCounterLabel, DelegateGridRatingWidget };
0030 
0031 ItemsGridViewDelegate::ItemsGridViewDelegate(QAbstractItemView *itemView, KNSCore::Engine *engine, QObject *parent)
0032     : ItemsViewBaseDelegate(itemView, engine, parent)
0033     , m_elementYPos(0)
0034 {
0035     createOperationBar();
0036 }
0037 
0038 ItemsGridViewDelegate::~ItemsGridViewDelegate()
0039 {
0040 }
0041 
0042 QList<QWidget *> ItemsGridViewDelegate::createItemWidgets(const QModelIndex &index) const
0043 {
0044     Q_UNUSED(index)
0045 
0046     QList<QWidget *> m_widgetList;
0047     KSqueezedTextLabel *titleLabel = new KSqueezedTextLabel();
0048     titleLabel->setOpenExternalLinks(true);
0049     titleLabel->setTextElideMode(Qt::ElideRight);
0050     // not so nice - work around constness to install the event filter
0051     ItemsGridViewDelegate *delegate = const_cast<ItemsGridViewDelegate *>(this);
0052     titleLabel->installEventFilter(delegate);
0053     m_widgetList << titleLabel;
0054 
0055     KSqueezedTextLabel *authorLabel = new KSqueezedTextLabel();
0056     authorLabel->setTextElideMode(Qt::ElideRight);
0057     m_widgetList << authorLabel;
0058 
0059     KSqueezedTextLabel *downloadCounterLabel = new KSqueezedTextLabel();
0060     downloadCounterLabel->setTextElideMode(Qt::ElideRight);
0061     m_widgetList << downloadCounterLabel;
0062 
0063     KRatingWidget *rating = new KRatingWidget();
0064     rating->setMaxRating(10);
0065     rating->setHalfStepsEnabled(true);
0066     m_widgetList << rating;
0067 
0068     return m_widgetList;
0069 }
0070 
0071 void ItemsGridViewDelegate::updateItemWidgets(const QList<QWidget *> widgets, const QStyleOptionViewItem &option, const QPersistentModelIndex &index) const
0072 {
0073     const KNSCore::ItemsModel *model = qobject_cast<const KNSCore::ItemsModel *>(index.model());
0074     if (!model) {
0075         qCDebug(KNEWSTUFF) << "WARNING - INVALID MODEL!";
0076         return;
0077     }
0078 
0079     KNSCore::EntryInternal entry = index.data(Qt::UserRole).value<KNSCore::EntryInternal>();
0080     int elementYPos = KNSCore::PreviewHeight + ItemMargin + FrameThickness * 2;
0081 
0082     // setup rating widget
0083     KRatingWidget *rating = qobject_cast<KRatingWidget *>(widgets.at(DelegateGridRatingWidget));
0084     if (rating) {
0085         if (entry.rating() > 0) {
0086             rating->setToolTip(i18n("Rating: %1%", entry.rating()));
0087             // 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
0088             rating->setRating((entry.rating() - 20) * 10 / 60);
0089             // make the rating widget smaller than the one at list view
0090             int newWidth = 68;
0091             QSize size(newWidth, 15);
0092             rating->resize(size);
0093             // put rating widget under image rectangle
0094             rating->move((ItemGridWidth - newWidth) / 2, elementYPos);
0095             elementYPos += rating->height();
0096         } else {
0097             // is it better to stay visible?
0098             rating->setVisible(false);
0099         }
0100     }
0101     elementYPos += ItemMargin;
0102 
0103     // setup title label
0104     QLabel *titleLabel = qobject_cast<QLabel *>(widgets.at(DelegateTitleLabel));
0105     if (titleLabel != nullptr) {
0106         titleLabel->setWordWrap(true);
0107         titleLabel->setAlignment(Qt::AlignHCenter);
0108         // titleLabel->setFrameStyle(QFrame::Panel);
0109         titleLabel->resize(QSize(option.rect.width() - (ItemMargin * 2), option.fontMetrics.height() * 2));
0110         titleLabel->move((ItemGridWidth - titleLabel->width()) / 2, elementYPos);
0111 
0112         QString title;
0113         QUrl link = qvariant_cast<QUrl>(entry.homepage());
0114         if (!link.isEmpty()) {
0115             title += QLatin1String("<b><a href=\"") + link.url() + QLatin1String("\">") + entry.name() + QLatin1String("</a></b>\n");
0116         } else {
0117             title += QLatin1String("<b>") + entry.name() + QLatin1String("</b>");
0118         }
0119 
0120         const auto downloadInfo = entry.downloadLinkInformationList();
0121         if (!downloadInfo.isEmpty() && downloadInfo.at(0).size > 0) {
0122             QString sizeString = KFormat().formatByteSize(downloadInfo.at(0).size * 1000);
0123             title += i18nc("Show the size of the file in a list", "<br />Size: %1", sizeString);
0124         }
0125 
0126         titleLabel->setText(title);
0127         elementYPos += titleLabel->height();
0128     }
0129     // setup author label
0130     QLabel *authorLabel = qobject_cast<QLabel *>(widgets.at(DelegateAuthorLabel));
0131     if (authorLabel) {
0132         authorLabel->setWordWrap(true);
0133         authorLabel->setAlignment(Qt::AlignHCenter);
0134         authorLabel->resize(QSize(option.rect.width() - (ItemMargin * 2), option.fontMetrics.height()));
0135         authorLabel->move((ItemGridWidth - authorLabel->width()) / 2, elementYPos);
0136 
0137         QString text;
0138         QString authorName = entry.author().name();
0139         QString email = entry.author().email();
0140         QString authorPage = entry.author().homepage();
0141 
0142         if (!authorName.isEmpty()) {
0143             if (!authorPage.isEmpty()) {
0144                 text += QLatin1String("<p>")
0145                     + i18nc("Show the author of this item in a list",
0146                             "By <i>%1</i>",
0147                             QLatin1String(" <a href=\"") + authorPage + QLatin1String("\">") + authorName + QLatin1String("</a>"))
0148                     + QLatin1String("</p>\n");
0149             } else if (!email.isEmpty()) {
0150                 text += QLatin1String("<p>") + i18nc("Show the author of this item in a list", "By <i>%1</i>", authorName) + QLatin1String(" <a href=\"mailto:")
0151                     + email + QLatin1String("\">") + email + QLatin1String("</a></p>\n");
0152             } else {
0153                 text += QLatin1String("<p>") + i18nc("Show the author of this item in a list", "By <i>%1</i>", authorName) + QLatin1String("</p>\n");
0154             }
0155         }
0156         authorLabel->setText(text);
0157         elementYPos += authorLabel->height();
0158     }
0159     elementYPos += ItemMargin;
0160 
0161     // setup download label
0162     QLabel *downloadLabel = qobject_cast<QLabel *>(widgets.at(DelegateDownloadCounterLabel));
0163     if (downloadLabel != nullptr) {
0164         downloadLabel->setWordWrap(true);
0165         downloadLabel->setAlignment(Qt::AlignHCenter);
0166         downloadLabel->resize(QSize(option.rect.width() - (ItemMargin * 2), option.fontMetrics.height()));
0167         downloadLabel->move((ItemGridWidth - downloadLabel->width()) / 2, elementYPos);
0168 
0169         unsigned int fans = entry.numberFans();
0170         unsigned int downloads = entry.downloadCount();
0171 
0172         QString text;
0173         QString fanString;
0174         QString downloadString;
0175         if (fans > 0) {
0176             fanString = i18ncp("fan as in supporter", "1 fan", "%1 fans", fans);
0177         }
0178         if (downloads > 0) {
0179             downloadString = i18np("1 download", "%1 downloads", downloads);
0180         }
0181         if (downloads > 0 || fans > 0) {
0182             text += QLatin1String("<p>") + downloadString;
0183             if (downloads > 0 && fans > 0) {
0184                 text += QLatin1String(", ");
0185             }
0186             text += fanString + QLatin1String("</p>");
0187         }
0188         downloadLabel->setText(text);
0189         elementYPos += downloadLabel->height();
0190     }
0191     elementYPos += ItemMargin;
0192     m_elementYPos = elementYPos;
0193 }
0194 
0195 void ItemsGridViewDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
0196 {
0197     if (option.state & QStyle::State_MouseOver) {
0198         QModelIndex focIndex = focusedIndex();
0199         if (m_oldIndex != focIndex || !m_operationBar->isVisible()) {
0200             ItemsGridViewDelegate *delegate = const_cast<ItemsGridViewDelegate *>(this);
0201 
0202             delegate->displayOperationBar(option.rect, index);
0203             delegate->m_oldIndex = focIndex;
0204         }
0205     } else {
0206         QModelIndex focindex = focusedIndex();
0207         if (!focindex.isValid()) {
0208             qCDebug(KNEWSTUFF) << "INVALID hide selection";
0209             m_operationBar->hide();
0210         }
0211     }
0212 
0213     QStyle *style = QApplication::style();
0214     style->drawPrimitive(QStyle::PE_PanelItemViewItem, &option, painter, nullptr);
0215 
0216     painter->save();
0217 
0218     if (option.state & QStyle::State_Selected) {
0219         painter->setPen(QPen(option.palette.highlightedText().color()));
0220     } else {
0221         painter->setPen(QPen(option.palette.text().color()));
0222     }
0223 
0224     const KNSCore::ItemsModel *realmodel = qobject_cast<const KNSCore::ItemsModel *>(index.model());
0225 
0226     if (realmodel->hasPreviewImages()) {
0227         int width = option.rect.width();
0228 
0229         KNSCore::EntryInternal entry = index.data(Qt::UserRole).value<KNSCore::EntryInternal>();
0230         if (entry.previewUrl(KNSCore::EntryInternal::PreviewSmall1).isEmpty()) {
0231             ;
0232         } else {
0233             QPoint centralPoint(option.rect.left() + width / 2, option.rect.top() + ItemMargin + FrameThickness + KNSCore::PreviewHeight / 2);
0234             QImage image = entry.previewImage(KNSCore::EntryInternal::PreviewSmall1);
0235             if (!image.isNull()) {
0236                 QPoint previewPoint(centralPoint.x() - image.width() / 2, centralPoint.y() - image.height() / 2);
0237                 painter->drawImage(previewPoint, image);
0238 
0239                 QPixmap frameImageScaled = m_frameImage.scaled(image.width() + FrameThickness * 2, image.height() + FrameThickness * 2);
0240                 QPoint framePoint(centralPoint.x() - frameImageScaled.width() / 2, centralPoint.y() - frameImageScaled.height() / 2);
0241                 painter->drawPixmap(framePoint, frameImageScaled);
0242             } else {
0243                 QPoint thumbnailPoint(option.rect.left() + ((width - KNSCore::PreviewWidth - FrameThickness * 2) / 2), option.rect.top() + ItemMargin);
0244                 QRect rect(thumbnailPoint, QSize(KNSCore::PreviewWidth + FrameThickness * 2, KNSCore::PreviewHeight + FrameThickness * 2));
0245                 painter->drawText(rect, Qt::AlignCenter | Qt::TextWordWrap, i18n("Loading Preview"));
0246             }
0247         }
0248     }
0249 
0250     painter->restore();
0251 }
0252 
0253 QSize ItemsGridViewDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const
0254 {
0255     Q_UNUSED(option);
0256     Q_UNUSED(index);
0257 
0258     QSize size;
0259 
0260     size.setWidth(ItemGridWidth);
0261     size.setHeight(qMax(option.fontMetrics.height() * 13, ItemGridHeight)); // up to 6 lines of text, and two margins
0262     return size;
0263 }
0264 
0265 void ItemsGridViewDelegate::createOperationBar()
0266 {
0267     m_operationBar = new QWidget(this->itemView()->viewport());
0268 
0269     m_detailsButton = new QToolButton();
0270     m_detailsButton->setToolButtonStyle(Qt::ToolButtonFollowStyle);
0271     m_detailsButton->setPopupMode(QToolButton::InstantPopup);
0272     m_detailsButton->setToolTip(i18n("Details"));
0273     m_detailsButton->setIcon(QIcon::fromTheme(QStringLiteral("documentinfo")));
0274     setBlockedEventTypes(m_detailsButton, QList<QEvent::Type>() << QEvent::MouseButtonPress << QEvent::MouseButtonRelease << QEvent::MouseButtonDblClick);
0275     connect(m_detailsButton, &QToolButton::clicked, this, static_cast<void (ItemsGridViewDelegate::*)()>(&ItemsGridViewDelegate::slotDetailsClicked));
0276 
0277     m_installButton = new QToolButton();
0278     m_installButton->setToolButtonStyle(Qt::ToolButtonFollowStyle);
0279     m_installButton->setPopupMode(QToolButton::InstantPopup);
0280 
0281     setBlockedEventTypes(m_installButton, QList<QEvent::Type>() << QEvent::MouseButtonPress << QEvent::MouseButtonRelease << QEvent::MouseButtonDblClick);
0282     connect(m_installButton, &QAbstractButton::clicked, this, &ItemsGridViewDelegate::slotInstallClicked);
0283     connect(m_installButton, &QToolButton::triggered, this, &ItemsGridViewDelegate::slotInstallActionTriggered);
0284 
0285     if (m_installButton->menu()) {
0286         QMenu *buttonMenu = m_installButton->menu();
0287         buttonMenu->clear();
0288         m_installButton->setMenu(nullptr);
0289         buttonMenu->deleteLater();
0290     }
0291 
0292     QHBoxLayout *layout = new QHBoxLayout(m_operationBar);
0293 
0294     layout->setSpacing(1);
0295     layout->addWidget(m_installButton);
0296     layout->addWidget(m_detailsButton);
0297 
0298     m_operationBar->adjustSize();
0299     m_operationBar->hide();
0300 }
0301 
0302 void ItemsGridViewDelegate::displayOperationBar(const QRect &rect, const QModelIndex &index)
0303 {
0304     KNSCore::EntryInternal entry = index.data(Qt::UserRole).value<KNSCore::EntryInternal>();
0305     if (m_installButton != nullptr) {
0306         if (m_installButton->menu() != nullptr) {
0307             QMenu *buttonMenu = m_installButton->menu();
0308             buttonMenu->clear();
0309             m_installButton->setMenu(nullptr);
0310             buttonMenu->deleteLater();
0311         }
0312 
0313         bool installable = false;
0314         bool enabled = true;
0315         QString text;
0316         QIcon icon;
0317 
0318         switch (entry.status()) {
0319         case Entry::Installed:
0320             text = i18n("Uninstall");
0321             icon = m_iconDelete;
0322             break;
0323         case Entry::Updateable:
0324             text = i18n("Update");
0325             icon = m_iconUpdate;
0326             installable = true;
0327             break;
0328         case Entry::Installing:
0329             text = i18n("Installing");
0330             enabled = false;
0331             icon = m_iconUpdate;
0332             break;
0333         case Entry::Updating:
0334             text = i18n("Updating");
0335             enabled = false;
0336             icon = m_iconUpdate;
0337             break;
0338         case Entry::Downloadable:
0339             text = i18n("Install");
0340             icon = m_iconInstall;
0341             installable = true;
0342             break;
0343         case Entry::Deleted:
0344             text = i18n("Install Again");
0345             icon = m_iconInstall;
0346             installable = true;
0347             break;
0348         default:
0349             text = i18n("Install");
0350         }
0351         m_installButton->setToolTip(text);
0352         m_installButton->setIcon(icon);
0353         m_installButton->setEnabled(enabled);
0354         if (installable && entry.downloadLinkCount() > 1) {
0355             QMenu *installMenu = new QMenu(m_installButton);
0356             const auto lst = entry.downloadLinkInformationList();
0357             for (const KNSCore::EntryInternal::DownloadLinkInformation &info : lst) {
0358                 QString text = info.name;
0359                 if (!info.distributionType.trimmed().isEmpty()) {
0360                     text + " (" + info.distributionType.trimmed() + QLatin1Char(')');
0361                 }
0362                 QAction *installAction = installMenu->addAction(m_iconInstall, text);
0363                 installAction->setData(QPoint(index.row(), info.id));
0364             }
0365             m_installButton->setMenu(installMenu);
0366         }
0367 
0368         m_operationBar->move(rect.left() + (ItemGridWidth - m_operationBar->width()) / 2, rect.top() + m_elementYPos);
0369         m_operationBar->show();
0370     }
0371 }
0372 }
0373 
0374 #include "moc_itemsgridviewdelegate_p.cpp"