Marble

RoutingWidget.cpp
1 // SPDX-License-Identifier: LGPL-2.1-or-later
2 //
3 // SPDX-FileCopyrightText: 2010 Dennis Nienhüser <[email protected]>
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 
53 namespace Marble
54 {
55 
56 struct 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 
75 class RoutingWidgetPrivate
76 {
77 public:
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 
136 private:
137  void createProgressAnimation();
138  RoutingWidget *m_parent;
139 };
140 
141 RoutingWidgetPrivate::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 
179 void 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 
188 void 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 
209 void 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 
220 void 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 
294 void 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 
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
377  addInputWidget();
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 
406 void 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 
443 void 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 
460 void 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 
495 void RoutingWidget::centerOnInputWidget( RoutingInputWidget *widget )
496 {
497  if ( widget->hasTargetPosition() ) {
498  d->m_widget->centerOn( widget->targetPosition() );
499  }
500 }
501 
502 void 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 
519 void 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 
540 void 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 
553 void 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 ) {
570  addInputWidget();
571  }
572 }
573 
574 void 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 
600 void 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 
611 void 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 
622 void RoutingWidget::adjustSearchButton()
623 {
624  d->adjustSearchButton();
625 }
626 
627 void 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 
637 void 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 
647 void 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 
656 void 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 
683 void 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 
703 void 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 
711 void 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 
718 void 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 
774 void 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 
781 void 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 
789 void 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 
796 void RoutingWidget::updateUploadProgress(qint64 sent, qint64 total)
797 {
798  Q_ASSERT( d->m_routeUploadDialog );
799  d->m_routeUploadDialog->setValue( 100.0 * sent / total );
800 }
801 
802 bool 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 
846 void RoutingWidget::resizeEvent(QResizeEvent *e)
847 {
849 }
850 
851 void 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 
876 void 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 
978 void 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 
988 void 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 
1002 void 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 
1011 void 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"
bool isNull() const const
T & first()
A 3d point representation.
QPoint pos() const const
AlignLeft
bool endsWith(const QString &s, Qt::CaseSensitivity cs) const const
QPixmap fromImage(const QImage &image, Qt::ImageConversionFlags flags)
MouseButtonPress
int size() const const
CaseInsensitive
Qt::MouseButton button() const const
void openCloudRoutesDialog()
Open cloud routes dialog.
void setMargin(int margin)
void uploadToCloud()
Upload route to the cloud.
T value() const const
QLayout * layout() const const
MarbleModel * model()
Return the model that this view shows.
virtual void resizeEvent(QResizeEvent *event)
virtual bool event(QEvent *event) override
A widget class that displays a view of the earth.
Definition: MarbleWidget.h:98
void saveRoute()
Ask the user for a kml file to save the current route to.
int x() const const
int y() const const
LeftButton
QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
virtual bool eventFilter(QObject *watched, QEvent *event)
bool empty() const const
QVariant data(int role) const const
int size() const const
KOSM_EXPORT double distance(const std::vector< const OSM::Node * > &path, Coordinate coord)
QStyle * style() const const
OtherFocusReason
void addInputWidget()
Add another input field at the end.
Combines a line edit for input and a couple of buttons to let the user type in a search term,...
int indexOf(const T &value, int from) const const
void pop_front()
bool isEmpty() const const
CrossCursor
@ CoordinateRole
The GeoDataCoordinates coordinate.
Binds a QML item to a specific geodetic location in screen coordinates.
void openRoute()
Ask the user for a kml file to open.
Key_Escape
void setupUi(QWidget *widget)
bool isValid() const const
void show()
void setShowDirectionsButtonVisible(bool visible)
Show or hide the "open file..." button.
@ Instant
Change camera position immediately (no interpolation)
Definition: MarbleGlobal.h:164
QString getSaveFileName(QWidget *parent, const QString &caption, const QString &dir, const QString &filter, QString *selectedFilter, QFileDialog::Options options)
QString getOpenFileName(QWidget *parent, const QString &caption, const QString &dir, const QString &filter, QString *selectedFilter, QFileDialog::Options options)
void setToolTip(const QString &)
int key() const const
QString arg(qlonglong a, int fieldWidth, int base, QChar fillChar) const const
QString path(const QString &relativePath)
RoutingWidget(MarbleWidget *marbleWidget, QWidget *parent)
Constructor.
static GeoDataLatLonBox fromLineString(const GeoDataLineString &lineString)
Create the smallest bounding box from a line string.
const QChar at(int position) const const
~RoutingWidget() override
Destructor.
QString tr(const char *sourceText, const char *disambiguation, int n)
QString toString() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2023 The KDE developers.
Generated on Sat Dec 2 2023 04:12:33 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.