File indexing completed on 2024-04-28 03:49:28

0001 // SPDX-License-Identifier: LGPL-2.1-or-later
0002 //
0003 // SPDX-FileCopyrightText: 2010 Dennis Nienhüser <nienhueser@kde.org>
0004 // SPDX-FileCopyrightText: 2012 Illya Kovalevskyy <illya.kovalevskyy@gmail.com>
0005 //
0006 
0007 #include "RoutingInputWidget.h"
0008 
0009 #include "MarblePlacemarkModel.h"
0010 #include "RouteRequest.h"
0011 
0012 #ifdef MARBLE_NO_WEBKITWIDGETS
0013 #include "NullTinyWebBrowser.h"
0014 #else
0015 #include "TinyWebBrowser.h"
0016 #endif
0017 
0018 #include "BookmarkManager.h"
0019 #include "MarbleModel.h"
0020 #include "MarbleWidget.h"
0021 #include "routing/RoutingManager.h"
0022 #include "GeoDataPlacemark.h"
0023 #include "GeoDataFolder.h"
0024 #include "GeoDataExtendedData.h"
0025 #include "GeoDataData.h"
0026 #include "PositionTracking.h"
0027 #include "ReverseGeocodingRunnerManager.h"
0028 #include "SearchRunnerManager.h"
0029 #include "MarbleColors.h"
0030 #include "MarbleLineEdit.h"
0031 #include "GoToDialog.h"
0032 
0033 #include <QTimer>
0034 #include <QHBoxLayout>
0035 #include <QIcon>
0036 #include <QPushButton>
0037 #include <QMenu>
0038 #include <QKeyEvent>
0039 #include <QPainter>
0040 
0041 namespace Marble
0042 {
0043 
0044 /**
0045   * A MarbleLineEdit that swallows enter/return pressed
0046   * key events
0047   */
0048 class RoutingInputLineEdit : public MarbleLineEdit
0049 {
0050 public:
0051     explicit RoutingInputLineEdit( QWidget *parent = nullptr );
0052 
0053 protected:
0054     void keyPressEvent(QKeyEvent *) override;
0055 };
0056 
0057 class RoutingInputWidgetPrivate
0058 {
0059 public:
0060     MarbleModel* m_marbleModel;
0061 
0062     RoutingInputLineEdit *m_lineEdit;
0063 
0064     QPushButton* m_removeButton;
0065 
0066     SearchRunnerManager m_placemarkRunnerManager;
0067     ReverseGeocodingRunnerManager m_reverseGeocodingRunnerManager;
0068 
0069     MarblePlacemarkModel *m_placemarkModel;
0070 
0071     RouteRequest *m_route;
0072 
0073     int m_index;
0074 
0075     QTimer m_nominatimTimer;
0076 
0077     QAction* m_bookmarkAction;
0078 
0079     QAction* m_mapInput;
0080 
0081     QAction* m_currentLocationAction;
0082 
0083     QAction* m_centerAction;
0084 
0085     QMenu *m_menu;
0086 
0087     /** Constructor */
0088     RoutingInputWidgetPrivate( MarbleModel* model, int index, QWidget *parent );
0089 
0090     /** Initiate reverse geocoding request to download address */
0091     void adjustText();
0092 
0093     void createMenu( RoutingInputWidget *parent );
0094 
0095     QMenu* createBookmarkMenu( RoutingInputWidget *parent );
0096 
0097     static void createBookmarkActions( QMenu* menu, GeoDataFolder* bookmarksFolder, QObject *parent );
0098 
0099     static QPixmap addDropDownIndicator( const QPixmap &pixmap );
0100 
0101     void updateDescription();
0102 };
0103 
0104 void RoutingInputWidgetPrivate::updateDescription()
0105 {
0106     GeoDataPlacemark const placemark = (*m_route)[m_index];
0107     GeoDataExtendedData const address = placemark.extendedData();
0108     if (address.contains(QStringLiteral("road")) && address.contains(QStringLiteral("city"))) {
0109         QString const road = address.value(QStringLiteral("road")).value().toString();
0110         QString const city = address.value(QStringLiteral("city")).value().toString();
0111 
0112         if (address.contains(QStringLiteral("house_number"))) {
0113             QString const houseNumber = address.value(QStringLiteral("house_number")).value().toString();
0114             QString const name = QObject::tr("%1 %2, %3", "An address with parameters %1=house number, %2=road, %3=city");
0115             m_lineEdit->setText( name.arg( houseNumber, road, city ) );
0116         } else {
0117             QString const name = QObject::tr("%2, %3", "An address with parameters %1=road, %2=city");
0118             m_lineEdit->setText( name.arg( road, city ) );
0119         }
0120     }
0121     else if ( m_route->name( m_index ).isEmpty() )
0122     {
0123         if ( !placemark.address().isEmpty() ) {
0124             m_lineEdit->setText( placemark.address() );
0125         }
0126         else {
0127             m_lineEdit->setText( placemark.coordinate().toString().trimmed() );
0128         }
0129     }
0130     else
0131     {
0132         m_lineEdit->setText( placemark.name() );
0133     }
0134     m_lineEdit->setCursorPosition( 0 );
0135 }
0136 
0137 RoutingInputLineEdit::RoutingInputLineEdit( QWidget *parent ) :
0138     MarbleLineEdit( parent )
0139 {
0140     setPlaceholderText( QObject::tr( "Address or search term..." ) );
0141 }
0142 
0143 void RoutingInputLineEdit::keyPressEvent(QKeyEvent *event)
0144 {
0145     MarbleLineEdit::keyPressEvent( event );
0146     bool const returnPressed = event->key() == Qt::Key_Return || event->key() == Qt::Key_Enter;
0147     if ( returnPressed ) {
0148         event->accept();
0149     }
0150 }
0151 
0152 RoutingInputWidgetPrivate::RoutingInputWidgetPrivate( MarbleModel* model, int index, QWidget *parent ) :
0153         m_marbleModel( model ),
0154         m_lineEdit( nullptr ),
0155         m_placemarkRunnerManager( m_marbleModel ),
0156         m_reverseGeocodingRunnerManager( m_marbleModel ),
0157         m_placemarkModel( nullptr ), m_route( m_marbleModel->routingManager()->routeRequest() ), m_index( index ),
0158         m_bookmarkAction( nullptr ), m_mapInput( nullptr ), m_currentLocationAction( nullptr ),
0159         m_centerAction( nullptr ),
0160         m_menu( nullptr )
0161 {
0162     m_lineEdit = new RoutingInputLineEdit( parent );
0163     m_lineEdit->setDecorator( addDropDownIndicator( m_route->pixmap( m_index ) ) );
0164 
0165     m_removeButton = new QPushButton( parent );
0166     m_removeButton->setIcon(QIcon(QStringLiteral(":/marble/routing/icon-remove.png")));
0167     m_removeButton->setToolTip( QObject::tr( "Remove via point" ) );
0168     m_removeButton->setFlat( true );
0169     m_removeButton->setMaximumWidth( 18 );
0170 
0171     m_nominatimTimer.setInterval( 1000 );
0172     m_nominatimTimer.setSingleShot( true );
0173 }
0174 
0175 void RoutingInputWidgetPrivate::adjustText()
0176 {
0177     m_nominatimTimer.start();
0178 }
0179 
0180 void RoutingInputWidgetPrivate::createMenu( RoutingInputWidget *parent )
0181 {
0182     QMenu* result = new QMenu( parent );
0183 
0184     m_centerAction = result->addAction( QIcon( m_route->pixmap( m_index ) ), QObject::tr( "&Center Map here" ),
0185                        parent, SLOT(requestActivity()) );
0186     result->addSeparator();
0187 
0188     m_currentLocationAction = result->addAction( QIcon(QStringLiteral(":/icons/gps.png")), QObject::tr("Current &Location"),
0189                                                  parent, SLOT(setCurrentLocation()) );
0190     m_currentLocationAction->setEnabled( false );
0191 
0192     m_mapInput = result->addAction(QIcon(QStringLiteral(":/icons/crosshairs.png")), QObject::tr("From &Map..."));
0193     m_mapInput->setCheckable( true );
0194     QObject::connect( m_mapInput, SIGNAL(triggered(bool)), parent, SLOT(setMapInputModeEnabled(bool)) );
0195 
0196     m_bookmarkAction = result->addAction(QIcon(QStringLiteral(":/icons/bookmarks.png")), QObject::tr("From &Bookmark"));
0197     m_bookmarkAction->setMenu( createBookmarkMenu( parent ) );
0198 
0199     m_menu = result;
0200 }
0201 
0202 QMenu* RoutingInputWidgetPrivate::createBookmarkMenu( RoutingInputWidget *parent )
0203 {
0204     QMenu* result = new QMenu( parent );
0205     result->addAction(QIcon(QStringLiteral(":/icons/go-home.png")), QObject::tr("&Home"), parent, SLOT(setHomePosition()));
0206 
0207     QVector<GeoDataFolder*> folders = m_marbleModel->bookmarkManager()->folders();
0208 
0209     if ( folders.size() == 1 ) {
0210         createBookmarkActions( result, folders.first(), parent );
0211     } else {
0212         QVector<GeoDataFolder*>::const_iterator i = folders.constBegin();
0213         QVector<GeoDataFolder*>::const_iterator end = folders.constEnd();
0214 
0215         for (; i != end; ++i ) {
0216             QMenu* menu = result->addMenu(QIcon(QStringLiteral(":/icons/folder-bookmark.png")), (*i)->name());
0217             createBookmarkActions( menu, *i, parent );
0218         }
0219     }
0220 
0221     return result;
0222 }
0223 
0224 void RoutingInputWidgetPrivate::createBookmarkActions( QMenu* menu, GeoDataFolder* bookmarksFolder, QObject *parent )
0225 {
0226     QVector<GeoDataPlacemark*> bookmarks = bookmarksFolder->placemarkList();
0227     QVector<GeoDataPlacemark*>::const_iterator i = bookmarks.constBegin();
0228     QVector<GeoDataPlacemark*>::const_iterator end = bookmarks.constEnd();
0229 
0230     for (; i != end; ++i ) {
0231         QAction *bookmarkAction = new QAction( (*i)->name(), parent );
0232         bookmarkAction->setData( QVariant::fromValue( (*i)->coordinate() ) );
0233         menu->addAction( bookmarkAction );
0234         QObject::connect( menu, SIGNAL(triggered(QAction*)), parent, SLOT(setBookmarkPosition(QAction*)) );
0235     }
0236 }
0237 
0238 QPixmap RoutingInputWidgetPrivate::addDropDownIndicator(const QPixmap &pixmap)
0239 {
0240     QPixmap result( pixmap.size() + QSize( 8, pixmap.height() ) );
0241     result.fill( QColor( Qt::transparent ) );
0242     QPainter painter( &result );
0243     painter.drawPixmap( 0, 0, pixmap );
0244     QPoint const one( pixmap.width() + 1, pixmap.height() - 8 );
0245     QPoint const two( one.x() + 6, one.y() );
0246     QPoint const three( one.x() + 3, one.y() + 4 );
0247     painter.setRenderHint( QPainter::Antialiasing, true );
0248     painter.setPen( Qt::NoPen );
0249     painter.setBrush( QColor( Oxygen::aluminumGray4 ) );
0250     painter.drawConvexPolygon( QPolygon() << one << two << three );
0251     return result;
0252 }
0253 
0254 RoutingInputWidget::RoutingInputWidget( MarbleModel* model, int index, QWidget *parent ) :
0255         QWidget( parent ), d( new RoutingInputWidgetPrivate( model, index, this ) )
0256 {
0257     QHBoxLayout *layout = new QHBoxLayout( this );
0258     layout->setSizeConstraint( QLayout::SetMinimumSize );
0259     layout->setSpacing( 0 );
0260     layout->setMargin( 0 );
0261     layout->addWidget( d->m_lineEdit );
0262     layout->addWidget( d->m_removeButton );
0263 
0264     bool const smallScreen = MarbleGlobal::getInstance()->profiles() & MarbleGlobal::SmallScreen;
0265     if ( smallScreen ) {
0266         connect( d->m_lineEdit, SIGNAL(decoratorButtonClicked()), this, SLOT(openTargetSelectionDialog()) );
0267     } else {
0268         d->createMenu( this );
0269         connect(d->m_lineEdit, SIGNAL(decoratorButtonClicked()), this, SLOT(showMenu()));
0270     }
0271 
0272     connect( d->m_removeButton, SIGNAL(clicked()), this, SLOT(requestRemoval()) );
0273     connect( d->m_marbleModel->bookmarkManager(), SIGNAL(bookmarksChanged()),
0274              this, SLOT(reloadBookmarks()) );
0275     connect( d->m_marbleModel->positionTracking(), SIGNAL(statusChanged(PositionProviderStatus)),
0276              this, SLOT(updateCurrentLocationButton(PositionProviderStatus)) );
0277     connect( &d->m_placemarkRunnerManager, SIGNAL(searchResultChanged(QAbstractItemModel*)),
0278              this, SLOT(setPlacemarkModel(QAbstractItemModel*)) );
0279     connect( &d->m_reverseGeocodingRunnerManager, SIGNAL(reverseGeocodingFinished(GeoDataCoordinates,GeoDataPlacemark)),
0280              this, SLOT(retrieveReverseGeocodingResult(GeoDataCoordinates,GeoDataPlacemark)) );
0281     connect( d->m_lineEdit, SIGNAL(returnPressed()),
0282              this, SLOT(findPlacemarks()) );
0283     connect( d->m_lineEdit, SIGNAL(textEdited(QString)),
0284              this, SLOT(setInvalid()) );
0285     connect( &d->m_placemarkRunnerManager, SIGNAL(searchFinished(QString)),
0286              this, SLOT(finishSearch()) );
0287     connect( d->m_marbleModel->routingManager()->routeRequest(), SIGNAL(positionChanged(int,GeoDataCoordinates)),
0288              this, SLOT(updatePosition(int,GeoDataCoordinates)) );
0289     connect( &d->m_nominatimTimer, SIGNAL(timeout()),
0290              this, SLOT(reverseGeocoding()) );
0291     connect( this, SIGNAL(targetValidityChanged(bool)), this, SLOT(updateCenterButton(bool)) );
0292     updateCenterButton( hasTargetPosition() );
0293 
0294     d->adjustText();
0295 }
0296 
0297 RoutingInputWidget::~RoutingInputWidget()
0298 {
0299     delete d;
0300 }
0301 
0302 void RoutingInputWidget::reverseGeocoding()
0303 {
0304     if ( !hasTargetPosition() ) {
0305         return;
0306     }
0307 
0308     QString const name = d->m_route->name( d->m_index );
0309     if ( name.isEmpty() || name == tr( "Current Location" ) ) {
0310         d->m_reverseGeocodingRunnerManager.reverseGeocoding( targetPosition() );
0311     } else {
0312         d->updateDescription();
0313     }
0314 }
0315 
0316 void RoutingInputWidget::setPlacemarkModel( QAbstractItemModel *model )
0317 {
0318     d->m_placemarkModel = dynamic_cast<MarblePlacemarkModel*>(model);
0319 }
0320 
0321 void RoutingInputWidget::setTargetPosition( const GeoDataCoordinates &position, const QString &name )
0322 {
0323     if ( d->m_mapInput ) {
0324         d->m_mapInput->setChecked( false );
0325     }
0326     d->m_route->setPosition( d->m_index, position, name );
0327     if ( !name.isEmpty() ) {
0328         d->updateDescription();
0329     }
0330     emit targetValidityChanged( true );
0331 }
0332 
0333 bool RoutingInputWidget::hasTargetPosition() const
0334 {
0335     return targetPosition().isValid();
0336 }
0337 
0338 GeoDataCoordinates RoutingInputWidget::targetPosition() const
0339 {
0340     if ( d->m_index < d->m_route->size() ) {
0341         return d->m_route->at( d->m_index );
0342     } else {
0343         return GeoDataCoordinates();
0344     }
0345 }
0346 
0347 void RoutingInputWidget::findPlacemarks()
0348 {
0349     QString text = d->m_lineEdit->text();
0350     if ( text.isEmpty() ) {
0351         setInvalid();
0352     } else {
0353         d->m_lineEdit->setBusy(true);
0354         d->m_placemarkRunnerManager.findPlacemarks( text );
0355     }
0356 }
0357 
0358 MarblePlacemarkModel *RoutingInputWidget::searchResultModel()
0359 {
0360     return d->m_placemarkModel;
0361 }
0362 
0363 void RoutingInputWidget::requestActivity()
0364 {
0365     if ( hasTargetPosition() ) {
0366         emit activityRequest( this );
0367     }
0368 }
0369 
0370 void RoutingInputWidget::requestRemoval()
0371 {
0372     emit removalRequest( this );
0373 }
0374 
0375 bool RoutingInputWidget::hasInput() const
0376 {
0377     return !d->m_lineEdit->text().isEmpty();
0378 }
0379 
0380 void RoutingInputWidget::setMapInputModeEnabled( bool enabled )
0381 {
0382     emit mapInputModeEnabled( this, enabled );
0383 }
0384 
0385 void RoutingInputWidget::finishSearch()
0386 {
0387     d->m_lineEdit->setBusy(false);
0388     emit searchFinished( this );
0389 }
0390 
0391 void RoutingInputWidget::setInvalid()
0392 {
0393     d->m_route->setPosition( d->m_index, GeoDataCoordinates() );
0394     emit targetValidityChanged( false );
0395 }
0396 
0397 void RoutingInputWidget::abortMapInputRequest()
0398 {
0399     if ( d->m_mapInput ) {
0400         d->m_mapInput->setChecked( false );
0401     }
0402 }
0403 
0404 void RoutingInputWidget::setIndex( int index )
0405 {
0406     d->m_index = index;
0407     d->m_lineEdit->setBusy(false);
0408     d->m_lineEdit->setDecorator( d->addDropDownIndicator( d->m_route->pixmap( index ) ) );
0409 }
0410 
0411 void RoutingInputWidget::updatePosition( int index, const GeoDataCoordinates & )
0412 {
0413     if ( index == d->m_index ) {
0414         d->m_lineEdit->setBusy(false);
0415         emit targetValidityChanged( hasTargetPosition() );
0416         d->adjustText();
0417     }
0418 }
0419 
0420 void RoutingInputWidget::clear()
0421 {
0422     d->m_nominatimTimer.stop();
0423     d->m_lineEdit->setBusy(false);
0424     d->m_route->setPosition( d->m_index, GeoDataCoordinates() );
0425     d->m_lineEdit->clear();
0426     emit targetValidityChanged( false );
0427 }
0428 
0429 void RoutingInputWidget::retrieveReverseGeocodingResult( const GeoDataCoordinates &, const GeoDataPlacemark &placemark )
0430 {
0431     (*d->m_route)[d->m_index] = placemark;
0432     d->updateDescription();
0433 }
0434 
0435 void RoutingInputWidget::reloadBookmarks()
0436 {
0437     if ( d->m_bookmarkAction ) {
0438         d->m_bookmarkAction->setMenu( d->createBookmarkMenu( this ) );
0439     }
0440 }
0441 
0442 void RoutingInputWidget::setHomePosition()
0443 {
0444     qreal lon( 0.0 ), lat( 0.0 );
0445     int zoom( 0 );
0446     d->m_marbleModel->home( lon, lat, zoom );
0447     GeoDataCoordinates home( lon, lat, 0.0, GeoDataCoordinates::Degree );
0448     setTargetPosition( home );
0449     requestActivity();
0450 }
0451 
0452 void RoutingInputWidget::updateCurrentLocationButton( PositionProviderStatus status )
0453 {
0454     if ( d->m_currentLocationAction ) {
0455         d->m_currentLocationAction->setEnabled( status == PositionProviderStatusAvailable );
0456     }
0457 }
0458 
0459 void RoutingInputWidget::setCurrentLocation()
0460 {
0461     setTargetPosition( d->m_marbleModel->positionTracking()->currentLocation() );
0462     requestActivity();
0463 }
0464 
0465 void RoutingInputWidget::updateCenterButton( bool hasPosition )
0466 {
0467     if ( d->m_centerAction ) {
0468         d->m_centerAction->setEnabled( hasPosition );
0469     }
0470 }
0471 
0472 void RoutingInputWidget::setBookmarkPosition( QAction* bookmark )
0473 {
0474     if ( !bookmark->data().isNull() ) {
0475         setTargetPosition( bookmark->data().value<GeoDataCoordinates>() );
0476         requestActivity();
0477     }
0478 }
0479 
0480 void RoutingInputWidget::openTargetSelectionDialog()
0481 {
0482     QPointer<GoToDialog> dialog = new GoToDialog( d->m_marbleModel, this );
0483     dialog->setWindowTitle( tr( "Choose Placemark" ) );
0484     dialog->setShowRoutingItems( false );
0485     dialog->setSearchEnabled( false );
0486     if ( dialog->exec() == QDialog::Accepted ) {
0487         const GeoDataCoordinates coordinates = dialog->coordinates();
0488         setTargetPosition( coordinates );
0489     }
0490     delete dialog;
0491 }
0492 
0493 void RoutingInputWidget::showMenu()
0494 {
0495     d->m_menu->exec( mapToGlobal( QPoint( 0, size().height() ) ) );
0496 }
0497 
0498 } // namespace Marble
0499 
0500 #include "moc_RoutingInputWidget.cpp"