KNewStuff

itemsviewdelegate.cpp
1 /*
2  This file is part of KNewStuff2.
3  Copyright (C) 2008 Jeremy Whiting <[email protected]>
4  Copyright (C) 2010 Reza Fatahilah Shah <[email protected]>
5  Copyright (C) 2010 Frederik Gladhorn <[email protected]>
6 
7  This library is free software; you can redistribute it and/or
8  modify it under the terms of the GNU Lesser General Public
9  License as published by the Free Software Foundation; either
10  version 2.1 of the License, or (at your option) any later version.
11 
12  This library is distributed in the hope that it will be useful,
13  but WITHOUT ANY WARRANTY; without even the implied warranty of
14  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15  Lesser General Public License for more details.
16 
17  You should have received a copy of the GNU Lesser General Public
18  License along with this library. If not, see <http://www.gnu.org/licenses/>.
19 */
20 
21 #include "itemsviewdelegate_p.h"
22 
23 #include <QPainter>
24 #include <QApplication>
25 #include <QToolButton>
26 #include <QMenu>
27 #include <QProcess>
28 #include <knewstuff_debug.h>
29 
30 #include <KFormat>
31 #include <KLocalizedString>
32 #include <KRatingWidget>
33 #include <KShell>
34 
35 #include "core/itemsmodel.h"
36 
37 #include "entrydetailsdialog_p.h"
38 
39 namespace KNS3
40 {
41 enum { DelegateLabel, DelegateInstallButton, DelegateDetailsButton, DelegateRatingWidget };
42 
43 ItemsViewDelegate::ItemsViewDelegate(QAbstractItemView *itemView, KNSCore::Engine *engine, QObject *parent)
44  : ItemsViewBaseDelegate(itemView, engine, parent)
45 {
46 }
47 
48 ItemsViewDelegate::~ItemsViewDelegate()
49 {
50 }
51 
52 QList<QWidget *> ItemsViewDelegate::createItemWidgets(const QModelIndex &index) const
53 {
54  Q_UNUSED(index);
56 
57  QLabel *infoLabel = new QLabel();
58  infoLabel->setOpenExternalLinks(true);
59  // not so nice - work around constness to install the event filter
60  ItemsViewDelegate *delegate = const_cast<ItemsViewDelegate *>(this);
61  infoLabel->installEventFilter(delegate);
62  list << infoLabel;
63 
64  QToolButton *installButton = new QToolButton();
66  list << installButton;
67  setBlockedEventTypes(installButton, QList<QEvent::Type>() << QEvent::MouseButtonPress
69  connect(installButton, &QAbstractButton::clicked, this, &ItemsViewDelegate::slotInstallClicked);
70  connect(installButton, &QToolButton::triggered, this, &ItemsViewDelegate::slotInstallActionTriggered);
71 
72  QToolButton *detailsButton = new QToolButton();
74  list << detailsButton;
75  setBlockedEventTypes(detailsButton, QList<QEvent::Type>() << QEvent::MouseButtonPress
77  connect(detailsButton, &QToolButton::clicked,
78  this, static_cast<void(ItemsViewDelegate::*)()>(&ItemsViewDelegate::slotDetailsClicked));
79 
80  KRatingWidget *rating = new KRatingWidget();
81  rating->setMaxRating(10);
82  rating->setHalfStepsEnabled(true);
83  list << rating;
85  connect(rating, static_cast<void (KRatingWidget::*)(unsigned int)>(&KRatingWidget::ratingChanged),
86  this, [this, entry](unsigned int newRating){m_engine->vote(entry, newRating * 10);});
87 
88  return list;
89 }
90 
91 void ItemsViewDelegate::updateItemWidgets(const QList<QWidget *> widgets,
92  const QStyleOptionViewItem &option,
93  const QPersistentModelIndex &index) const
94 {
95  const KNSCore::ItemsModel *model = qobject_cast<const KNSCore::ItemsModel *>(index.model());
96  if (!model) {
97  qCDebug(KNEWSTUFF) << "WARNING - INVALID MODEL!";
98  return;
99  }
100 
102 
103  // setup the install button
104  int margin = option.fontMetrics.height() / 2;
105  int right = option.rect.width();
106 
107  QToolButton *installButton = qobject_cast<QToolButton *>(widgets.at(DelegateInstallButton));
108  if (installButton) {
109 
110  if (installButton->menu()) {
111  QMenu *buttonMenu = installButton->menu();
112  buttonMenu->clear();
113  installButton->setMenu(nullptr);
114  buttonMenu->deleteLater();
115  }
116 
117  bool installable = false;
118  bool enabled = true;
119  QString text;
120  QIcon icon;
121 
122  switch (entry.status()) {
123  case Entry::Installed:
124  text = i18n("Uninstall");
125  icon = m_iconDelete;
126  break;
127  case Entry::Updateable:
128  text = i18n("Update");
129  icon = m_iconUpdate;
130  installable = true;
131  break;
132  case Entry::Installing:
133  text = i18n("Installing");
134  enabled = false;
135  icon = m_iconUpdate;
136  break;
137  case Entry::Updating:
138  text = i18n("Updating");
139  enabled = false;
140  icon = m_iconUpdate;
141  break;
142  case Entry::Downloadable:
143  text = i18n("Install");
144  icon = m_iconInstall;
145  installable = true;
146  break;
147  case Entry::Deleted:
148  text = i18n("Install Again");
149  icon = m_iconInstall;
150  installable = true;
151  break;
152  default:
153  text = i18n("Install");
154  }
155  installButton->setText(text);
156  installButton->setEnabled(enabled);
157  installButton->setIcon(icon);
158  installButton->setPopupMode(QToolButton::InstantPopup);
159 
160  // If there are multiple files we want to show a dropdown, but not if it is just an update
161  if (installable && entry.downloadLinkCount() > 1 && entry.status() != Entry::Updateable) {
162  QMenu *installMenu = new QMenu(installButton);
163  const auto lst = entry.downloadLinkInformationList();
164  for (const KNSCore::EntryInternal::DownloadLinkInformation &info : lst) {
165  QString text = info.name;
166  if (!info.distributionType.trimmed().isEmpty()) {
167  text += QLatin1String(" (") + info.distributionType.trimmed() + QLatin1Char(')');
168  }
169  QAction *installAction = installMenu->addAction(m_iconInstall, text);
170  installAction->setData(QPoint(index.row(), info.id));
171  }
172  installButton->setMenu(installMenu);
173  } else if (entry.status() == Entry::Installed && m_engine->hasAdoptionCommand()) {
174  QMenu* m = new QMenu(installButton);
175  // Add icon to use dropdown, see also BUG: 385858
176  QAction *action = m->addAction(QIcon::fromTheme(QStringLiteral("checkmark")), i18n("Use"));
177  connect(action, &QAction::triggered, m, [this, entry](bool) {
178  QStringList args = KShell::splitArgs(m_engine->adoptionCommand(entry));
179  qCDebug(KNEWSTUFF) << "executing AdoptionCommand" << args;
180  QProcess::startDetached(args.takeFirst(), args);
181  });
183  installButton->setMenu(m);
184  }
185  // Add uninstall option for updatable entries, BUG: 422047
186  if (entry.status() == Entry::Updateable) {
187  QMenu* m = installButton->menu();
188  if (!m) {
189  m = new QMenu(installButton);
190  }
191  QAction *action = m->addAction(m_iconDelete, i18n("Uninstall"));
192  connect(action, &QAction::triggered, m, [this, entry](bool){
193  m_engine->uninstall(entry);
194  });
196  installButton->setMenu(m);
197  }
198  }
199 
200  QToolButton *detailsButton = qobject_cast<QToolButton *>(widgets.at(DelegateDetailsButton));
201  if (detailsButton) {
202  detailsButton->setText(i18n("Details"));
203  detailsButton->setIcon(QIcon::fromTheme(QStringLiteral("documentinfo")));
204  }
205 
206  if (installButton && detailsButton) {
207  if (m_buttonSize.width() < installButton->sizeHint().width()) {
208  const_cast<QSize &>(m_buttonSize) = QSize(
209  qMax(option.fontMetrics.height() * 7,
210  qMax(installButton->sizeHint().width(), detailsButton->sizeHint().width())),
211  installButton->sizeHint().height());
212  }
213  installButton->resize(m_buttonSize);
214  installButton->move(right - installButton->width() - margin, option.rect.height() / 2 - installButton->height() * 1.5);
215  detailsButton->resize(m_buttonSize);
216  detailsButton->move(right - installButton->width() - margin, option.rect.height() / 2 - installButton->height() / 2);
217  }
218 
219  QLabel *infoLabel = qobject_cast<QLabel *>(widgets.at(DelegateLabel));
220  if (infoLabel != nullptr) {
221  infoLabel->setWordWrap(true);
222  if (model->hasPreviewImages()) {
223  // move the text right by kPreviewWidth + margin pixels to fit the preview
224  infoLabel->move(KNSCore::PreviewWidth + margin * 2, 0);
225  infoLabel->resize(QSize(option.rect.width() - KNSCore::PreviewWidth - (margin * 6) - m_buttonSize.width(), option.fontMetrics.height() * 7));
226 
227  } else {
228  infoLabel->move(margin, 0);
229  infoLabel->resize(QSize(option.rect.width() - (margin * 4) - m_buttonSize.width(), option.fontMetrics.height() * 7));
230  }
231 
232  QString text = QStringLiteral("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0//EN\" \"http://www.w3.org/TR/REC-html40/strict.dtd\">\n"
233  "<html><head><meta name=\"qrichtext\" content=\"1\" /><style type=\"text/css\">p, li { white-space: pre-wrap; margin:0 0 0 0;}\n"
234  "</style></head><body><p><b>");
235 
236  QUrl link = qvariant_cast<QUrl>(entry.homepage());
237  if (!link.isEmpty()) {
238  text += QLatin1String("<p><a href=\"") + link.url() + QLatin1String("\">") + entry.name() + QLatin1String("</a></p>\n");
239  } else {
240  text += entry.name();
241  }
242 
243  const auto downloadInfo = entry.downloadLinkInformationList();
244  if (!downloadInfo.isEmpty() && downloadInfo.at(0).size > 0) {
245  QString sizeString = KFormat().formatByteSize(downloadInfo.at(0).size * 1000);
246  text += i18nc("Show the size of the file in a list", "<p>Size: %1</p>", sizeString);
247  }
248 
249  text += QLatin1String("</b></p>\n");
250 
251  QString authorName = entry.author().name();
252  QString email = entry.author().email();
253  QString authorPage = entry.author().homepage();
254 
255  if (!authorName.isEmpty()) {
256  if (!authorPage.isEmpty()) {
257  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");
258  } else if (!email.isEmpty()) {
259  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");
260  } else {
261  text += QLatin1String("<p>") + i18nc("Show the author of this item in a list", "By <i>%1</i>", authorName) + QLatin1String("</p>\n");
262  }
263  }
264 
265  QString summary = QLatin1String("<p>") + option.fontMetrics.elidedText(entry.summary(),
266  Qt::ElideRight, infoLabel->width() * 3) + QStringLiteral("</p>\n");
267  text += summary;
268 
269  unsigned int fans = entry.numberFans();
270  unsigned int downloads = entry.downloadCount();
271 
272  QString fanString;
273  QString downloadString;
274  if (fans > 0) {
275  fanString = i18ncp("fan as in supporter", "1 fan", "%1 fans", fans);
276  }
277  if (downloads > 0) {
278  downloadString = i18np("1 download", "%1 downloads", downloads);
279  }
280  if (downloads > 0 || fans > 0) {
281  text += QLatin1String("<p>") + downloadString;
282  if (downloads > 0 && fans > 0) {
283  text += QLatin1String(", ");
284  }
285  text += fanString + QLatin1String("</p>\n");
286  }
287 
288  text += QLatin1String("</body></html>");
289  // use simplified to get rid of newlines etc
290  text = KNSCore::replaceBBCode(text).simplified();
291  infoLabel->setText(text);
292  }
293 
294  KRatingWidget *rating = qobject_cast<KRatingWidget *>(widgets.at(DelegateRatingWidget));
295  if (rating) {
296  if (entry.rating() > 0) {
297  rating->setToolTip(i18n("Rating: %1%", entry.rating()));
298  // Don't attempt to send a rating to the server if we're just updating the UI
299  rating->blockSignals(true);
300  // 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
301  rating->setRating((entry.rating() - 20) * 10 / 60);
302  rating->blockSignals(false);
303  // put the rating label below the install button
304  rating->move(right - installButton->width() - margin, option.rect.height() / 2 + installButton->height() / 2);
305  rating->resize(m_buttonSize);
306  } else {
307  rating->setVisible(false);
308  }
309  }
310 }
311 
312 // draws the preview
313 void ItemsViewDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
314 {
315  int margin = option.fontMetrics.height() / 2;
316 
317  QStyle *style = QApplication::style();
318  style->drawPrimitive(QStyle::PE_PanelItemViewItem, &option, painter, nullptr);
319 
320  painter->save();
321 
322  if (option.state & QStyle::State_Selected) {
323  painter->setPen(QPen(option.palette.highlightedText().color()));
324  } else {
325  painter->setPen(QPen(option.palette.text().color()));
326  }
327 
328  const KNSCore::ItemsModel *realmodel = qobject_cast<const KNSCore::ItemsModel *>(index.model());
329 
330  if (realmodel->hasPreviewImages()) {
331  int height = option.rect.height();
332  QPoint point(option.rect.left() + margin, option.rect.top() + ((height - KNSCore::PreviewHeight) / 2));
333 
335  if (entry.previewUrl(KNSCore::EntryInternal::PreviewSmall1).isEmpty()) {
336  // paint the no preview icon
337  //point.setX((PreviewWidth - m_noImage.width())/2 + 5);
338  //point.setY(option.rect.top() + ((height - m_noImage.height()) / 2));
339  //painter->drawPixmap(point, m_noImage);
340  } else {
341  QImage image = entry.previewImage(KNSCore::EntryInternal::PreviewSmall1);
342  if (!image.isNull()) {
343  point.setX((KNSCore::PreviewWidth - image.width()) / 2 + 5);
344  point.setY(option.rect.top() + ((height - image.height()) / 2));
345  painter->drawImage(point, image);
346 
347  QPoint framePoint(point.x() - 5, point.y() - 5);
348  painter->drawPixmap(framePoint, m_frameImage.scaled(image.width() + 10, image.height() + 10));
349  } else {
350  QRect rect(point, QSize(KNSCore::PreviewWidth, KNSCore::PreviewHeight));
351  painter->drawText(rect, Qt::AlignCenter | Qt::TextWordWrap, i18n("Loading Preview"));
352  }
353  }
354 
355  }
356  painter->restore();
357 }
358 
359 QSize ItemsViewDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const
360 {
361  Q_UNUSED(option);
362  Q_UNUSED(index);
363 
364  QSize size;
365 
366  size.setWidth(option.fontMetrics.height() * 4);
367  size.setHeight(qMax(option.fontMetrics.height() * 7, KNSCore::PreviewHeight)); // up to 6 lines of text, and two margins
368  return size;
369 }
370 
371 } // namespace
372 
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...
bool startDetached(qint64 *pid)
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
KCOREADDONS_EXPORT QStringList splitArgs(const QString &cmd, Options flags=NoOptions, Errors *err=nullptr)
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:60
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:70
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.
Definition: button.cpp:29
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.
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:93
This file is part of the KDE documentation.
Documentation copyright © 1996-2020 The KDE developers.
Generated on Sun Aug 9 2020 22:43:40 by doxygen 1.8.11 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.