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"