Marble

MapThemeDownloadDialog.cpp
1 // SPDX-License-Identifier: LGPL-2.1-or-later
2 //
3 // SPDX-FileCopyrightText: 2013 Bernhard Beschow <[email protected]>
4 // SPDX-FileCopyrightText: 2013 Dennis Nienhüser <[email protected]>
5 //
6 
7 #include "MapThemeDownloadDialog.h"
8 #include "ui_MapThemeDownloadDialog.h"
9 
10 #include "MarbleDirs.h"
11 #include "NewstuffModel.h"
12 #include "MarbleWidget.h"
13 
14 #include <QPainter>
15 #include <QTextDocument>
16 #include <QAbstractTextDocumentLayout>
17 #include <QStyledItemDelegate>
18 
19 namespace Marble
20 {
21 
22 class MapItemDelegate : public QStyledItemDelegate
23 {
24  Q_OBJECT
25 
26 public:
27  MapItemDelegate( QListView* view, NewstuffModel* newstuffModel, MarbleWidget* marbleWidget );
28  void paint( QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index ) const override;
29  QSize sizeHint( const QStyleOptionViewItem &option, const QModelIndex &index ) const override;
30 
31 protected:
32  bool editorEvent( QEvent *event, QAbstractItemModel *model, const QStyleOptionViewItem &option, const QModelIndex &index ) override;
33 
34 private:
35  enum Element {
36  Icon,
37  Text,
38  InstallButton,
39  UpgradeButton,
40  OpenButton,
41  CancelButton,
42  RemoveButton,
43  ProgressReport
44  };
45 
46  int buttonWidth( const QStyleOptionViewItem &option ) const;
47  QStyleOptionButton button( Element element, const QStyleOptionViewItem &option ) const;
48  QRect position( Element element, const QStyleOptionViewItem &option ) const;
49  static QString text( const QModelIndex &index );
50  QListView* m_view;
51  NewstuffModel* m_newstuffModel;
52  mutable int m_buttonWidth;
53  int const m_margin;
54  int const m_iconSize;
55  MarbleWidget* m_marbleWidget;
56 };
57 
58 class Q_DECL_HIDDEN MapThemeDownloadDialog::Private : public Ui::MapThemeDownloadDialog
59 {
60 public:
61  Private() :
62  m_model()
63  {}
64 
65  NewstuffModel m_model;
66 };
67 
68 MapThemeDownloadDialog::MapThemeDownloadDialog( MarbleWidget* marbleWidget ) :
69  QDialog( marbleWidget ),
70  d( new Private )
71 {
72  d->setupUi( this );
73 
74  d->m_model.setTargetDirectory(MarbleDirs::localPath() + QLatin1String("/maps"));
75  d->m_model.setProvider( "https://marble.kde.org/maps-v3.xml" );
76  d->m_model.setRegistryFile(MarbleDirs::localPath() + QLatin1String("/newstuff/marble-map-themes.knsregistry"), Marble::NewstuffModel::NameTag);
77 
78  d->listView->setIconSize( QSize( 130, 130 ) );
79  d->listView->setAlternatingRowColors( true );
80  d->listView->setUniformItemSizes( false );
81  d->listView->setResizeMode( QListView::Adjust );
82  d->listView->setItemDelegate( new MapItemDelegate( d->listView, &d->m_model, marbleWidget ) );
83  d->listView->setModel( &d->m_model );
84 }
85 
86 MapThemeDownloadDialog::~MapThemeDownloadDialog()
87 {
88  delete d;
89 }
90 
91 MapItemDelegate::MapItemDelegate( QListView *view , NewstuffModel *newstuffModel, MarbleWidget* marbleWidget ) :
92  m_view( view ),
93  m_newstuffModel( newstuffModel ),
94  m_buttonWidth( 0 ),
95  m_margin( 5 ),
96  m_iconSize( 16 ),
97  m_marbleWidget( marbleWidget )
98 {
99  // nothing to do
100 }
101 
102 void MapItemDelegate::paint( QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index ) const
103 {
104  QStyleOptionViewItem styleOption = option;
105  styleOption.text = QString();
106  QApplication::style()->drawControl(QStyle::CE_ItemViewItem, &styleOption, painter);
107 
109  if (styleOption.state & QStyle::State_Selected) {
110  paintContext.palette.setColor(QPalette::Text,
111  styleOption.palette.color(QPalette::Active, QPalette::HighlightedText));
112  }
113 
114  // Draw the map preview icon
115  QRect const iconRect = position( Icon, option );
116  QIcon const icon = index.data( Qt::DecorationRole ).value<QIcon>();
117  painter->drawPixmap( iconRect, icon.pixmap( iconRect.size() ) );
118 
119  // Draw summary, author, and similar information
120  QTextDocument document;
121  QRect const textRect = position( Text, option );
122  document.setTextWidth( textRect.width() );
123  document.setDefaultFont( option.font );
124  document.setHtml( text( index ) );
125 
126  painter->save();
127  painter->translate( textRect.topLeft() );
128  painter->setClipRect( 0, 0, textRect.width(), textRect.height() );
129  document.documentLayout()->draw( painter, paintContext );
130  painter->restore();
131 
132  // Draw buttons and installation progress
133  if ( index.data( NewstuffModel::IsTransitioning ).toBool() ) {
134  qint64 total = qMax( qint64( 1 ), index.data( NewstuffModel::PayloadSize ).value<qint64>() );
135  qint64 progress = index.data( NewstuffModel::DownloadedSize ).value<qint64>();
136 
137  QStyleOptionProgressBar progressBarOption;
138  progressBarOption.rect = position( ProgressReport, option );
139  progressBarOption.minimum = 0;
140  progressBarOption.maximum = 100;
141  progressBarOption.progress = ( 100.0 * progress / total );
142  progressBarOption.text = QString::number(progressBarOption.progress) + QLatin1Char('%');
143  progressBarOption.textVisible = true;
144  QApplication::style()->drawControl(QStyle::CE_ProgressBar, &progressBarOption, painter);
145 
146  QStyleOptionButton cancelButton = button( CancelButton, option );
147  QRect installRect = position( CancelButton, option );
148  cancelButton.rect = installRect;
149  QApplication::style()->drawControl( QStyle::CE_PushButton, &cancelButton, painter );
150  QRect buttonTextRect(installRect);
151  buttonTextRect.adjust(cancelButton.iconSize.width() + 4, 0, 0, 0);
152  painter->drawText(buttonTextRect, Qt::AlignCenter, cancelButton.text);
153  } else {
154  bool const installed = index.data( NewstuffModel::IsInstalled ).toBool();
155  bool const upgradable = index.data( NewstuffModel::IsUpgradable ).toBool();
156  Element element = InstallButton;
157  if ( installed ) {
158  element = upgradable ? UpgradeButton : OpenButton;
159  }
160  QStyleOptionButton actionButton = button( element, option );
161  QRect installRect = position( element, option );
162  actionButton.rect = installRect;
163  QApplication::style()->drawControl( QStyle::CE_PushButton, &actionButton, painter );
164  QRect buttonTextRect(installRect);
165  buttonTextRect.adjust(actionButton.iconSize.width() + 4, 0, 0, 0);
166  painter->drawText(buttonTextRect, Qt::AlignCenter, actionButton.text);
167 
168  if ( installed ) {
169  QStyleOptionButton removeButton = button( RemoveButton, option );
170  QRect removeRect = position( RemoveButton, option );
171  removeButton.rect = removeRect;
172  QApplication::style()->drawControl( QStyle::CE_PushButton, &removeButton, painter );
173  buttonTextRect = removeRect;
174  buttonTextRect.adjust(removeButton.iconSize.width() + 4, 0, 0 ,0);
175  painter->drawText(buttonTextRect, Qt::AlignCenter, removeButton.text);
176  }
177  }
178 }
179 
180 QSize MapItemDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const
181 {
182  if ( index.column() == 0 ) {
183  QSize const iconSize = option.decorationSize;
184  QTextDocument doc;
185  doc.setDefaultFont( option.font );
186  doc.setTextWidth( qMax( 200, m_view->contentsRect().width() - iconSize.width() - buttonWidth( option ) - 3 * m_margin ) );
187  doc.setHtml( text( index ) );
188  return QSize( iconSize.width() + doc.size().width() + buttonWidth( option ) + 3 * m_margin,
189  2 + qMax( iconSize.height(), qRound( doc.size().height() ) ) );
190  }
191 
192  return QSize();
193 }
194 
195 bool MapItemDelegate::editorEvent(QEvent *event, QAbstractItemModel *, const QStyleOptionViewItem &option, const QModelIndex &index)
196 {
197  if ( ( event->type() == QEvent::MouseButtonRelease ) ) {
198  QMouseEvent *mouseEvent = static_cast<QMouseEvent*>( event );
199  if ( index.data( NewstuffModel::IsTransitioning ).toBool() ) {
200  QRect cancelRect = position( CancelButton, option );
201  if ( cancelRect.contains( mouseEvent->pos() ) ) {
202  m_newstuffModel->cancel( index.row() );
203  return true;
204  }
205  } else {
206  bool const installed = index.data( NewstuffModel::IsInstalled ).toBool();
207  bool const upgradable = index.data( NewstuffModel::IsUpgradable ).toBool();
208 
209  if ( !installed || upgradable ) {
210  QRect installRect = position( InstallButton, option );
211  if ( installRect.contains( mouseEvent->pos() ) ) {
212  m_newstuffModel->install( index.row() );
213  return true;
214  }
215  }
216 
217  if ( installed && !upgradable && m_marbleWidget ) {
218  QRect openRect = position( OpenButton, option );
219  if ( openRect.contains( mouseEvent->pos() ) ) {
220  QStringList const files = index.data( NewstuffModel::InstalledFiles ).toStringList();
221  for( const QString &file: files ) {
222  if ( file.endsWith( QLatin1String( ".dgml" ) ) ) {
223  QFileInfo dgmlFile( file );
224  QDir baseDir = dgmlFile.dir();
225  baseDir.cdUp();
226  baseDir.cdUp();
227  int const index = baseDir.absolutePath().size();
228  QString const mapTheme = dgmlFile.absoluteFilePath().mid( index+1 );
229  m_marbleWidget->setMapThemeId( mapTheme );
230  return true;
231  }
232  }
233  }
234  }
235 
236  if ( installed ) {
237  QRect removeRect = position( RemoveButton, option );
238  if ( removeRect.contains( mouseEvent->pos() ) ) {
239  m_newstuffModel->uninstall( index.row() );
240  return true;
241  }
242  }
243  }
244  }
245 
246  return false;
247 }
248 
249 int MapItemDelegate::buttonWidth(const QStyleOptionViewItem &option) const
250 {
251  if ( m_buttonWidth <= 0 ) {
252  int const installWidth = option.fontMetrics.size( 0, tr( "Install" ) ).width();
253  int const removeWidth = option.fontMetrics.size( 0, tr( "Remove" ) ).width();
254  int const cancelWidth = option.fontMetrics.size( 0, tr( "Cancel" ) ).width();
255  int const upgradeWidth = option.fontMetrics.size( 0, tr( "Upgrade" ) ).width();
256  m_buttonWidth = 2 * m_iconSize + qMax( qMax( installWidth, removeWidth ),
257  qMax( cancelWidth, upgradeWidth ) );
258  }
259 
260  return m_buttonWidth;
261 }
262 
263 QStyleOptionButton MapItemDelegate::button( Element element, const QStyleOptionViewItem &option ) const
264 {
265  QStyleOptionButton result;
266  result.state = option.state;
267  result.state &= ~QStyle::State_HasFocus;
268 
269  result.palette = option.palette;
270  result.features = QStyleOptionButton::None;
271 
272  switch (element) {
273  case InstallButton:
274  result.text = tr( "Install" );
275  result.icon = QIcon(QStringLiteral(":/marble/dialog-ok.png"));
276  result.iconSize = QSize( m_iconSize, m_iconSize );
277  break;
278  case UpgradeButton:
279  result.text = tr( "Update" );
280  result.icon = QIcon(QStringLiteral(":/marble/system-software-update.png"));
281  result.iconSize = QSize( m_iconSize, m_iconSize );
282  break;
283  case OpenButton:
284  result.text = tr( "Open" );
285  result.icon = QIcon(QStringLiteral(":/marble/document-open.png"));
286  result.iconSize = QSize( m_iconSize, m_iconSize );
287  break;
288  case CancelButton:
289  result.text = tr( "Cancel" );
290  break;
291  case RemoveButton:
292  result.text = tr( "Remove" );
293  result.icon = QIcon(QStringLiteral(":/marble/edit-delete.png"));
294  result.iconSize = QSize( m_iconSize, m_iconSize );
295  break;
296  default:
297  // ignored
298  break;
299  }
300 
301  return result;
302 }
303 
304 QRect MapItemDelegate::position(Element element, const QStyleOptionViewItem &option ) const
305 {
306  int const width = buttonWidth( option );
307  QPoint const topLeftCol1 = option.rect.topLeft() + QPoint( 0, 2 );
308  QPoint const topLeftCol2 = topLeftCol1 + QPoint( option.decorationSize.width(), 0 );
309  QPoint const topLeftCol3 = topLeftCol2 + QPoint( option.rect.width() - 3 * m_margin - width - option.decorationSize.width(), 0 );
310  switch (element) {
311  case Icon:
312  return QRect( topLeftCol1, option.decorationSize );
313  case Text:
314  return QRect( topLeftCol2, QSize( topLeftCol3.x()-topLeftCol2.x(), option.rect.height() ) );
315  case InstallButton:
316  case UpgradeButton:
317  case OpenButton:
318  {
319  QStyleOptionButton optionButton = button( element, option );
320  QSize size = option.fontMetrics.size( 0, optionButton.text ) + QSize( 4, 4 );
321  QSize buttonSize = QApplication::style()->sizeFromContents( QStyle::CT_PushButton, &optionButton, size );
322  buttonSize.setWidth( width );
323  return QRect( topLeftCol3, buttonSize );
324  }
325  case RemoveButton:
326  case CancelButton:
327  {
328  QStyleOptionButton optionButton = button( element, option );
329  QSize size = option.fontMetrics.size( 0, optionButton.text ) + QSize( 4, 4 );
330  QSize buttonSize = QApplication::style()->sizeFromContents( QStyle::CT_PushButton, &optionButton, size );
331  buttonSize.setWidth( width );
332  return QRect( topLeftCol3 + QPoint( 0, option.fontMetrics.height() + 8 + m_margin ), buttonSize );
333  }
334  case ProgressReport:
335  {
336  QSize const progressSize = QSize( width, option.fontMetrics.height() + 4 );
337  return QRect( topLeftCol3 + QPoint( 0, m_margin ), progressSize );
338  }
339  }
340 
341  Q_ASSERT(false);
342  return QRect();
343 }
344 
345 QString MapItemDelegate::text( const QModelIndex &index )
346 {
347  qreal const size = index.data( NewstuffModel::PayloadSize ).toLongLong() / 1024.0 / 1024.0;
348  // Fields are typically not longer than 200 characters. Prevent excessive long text here anyway
349  // due to bug 319542
350  int const maxEntrySize = 4096;
351  return QString("<p><b>%1</b><br />%2</p><p>Author: %3<br />License: %4<br />Version %5 (%6) %7</p>")
352  .arg( index.data().toString() )
353  .arg( index.data( NewstuffModel::Summary ).toString().left( maxEntrySize ) )
354  .arg( index.data( NewstuffModel::Author ).toString().left( maxEntrySize ) )
355  .arg( index.data( NewstuffModel::License ).toString().left( maxEntrySize ) )
356  .arg( index.data( NewstuffModel::Version ).toString().left( maxEntrySize ) )
357  .arg( index.data( NewstuffModel::ReleaseDate ).toString().left( maxEntrySize ) )
358  .arg( size > 0 ? QString( "%1 MB" ).arg( size, 0, 'f', 1 ) : QString() );
359 }
360 
361 }
362 
363 #include "MapThemeDownloadDialog.moc" // needed for Q_OBJECT here in source
Q_OBJECTQ_OBJECT
QPoint pos() const const
AlignCenter
virtual void drawControl(QStyle::ControlElement element, const QStyleOption *option, QPainter *painter, const QWidget *widget) const const=0
DecorationRole
MouseButtonRelease
QPoint topLeft() const const
QString number(int n, int base)
bool cdUp()
void drawPixmap(const QRectF &target, const QPixmap &pixmap, const QRectF &source)
int size() const const
QSize size() const const
int column() const const
T value() const const
void setClipRect(const QRectF &rectangle, Qt::ClipOperation operation)
QAbstractTextDocumentLayout * documentLayout() const const
void adjust(int dx1, int dy1, int dx2, int dy2)
int width() const const
int x() const const
int width() const const
qlonglong toLongLong(bool *ok) const const
bool contains(const QRect &rectangle, bool proper) const const
void drawText(const QPointF &position, const QString &text)
void setWidth(int width)
QVariant data(int role) const const
void setTextWidth(qreal width)
int height() const const
void setDefaultFont(const QFont &font)
Binds a QML item to a specific geodetic location in screen coordinates.
AKONADI_CALENDAR_EXPORT KCalendarCore::Event::Ptr event(const Akonadi::Item &item)
QString absolutePath() const const
bool toBool() const const
int row() const const
virtual QSize sizeFromContents(QStyle::ContentsType type, const QStyleOption *option, const QSize &contentsSize, const QWidget *widget) const const=0
int height() const const
QPixmap pixmap(const QSize &size, QIcon::Mode mode, QIcon::State state) const const
QString arg(qlonglong a, int fieldWidth, int base, QChar fillChar) const const
QString left(int n) const const
void setHtml(const QString &html)
void translate(const QPointF &offset)
void restore()
void save()
QStringList toStringList() const const
virtual void draw(QPainter *painter, const QAbstractTextDocumentLayout::PaintContext &context)=0
QString mid(int position, int n) const const
QStyle * style()
QString toString() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2022 The KDE developers.
Generated on Thu May 26 2022 04:07:49 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.