File indexing completed on 2024-04-21 03:49:38

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"