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