Marble

RoutingWidget.cpp
1// SPDX-License-Identifier: LGPL-2.1-or-later
2//
3// SPDX-FileCopyrightText: 2010 Dennis Nienhüser <nienhueser@kde.org>
4//
5
6#include "RoutingWidget.h"
7
8#include "GeoDataLineString.h"
9#include "GeoDataLookAt.h"
10#include "GeoDataPlaylist.h"
11#include "GeoDataTour.h"
12#include "GeoDataFlyTo.h"
13#include "GeoDataStyle.h"
14#include "GeoDataIconStyle.h"
15#include "GeoDataPlacemark.h"
16#include "TourPlayback.h"
17#include "Maneuver.h"
18#include "MarbleModel.h"
19#include "MarblePlacemarkModel.h"
20#include "MarbleWidget.h"
21#include "MarbleWidgetInputHandler.h"
22#include "Route.h"
23#include "RouteRequest.h"
24#include "RoutingInputWidget.h"
25#include "RoutingLayer.h"
26#include "RoutingModel.h"
27#include "RoutingProfilesModel.h"
28#include "RoutingProfileSettingsDialog.h"
29#include "GeoDataDocument.h"
30#include "GeoDataTreeModel.h"
31#include "GeoDataCreate.h"
32#include "GeoDataUpdate.h"
33#include "GeoDataDelete.h"
34#include "AlternativeRoutesModel.h"
35#include "RouteSyncManager.h"
36#include "CloudRoutesDialog.h"
37#include "CloudSyncManager.h"
38#include "PlaybackAnimatedUpdateItem.h"
39#include "GeoDataAnimatedUpdate.h"
40#include "Planet.h"
41
42#include <QTimer>
43#include <QPainter>
44#include <QFileDialog>
45#include <QKeyEvent>
46#include <QMouseEvent>
47#include <QToolBar>
48#include <QToolButton>
49#include <QProgressDialog>
50
51#include "ui_RoutingWidget.h"
52
53namespace Marble
54{
55
56struct WaypointInfo
57{
58 int index;
59 double distance; // distance to route start
60 GeoDataCoordinates coordinates;
61 Maneuver maneuver;
62 QString info;
63
64 WaypointInfo( int index_, double distance_, const GeoDataCoordinates &coordinates_, Maneuver maneuver_, const QString& info_ ) :
65 index( index_ ),
66 distance( distance_ ),
67 coordinates( coordinates_ ),
68 maneuver( maneuver_ ),
69 info( info_ )
70 {
71 // nothing to do
72 }
73};
74
75class RoutingWidgetPrivate
76{
77public:
78 Ui::RoutingWidget m_ui;
79 MarbleWidget *const m_widget;
80 RoutingManager *const m_routingManager;
81 RoutingLayer *const m_routingLayer;
82 RoutingInputWidget *m_activeInput;
83 QVector<RoutingInputWidget*> m_inputWidgets;
84 RoutingInputWidget *m_inputRequest;
85 QAbstractItemModel *const m_routingModel;
86 RouteRequest *const m_routeRequest;
87 RouteSyncManager *m_routeSyncManager;
88 bool m_zoomRouteAfterDownload;
89 QTimer m_progressTimer;
90 QVector<QIcon> m_progressAnimation;
91 GeoDataDocument *m_document;
92 GeoDataTour *m_tour;
93 TourPlayback *m_playback;
94 int m_currentFrame;
95 int m_iconSize;
96 int m_collapse_width;
97 bool m_playing;
98 QString m_planetId;
99
100 QToolBar *m_toolBar;
101
102 QToolButton *m_openRouteButton;
103 QToolButton *m_saveRouteButton;
104 QAction *m_cloudSyncSeparator;
105 QAction *m_uploadToCloudAction;
106 QAction *m_openCloudRoutesAction;
107 QToolButton *m_addViaButton;
108 QToolButton *m_reverseRouteButton;
109 QToolButton *m_clearRouteButton;
110 QToolButton *m_configureButton;
111 QToolButton *m_playButton;
112
113 QProgressDialog* m_routeUploadDialog;
114
115 /** Constructor */
116 RoutingWidgetPrivate(RoutingWidget *parent, MarbleWidget *marbleWidget );
117
118 /**
119 * @brief Toggle between simple search view and route view
120 * If only one input field exists, hide all buttons
121 */
122 void adjustInputWidgets();
123
124 void adjustSearchButton();
125
126 /**
127 * @brief Change the active input widget
128 * The active input widget influences what is shown in the paint layer
129 * and in the list view: Either a set of placemarks that correspond to
130 * a runner search result or the current route
131 */
132 void setActiveInput( RoutingInputWidget* widget );
133
134 void setupToolBar();
135
136private:
137 void createProgressAnimation();
138 RoutingWidget *m_parent;
139};
140
141RoutingWidgetPrivate::RoutingWidgetPrivate( RoutingWidget *parent, MarbleWidget *marbleWidget ) :
142 m_widget( marbleWidget ),
143 m_routingManager( marbleWidget->model()->routingManager() ),
144 m_routingLayer( marbleWidget->routingLayer() ),
145 m_activeInput( nullptr ),
146 m_inputRequest( nullptr ),
147 m_routingModel( m_routingManager->routingModel() ),
148 m_routeRequest( marbleWidget->model()->routingManager()->routeRequest() ),
149 m_routeSyncManager( nullptr ),
150 m_zoomRouteAfterDownload( false ),
151 m_document( nullptr ),
152 m_tour( nullptr ),
153 m_playback( nullptr ),
154 m_currentFrame( 0 ),
155 m_iconSize( 16 ),
156 m_collapse_width( 0 ),
157 m_playing( false ),
158 m_planetId(marbleWidget->model()->planetId()),
159 m_toolBar( nullptr ),
160 m_openRouteButton( nullptr ),
161 m_saveRouteButton( nullptr ),
162 m_cloudSyncSeparator( nullptr ),
163 m_uploadToCloudAction( nullptr ),
164 m_openCloudRoutesAction( nullptr ),
165 m_addViaButton( nullptr ),
166 m_reverseRouteButton( nullptr ),
167 m_clearRouteButton( nullptr ),
168 m_configureButton( nullptr ),
169 m_routeUploadDialog( nullptr ),
170 m_parent( parent )
171{
172 createProgressAnimation();
173 m_progressTimer.setInterval( 100 );
174 if ( MarbleGlobal::getInstance()->profiles() & MarbleGlobal::SmallScreen ) {
175 m_iconSize = 32;
176 }
177}
178
179void RoutingWidgetPrivate::adjustInputWidgets()
180{
181 for ( int i = 0; i < m_inputWidgets.size(); ++i ) {
182 m_inputWidgets[i]->setIndex( i );
183 }
184
185 adjustSearchButton();
186}
187
188void RoutingWidgetPrivate::adjustSearchButton()
189{
190 QString text = QObject::tr( "Get Directions" );
191 QString tooltip = QObject::tr( "Retrieve routing instructions for the selected destinations." );
192
193 int validInputs = 0;
194 for ( int i = 0; i < m_inputWidgets.size(); ++i ) {
195 if ( m_inputWidgets[i]->hasTargetPosition() ) {
196 ++validInputs;
197 }
198 }
199
200 if ( validInputs < 2 ) {
201 text = QObject::tr( "Search" );
202 tooltip = QObject::tr( "Find places matching the search term" );
203 }
204
205 m_ui.searchButton->setText( text );
206 m_ui.searchButton->setToolTip( tooltip );
207}
208
209void RoutingWidgetPrivate::setActiveInput( RoutingInputWidget *widget )
210{
211 Q_ASSERT( widget && "Must not pass null" );
212 MarblePlacemarkModel *model = widget->searchResultModel();
213
214 m_activeInput = widget;
215 m_ui.directionsListView->setModel( model );
216 m_routingLayer->setPlacemarkModel( model );
217 m_routingLayer->synchronizeWith( m_ui.directionsListView->selectionModel() );
218}
219
220void RoutingWidgetPrivate::setupToolBar()
221{
222 m_toolBar = new QToolBar;
223
224 m_openRouteButton = new QToolButton;
225 m_openRouteButton->setToolTip( QObject::tr("Open Route") );
226 m_openRouteButton->setIcon(QIcon(QStringLiteral(":/icons/16x16/document-open.png")));
227 m_toolBar->addWidget(m_openRouteButton);
228
229 m_saveRouteButton = new QToolButton;
230 m_saveRouteButton->setToolTip( QObject::tr("Save Route") );
231 m_saveRouteButton->setIcon(QIcon(QStringLiteral(":/icons/16x16/document-save.png")));
232 m_toolBar->addWidget(m_saveRouteButton);
233
234 m_playButton = new QToolButton;
235 m_playButton->setToolTip( QObject::tr("Preview Route") );
236 m_playButton->setIcon(QIcon(QStringLiteral(":/marble/playback-play.png")));
237 m_toolBar->addWidget(m_playButton);
238
239 m_cloudSyncSeparator = m_toolBar->addSeparator();
240 m_uploadToCloudAction = m_toolBar->addAction( QObject::tr("Upload to Cloud") );
241 m_uploadToCloudAction->setToolTip( QObject::tr("Upload to Cloud") );
242 m_uploadToCloudAction->setIcon(QIcon(QStringLiteral(":/icons/cloud-upload.png")));
243
244 m_openCloudRoutesAction = m_toolBar->addAction( QObject::tr("Manage Cloud Routes") );
245 m_openCloudRoutesAction->setToolTip( QObject::tr("Manage Cloud Routes") );
246 m_openCloudRoutesAction->setIcon(QIcon(QStringLiteral(":/icons/cloud-download.png")));
247
248 m_toolBar->addSeparator();
249 m_addViaButton = new QToolButton;
250 m_addViaButton->setToolTip( QObject::tr("Add Via") );
251 m_addViaButton->setIcon(QIcon(QStringLiteral(":/marble/list-add.png")));
252 m_toolBar->addWidget(m_addViaButton);
253
254 m_reverseRouteButton = new QToolButton;
255 m_reverseRouteButton->setToolTip( QObject::tr("Reverse Route") );
256 m_reverseRouteButton->setIcon(QIcon(QStringLiteral(":/marble/reverse.png")));
257 m_toolBar->addWidget(m_reverseRouteButton);
258
259 m_clearRouteButton = new QToolButton;
260 m_clearRouteButton->setToolTip( QObject::tr("Clear Route") );
261 m_clearRouteButton->setIcon(QIcon(QStringLiteral(":/marble/edit-clear.png")));
262 m_toolBar->addWidget(m_clearRouteButton);
263
264 m_toolBar->addSeparator();
265
266 m_configureButton = new QToolButton;
267 m_configureButton->setToolTip( QObject::tr("Settings") );
268 m_configureButton->setIcon(QIcon(QStringLiteral(":/icons/16x16/configure.png")));
269 m_toolBar->addWidget(m_configureButton);
270
271 QObject::connect( m_openRouteButton, SIGNAL(clicked()),
272 m_parent, SLOT(openRoute()) );
273 QObject::connect( m_saveRouteButton, SIGNAL(clicked()),
274 m_parent, SLOT(saveRoute()) );
275 QObject::connect( m_uploadToCloudAction, SIGNAL(triggered()),
276 m_parent, SLOT(uploadToCloud()) );
277 QObject::connect( m_openCloudRoutesAction, SIGNAL(triggered()),
278 m_parent, SLOT(openCloudRoutesDialog()));
279 QObject::connect( m_addViaButton, SIGNAL(clicked()),
280 m_parent, SLOT(addInputWidget()) );
281 QObject::connect( m_reverseRouteButton, SIGNAL(clicked()),
282 m_routingManager, SLOT(reverseRoute()) );
283 QObject::connect( m_clearRouteButton, SIGNAL(clicked()),
284 m_routingManager, SLOT(clearRoute()) );
285 QObject::connect( m_configureButton, SIGNAL(clicked()),
286 m_parent, SLOT(configureProfile()) );
287 QObject::connect( m_playButton, SIGNAL(clicked()),
288 m_parent, SLOT(toggleRoutePlay()) );
289
290 m_toolBar->setIconSize(QSize(16, 16));
291 m_ui.toolBarLayout->addWidget(m_toolBar, 0, Qt::AlignLeft);
292}
293
294void RoutingWidgetPrivate::createProgressAnimation()
295{
296 // Size parameters
297 qreal const h = m_iconSize / 2.0; // Half of the icon size
298 qreal const q = h / 2.0; // Quarter of the icon size
299 qreal const d = 7.5; // Circle diameter
300 qreal const r = d / 2.0; // Circle radius
301
302 // Canvas parameters
303 QImage canvas( m_iconSize, m_iconSize, QImage::Format_ARGB32 );
304 QPainter painter( &canvas );
305 painter.setRenderHint( QPainter::Antialiasing, true );
306 painter.setPen( QColor ( Qt::gray ) );
307 painter.setBrush( QColor( Qt::white ) );
308
309 // Create all frames
310 for( double t = 0.0; t < 2 * M_PI; t += M_PI / 8.0 ) {
311 canvas.fill( Qt::transparent );
312 QRectF firstCircle( h - r + q * cos( t ), h - r + q * sin( t ), d, d );
313 QRectF secondCircle( h - r + q * cos( t + M_PI ), h - r + q * sin( t + M_PI ), d, d );
314 painter.drawEllipse( firstCircle );
315 painter.drawEllipse( secondCircle );
316 m_progressAnimation.push_back( QIcon( QPixmap::fromImage( canvas ) ) );
317 }
318}
319
320RoutingWidget::RoutingWidget( MarbleWidget *marbleWidget, QWidget *parent ) :
321 QWidget( parent ), d( new RoutingWidgetPrivate( this, marbleWidget ) )
322{
323 d->m_ui.setupUi( this );
324 d->setupToolBar();
325 d->m_ui.routeComboBox->setVisible( false );
326 d->m_ui.routeComboBox->setModel( d->m_routingManager->alternativeRoutesModel() );
327 layout()->setMargin( 0 );
328
329 d->m_ui.routingProfileComboBox->setModel( d->m_routingManager->profilesModel() );
330
331 connect( d->m_routingManager->profilesModel(), SIGNAL(rowsInserted(QModelIndex,int,int)),
332 this, SLOT(selectFirstProfile()) );
333 connect( d->m_routingManager->profilesModel(), SIGNAL(modelReset()),
334 this, SLOT(selectFirstProfile()) );
335 connect( d->m_routingLayer, SIGNAL(placemarkSelected(QModelIndex)),
336 this, SLOT(activatePlacemark(QModelIndex)) );
337 connect( d->m_routingManager, SIGNAL(stateChanged(RoutingManager::State)),
338 this, SLOT(updateRouteState(RoutingManager::State)) );
339 connect( d->m_routeRequest, SIGNAL(positionAdded(int)),
340 this, SLOT(insertInputWidget(int)) );
341 connect( d->m_routeRequest, SIGNAL(positionRemoved(int)),
342 this, SLOT(removeInputWidget(int)) );
343 connect( d->m_routeRequest, SIGNAL(routingProfileChanged()),
344 this, SLOT(updateActiveRoutingProfile()) );
345 connect( &d->m_progressTimer, SIGNAL(timeout()),
346 this, SLOT(updateProgress()) );
347 connect( d->m_ui.routeComboBox, SIGNAL(currentIndexChanged(int)),
348 d->m_routingManager->alternativeRoutesModel(), SLOT(setCurrentRoute(int)) );
349 connect( d->m_routingManager->alternativeRoutesModel(), SIGNAL(currentRouteChanged(int)),
350 d->m_ui.routeComboBox, SLOT(setCurrentIndex(int)) );
351 connect( d->m_ui.routingProfileComboBox, SIGNAL(currentIndexChanged(int)),
352 this, SLOT(setRoutingProfile(int)) );
353 connect( d->m_ui.routingProfileComboBox, SIGNAL(activated(int)),
354 this, SLOT(retrieveRoute()) );
355 connect( d->m_routingManager->alternativeRoutesModel(), SIGNAL(rowsInserted(QModelIndex,int,int)),
356 this, SLOT(updateAlternativeRoutes()) );
357
358 d->m_ui.directionsListView->setModel( d->m_routingModel );
359
360 QItemSelectionModel *selectionModel = d->m_ui.directionsListView->selectionModel();
361 d->m_routingLayer->synchronizeWith( selectionModel );
362 connect( d->m_ui.directionsListView, SIGNAL(activated(QModelIndex)),
363 this, SLOT(activateItem(QModelIndex)) );
364
365 // FIXME: apply for this sector
366 connect( d->m_ui.searchButton, SIGNAL(clicked()),
367 this, SLOT(retrieveRoute()) );
368 connect( d->m_ui.showInstructionsButton, SIGNAL(clicked(bool)),
369 this, SLOT(showDirections()) );
370
371 for( int i=0; i<d->m_routeRequest->size(); ++i ) {
372 insertInputWidget( i );
373 }
374
375 for ( int i=0; i<2 && d->m_inputWidgets.size()<2; ++i ) {
376 // Start with source and destination if the route is empty yet
378 }
379 //d->m_ui.descriptionLabel->setVisible( false );
380 d->m_ui.resultLabel->setVisible( false );
382 updateActiveRoutingProfile();
383 updateCloudSyncButtons();
384
385 if ( MarbleGlobal::getInstance()->profiles() & MarbleGlobal::SmallScreen ) {
386 d->m_ui.directionsListView->setVisible( false );
387 d->m_openRouteButton->setVisible( false );
388 d->m_saveRouteButton->setVisible( false );
389 }
390
391 connect( marbleWidget->model(), SIGNAL(themeChanged(QString)),
392 this, SLOT(handlePlanetChange()) );
393}
394
396{
397 delete d->m_playback;
398 delete d->m_tour;
399 if( d->m_document ){
400 d->m_widget->model()->treeModel()->removeDocument( d->m_document );
401 delete d->m_document;
402 }
403 delete d;
404}
405
406void RoutingWidget::retrieveRoute()
407{
408 if ( d->m_inputWidgets.size() == 1 ) {
409 // Search mode
410 d->m_inputWidgets.first()->findPlacemarks();
411 return;
412 }
413
414 int index = d->m_ui.routingProfileComboBox->currentIndex();
415 if ( index == -1 ) {
416 return;
417 }
418 d->m_routeRequest->setRoutingProfile( d->m_routingManager->profilesModel()->profiles().at( index ) );
419
420 Q_ASSERT( d->m_routeRequest->size() == d->m_inputWidgets.size() );
421 for ( int i = 0; i < d->m_inputWidgets.size(); ++i ) {
422 RoutingInputWidget *widget = d->m_inputWidgets.at( i );
423 if ( !widget->hasTargetPosition() && widget->hasInput() ) {
424 widget->findPlacemarks();
425 return;
426 }
427 }
428
429 d->m_activeInput = nullptr;
430 if ( d->m_routeRequest->size() > 1 ) {
431 d->m_zoomRouteAfterDownload = true;
432 d->m_routingLayer->setPlacemarkModel( nullptr );
433 d->m_routingManager->retrieveRoute();
434 d->m_ui.directionsListView->setModel( d->m_routingModel );
435 d->m_routingLayer->synchronizeWith( d->m_ui.directionsListView->selectionModel() );
436 }
437
438 if( d->m_playback ) {
439 d->m_playback->stop();
440 }
441}
442
443void RoutingWidget::activateItem ( const QModelIndex &index )
444{
446
447 if ( !data.isNull() ) {
448 GeoDataCoordinates position = qvariant_cast<GeoDataCoordinates>( data );
449 d->m_widget->centerOn( position, true );
450 }
451
452 if ( d->m_activeInput && index.isValid() ) {
454 if ( !data.isNull() ) {
455 d->m_activeInput->setTargetPosition( data.value<GeoDataCoordinates>(), index.data().toString() );
456 }
457 }
458}
459
460void RoutingWidget::handleSearchResult( RoutingInputWidget *widget )
461{
462 d->setActiveInput( widget );
463 MarblePlacemarkModel *model = widget->searchResultModel();
464
465 if ( model->rowCount() ) {
466 QString const results = tr( "placemarks found: %1" ).arg( model->rowCount() );
467 d->m_ui.resultLabel->setText( results );
468 d->m_ui.resultLabel->setVisible( true );
469 // Make sure we have a selection
470 activatePlacemark( model->index( 0, 0 ) );
471 } else {
472 QString const results = tr( "No placemark found" );
473 d->m_ui.resultLabel->setText(QLatin1String("<font color=\"red\">") + results + QLatin1String("</font>"));
474 d->m_ui.resultLabel->setVisible( true );
475 }
476
477 GeoDataLineString placemarks;
478 for ( int i = 0; i < model->rowCount(); ++i ) {
479 QVariant data = model->index( i, 0 ).data( MarblePlacemarkModel::CoordinateRole );
480 if ( !data.isNull() ) {
481 placemarks << data.value<GeoDataCoordinates>();
482 }
483 }
484
485 if ( placemarks.size() > 1 ) {
486 d->m_widget->centerOn( GeoDataLatLonBox::fromLineString( placemarks ) );
487 //d->m_ui.descriptionLabel->setVisible( false );
488
489 if ( MarbleGlobal::getInstance()->profiles() & MarbleGlobal::SmallScreen ) {
490 d->m_ui.directionsListView->setVisible( true );
491 }
492 }
493}
494
495void RoutingWidget::centerOnInputWidget( RoutingInputWidget *widget )
496{
497 if ( widget->hasTargetPosition() ) {
498 d->m_widget->centerOn( widget->targetPosition() );
499 }
500}
501
502void RoutingWidget::activatePlacemark( const QModelIndex &index )
503{
504 if ( d->m_activeInput && index.isValid() ) {
506 if ( !data.isNull() ) {
507 d->m_activeInput->setTargetPosition( data.value<GeoDataCoordinates>() );
508 }
509 }
510
511 d->m_ui.directionsListView->setCurrentIndex( index );
512}
513
515{
516 d->m_routeRequest->append( GeoDataCoordinates() );
517}
518
519void RoutingWidget::insertInputWidget( int index )
520{
521 if ( index >= 0 && index <= d->m_inputWidgets.size() ) {
522 RoutingInputWidget *input = new RoutingInputWidget( d->m_widget->model(), index, this );
523 d->m_inputWidgets.insert( index, input );
524 connect( input, SIGNAL(searchFinished(RoutingInputWidget*)),
525 this, SLOT(handleSearchResult(RoutingInputWidget*)) );
526 connect( input, SIGNAL(removalRequest(RoutingInputWidget*)),
527 this, SLOT(removeInputWidget(RoutingInputWidget*)) );
528 connect( input, SIGNAL(activityRequest(RoutingInputWidget*)),
529 this, SLOT(centerOnInputWidget(RoutingInputWidget*)) );
530 connect( input, SIGNAL(mapInputModeEnabled(RoutingInputWidget*,bool)),
531 this, SLOT(requestMapPosition(RoutingInputWidget*,bool)) );
532 connect( input, SIGNAL(targetValidityChanged(bool)),
533 this, SLOT(adjustSearchButton()) );
534
535 d->m_ui.inputLayout->insertWidget( index, input );
536 d->adjustInputWidgets();
537 }
538}
539
540void RoutingWidget::removeInputWidget( RoutingInputWidget *widget )
541{
542 int index = d->m_inputWidgets.indexOf( widget );
543 if ( index >= 0 ) {
544 if ( d->m_inputWidgets.size() < 3 ) {
545 widget->clear();
546 } else {
547 d->m_routeRequest->remove( index );
548 }
549 d->m_routingManager->retrieveRoute();
550 }
551}
552
553void RoutingWidget::removeInputWidget( int index )
554{
555 if ( index >= 0 && index < d->m_inputWidgets.size() ) {
556 RoutingInputWidget *widget = d->m_inputWidgets.at( index );
557 d->m_inputWidgets.remove( index );
558 d->m_ui.inputLayout->removeWidget( widget );
559 widget->deleteLater();
560 if ( widget == d->m_activeInput ) {
561 d->m_activeInput = nullptr;
562 d->m_routingLayer->setPlacemarkModel( nullptr );
563 d->m_ui.directionsListView->setModel( d->m_routingModel );
564 d->m_routingLayer->synchronizeWith( d->m_ui.directionsListView->selectionModel() );
565 }
566 d->adjustInputWidgets();
567 }
568
569 if ( d->m_inputWidgets.size() < 2 ) {
571 }
572}
573
574void RoutingWidget::updateRouteState( RoutingManager::State state )
575{
576 clearTour();
577
578 switch ( state ) {
579 case RoutingManager::Downloading:
580 d->m_ui.routeComboBox->setVisible( false );
581 d->m_ui.routeComboBox->clear();
582 d->m_progressTimer.start();
583 d->m_ui.resultLabel->setVisible( false );
584 break;
585 case RoutingManager::Retrieved: {
586 d->m_progressTimer.stop();
587 d->m_ui.searchButton->setIcon( QIcon() );
588 if ( d->m_routingManager->routingModel()->rowCount() == 0 ) {
589 const QString results = tr( "No route found" );
590 d->m_ui.resultLabel->setText(QLatin1String("<font color=\"red\">") + results + QLatin1String("</font>"));
591 d->m_ui.resultLabel->setVisible( true );
592 }
593 }
594 break;
595 }
596
597 d->m_saveRouteButton->setEnabled( d->m_routingManager->routingModel()->rowCount() > 0 );
598}
599
600void RoutingWidget::requestMapPosition( RoutingInputWidget *widget, bool enabled )
601{
602 pointSelectionCanceled();
603
604 if ( enabled ) {
605 d->m_inputRequest = widget;
606 d->m_widget->installEventFilter( this );
607 d->m_widget->setFocus( Qt::OtherFocusReason );
608 }
609}
610
611void RoutingWidget::retrieveSelectedPoint( const GeoDataCoordinates &coordinates )
612{
613 if ( d->m_inputRequest && d->m_inputWidgets.contains( d->m_inputRequest ) ) {
614 d->m_inputRequest->setTargetPosition( coordinates );
615 d->m_widget->update();
616 }
617
618 d->m_inputRequest = nullptr;
619 d->m_widget->removeEventFilter( this );
620}
621
622void RoutingWidget::adjustSearchButton()
623{
624 d->adjustSearchButton();
625}
626
627void RoutingWidget::pointSelectionCanceled()
628{
629 if ( d->m_inputRequest && d->m_inputWidgets.contains( d->m_inputRequest ) ) {
630 d->m_inputRequest->abortMapInputRequest();
631 }
632
633 d->m_inputRequest = nullptr;
634 d->m_widget->removeEventFilter( this );
635}
636
637void RoutingWidget::configureProfile()
638{
639 int index = d->m_ui.routingProfileComboBox->currentIndex();
640 if ( index != -1 ) {
641 RoutingProfileSettingsDialog dialog( d->m_widget->model()->pluginManager(), d->m_routingManager->profilesModel(), this );
642 dialog.editProfile( d->m_ui.routingProfileComboBox->currentIndex() );
643 d->m_routeRequest->setRoutingProfile( d->m_routingManager->profilesModel()->profiles().at( index ) );
644 }
645}
646
647void RoutingWidget::updateProgress()
648{
649 if ( !d->m_progressAnimation.isEmpty() ) {
650 d->m_currentFrame = ( d->m_currentFrame + 1 ) % d->m_progressAnimation.size();
651 QIcon frame = d->m_progressAnimation[d->m_currentFrame];
652 d->m_ui.searchButton->setIcon( frame );
653 }
654}
655
656void RoutingWidget::updateAlternativeRoutes()
657{
658 if ( d->m_ui.routeComboBox->count() == 1) {
659 // Parts of the route may lie outside the route trip points
660 GeoDataLatLonBox const bbox = d->m_routingManager->routingModel()->route().bounds();
661 if ( d->m_zoomRouteAfterDownload ) {
662 d->m_zoomRouteAfterDownload = false;
663 d->m_widget->centerOn( bbox );
664 }
665 }
666
667 d->m_ui.routeComboBox->setVisible( d->m_ui.routeComboBox->count() > 0 );
668 if ( d->m_ui.routeComboBox->currentIndex() < 0 && d->m_ui.routeComboBox->count() > 0 ) {
669 d->m_ui.routeComboBox->setCurrentIndex( 0 );
670 }
671
672 QString const results = tr( "routes found: %1" ).arg( d->m_ui.routeComboBox->count() );
673 d->m_ui.resultLabel->setText( results );
674 d->m_ui.resultLabel->setVisible( true );
675 d->m_saveRouteButton->setEnabled( d->m_routingManager->routingModel()->rowCount() > 0 );
676}
677
679{
680 d->m_ui.showInstructionsButton->setVisible( visible );
681}
682
683void RoutingWidget::setRouteSyncManager(RouteSyncManager *manager)
684{
685 d->m_routeSyncManager = manager;
686 connect( d->m_routeSyncManager, SIGNAL(routeSyncEnabledChanged(bool)),
687 this, SLOT(updateCloudSyncButtons()) );
688 updateCloudSyncButtons();
689}
690
692{
693 QString const file = QFileDialog::getOpenFileName( this, tr( "Open Route" ),
694 d->m_routingManager->lastOpenPath(), tr("KML Files (*.kml)") );
695 if ( !file.isEmpty() ) {
696 d->m_routingManager->setLastOpenPath( QFileInfo( file ).absolutePath() );
697 d->m_zoomRouteAfterDownload = true;
698 d->m_routingManager->loadRoute( file );
699 updateAlternativeRoutes();
700 }
701}
702
703void RoutingWidget::selectFirstProfile()
704{
705 int count = d->m_routingManager->profilesModel()->rowCount();
706 if ( count && d->m_ui.routingProfileComboBox->currentIndex() < 0 ) {
707 d->m_ui.routingProfileComboBox->setCurrentIndex( 0 );
708 }
709}
710
711void RoutingWidget::setRoutingProfile( int index )
712{
713 if ( index >= 0 && index < d->m_routingManager->profilesModel()->rowCount() ) {
714 d->m_routeRequest->setRoutingProfile( d->m_routingManager->profilesModel()->profiles().at( index ) );
715 }
716}
717
718void RoutingWidget::showDirections()
719{
720 d->m_ui.directionsListView->setVisible( true );
721}
722
724{
725 QString fileName = QFileDialog::getSaveFileName( this,
726 tr( "Save Route" ), // krazy:exclude=qclasses
727 d->m_routingManager->lastSavePath(),
728 tr( "KML files (*.kml)" ) );
729
730 if ( !fileName.isEmpty() ) {
731 // maemo 5 file dialog does not append the file extension
732 if ( !fileName.endsWith(QLatin1String( ".kml" ), Qt::CaseInsensitive) ) {
733 fileName += QLatin1String(".kml");
734 }
735 d->m_routingManager->setLastSavePath( QFileInfo( fileName ).absolutePath() );
736 d->m_routingManager->saveRoute( fileName );
737 }
738}
739
741{
742 Q_ASSERT( d->m_routeSyncManager );
743
744 if (!d->m_routeUploadDialog) {
745 d->m_routeUploadDialog = new QProgressDialog( d->m_widget );
746 d->m_routeUploadDialog->setWindowTitle( tr( "Uploading route..." ) );
747 d->m_routeUploadDialog->setMinimum( 0 );
748 d->m_routeUploadDialog->setMaximum( 100 );
749 d->m_routeUploadDialog->setAutoClose( true );
750 d->m_routeUploadDialog->setAutoReset( true );
751 connect( d->m_routeSyncManager, SIGNAL(routeUploadProgress(qint64,qint64)), this, SLOT(updateUploadProgress(qint64,qint64)) );
752 }
753
754 d->m_routeUploadDialog->show();
755 d->m_routeSyncManager->uploadRoute();
756}
757
759{
760 Q_ASSERT( d->m_routeSyncManager );
761 d->m_routeSyncManager->prepareRouteList();
762
763 QPointer<CloudRoutesDialog> dialog = new CloudRoutesDialog( d->m_routeSyncManager->model(), d->m_widget );
764 connect( d->m_routeSyncManager, SIGNAL(routeListDownloadProgress(qint64,qint64)), dialog, SLOT(updateListDownloadProgressbar(qint64,qint64)) );
765 connect( dialog, SIGNAL(downloadButtonClicked(QString)), d->m_routeSyncManager, SLOT(downloadRoute(QString)) );
766 connect( dialog, SIGNAL(openButtonClicked(QString)), this, SLOT(openCloudRoute(QString)) );
767 connect( dialog, SIGNAL(deleteButtonClicked(QString)), d->m_routeSyncManager, SLOT(deleteRoute(QString)) );
768 connect( dialog, SIGNAL(removeFromCacheButtonClicked(QString)), d->m_routeSyncManager, SLOT(removeRouteFromCache(QString)) );
769 connect( dialog, SIGNAL(uploadToCloudButtonClicked(QString)), d->m_routeSyncManager, SLOT(uploadRoute(QString)) );
770 dialog->exec();
771 delete dialog;
772}
773
774void RoutingWidget::updateActiveRoutingProfile()
775{
776 RoutingProfile const profile = d->m_routingManager->routeRequest()->routingProfile();
777 QList<RoutingProfile> const profiles = d->m_routingManager->profilesModel()->profiles();
778 d->m_ui.routingProfileComboBox->setCurrentIndex( profiles.indexOf( profile ) );
779}
780
781void RoutingWidget::updateCloudSyncButtons()
782{
783 bool const show = d->m_routeSyncManager && d->m_routeSyncManager->isRouteSyncEnabled();
784 d->m_cloudSyncSeparator->setVisible( show );
785 d->m_uploadToCloudAction->setVisible( show );
786 d->m_openCloudRoutesAction->setVisible( show );
787}
788
789void RoutingWidget::openCloudRoute(const QString &identifier)
790{
791 Q_ASSERT( d->m_routeSyncManager );
792 d->m_routeSyncManager->openRoute( identifier );
793 d->m_widget->centerOn( d->m_routingManager->routingModel()->route().bounds() );
794}
795
796void RoutingWidget::updateUploadProgress(qint64 sent, qint64 total)
797{
798 Q_ASSERT( d->m_routeUploadDialog );
799 d->m_routeUploadDialog->setValue( 100.0 * sent / total );
800}
801
802bool RoutingWidget::eventFilter( QObject *o, QEvent *event )
803{
804 if ( o != d->m_widget ) {
805 return QWidget::eventFilter( o, event );
806 }
807
808 Q_ASSERT( d->m_inputRequest != nullptr );
809 Q_ASSERT( d->m_inputWidgets.contains( d->m_inputRequest ) );
810
811 if ( event->type() == QEvent::MouseButtonPress ) {
812 QMouseEvent *e = static_cast<QMouseEvent*>( event );
813 return e->button() == Qt::LeftButton;
814 }
815
816 if ( event->type() == QEvent::MouseButtonRelease ) {
817 QMouseEvent *e = static_cast<QMouseEvent*>( event );
818 qreal lon( 0.0 ), lat( 0.0 );
819 if ( e->button() == Qt::LeftButton && d->m_widget->geoCoordinates( e->pos().x(), e->pos().y(),
820 lon, lat, GeoDataCoordinates::Radian ) ) {
821 retrieveSelectedPoint( GeoDataCoordinates( lon, lat ) );
822 return true;
823 } else {
824 return QWidget::eventFilter( o, event );
825 }
826 }
827
828 if ( event->type() == QEvent::MouseMove ) {
829 d->m_widget->setCursor( Qt::CrossCursor );
830 return true;
831 }
832
833 if ( event->type() == QEvent::KeyPress ) {
834 QKeyEvent *e = static_cast<QKeyEvent*>( event );
835 if ( e->key() == Qt::Key_Escape ) {
836 pointSelectionCanceled();
837 return true;
838 }
839
840 return QWidget::eventFilter( o, event );
841 }
842
843 return QWidget::eventFilter( o, event );
844}
845
846void RoutingWidget::resizeEvent(QResizeEvent *e)
847{
849}
850
851void RoutingWidget::toggleRoutePlay()
852{
853 if( !d->m_playback ){
854 if( d->m_routingModel->rowCount() != 0 ){
855 initializeTour();
856 }
857 }
858
859 if (!d->m_playback)
860 return;
861
862 if( !d->m_playing ){
863 d->m_playing = true;
864 d->m_playButton->setIcon(QIcon(QStringLiteral(":/marble/playback-pause.png")));
865
866 if( d->m_playback ){
867 d->m_playback->play();
868 }
869 } else {
870 d->m_playing = false;
871 d->m_playButton->setIcon(QIcon(QStringLiteral(":/marble/playback-play.png")));
872 d->m_playback->pause();
873 }
874}
875
876void RoutingWidget::initializeTour()
877{
878 d->m_tour = new GeoDataTour;
879 if( d->m_document ){
880 d->m_widget->model()->treeModel()->removeDocument( d->m_document );
881 delete d->m_document;
882 }
883 d->m_document = new GeoDataDocument;
884 d->m_document->setId(QStringLiteral("tourdoc"));
885 d->m_document->append( d->m_tour );
886
887 d->m_tour->setPlaylist( new GeoDataPlaylist );
888 Route const route = d->m_widget->model()->routingManager()->routingModel()->route();
889 GeoDataLineString path = route.path();
890 if ( path.size() < 1 ){
891 return;
892 }
893
894 QList<WaypointInfo> waypoints;
895 double totalDistance = 0.0;
896 for( int i=0; i<route.size(); ++i ){
897 // TODO: QString( i )?
898 waypoints << WaypointInfo(i, totalDistance, route.at(i).path().first(), route.at(i).maneuver(), QLatin1String("start ") + QString(i));
899 totalDistance += route.at( i ).distance();
900 }
901
902 if( waypoints.size() < 1 ){
903 return;
904 }
905
906 QList<WaypointInfo> const allWaypoints = waypoints;
907 totalDistance = 0.0;
908 GeoDataCoordinates last = path.at( 0 );
909 int j=0; // next waypoint
910 qreal planetRadius = d->m_widget->model()->planet()->radius();
911 for( int i=1; i<path.size(); ++i ){
912 GeoDataCoordinates coordinates = path.at( i );
913 totalDistance += planetRadius * path.at(i - 1).sphericalDistanceTo(coordinates); // Distance to route start
914 while (totalDistance >= allWaypoints[j].distance && j+1<allWaypoints.size()) {
915 ++j;
916 }
917 int const lastIndex = qBound( 0, j-1, allWaypoints.size()-1 ); // previous waypoint
918 double const lastDistance = qAbs( totalDistance - allWaypoints[lastIndex].distance );
919 double const nextDistance = qAbs( allWaypoints[j].distance - totalDistance );
920 double const waypointDistance = qMin( lastDistance, nextDistance ); // distance to closest waypoint
921 double const step = qBound( 100.0, waypointDistance*2, 1000.0 ); // support point distance (higher density close to waypoints)
922
923 double const distance = planetRadius * last.sphericalDistanceTo(coordinates);
924 if( i > 1 && distance < step ){
925 continue;
926 }
927 last = coordinates;
928
929 GeoDataLookAt* lookat = new GeoDataLookAt;
930 // Choose a zoom distance of 400, 600 or 800 meters based on the distance to the closest waypoint
931 double const range = waypointDistance < 400 ? 400 : ( waypointDistance < 2000 ? 600 : 800 );
932 coordinates.setAltitude( range );
933 lookat->setCoordinates( coordinates );
934 lookat->setRange( range );
935 GeoDataFlyTo* flyto = new GeoDataFlyTo;
936 double const duration = 0.75;
937 flyto->setDuration( duration );
938 flyto->setView( lookat );
939 flyto->setFlyToMode( GeoDataFlyTo::Smooth );
940 d->m_tour->playlist()->addPrimitive( flyto );
941
942 if( !waypoints.empty() && totalDistance > waypoints.first().distance-100 ){
943 WaypointInfo const waypoint = waypoints.first();
944 waypoints.pop_front();
945 GeoDataAnimatedUpdate *updateCreate = new GeoDataAnimatedUpdate;
946 updateCreate->setUpdate( new GeoDataUpdate );
947 updateCreate->update()->setCreate( new GeoDataCreate );
948 GeoDataPlacemark *placemarkCreate = new GeoDataPlacemark;
949 QString const waypointId = QString( "waypoint-%1" ).arg( i, 0, 10 );
950 placemarkCreate->setId( waypointId );
951 placemarkCreate->setTargetId( d->m_document->id() );
952 placemarkCreate->setCoordinate( waypoint.coordinates );
953 GeoDataStyle::Ptr style(new GeoDataStyle);
954 style->iconStyle().setIconPath( waypoint.maneuver.directionPixmap() );
955 placemarkCreate->setStyle( style );
956 updateCreate->update()->create()->append( placemarkCreate );
957 d->m_tour->playlist()->addPrimitive( updateCreate );
958
959 GeoDataAnimatedUpdate *updateDelete = new GeoDataAnimatedUpdate;
960 updateDelete->setDelayedStart( 2 );
961 updateDelete->setUpdate( new GeoDataUpdate );
962 updateDelete->update()->setDelete( new GeoDataDelete );
963 GeoDataPlacemark *placemarkDelete = new GeoDataPlacemark;
964 placemarkDelete->setTargetId( waypointId );
965 updateDelete->update()->getDelete()->append( placemarkDelete );
966 d->m_tour->playlist()->addPrimitive( updateDelete );
967 }
968 }
969
970 d->m_playback = new TourPlayback;
971 d->m_playback->setMarbleWidget( d->m_widget );
972 d->m_playback->setTour( d->m_tour );
973 d->m_widget->model()->treeModel()->addDocument( d->m_document );
974 QObject::connect( d->m_playback, SIGNAL(finished()),
975 this, SLOT(seekTourToStart()) );
976}
977
978void RoutingWidget::centerOn( const GeoDataCoordinates &coordinates )
979{
980 if ( d->m_widget ) {
981 GeoDataLookAt lookat;
982 lookat.setCoordinates( coordinates );
983 lookat.setRange( coordinates.altitude() );
984 d->m_widget->flyTo( lookat, Instant );
985 }
986}
987
988void RoutingWidget::clearTour()
989{
990 d->m_playing = false;
991 d->m_playButton->setIcon(QIcon(QStringLiteral(":/marble/playback-play.png")));
992 delete d->m_playback;
993 d->m_playback = nullptr;
994 if( d->m_document ){
995 d->m_widget->model()->treeModel()->removeDocument( d->m_document );
996 delete d->m_document;
997 d->m_document = nullptr;
998 d->m_tour = nullptr;
999 }
1000}
1001
1002void RoutingWidget::seekTourToStart()
1003{
1004 Q_ASSERT( d->m_playback );
1005 d->m_playback->stop();
1006 d->m_playback->seek( 0 );
1007 d->m_playButton->setIcon(QIcon(QStringLiteral(":/marble/playback-play.png")));
1008 d->m_playing = false;
1009}
1010
1011void RoutingWidget::handlePlanetChange()
1012{
1013 const QString newPlanetId = d->m_widget->model()->planetId();
1014
1015 if (newPlanetId == d->m_planetId) {
1016 return;
1017 }
1018
1019 d->m_planetId = newPlanetId;
1020 d->m_routingManager->clearRoute();
1021}
1022
1023} // namespace Marble
1024
1025#include "moc_RoutingWidget.cpp"
This file contains the headers for MarbleModel.
This file contains the headers for MarbleWidget.
A 3d point representation.
static GeoDataLatLonBox fromLineString(const GeoDataLineString &lineString)
Create the smallest bounding box from a line string.
@ CoordinateRole
The GeoDataCoordinates coordinate.
A widget class that displays a view of the earth.
MarbleModel * model()
Return the model that this view shows.
Combines a line edit for input and a couple of buttons to let the user type in a search term,...
void setShowDirectionsButtonVisible(bool visible)
Show or hide the "open file..." button.
void openCloudRoutesDialog()
Open cloud routes dialog.
~RoutingWidget() override
Destructor.
void addInputWidget()
Add another input field at the end.
void openRoute()
Ask the user for a kml file to open.
void uploadToCloud()
Upload route to the cloud.
void saveRoute()
Ask the user for a kml file to save the current route to.
QString path(const QString &relativePath)
Binds a QML item to a specific geodetic location in screen coordinates.
@ Instant
Change camera position immediately (no interpolation)
KOSM_EXPORT double distance(const std::vector< const OSM::Node * > &path, Coordinate coord)
MouseButtonPress
QString getOpenFileName(QWidget *parent, const QString &caption, const QString &dir, const QString &filter, QString *selectedFilter, Options options)
QString getSaveFileName(QWidget *parent, const QString &caption, const QString &dir, const QString &filter, QString *selectedFilter, Options options)
int key() const const
bool empty() const const
T & first()
qsizetype indexOf(const AT &value, qsizetype from) const const
void pop_front()
qsizetype size() const const
QVariant data(int role) const const
bool isValid() const const
QPoint pos() const const
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
virtual bool eventFilter(QObject *watched, QEvent *event)
T qobject_cast(QObject *object)
QString tr(const char *sourceText, const char *disambiguation, int n)
QPixmap fromImage(QImage &&image, Qt::ImageConversionFlags flags)
int x() const const
int y() const const
Qt::MouseButton button() const const
QString arg(Args &&... args) const const
const QChar at(qsizetype position) const const
bool endsWith(QChar c, Qt::CaseSensitivity cs) const const
bool isEmpty() const const
qsizetype size() const const
AlignLeft
CaseInsensitive
CrossCursor
OtherFocusReason
Key_Escape
LeftButton
void * data()
bool isNull() const const
QString toString() const const
T value() const const
virtual bool event(QEvent *event) override
QLayout * layout() const const
virtual void resizeEvent(QResizeEvent *event)
void show()
QStyle * style() const const
void setToolTip(const QString &)
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Fri Jun 14 2024 11:54:17 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.