File indexing completed on 2024-04-28 11:30:59
0001 // SPDX-License-Identifier: LGPL-2.1-or-later 0002 // 0003 // SPDX-FileCopyrightText: 2013 Bernhard Beschow <bbeschow@cs.tu-berlin.de> 0004 // SPDX-FileCopyrightText: 2013 Dennis Nienhüser <nienhueser@kde.org> 0005 // 0006 0007 #include "MapThemeDownloadDialog.h" 0008 #include "ui_MapThemeDownloadDialog.h" 0009 0010 #include "MarbleDirs.h" 0011 #include "NewstuffModel.h" 0012 #include "MarbleWidget.h" 0013 0014 #include <QPainter> 0015 #include <QTextDocument> 0016 #include <QAbstractTextDocumentLayout> 0017 #include <QStyledItemDelegate> 0018 0019 namespace Marble 0020 { 0021 0022 class MapItemDelegate : public QStyledItemDelegate 0023 { 0024 Q_OBJECT 0025 0026 public: 0027 MapItemDelegate( QListView* view, NewstuffModel* newstuffModel, MarbleWidget* marbleWidget ); 0028 void paint( QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index ) const override; 0029 QSize sizeHint( const QStyleOptionViewItem &option, const QModelIndex &index ) const override; 0030 0031 protected: 0032 bool editorEvent( QEvent *event, QAbstractItemModel *model, const QStyleOptionViewItem &option, const QModelIndex &index ) override; 0033 0034 private: 0035 enum Element { 0036 Icon, 0037 Text, 0038 InstallButton, 0039 UpgradeButton, 0040 OpenButton, 0041 CancelButton, 0042 RemoveButton, 0043 ProgressReport 0044 }; 0045 0046 int buttonWidth( const QStyleOptionViewItem &option ) const; 0047 QStyleOptionButton button( Element element, const QStyleOptionViewItem &option ) const; 0048 QRect position( Element element, const QStyleOptionViewItem &option ) const; 0049 static QString text( const QModelIndex &index ); 0050 QListView* m_view; 0051 NewstuffModel* m_newstuffModel; 0052 mutable int m_buttonWidth; 0053 int const m_margin; 0054 int const m_iconSize; 0055 MarbleWidget* m_marbleWidget; 0056 }; 0057 0058 class Q_DECL_HIDDEN MapThemeDownloadDialog::Private : public Ui::MapThemeDownloadDialog 0059 { 0060 public: 0061 Private() : 0062 m_model() 0063 {} 0064 0065 NewstuffModel m_model; 0066 }; 0067 0068 MapThemeDownloadDialog::MapThemeDownloadDialog( MarbleWidget* marbleWidget ) : 0069 QDialog( marbleWidget ), 0070 d( new Private ) 0071 { 0072 d->setupUi( this ); 0073 0074 d->m_model.setTargetDirectory(MarbleDirs::localPath() + QLatin1String("/maps")); 0075 d->m_model.setProvider( "https://marble.kde.org/maps-v3.xml" ); 0076 d->m_model.setRegistryFile(MarbleDirs::localPath() + QLatin1String("/newstuff/marble-map-themes.knsregistry"), Marble::NewstuffModel::NameTag); 0077 0078 d->listView->setIconSize( QSize( 130, 130 ) ); 0079 d->listView->setAlternatingRowColors( true ); 0080 d->listView->setUniformItemSizes( false ); 0081 d->listView->setResizeMode( QListView::Adjust ); 0082 d->listView->setItemDelegate( new MapItemDelegate( d->listView, &d->m_model, marbleWidget ) ); 0083 d->listView->setModel( &d->m_model ); 0084 } 0085 0086 MapThemeDownloadDialog::~MapThemeDownloadDialog() 0087 { 0088 delete d; 0089 } 0090 0091 MapItemDelegate::MapItemDelegate( QListView *view , NewstuffModel *newstuffModel, MarbleWidget* marbleWidget ) : 0092 m_view( view ), 0093 m_newstuffModel( newstuffModel ), 0094 m_buttonWidth( 0 ), 0095 m_margin( 5 ), 0096 m_iconSize( 16 ), 0097 m_marbleWidget( marbleWidget ) 0098 { 0099 // nothing to do 0100 } 0101 0102 void MapItemDelegate::paint( QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index ) const 0103 { 0104 QStyleOptionViewItem styleOption = option; 0105 styleOption.text = QString(); 0106 QApplication::style()->drawControl(QStyle::CE_ItemViewItem, &styleOption, painter); 0107 0108 QAbstractTextDocumentLayout::PaintContext paintContext; 0109 if (styleOption.state & QStyle::State_Selected) { 0110 paintContext.palette.setColor(QPalette::Text, 0111 styleOption.palette.color(QPalette::Active, QPalette::HighlightedText)); 0112 } 0113 0114 // Draw the map preview icon 0115 QRect const iconRect = position( Icon, option ); 0116 QIcon const icon = index.data( Qt::DecorationRole ).value<QIcon>(); 0117 painter->drawPixmap( iconRect, icon.pixmap( iconRect.size() ) ); 0118 0119 // Draw summary, author, and similar information 0120 QTextDocument document; 0121 QRect const textRect = position( Text, option ); 0122 document.setTextWidth( textRect.width() ); 0123 document.setDefaultFont( option.font ); 0124 document.setHtml( text( index ) ); 0125 0126 painter->save(); 0127 painter->translate( textRect.topLeft() ); 0128 painter->setClipRect( 0, 0, textRect.width(), textRect.height() ); 0129 document.documentLayout()->draw( painter, paintContext ); 0130 painter->restore(); 0131 0132 // Draw buttons and installation progress 0133 if ( index.data( NewstuffModel::IsTransitioning ).toBool() ) { 0134 qint64 total = qMax( qint64( 1 ), index.data( NewstuffModel::PayloadSize ).value<qint64>() ); 0135 qint64 progress = index.data( NewstuffModel::DownloadedSize ).value<qint64>(); 0136 0137 QStyleOptionProgressBar progressBarOption; 0138 progressBarOption.rect = position( ProgressReport, option ); 0139 progressBarOption.minimum = 0; 0140 progressBarOption.maximum = 100; 0141 progressBarOption.progress = ( 100.0 * progress / total ); 0142 progressBarOption.text = QString::number(progressBarOption.progress) + QLatin1Char('%'); 0143 progressBarOption.textVisible = true; 0144 QApplication::style()->drawControl(QStyle::CE_ProgressBar, &progressBarOption, painter); 0145 0146 QStyleOptionButton cancelButton = button( CancelButton, option ); 0147 QRect installRect = position( CancelButton, option ); 0148 cancelButton.rect = installRect; 0149 QApplication::style()->drawControl( QStyle::CE_PushButton, &cancelButton, painter ); 0150 QRect buttonTextRect(installRect); 0151 buttonTextRect.adjust(cancelButton.iconSize.width() + 4, 0, 0, 0); 0152 painter->drawText(buttonTextRect, Qt::AlignCenter, cancelButton.text); 0153 } else { 0154 bool const installed = index.data( NewstuffModel::IsInstalled ).toBool(); 0155 bool const upgradable = index.data( NewstuffModel::IsUpgradable ).toBool(); 0156 Element element = InstallButton; 0157 if ( installed ) { 0158 element = upgradable ? UpgradeButton : OpenButton; 0159 } 0160 QStyleOptionButton actionButton = button( element, option ); 0161 QRect installRect = position( element, option ); 0162 actionButton.rect = installRect; 0163 QApplication::style()->drawControl( QStyle::CE_PushButton, &actionButton, painter ); 0164 QRect buttonTextRect(installRect); 0165 buttonTextRect.adjust(actionButton.iconSize.width() + 4, 0, 0, 0); 0166 painter->drawText(buttonTextRect, Qt::AlignCenter, actionButton.text); 0167 0168 if ( installed ) { 0169 QStyleOptionButton removeButton = button( RemoveButton, option ); 0170 QRect removeRect = position( RemoveButton, option ); 0171 removeButton.rect = removeRect; 0172 QApplication::style()->drawControl( QStyle::CE_PushButton, &removeButton, painter ); 0173 buttonTextRect = removeRect; 0174 buttonTextRect.adjust(removeButton.iconSize.width() + 4, 0, 0 ,0); 0175 painter->drawText(buttonTextRect, Qt::AlignCenter, removeButton.text); 0176 } 0177 } 0178 } 0179 0180 QSize MapItemDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const 0181 { 0182 if ( index.column() == 0 ) { 0183 QSize const iconSize = option.decorationSize; 0184 QTextDocument doc; 0185 doc.setDefaultFont( option.font ); 0186 doc.setTextWidth( qMax( 200, m_view->contentsRect().width() - iconSize.width() - buttonWidth( option ) - 3 * m_margin ) ); 0187 doc.setHtml( text( index ) ); 0188 return QSize( iconSize.width() + doc.size().width() + buttonWidth( option ) + 3 * m_margin, 0189 2 + qMax( iconSize.height(), qRound( doc.size().height() ) ) ); 0190 } 0191 0192 return QSize(); 0193 } 0194 0195 bool MapItemDelegate::editorEvent(QEvent *event, QAbstractItemModel *, const QStyleOptionViewItem &option, const QModelIndex &index) 0196 { 0197 if ( ( event->type() == QEvent::MouseButtonRelease ) ) { 0198 QMouseEvent *mouseEvent = static_cast<QMouseEvent*>( event ); 0199 if ( index.data( NewstuffModel::IsTransitioning ).toBool() ) { 0200 QRect cancelRect = position( CancelButton, option ); 0201 if ( cancelRect.contains( mouseEvent->pos() ) ) { 0202 m_newstuffModel->cancel( index.row() ); 0203 return true; 0204 } 0205 } else { 0206 bool const installed = index.data( NewstuffModel::IsInstalled ).toBool(); 0207 bool const upgradable = index.data( NewstuffModel::IsUpgradable ).toBool(); 0208 0209 if ( !installed || upgradable ) { 0210 QRect installRect = position( InstallButton, option ); 0211 if ( installRect.contains( mouseEvent->pos() ) ) { 0212 m_newstuffModel->install( index.row() ); 0213 return true; 0214 } 0215 } 0216 0217 if ( installed && !upgradable && m_marbleWidget ) { 0218 QRect openRect = position( OpenButton, option ); 0219 if ( openRect.contains( mouseEvent->pos() ) ) { 0220 QStringList const files = index.data( NewstuffModel::InstalledFiles ).toStringList(); 0221 for( const QString &file: files ) { 0222 if ( file.endsWith( QLatin1String( ".dgml" ) ) ) { 0223 QFileInfo dgmlFile( file ); 0224 QDir baseDir = dgmlFile.dir(); 0225 baseDir.cdUp(); 0226 baseDir.cdUp(); 0227 int const index = baseDir.absolutePath().size(); 0228 QString const mapTheme = dgmlFile.absoluteFilePath().mid( index+1 ); 0229 m_marbleWidget->setMapThemeId( mapTheme ); 0230 return true; 0231 } 0232 } 0233 } 0234 } 0235 0236 if ( installed ) { 0237 QRect removeRect = position( RemoveButton, option ); 0238 if ( removeRect.contains( mouseEvent->pos() ) ) { 0239 m_newstuffModel->uninstall( index.row() ); 0240 return true; 0241 } 0242 } 0243 } 0244 } 0245 0246 return false; 0247 } 0248 0249 int MapItemDelegate::buttonWidth(const QStyleOptionViewItem &option) const 0250 { 0251 if ( m_buttonWidth <= 0 ) { 0252 int const installWidth = option.fontMetrics.size( 0, tr( "Install" ) ).width(); 0253 int const removeWidth = option.fontMetrics.size( 0, tr( "Remove" ) ).width(); 0254 int const cancelWidth = option.fontMetrics.size( 0, tr( "Cancel" ) ).width(); 0255 int const upgradeWidth = option.fontMetrics.size( 0, tr( "Upgrade" ) ).width(); 0256 m_buttonWidth = 2 * m_iconSize + qMax( qMax( installWidth, removeWidth ), 0257 qMax( cancelWidth, upgradeWidth ) ); 0258 } 0259 0260 return m_buttonWidth; 0261 } 0262 0263 QStyleOptionButton MapItemDelegate::button( Element element, const QStyleOptionViewItem &option ) const 0264 { 0265 QStyleOptionButton result; 0266 result.state = option.state; 0267 result.state &= ~QStyle::State_HasFocus; 0268 0269 result.palette = option.palette; 0270 result.features = QStyleOptionButton::None; 0271 0272 switch (element) { 0273 case InstallButton: 0274 result.text = tr( "Install" ); 0275 result.icon = QIcon(QStringLiteral(":/marble/dialog-ok.png")); 0276 result.iconSize = QSize( m_iconSize, m_iconSize ); 0277 break; 0278 case UpgradeButton: 0279 result.text = tr( "Update" ); 0280 result.icon = QIcon(QStringLiteral(":/marble/system-software-update.png")); 0281 result.iconSize = QSize( m_iconSize, m_iconSize ); 0282 break; 0283 case OpenButton: 0284 result.text = tr( "Open" ); 0285 result.icon = QIcon(QStringLiteral(":/marble/document-open.png")); 0286 result.iconSize = QSize( m_iconSize, m_iconSize ); 0287 break; 0288 case CancelButton: 0289 result.text = tr( "Cancel" ); 0290 break; 0291 case RemoveButton: 0292 result.text = tr( "Remove" ); 0293 result.icon = QIcon(QStringLiteral(":/marble/edit-delete.png")); 0294 result.iconSize = QSize( m_iconSize, m_iconSize ); 0295 break; 0296 default: 0297 // ignored 0298 break; 0299 } 0300 0301 return result; 0302 } 0303 0304 QRect MapItemDelegate::position(Element element, const QStyleOptionViewItem &option ) const 0305 { 0306 int const width = buttonWidth( option ); 0307 QPoint const topLeftCol1 = option.rect.topLeft() + QPoint( 0, 2 ); 0308 QPoint const topLeftCol2 = topLeftCol1 + QPoint( option.decorationSize.width(), 0 ); 0309 QPoint const topLeftCol3 = topLeftCol2 + QPoint( option.rect.width() - 3 * m_margin - width - option.decorationSize.width(), 0 ); 0310 switch (element) { 0311 case Icon: 0312 return QRect( topLeftCol1, option.decorationSize ); 0313 case Text: 0314 return QRect( topLeftCol2, QSize( topLeftCol3.x()-topLeftCol2.x(), option.rect.height() ) ); 0315 case InstallButton: 0316 case UpgradeButton: 0317 case OpenButton: 0318 { 0319 QStyleOptionButton optionButton = button( element, option ); 0320 QSize size = option.fontMetrics.size( 0, optionButton.text ) + QSize( 4, 4 ); 0321 QSize buttonSize = QApplication::style()->sizeFromContents( QStyle::CT_PushButton, &optionButton, size ); 0322 buttonSize.setWidth( width ); 0323 return QRect( topLeftCol3, buttonSize ); 0324 } 0325 case RemoveButton: 0326 case CancelButton: 0327 { 0328 QStyleOptionButton optionButton = button( element, option ); 0329 QSize size = option.fontMetrics.size( 0, optionButton.text ) + QSize( 4, 4 ); 0330 QSize buttonSize = QApplication::style()->sizeFromContents( QStyle::CT_PushButton, &optionButton, size ); 0331 buttonSize.setWidth( width ); 0332 return QRect( topLeftCol3 + QPoint( 0, option.fontMetrics.height() + 8 + m_margin ), buttonSize ); 0333 } 0334 case ProgressReport: 0335 { 0336 QSize const progressSize = QSize( width, option.fontMetrics.height() + 4 ); 0337 return QRect( topLeftCol3 + QPoint( 0, m_margin ), progressSize ); 0338 } 0339 } 0340 0341 Q_ASSERT(false); 0342 return QRect(); 0343 } 0344 0345 QString MapItemDelegate::text( const QModelIndex &index ) 0346 { 0347 qreal const size = index.data( NewstuffModel::PayloadSize ).toLongLong() / 1024.0 / 1024.0; 0348 // Fields are typically not longer than 200 characters. Prevent excessive long text here anyway 0349 // due to bug 319542 0350 int const maxEntrySize = 4096; 0351 return QString("<p><b>%1</b><br />%2</p><p>Author: %3<br />License: %4<br />Version %5 (%6) %7</p>") 0352 .arg( index.data().toString() ) 0353 .arg( index.data( NewstuffModel::Summary ).toString().left( maxEntrySize ) ) 0354 .arg( index.data( NewstuffModel::Author ).toString().left( maxEntrySize ) ) 0355 .arg( index.data( NewstuffModel::License ).toString().left( maxEntrySize ) ) 0356 .arg( index.data( NewstuffModel::Version ).toString().left( maxEntrySize ) ) 0357 .arg( index.data( NewstuffModel::ReleaseDate ).toString().left( maxEntrySize ) ) 0358 .arg( size > 0 ? QString( "%1 MB" ).arg( size, 0, 'f', 1 ) : QString() ); 0359 } 0360 0361 } 0362 0363 #include "MapThemeDownloadDialog.moc" // needed for Q_OBJECT here in source 0364 0365 #include "moc_MapThemeDownloadDialog.cpp"