File indexing completed on 2024-04-14 03:47:46

0001 // SPDX-License-Identifier: LGPL-2.1-or-later
0002 //
0003 // SPDX-FileCopyrightText: 2010 Dennis Nienhüser <nienhueser@kde.org>
0004 // SPDX-FileCopyrightText: 2011 Bernhard Beschow <bbeschow@cs.tu-berlin.de>
0005 //
0006 
0007 #include "GoToDialog.h"
0008 #include "ui_GoToDialog.h"
0009 
0010 #include "BookmarkManager.h"
0011 #include "MarbleWidget.h"
0012 #include "MarbleModel.h"
0013 #include "MarblePlacemarkModel.h"
0014 #include "GeoDataLookAt.h"
0015 #include "GeoDataTreeModel.h"
0016 #include "GeoDataDocument.h"
0017 #include "GeoDataFolder.h"
0018 #include "GeoDataPlacemark.h"
0019 #include "PositionTracking.h"
0020 #include "SearchRunnerManager.h"
0021 #include "routing/RoutingManager.h"
0022 #include "routing/RouteRequest.h"
0023 
0024 #include <QAbstractListModel>
0025 #include <QTimer>
0026 #include <QPainter>
0027 
0028 namespace Marble
0029 {
0030 
0031 class TargetModel : public QAbstractListModel
0032 {
0033     Q_OBJECT
0034 public:
0035     TargetModel( MarbleModel* marbleModel, QObject * parent = nullptr );
0036 
0037     int rowCount ( const QModelIndex & parent = QModelIndex() ) const override;
0038 
0039     QVariant data ( const QModelIndex & index, int role = Qt::DisplayRole ) const override;
0040 
0041     void setShowRoutingItems( bool show );
0042 
0043 private:
0044     QVariant currentLocationData ( int role ) const;
0045 
0046     QVariant routeData ( const QVector<GeoDataPlacemark> &via, int index, int role ) const;
0047 
0048     QVariant homeData ( int role ) const;
0049 
0050     QVariant bookmarkData ( int index, int role ) const;
0051 
0052     QVector<GeoDataPlacemark> viaPoints() const;
0053 
0054     MarbleModel *const m_marbleModel;
0055 
0056     QVector<GeoDataPlacemark*> m_bookmarks;
0057 
0058     bool m_hasCurrentLocation;
0059 
0060     bool m_showRoutingItems;
0061 };
0062 
0063 class GoToDialogPrivate : public Ui::GoTo
0064 {
0065 public:
0066     GoToDialog* m_parent;
0067 
0068     GeoDataCoordinates m_coordinates;
0069 
0070     MarbleModel *const m_marbleModel;
0071 
0072     TargetModel m_targetModel;
0073 
0074     SearchRunnerManager m_runnerManager;
0075 
0076     GeoDataDocument *m_searchResult;
0077 
0078     GeoDataTreeModel m_searchResultModel;
0079 
0080     QTimer m_progressTimer;
0081 
0082     int m_currentFrame;
0083 
0084     QVector<QIcon> m_progressAnimation;
0085 
0086     GoToDialogPrivate( GoToDialog* parent, MarbleModel* marbleModel );
0087 
0088     void saveSelection( const QModelIndex &index );
0089 
0090     void createProgressAnimation();
0091 
0092     void startSearch();
0093 
0094     void updateSearchResult( const QVector<GeoDataPlacemark*>& placemarks );
0095 
0096     void updateSearchMode();
0097 
0098     void updateProgress();
0099 
0100     void stopProgressAnimation();
0101 
0102     void updateResultMessage( int results );
0103 };
0104 
0105 TargetModel::TargetModel( MarbleModel *marbleModel, QObject * parent ) :
0106     QAbstractListModel( parent ),
0107     m_marbleModel( marbleModel ),
0108     m_hasCurrentLocation( false ),
0109     m_showRoutingItems( true )
0110 {
0111     BookmarkManager* manager = m_marbleModel->bookmarkManager();
0112     for( GeoDataFolder * folder: manager->folders() ) {
0113         QVector<GeoDataPlacemark*> bookmarks = folder->placemarkList();
0114         QVector<GeoDataPlacemark*>::const_iterator iter = bookmarks.constBegin();
0115         QVector<GeoDataPlacemark*>::const_iterator end = bookmarks.constEnd();
0116 
0117         for ( ; iter != end; ++iter ) {
0118             m_bookmarks.push_back( *iter );
0119         }
0120     }
0121 
0122     PositionTracking* tracking = m_marbleModel->positionTracking();
0123     m_hasCurrentLocation = tracking && tracking->status() == PositionProviderStatusAvailable;
0124 }
0125 
0126 QVector<GeoDataPlacemark> TargetModel::viaPoints() const
0127 {
0128     if ( !m_showRoutingItems ) {
0129         return QVector<GeoDataPlacemark>();
0130     }
0131 
0132     RouteRequest* request = m_marbleModel->routingManager()->routeRequest();
0133     QVector<GeoDataPlacemark> result;
0134     for ( int i = 0; i < request->size(); ++i ) {
0135         if ( request->at( i ).isValid() ) {
0136             GeoDataPlacemark placemark;
0137             placemark.setCoordinate( request->at( i ) );
0138             placemark.setName( request->name( i ) );
0139             result.push_back( placemark );
0140         }
0141     }
0142     return result;
0143 }
0144 
0145 int TargetModel::rowCount ( const QModelIndex & parent ) const
0146 {
0147     int result = 0;
0148     if ( !parent.isValid() ) {
0149         result += m_hasCurrentLocation ? 1 : 0;
0150         result += viaPoints().size(); // route
0151         result += 1; // home location
0152         result += m_bookmarks.size(); // bookmarks
0153         return result;
0154     }
0155 
0156     return result;
0157 }
0158 
0159 QVariant TargetModel::currentLocationData ( int role ) const
0160 {
0161     const PositionTracking* tracking = m_marbleModel->positionTracking();
0162     if ( tracking->status() == PositionProviderStatusAvailable ) {
0163         GeoDataCoordinates currentLocation = tracking->currentLocation();
0164         switch( role ) {
0165         case Qt::DisplayRole: return tr( "Current Location: %1" ).arg( currentLocation.toString() ) ;
0166         case Qt::DecorationRole: return QIcon(QStringLiteral(":/icons/gps.png"));
0167         case MarblePlacemarkModel::CoordinateRole: {
0168             return QVariant::fromValue( currentLocation );
0169         }
0170         }
0171     }
0172 
0173     return QVariant();
0174 }
0175 
0176 QVariant TargetModel::routeData ( const QVector<GeoDataPlacemark> &via, int index, int role ) const
0177 {
0178     RouteRequest* request = m_marbleModel->routingManager()->routeRequest();
0179     switch( role ) {
0180     case Qt::DisplayRole: return via.at( index ).name();
0181     case Qt::DecorationRole: return QIcon( request->pixmap( index ) );
0182     case MarblePlacemarkModel::CoordinateRole: {
0183         const GeoDataCoordinates coordinates = via.at( index ).coordinate();
0184         return QVariant::fromValue( coordinates );
0185     }
0186     }
0187 
0188     return QVariant();
0189 }
0190 
0191 QVariant TargetModel::homeData ( int role ) const
0192 {
0193     switch( role ) {
0194     case Qt::DisplayRole: return tr( "Home" );
0195     case Qt::DecorationRole: return QIcon(QStringLiteral(":/icons/go-home.png"));
0196     case MarblePlacemarkModel::CoordinateRole: {
0197         qreal lon( 0.0 ), lat( 0.0 );
0198         int zoom( 0 );
0199         m_marbleModel->home( lon, lat, zoom );
0200         const GeoDataCoordinates coordinates = GeoDataCoordinates( lon, lat, 0, GeoDataCoordinates::Degree );
0201         return QVariant::fromValue( coordinates );
0202     }
0203     }
0204 
0205     return QVariant();
0206 }
0207 
0208 QVariant TargetModel::bookmarkData ( int index, int role ) const
0209 {
0210     switch( role ) {
0211     case Qt::DisplayRole: {
0212         const GeoDataFolder *folder = geodata_cast<GeoDataFolder>(m_bookmarks[index]->parent());
0213         Q_ASSERT( folder && "Internal bookmark representation has changed. Please report this as a bug at https://bugs.kde.org." );
0214         if ( folder ) {
0215             return QString(folder->name() + QLatin1String(" / ") + m_bookmarks[index]->name());
0216         }
0217         return QVariant();
0218     }
0219     case Qt::DecorationRole: return QIcon(QStringLiteral(":/icons/bookmarks.png"));
0220     case MarblePlacemarkModel::CoordinateRole: return QVariant::fromValue( m_bookmarks[index]->lookAt()->coordinates() );
0221     }
0222 
0223     return QVariant();
0224 }
0225 
0226 
0227 QVariant TargetModel::data ( const QModelIndex & index, int role ) const
0228 {
0229     if ( index.isValid() && index.row() >= 0 && index.row() < rowCount() ) {
0230         int row = index.row();
0231         bool const isCurrentLocation = row == 0 && m_hasCurrentLocation;
0232         int homeOffset = m_hasCurrentLocation ? 1 : 0;
0233         QVector<GeoDataPlacemark> via = viaPoints();
0234         bool const isRoute = row >= homeOffset && row < homeOffset + via.size();
0235 
0236         if ( isCurrentLocation ) {
0237             return currentLocationData( role );
0238         } else if ( isRoute ) {
0239             int routeIndex = row - homeOffset;
0240             Q_ASSERT( routeIndex >= 0 && routeIndex < via.size() );
0241             return routeData( via, routeIndex, role );
0242         } else {
0243             int bookmarkIndex = row - homeOffset - via.size();
0244             if ( bookmarkIndex == 0 ) {
0245                 return homeData( role );
0246             } else {
0247                 --bookmarkIndex;
0248                 Q_ASSERT( bookmarkIndex >= 0 && bookmarkIndex < m_bookmarks.size() );
0249                 return bookmarkData( bookmarkIndex, role );
0250             }
0251         }
0252     }
0253 
0254     return QVariant();
0255 }
0256 
0257 void TargetModel::setShowRoutingItems( bool show )
0258 {
0259     m_showRoutingItems = show;
0260     beginResetModel();
0261     endResetModel();
0262 }
0263 
0264 void GoToDialogPrivate::createProgressAnimation()
0265 {
0266     bool const smallScreen = MarbleGlobal::getInstance()->profiles() & MarbleGlobal::SmallScreen;
0267     int const iconSize = smallScreen ? 32 : 16;
0268 
0269     // Size parameters
0270     qreal const h = iconSize / 2.0; // Half of the icon size
0271     qreal const q = h / 2.0; // Quarter of the icon size
0272     qreal const d = 7.5; // Circle diameter
0273     qreal const r = d / 2.0; // Circle radius
0274 
0275     // Canvas parameters
0276     QImage canvas( iconSize, iconSize, QImage::Format_ARGB32 );
0277     QPainter painter( &canvas );
0278     painter.setRenderHint( QPainter::Antialiasing, true );
0279     painter.setPen( QColor ( Qt::gray ) );
0280     painter.setBrush( QColor( Qt::white ) );
0281 
0282     // Create all frames
0283     for( double t = 0.0; t < 2 * M_PI; t += M_PI / 8.0 ) {
0284         canvas.fill( Qt::transparent );
0285         QRectF firstCircle( h - r + q * cos( t ), h - r + q * sin( t ), d, d );
0286         QRectF secondCircle( h - r + q * cos( t + M_PI ), h - r + q * sin( t + M_PI ), d, d );
0287         painter.drawEllipse( firstCircle );
0288         painter.drawEllipse( secondCircle );
0289         m_progressAnimation.push_back( QIcon( QPixmap::fromImage( canvas ) ) );
0290     }
0291 }
0292 
0293 GoToDialogPrivate::GoToDialogPrivate( GoToDialog* parent, MarbleModel* marbleModel ) :
0294     m_parent( parent),
0295     m_marbleModel( marbleModel ),
0296     m_targetModel( marbleModel ),
0297     m_runnerManager( marbleModel ),
0298     m_searchResult( new GeoDataDocument ),
0299     m_currentFrame( 0 )
0300 {
0301     setupUi( parent );
0302 
0303     m_progressTimer.setInterval( 100 );
0304 }
0305 
0306 void GoToDialogPrivate::saveSelection( const QModelIndex &index )
0307 {
0308     if ( searchButton->isChecked() && m_searchResult->size() ) {
0309         QVariant coordinates = m_searchResultModel.data( index, MarblePlacemarkModel::CoordinateRole );
0310         m_coordinates = coordinates.value<GeoDataCoordinates>();
0311     } else {
0312         QVariant coordinates = index.data( MarblePlacemarkModel::CoordinateRole );
0313         m_coordinates = coordinates.value<GeoDataCoordinates>();
0314     }
0315     m_parent->accept();
0316 }
0317 
0318 void GoToDialogPrivate::startSearch()
0319 {
0320     QString const searchTerm = searchLineEdit->text().trimmed();
0321     if ( searchTerm.isEmpty() ) {
0322         return;
0323     }
0324 
0325     m_runnerManager.findPlacemarks( searchTerm );
0326     if ( m_progressAnimation.isEmpty() ) {
0327         createProgressAnimation();
0328     }
0329     m_progressTimer.start();
0330     progressButton->setVisible( true );
0331     searchLineEdit->setEnabled( false );
0332     updateResultMessage( 0 );
0333 }
0334 
0335 void GoToDialogPrivate::updateSearchResult( const QVector<GeoDataPlacemark*>& placemarks )
0336 {
0337     m_searchResultModel.setRootDocument( nullptr );
0338     m_searchResult->clear();
0339     for (GeoDataPlacemark *placemark: placemarks) {
0340         m_searchResult->append( new GeoDataPlacemark( *placemark ) );
0341     }
0342     m_searchResultModel.setRootDocument( m_searchResult );
0343     bookmarkListView->setModel( &m_searchResultModel );
0344     updateResultMessage( m_searchResultModel.rowCount() );
0345 }
0346 
0347 GoToDialog::GoToDialog( MarbleModel* marbleModel, QWidget * parent, Qt::WindowFlags flags ) :
0348     QDialog( parent, flags ),
0349     d( new GoToDialogPrivate( this, marbleModel ) )
0350 {
0351     d->searchLineEdit->setPlaceholderText( tr( "Address or search term" ) );
0352 
0353     d->m_searchResultModel.setRootDocument( d->m_searchResult );
0354     d->bookmarkListView->setModel( &d->m_targetModel );
0355     connect( d->bookmarkListView, SIGNAL(activated(QModelIndex)),
0356              this, SLOT(saveSelection(QModelIndex)) );
0357     connect( d->searchLineEdit, SIGNAL(returnPressed()),
0358              this, SLOT(startSearch()) );
0359     d->buttonBox->button( QDialogButtonBox::Close )->setAutoDefault( false );
0360     connect( d->searchButton, SIGNAL(clicked(bool)),
0361              this, SLOT(updateSearchMode()) );
0362     connect( d->browseButton, SIGNAL(clicked(bool)),
0363              this, SLOT(updateSearchMode()) );
0364     connect( &d->m_progressTimer, SIGNAL(timeout()),
0365              this, SLOT(updateProgress()) );
0366     connect( d->progressButton, SIGNAL(clicked(bool)),
0367              this, SLOT(stopProgressAnimation()) );
0368     d->updateSearchMode();
0369     d->progressButton->setVisible( false );
0370 
0371     connect( &d->m_runnerManager, SIGNAL(searchResultChanged(QVector<GeoDataPlacemark*>)),
0372              this, SLOT(updateSearchResult(QVector<GeoDataPlacemark*>)) );
0373     connect( &d->m_runnerManager, SIGNAL(searchFinished(QString)),
0374              this, SLOT(stopProgressAnimation()) );
0375 }
0376 
0377 GoToDialog::~GoToDialog()
0378 {
0379     delete d;
0380 }
0381 
0382 GeoDataCoordinates GoToDialog::coordinates() const
0383 {
0384     return d->m_coordinates;
0385 }
0386 
0387 void GoToDialog::setShowRoutingItems( bool show )
0388 {
0389     d->m_targetModel.setShowRoutingItems( show );
0390 }
0391 
0392 void GoToDialog::setSearchEnabled( bool enabled )
0393 {
0394     d->browseButton->setVisible( enabled );
0395     d->searchButton->setVisible( enabled );
0396     if ( !enabled ) {
0397         d->searchButton->setChecked( false );
0398         d->updateSearchMode();
0399     }
0400 }
0401 
0402 void GoToDialogPrivate::updateSearchMode()
0403 {
0404     bool const searchEnabled = searchButton->isChecked();
0405     searchLineEdit->setVisible( searchEnabled );
0406     descriptionLabel->setVisible( searchEnabled );
0407     progressButton->setVisible( searchEnabled && m_progressTimer.isActive() );
0408     if ( searchEnabled ) {
0409         bookmarkListView->setModel( &m_searchResultModel );
0410         searchLineEdit->setFocus();
0411     } else {
0412         bookmarkListView->setModel( &m_targetModel );
0413     }
0414 }
0415 
0416 void GoToDialogPrivate::updateProgress()
0417 {
0418     if ( !m_progressAnimation.isEmpty() ) {
0419         m_currentFrame = ( m_currentFrame + 1 ) % m_progressAnimation.size();
0420         QIcon frame = m_progressAnimation[m_currentFrame];
0421         progressButton->setIcon( frame );
0422     }
0423 }
0424 
0425 void GoToDialogPrivate::stopProgressAnimation()
0426 {
0427     searchLineEdit->setEnabled( true );
0428     m_progressTimer.stop();
0429     updateResultMessage( bookmarkListView->model()->rowCount() );
0430     progressButton->setVisible( false );
0431 }
0432 
0433 void GoToDialogPrivate::updateResultMessage( int results )
0434 {
0435     //~ singular %n result found.
0436     //~ plural %n results found.
0437     descriptionLabel->setText( QObject::tr( "%n result(s) found.", "Number of search results", results ) );
0438 }
0439 
0440 }
0441 
0442 #include "moc_GoToDialog.cpp" // needed for private slots in header
0443 #include "GoToDialog.moc" // needed for Q_OBJECT here in source