KNewStuff

itemsviewdelegate.cpp
1 /*
2  This file is part of KNewStuff2.
3  SPDX-FileCopyrightText: 2008 Jeremy Whiting <[email protected]>
4  SPDX-FileCopyrightText: 2010 Reza Fatahilah Shah <[email protected]>
5  SPDX-FileCopyrightText: 2010 Frederik Gladhorn <[email protected]>
6 
7  SPDX-License-Identifier: LGPL-2.1-or-later
8 */
9 
10 #include "itemsviewdelegate_p.h"
11 
12 #include <QPainter>
13 #include <QApplication>
14 #include <QToolButton>
15 #include <QMenu>
16 #include <QProcess>
17 #include <knewstuff_debug.h>
18 
19 #include <KFormat>
20 #include <KLocalizedString>
21 #include <KRatingWidget>
22 #include <KShell>
23 
24 #include "core/itemsmodel.h"
25 
26 #include "entrydetailsdialog_p.h"
27 
28 namespace KNS3
29 {
30 enum { DelegateLabel, DelegateInstallButton, DelegateDetailsButton, DelegateRatingWidget };
31 
32 ItemsViewDelegate::ItemsViewDelegate(QAbstractItemView *itemView, KNSCore::Engine *engine, QObject *parent)
33  : ItemsViewBaseDelegate(itemView, engine, parent)
34 {
35 }
36 
37 ItemsViewDelegate::~ItemsViewDelegate()
38 {
39 }
40 
41 QList<QWidget *> ItemsViewDelegate::createItemWidgets(const QModelIndex &index) const
42 {
43  Q_UNUSED(index);
45 
46  QLabel *infoLabel = new QLabel();
47  infoLabel->setOpenExternalLinks(true);
48  // not so nice - work around constness to install the event filter
49  ItemsViewDelegate *delegate = const_cast<ItemsViewDelegate *>(this);
50  infoLabel->installEventFilter(delegate);
51  list << infoLabel;
52 
53  QToolButton *installButton = new QToolButton();
55  list << installButton;
56  setBlockedEventTypes(installButton, QList<QEvent::Type>() << QEvent::MouseButtonPress
58  connect(installButton, &QAbstractButton::clicked, this, &ItemsViewDelegate::slotInstallClicked);
59  connect(installButton, &QToolButton::triggered, this, &ItemsViewDelegate::slotInstallActionTriggered);
60 
61  QToolButton *detailsButton = new QToolButton();
63  list << detailsButton;
64  setBlockedEventTypes(detailsButton, QList<QEvent::Type>() << QEvent::MouseButtonPress
66  connect(detailsButton, &QToolButton::clicked,
67  this, static_cast<void(ItemsViewDelegate::*)()>(&ItemsViewDelegate::slotDetailsClicked));
68 
69  KRatingWidget *rating = new KRatingWidget();
70  rating->setMaxRating(10);
71  rating->setHalfStepsEnabled(true);
72  list << rating;
74  connect(rating, static_cast<void (KRatingWidget::*)(unsigned int)>(&KRatingWidget::ratingChanged),
75  this, [this, entry](unsigned int newRating){m_engine->vote(entry, newRating * 10);});
76 
77  return list;
78 }
79 
80 void ItemsViewDelegate::updateItemWidgets(const QList<QWidget *> widgets,
81  const QStyleOptionViewItem &option,
82  const QPersistentModelIndex &index) const
83 {
84  const KNSCore::ItemsModel *model = qobject_cast<const KNSCore::ItemsModel *>(index.model());
85  if (!model) {
86  qCDebug(KNEWSTUFF) << "WARNING - INVALID MODEL!";
87  return;
88  }
89 
91 
92  // setup the install button
93  int margin = option.fontMetrics.height() / 2;
94  int right = option.rect.width();
95 
96  QToolButton *installButton = qobject_cast<QToolButton *>(widgets.at(DelegateInstallButton));
97  if (installButton) {
98 
99  if (installButton->menu()) {
100  QMenu *buttonMenu = installButton->menu();
101  buttonMenu->clear();
102  installButton->setMenu(nullptr);
103  buttonMenu->deleteLater();
104  }
105 
106  bool installable = false;
107  bool enabled = true;
108  QString text;
109  QIcon icon;
110 
111  switch (entry.status()) {
112  case Entry::Installed:
113  text = i18n("Uninstall");
114  icon = m_iconDelete;
115  break;
116  case Entry::Updateable:
117  text = i18n("Update");
118  icon = m_iconUpdate;
119  installable = true;
120  break;
121  case Entry::Installing:
122  text = i18n("Installing");
123  enabled = false;
124  icon = m_iconUpdate;
125  break;
126  case Entry::Updating:
127  text = i18n("Updating");
128  enabled = false;
129  icon = m_iconUpdate;
130  break;
131  case Entry::Downloadable:
132  text = i18n("Install");
133  icon = m_iconInstall;
134  installable = true;
135  break;
136  case Entry::Deleted:
137  text = i18n("Install Again");
138  icon = m_iconInstall;
139  installable = true;
140  break;
141  default:
142  text = i18n("Install");
143  }
144  installButton->setText(text);
145  installButton->setEnabled(enabled);
146  installButton->setIcon(icon);
147  installButton->setPopupMode(QToolButton::InstantPopup);
148 
149  // If there are multiple files we want to show a dropdown, but not if it is just an update
150  if (installable && entry.downloadLinkCount() > 1 && entry.status() != Entry::Updateable) {
151  QMenu *installMenu = new QMenu(installButton);
152  const auto lst = entry.downloadLinkInformationList();
153  for (const KNSCore::EntryInternal::DownloadLinkInformation &info : lst) {
154  QString text = info.name;
155  if (!info.distributionType.trimmed().isEmpty()) {
156  text += QLatin1String(" (") + info.distributionType.trimmed() + QLatin1Char(')');
157  }
158  QAction *installAction = installMenu->addAction(m_iconInstall, text);
159  installAction->setData(QPoint(index.row(), info.id));
160  }
161  installButton->setMenu(installMenu);
162  } else if (entry.status() == Entry::Installed && m_engine->hasAdoptionCommand()) {
163  QMenu* m = new QMenu(installButton);
164  // Add icon to use dropdown, see also BUG: 385858
165  QAction *action = m->addAction(QIcon::fromTheme(QStringLiteral("checkmark")), m_engine->useLabel());
166  connect(action, &QAction::triggered, m, [this, entry](bool) {
167  m_engine->adoptEntry(entry);
168  });
170  installButton->setMenu(m);
171  }
172  // Add uninstall option for updatable entries, BUG: 422047
173  if (entry.status() == Entry::Updateable) {
174  QMenu* m = installButton->menu();
175  if (!m) {
176  m = new QMenu(installButton);
177  }
178  QAction *action = m->addAction(m_iconDelete, i18n("Uninstall"));
179  connect(action, &QAction::triggered, m, [this, entry](bool){
180  m_engine->uninstall(entry);
181  });
183  installButton->setMenu(m);
184  }
185  }
186 
187  QToolButton *detailsButton = qobject_cast<QToolButton *>(widgets.at(DelegateDetailsButton));
188  if (detailsButton) {
189  detailsButton->setText(i18n("Details"));
190  detailsButton->setIcon(QIcon::fromTheme(QStringLiteral("documentinfo")));
191  }
192 
193  if (installButton && detailsButton) {
194  if (m_buttonSize.width() < installButton->sizeHint().width()) {
195  const_cast<QSize &>(m_buttonSize) = QSize(
196  qMax(option.fontMetrics.height() * 7,
197  qMax(installButton->sizeHint().width(), detailsButton->sizeHint().width())),
198  installButton->sizeHint().height());
199  }
200  installButton->resize(m_buttonSize);
201  installButton->move(right - installButton->width() - margin, option.rect.height() / 2 - installButton->height() * 1.5);
202  detailsButton->resize(m_buttonSize);
203  detailsButton->move(right - installButton->width() - margin, option.rect.height() / 2 - installButton->height() / 2);
204  }
205 
206  QLabel *infoLabel = qobject_cast<QLabel *>(widgets.at(DelegateLabel));
207  if (infoLabel != nullptr) {
208  infoLabel->setWordWrap(true);
209  if (model->hasPreviewImages()) {
210  // move the text right by kPreviewWidth + margin pixels to fit the preview
211  infoLabel->move(KNSCore::PreviewWidth + margin * 2, 0);
212  infoLabel->resize(QSize(option.rect.width() - KNSCore::PreviewWidth - (margin * 6) - m_buttonSize.width(), option.fontMetrics.height() * 7));
213 
214  } else {
215  infoLabel->move(margin, 0);
216  infoLabel->resize(QSize(option.rect.width() - (margin * 4) - m_buttonSize.width(), option.fontMetrics.height() * 7));
217  }
218 
219  QString text = QStringLiteral("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0//EN\" \"http://www.w3.org/TR/REC-html40/strict.dtd\">\n"
220  "<html><head><meta name=\"qrichtext\" content=\"1\" /><style type=\"text/css\">p, li { white-space: pre-wrap; margin:0 0 0 0;}\n"
221  "</style></head><body><p><b>");
222 
223  QUrl link = qvariant_cast<QUrl>(entry.homepage());
224  if (!link.isEmpty()) {
225  text += QLatin1String("<p><a href=\"") + link.url() + QLatin1String("\">") + entry.name() + QLatin1String("</a></p>\n");
226  } else {
227  text += entry.name();
228  }
229 
230  const auto downloadInfo = entry.downloadLinkInformationList();
231  if (!downloadInfo.isEmpty() && downloadInfo.at(0).size > 0) {
232  QString sizeString = KFormat().formatByteSize(downloadInfo.at(0).size * 1000);
233  text += i18nc("Show the size of the file in a list", "<p>Size: %1</p>", sizeString);
234  }
235 
236  text += QLatin1String("</b></p>\n");
237 
238  QString authorName = entry.author().name();
239  QString email = entry.author().email();
240  QString authorPage = entry.author().homepage();
241 
242  if (!authorName.isEmpty()) {
243  if (!authorPage.isEmpty()) {
244  text += QLatin1String("<p>") + i18nc("Show the author of this item in a list", "By <i>%1</i>", QLatin1String(" <a href=\"") + authorPage + QLatin1String("\">") + authorName + QLatin1String("</a>")) + QLatin1String("</p>\n");
245  } else if (!email.isEmpty()) {
246  text += QLatin1String("<p>") + i18nc("Show the author of this item in a list", "By <i>%1</i>", authorName) + QLatin1String(" <a href=\"mailto:") + email + QLatin1String("\">") + email + QLatin1String("</a></p>\n");
247  } else {
248  text += QLatin1String("<p>") + i18nc("Show the author of this item in a list", "By <i>%1</i>", authorName) + QLatin1String("</p>\n");
249  }
250  }
251 
252  QString summary = QLatin1String("<p>") + option.fontMetrics.elidedText(entry.summary(),
253  Qt::ElideRight, infoLabel->width() * 3) + QStringLiteral("</p>\n");
254  text += summary;
255 
256  unsigned int fans = entry.numberFans();
257  unsigned int downloads = entry.downloadCount();
258 
259  QString fanString;
260  QString downloadString;
261  if (fans > 0) {
262  fanString = i18ncp("fan as in supporter", "1 fan", "%1 fans", fans);
263  }
264  if (downloads > 0) {
265  downloadString = i18np("1 download", "%1 downloads", downloads);
266  }
267  if (downloads > 0 || fans > 0) {
268  text += QLatin1String("<p>") + downloadString;
269  if (downloads > 0 && fans > 0) {
270  text += QLatin1String(", ");
271  }
272  text += fanString + QLatin1String("</p>\n");
273  }
274 
275  text += QLatin1String("</body></html>");
276  // use simplified to get rid of newlines etc
277  text = KNSCore::replaceBBCode(text).simplified();
278  infoLabel->setText(text);
279  }
280 
281  KRatingWidget *rating = qobject_cast<KRatingWidget *>(widgets.at(DelegateRatingWidget));
282  if (rating) {
283  if (entry.rating() > 0) {
284  rating->setToolTip(i18n("Rating: %1%", entry.rating()));
285  // Don't attempt to send a rating to the server if we're just updating the UI
286  rating->blockSignals(true);
287  // 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
288  rating->setRating((entry.rating() - 20) * 10 / 60);
289  rating->blockSignals(false);
290  // put the rating label below the install button
291  rating->move(right - installButton->width() - margin, option.rect.height() / 2 + installButton->height() / 2);
292  rating->resize(m_buttonSize);
293  } else {
294  rating->setVisible(false);
295  }
296  }
297 }
298 
299 // draws the preview
300 void ItemsViewDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
301 {
302  int margin = option.fontMetrics.height() / 2;
303 
304  QStyle *style = QApplication::style();
305  style->drawPrimitive(QStyle::PE_PanelItemViewItem, &option, painter, nullptr);
306 
307  painter->save();
308 
309  if (option.state & QStyle::State_Selected) {
310  painter->setPen(QPen(option.palette.highlightedText().color()));
311  } else {
312  painter->setPen(QPen(option.palette.text().color()));
313  }
314 
315  const KNSCore::ItemsModel *realmodel = qobject_cast<const KNSCore::ItemsModel *>(index.model());
316 
317  if (realmodel->hasPreviewImages()) {
318  int height = option.rect.height();
319  QPoint point(option.rect.left() + margin, option.rect.top() + ((height - KNSCore::PreviewHeight) / 2));
320 
322  if (entry.previewUrl(KNSCore::EntryInternal::PreviewSmall1).isEmpty()) {
323  // paint the no preview icon
324  //point.setX((PreviewWidth - m_noImage.width())/2 + 5);
325  //point.setY(option.rect.top() + ((height - m_noImage.height()) / 2));
326  //painter->drawPixmap(point, m_noImage);
327  } else {
328  QImage image = entry.previewImage(KNSCore::EntryInternal::PreviewSmall1);
329  if (!image.isNull()) {
330  point.setX((KNSCore::PreviewWidth - image.width()) / 2 + 5);
331  point.setY(option.rect.top() + ((height - image.height()) / 2));
332  painter->drawImage(point, image);
333 
334  QPoint framePoint(point.x() - 5, point.y() - 5);
335  if (m_frameImage.isNull()) {
336  painter->drawPixmap(framePoint, m_frameImage);
337  } else {
338  painter->drawPixmap(framePoint, m_frameImage.scaled(image.width() + 10, image.height() + 10));
339  }
340  } else {
341  QRect rect(point, QSize(KNSCore::PreviewWidth, KNSCore::PreviewHeight));
342  painter->drawText(rect, Qt::AlignCenter | Qt::TextWordWrap, i18n("Loading Preview"));
343  }
344  }
345 
346  }
347  painter->restore();
348 }
349 
350 QSize ItemsViewDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const
351 {
352  Q_UNUSED(option);
353  Q_UNUSED(index);
354 
355  QSize size;
356 
357  size.setWidth(option.fontMetrics.height() * 4);
358  size.setHeight(qMax(option.fontMetrics.height() * 7, KNSCore::PreviewHeight)); // up to 6 lines of text, and two margins
359  return size;
360 }
361 
362 } // namespace
363 
QString url(QUrl::FormattingOptions options) const const
void resize(int w, int h)
void triggered(bool checked)
void setHeight(int height)
MouseButtonPress
void setRating(int rating)
void setMenu(QMenu *menu)
int width() const const
int numberFans() const
How many people have marked themselves as fans of this entry.
virtual QSize sizeHint() const const override
QString summary() const
Retrieve a (potentially very long) description of the object.
Author author() const
Retrieve the author of the object.
KNEWSTUFFCORE_EXPORT QString replaceBBCode(const QString &unformattedText)
function to remove bb code formatting that opendesktop sends
int downloadCount() const
Retrieve the download count for the object, which has been determined by its hosting sites and thus m...
const T & at(int i) const const
QTextStream & right(QTextStream &stream)
QString simplified() const const
virtual void setVisible(bool visible)
void save()
T value() const const
void setHalfStepsEnabled(bool enabled)
QString i18np(const char *singular, const char *plural, const TYPE &arg...)
bool isNull() const const
const QAbstractItemModel * model() const const
void setMaxRating(int max)
bool isEmpty() const const
void triggered(QAction *action)
void setIcon(const QIcon &icon)
QAction * addAction(const QString &text)
AlignCenter
void clear()
void setEnabled(bool)
void installEventFilter(QObject *filterObj)
QUrl homepage() const
A link to a website containing information about this entry.
QLatin1String trimmed() const const
QString i18nc(const char *context, const char *text, const TYPE &arg...)
void setPen(const QColor &color)
QList< DownloadLinkInformation > downloadLinkInformationList() const
A list of downloadable data for this entry.
int width() const const
void drawPixmap(const QRectF &target, const QPixmap &pixmap, const QRectF &source)
void setWidth(int width)
int downloadLinkCount() const
The number of available download options for this entry.
UserRole
bool isEmpty() const const
void clicked(bool checked)
QVariant data(int role) const const
void drawText(const QPointF &position, const QString &text)
ToolButtonTextBesideIcon
void deleteLater()
void setText(const QString &)
void ratingChanged(unsigned int rating)
QString homepage() const
Retrieve the author&#39;s homepage.
PE_PanelItemViewItem
void setData(const QVariant &userData)
bool blockSignals(bool block)
QString formatByteSize(double size, int precision=1, KFormat::BinaryUnitDialect dialect=KFormat::DefaultBinaryDialect, KFormat::BinarySizeUnits units=KFormat::DefaultBinaryUnits) const
QMenu * menu() const const
QString i18ncp(const char *context, const char *singular, const char *plural, const TYPE &arg...)
void move(int x, int y)
void restore()
QString i18n(const char *text, const TYPE &arg...)
void drawImage(const QRectF &target, const QImage &image, const QRectF &source, Qt::ImageConversionFlags flags)
const QAbstractItemModel * model() const const
QVariant data(int role) const const
QStyle * style()
KNewStuff data entry container.
Definition: entryinternal.h:49
int rating() const
Retrieve the rating for the object, which has been determined by its users and thus might change over...
KIOCORE_EXPORT CopyJob * link(const QUrl &src, const QUrl &destDir, JobFlags flags=DefaultFlags)
void setPopupMode(QToolButton::ToolButtonPopupMode mode)
KNewStuff engine.
Definition: engine.h:59
int height() const const
TextWordWrap
ElideRight
virtual void drawPrimitive(QStyle::PrimitiveElement element, const QStyleOption *option, QPainter *painter, const QWidget *widget) const const =0
void setText(const QString &text)
int height() const const
QIcon fromTheme(const QString &name)
void setToolTip(const QString &)
KNS3::Entry::Status status() const
Retrieves the entry&#39;s status.
The namespace for the KNewStuff classes.
Definition: button.cpp:19
QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
QString name() const
Retrieve the name of the data object.
T qobject_cast(QObject *object)
void setToolButtonStyle(Qt::ToolButtonStyle style)
QString email() const
Retrieve the author&#39;s email address.
Definition: core/author.cpp:92
void setWordWrap(bool on)
int row() const const
KIOFILEWIDGETS_EXPORT QStringList list(const QString &fileClass)
QString name() const
Retrieve the author&#39;s name.
Definition: core/author.cpp:82
This file is part of the KDE documentation.
Documentation copyright © 1996-2021 The KDE developers.
Generated on Mon Jan 18 2021 22:43:50 by doxygen 1.8.11 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.