1 // SPDX-License-Identifier: LGPL-2.1-or-later
2 //
3 // SPDX-FileCopyrightText: 2010 Dennis Nienhüser <nienhueser@kde.org>
4 // SPDX-FileCopyrightText: 2011 Bernhard Beschow <bbeschow@cs.tu-berlin.de>
5 //
6
7 #include "GoToDialog.h"
8 #include "ui_GoToDialog.h"
9
10 #include "BookmarkManager.h"
11 #include "MarbleWidget.h"
12 #include "MarbleModel.h"
13 #include "MarblePlacemarkModel.h"
14 #include "GeoDataLookAt.h"
15 #include "GeoDataTreeModel.h"
16 #include "GeoDataDocument.h"
17 #include "GeoDataFolder.h"
18 #include "GeoDataPlacemark.h"
19 #include "PositionTracking.h"
20 #include "SearchRunnerManager.h"
21 #include "routing/RoutingManager.h"
22 #include "routing/RouteRequest.h"
23
24 #include <QAbstractListModel>
25 #include <QTimer>
26 #include <QPainter>
27
28 namespace Marble
29 {
30
31 class TargetModel : public QAbstractListModel
32 {
33 Q_OBJECT
34 public:
35 TargetModel( MarbleModel* marbleModel, QObject * parent = nullptr );
36
37 int rowCount ( const QModelIndex & parent = QModelIndex() ) const override;
38
39 QVariant data ( const QModelIndex & index, int role = Qt::DisplayRole ) const override;
40
41 void setShowRoutingItems( bool show );
42
43 private:
44 QVariant currentLocationData ( int role ) const;
45
46 QVariant routeData ( const QVector<GeoDataPlacemark> &via, int index, int role ) const;
47
48 QVariant homeData ( int role ) const;
49
50 QVariant bookmarkData ( int index, int role ) const;
51
52 QVector<GeoDataPlacemark> viaPoints() const;
53
54 MarbleModel *const m_marbleModel;
55
56 QVector<GeoDataPlacemark*> m_bookmarks;
57
58 bool m_hasCurrentLocation;
59
60 bool m_showRoutingItems;
61 };
62
63 class GoToDialogPrivate : public Ui::GoTo
64 {
65 public:
66 GoToDialog* m_parent;
67
68 GeoDataCoordinates m_coordinates;
69
70 MarbleModel *const m_marbleModel;
71
72 TargetModel m_targetModel;
73
74 SearchRunnerManager m_runnerManager;
75
76 GeoDataDocument *m_searchResult;
77
78 GeoDataTreeModel m_searchResultModel;
79
80 QTimer m_progressTimer;
81
82 int m_currentFrame;
83
84 QVector<QIcon> m_progressAnimation;
85
86 GoToDialogPrivate( GoToDialog* parent, MarbleModel* marbleModel );
87
88 void saveSelection( const QModelIndex &index );
89
90 void createProgressAnimation();
91
92 void startSearch();
93
94 void updateSearchResult( const QVector<GeoDataPlacemark*>& placemarks );
95
96 void updateSearchMode();
97
98 void updateProgress();
99
100 void stopProgressAnimation();
101
102 void updateResultMessage( int results );
103 };
104
TargetModel(MarbleModel * marbleModel,QObject * parent)105 TargetModel::TargetModel( MarbleModel *marbleModel, QObject * parent ) :
106 QAbstractListModel( parent ),
107 m_marbleModel( marbleModel ),
108 m_hasCurrentLocation( false ),
109 m_showRoutingItems( true )
110 {
111 BookmarkManager* manager = m_marbleModel->bookmarkManager();
112 for( GeoDataFolder * folder: manager->folders() ) {
113 QVector<GeoDataPlacemark*> bookmarks = folder->placemarkList();
114 QVector<GeoDataPlacemark*>::const_iterator iter = bookmarks.constBegin();
115 QVector<GeoDataPlacemark*>::const_iterator end = bookmarks.constEnd();
116
117 for ( ; iter != end; ++iter ) {
118 m_bookmarks.push_back( *iter );
119 }
120 }
121
122 PositionTracking* tracking = m_marbleModel->positionTracking();
123 m_hasCurrentLocation = tracking && tracking->status() == PositionProviderStatusAvailable;
124 }
125
viaPoints() const126 QVector<GeoDataPlacemark> TargetModel::viaPoints() const
127 {
128 if ( !m_showRoutingItems ) {
129 return QVector<GeoDataPlacemark>();
130 }
131
132 RouteRequest* request = m_marbleModel->routingManager()->routeRequest();
133 QVector<GeoDataPlacemark> result;
134 for ( int i = 0; i < request->size(); ++i ) {
135 if ( request->at( i ).isValid() ) {
136 GeoDataPlacemark placemark;
137 placemark.setCoordinate( request->at( i ) );
138 placemark.setName( request->name( i ) );
139 result.push_back( placemark );
140 }
141 }
142 return result;
143 }
144
rowCount(const QModelIndex & parent) const145 int TargetModel::rowCount ( const QModelIndex & parent ) const
146 {
147 int result = 0;
148 if ( !parent.isValid() ) {
149 result += m_hasCurrentLocation ? 1 : 0;
150 result += viaPoints().size(); // route
151 result += 1; // home location
152 result += m_bookmarks.size(); // bookmarks
153 return result;
154 }
155
156 return result;
157 }
158
currentLocationData(int role) const159 QVariant TargetModel::currentLocationData ( int role ) const
160 {
161 const PositionTracking* tracking = m_marbleModel->positionTracking();
162 if ( tracking->status() == PositionProviderStatusAvailable ) {
163 GeoDataCoordinates currentLocation = tracking->currentLocation();
164 switch( role ) {
165 case Qt::DisplayRole: return tr( "Current Location: %1" ).arg( currentLocation.toString() ) ;
166 case Qt::DecorationRole: return QIcon(QStringLiteral(":/icons/gps.png"));
167 case MarblePlacemarkModel::CoordinateRole: {
168 return QVariant::fromValue( currentLocation );
169 }
170 }
171 }
172
173 return QVariant();
174 }
175
routeData(const QVector<GeoDataPlacemark> & via,int index,int role) const176 QVariant TargetModel::routeData ( const QVector<GeoDataPlacemark> &via, int index, int role ) const
177 {
178 RouteRequest* request = m_marbleModel->routingManager()->routeRequest();
179 switch( role ) {
180 case Qt::DisplayRole: return via.at( index ).name();
181 case Qt::DecorationRole: return QIcon( request->pixmap( index ) );
182 case MarblePlacemarkModel::CoordinateRole: {
183 const GeoDataCoordinates coordinates = via.at( index ).coordinate();
184 return QVariant::fromValue( coordinates );
185 }
186 }
187
188 return QVariant();
189 }
190
homeData(int role) const191 QVariant TargetModel::homeData ( int role ) const
192 {
193 switch( role ) {
194 case Qt::DisplayRole: return tr( "Home" );
195 case Qt::DecorationRole: return QIcon(QStringLiteral(":/icons/go-home.png"));
196 case MarblePlacemarkModel::CoordinateRole: {
197 qreal lon( 0.0 ), lat( 0.0 );
198 int zoom( 0 );
199 m_marbleModel->home( lon, lat, zoom );
200 const GeoDataCoordinates coordinates = GeoDataCoordinates( lon, lat, 0, GeoDataCoordinates::Degree );
201 return QVariant::fromValue( coordinates );
202 }
203 }
204
205 return QVariant();
206 }
207
bookmarkData(int index,int role) const208 QVariant TargetModel::bookmarkData ( int index, int role ) const
209 {
210 switch( role ) {
211 case Qt::DisplayRole: {
212 const GeoDataFolder *folder = geodata_cast<GeoDataFolder>(m_bookmarks[index]->parent());
213 Q_ASSERT( folder && "Internal bookmark representation has changed. Please report this as a bug at https://bugs.kde.org." );
214 if ( folder ) {
215 return QString(folder->name() + QLatin1String(" / ") + m_bookmarks[index]->name());
216 }
217 return QVariant();
218 }
219 case Qt::DecorationRole: return QIcon(QStringLiteral(":/icons/bookmarks.png"));
220 case MarblePlacemarkModel::CoordinateRole: return QVariant::fromValue( m_bookmarks[index]->lookAt()->coordinates() );
221 }
222
223 return QVariant();
224 }
225
226
data(const QModelIndex & index,int role) const227 QVariant TargetModel::data ( const QModelIndex & index, int role ) const
228 {
229 if ( index.isValid() && index.row() >= 0 && index.row() < rowCount() ) {
230 int row = index.row();
231 bool const isCurrentLocation = row == 0 && m_hasCurrentLocation;
232 int homeOffset = m_hasCurrentLocation ? 1 : 0;
233 QVector<GeoDataPlacemark> via = viaPoints();
234 bool const isRoute = row >= homeOffset && row < homeOffset + via.size();
235
236 if ( isCurrentLocation ) {
237 return currentLocationData( role );
238 } else if ( isRoute ) {
239 int routeIndex = row - homeOffset;
240 Q_ASSERT( routeIndex >= 0 && routeIndex < via.size() );
241 return routeData( via, routeIndex, role );
242 } else {
243 int bookmarkIndex = row - homeOffset - via.size();
244 if ( bookmarkIndex == 0 ) {
245 return homeData( role );
246 } else {
247 --bookmarkIndex;
248 Q_ASSERT( bookmarkIndex >= 0 && bookmarkIndex < m_bookmarks.size() );
249 return bookmarkData( bookmarkIndex, role );
250 }
251 }
252 }
253
254 return QVariant();
255 }
256
setShowRoutingItems(bool show)257 void TargetModel::setShowRoutingItems( bool show )
258 {
259 m_showRoutingItems = show;
260 beginResetModel();
261 endResetModel();
262 }
263
createProgressAnimation()264 void GoToDialogPrivate::createProgressAnimation()
265 {
266 bool const smallScreen = MarbleGlobal::getInstance()->profiles() & MarbleGlobal::SmallScreen;
267 int const iconSize = smallScreen ? 32 : 16;
268
269 // Size parameters
270 qreal const h = iconSize / 2.0; // Half of the icon size
271 qreal const q = h / 2.0; // Quarter of the icon size
272 qreal const d = 7.5; // Circle diameter
273 qreal const r = d / 2.0; // Circle radius
274
275 // Canvas parameters
276 QImage canvas( iconSize, iconSize, QImage::Format_ARGB32 );
277 QPainter painter( &canvas );
278 painter.setRenderHint( QPainter::Antialiasing, true );
279 painter.setPen( QColor ( Qt::gray ) );
280 painter.setBrush( QColor( Qt::white ) );
281
282 // Create all frames
283 for( double t = 0.0; t < 2 * M_PI; t += M_PI / 8.0 ) {
284 canvas.fill( Qt::transparent );
285 QRectF firstCircle( h - r + q * cos( t ), h - r + q * sin( t ), d, d );
286 QRectF secondCircle( h - r + q * cos( t + M_PI ), h - r + q * sin( t + M_PI ), d, d );
287 painter.drawEllipse( firstCircle );
288 painter.drawEllipse( secondCircle );
289 m_progressAnimation.push_back( QIcon( QPixmap::fromImage( canvas ) ) );
290 }
291 }
292
GoToDialogPrivate(GoToDialog * parent,MarbleModel * marbleModel)293 GoToDialogPrivate::GoToDialogPrivate( GoToDialog* parent, MarbleModel* marbleModel ) :
294 m_parent( parent),
295 m_marbleModel( marbleModel ),
296 m_targetModel( marbleModel ),
297 m_runnerManager( marbleModel ),
298 m_searchResult( new GeoDataDocument ),
299 m_currentFrame( 0 )
300 {
301 setupUi( parent );
302
303 m_progressTimer.setInterval( 100 );
304 }
305
saveSelection(const QModelIndex & index)306 void GoToDialogPrivate::saveSelection( const QModelIndex &index )
307 {
308 if ( searchButton->isChecked() && m_searchResult->size() ) {
309 QVariant coordinates = m_searchResultModel.data( index, MarblePlacemarkModel::CoordinateRole );
310 m_coordinates = coordinates.value<GeoDataCoordinates>();
311 } else {
312 QVariant coordinates = index.data( MarblePlacemarkModel::CoordinateRole );
313 m_coordinates = coordinates.value<GeoDataCoordinates>();
314 }
315 m_parent->accept();
316 }
317
startSearch()318 void GoToDialogPrivate::startSearch()
319 {
320 QString const searchTerm = searchLineEdit->text().trimmed();
321 if ( searchTerm.isEmpty() ) {
322 return;
323 }
324
325 m_runnerManager.findPlacemarks( searchTerm );
326 if ( m_progressAnimation.isEmpty() ) {
327 createProgressAnimation();
328 }
329 m_progressTimer.start();
330 progressButton->setVisible( true );
331 searchLineEdit->setEnabled( false );
332 updateResultMessage( 0 );
333 }
334
updateSearchResult(const QVector<GeoDataPlacemark * > & placemarks)335 void GoToDialogPrivate::updateSearchResult( const QVector<GeoDataPlacemark*>& placemarks )
336 {
337 m_searchResultModel.setRootDocument( nullptr );
338 m_searchResult->clear();
339 for (GeoDataPlacemark *placemark: placemarks) {
340 m_searchResult->append( new GeoDataPlacemark( *placemark ) );
341 }
342 m_searchResultModel.setRootDocument( m_searchResult );
343 bookmarkListView->setModel( &m_searchResultModel );
344 updateResultMessage( m_searchResultModel.rowCount() );
345 }
346
GoToDialog(MarbleModel * marbleModel,QWidget * parent,Qt::WindowFlags flags)347 GoToDialog::GoToDialog( MarbleModel* marbleModel, QWidget * parent, Qt::WindowFlags flags ) :
348 QDialog( parent, flags ),
349 d( new GoToDialogPrivate( this, marbleModel ) )
350 {
351 d->searchLineEdit->setPlaceholderText( tr( "Address or search term" ) );
352
353 d->m_searchResultModel.setRootDocument( d->m_searchResult );
354 d->bookmarkListView->setModel( &d->m_targetModel );
355 connect( d->bookmarkListView, SIGNAL(activated(QModelIndex)),
356 this, SLOT(saveSelection(QModelIndex)) );
357 connect( d->searchLineEdit, SIGNAL(returnPressed()),
358 this, SLOT(startSearch()) );
359 d->buttonBox->button( QDialogButtonBox::Close )->setAutoDefault( false );
360 connect( d->searchButton, SIGNAL(clicked(bool)),
361 this, SLOT(updateSearchMode()) );
362 connect( d->browseButton, SIGNAL(clicked(bool)),
363 this, SLOT(updateSearchMode()) );
364 connect( &d->m_progressTimer, SIGNAL(timeout()),
365 this, SLOT(updateProgress()) );
366 connect( d->progressButton, SIGNAL(clicked(bool)),
367 this, SLOT(stopProgressAnimation()) );
368 d->updateSearchMode();
369 d->progressButton->setVisible( false );
370
371 connect( &d->m_runnerManager, SIGNAL(searchResultChanged(QVector<GeoDataPlacemark*>)),
372 this, SLOT(updateSearchResult(QVector<GeoDataPlacemark*>)) );
373 connect( &d->m_runnerManager, SIGNAL(searchFinished(QString)),
374 this, SLOT(stopProgressAnimation()) );
375 }
376
~GoToDialog()377 GoToDialog::~GoToDialog()
378 {
379 delete d;
380 }
381
coordinates() const382 GeoDataCoordinates GoToDialog::coordinates() const
383 {
384 return d->m_coordinates;
385 }
386
setShowRoutingItems(bool show)387 void GoToDialog::setShowRoutingItems( bool show )
388 {
389 d->m_targetModel.setShowRoutingItems( show );
390 }
391
setSearchEnabled(bool enabled)392 void GoToDialog::setSearchEnabled( bool enabled )
393 {
394 d->browseButton->setVisible( enabled );
395 d->searchButton->setVisible( enabled );
396 if ( !enabled ) {
397 d->searchButton->setChecked( false );
398 d->updateSearchMode();
399 }
400 }
401
updateSearchMode()402 void GoToDialogPrivate::updateSearchMode()
403 {
404 bool const searchEnabled = searchButton->isChecked();
405 searchLineEdit->setVisible( searchEnabled );
406 descriptionLabel->setVisible( searchEnabled );
407 progressButton->setVisible( searchEnabled && m_progressTimer.isActive() );
408 if ( searchEnabled ) {
409 bookmarkListView->setModel( &m_searchResultModel );
410 searchLineEdit->setFocus();
411 } else {
412 bookmarkListView->setModel( &m_targetModel );
413 }
414 }
415
updateProgress()416 void GoToDialogPrivate::updateProgress()
417 {
418 if ( !m_progressAnimation.isEmpty() ) {
419 m_currentFrame = ( m_currentFrame + 1 ) % m_progressAnimation.size();
420 QIcon frame = m_progressAnimation[m_currentFrame];
421 progressButton->setIcon( frame );
422 }
423 }
424
stopProgressAnimation()425 void GoToDialogPrivate::stopProgressAnimation()
426 {
427 searchLineEdit->setEnabled( true );
428 m_progressTimer.stop();
429 updateResultMessage( bookmarkListView->model()->rowCount() );
430 progressButton->setVisible( false );
431 }
432
updateResultMessage(int results)433 void GoToDialogPrivate::updateResultMessage( int results )
434 {
435 //~ singular %n result found.
436 //~ plural %n results found.
437 descriptionLabel->setText( QObject::tr( "%n result(s) found.", "Number of search results", results ) );
438 }
439
440 }
441
442 #include "moc_GoToDialog.cpp" // needed for private slots in header
443 #include "GoToDialog.moc" // needed for Q_OBJECT here in source
444