1 // SPDX-License-Identifier: LGPL-2.1-or-later
2 //
3 // SPDX-FileCopyrightText: 2010 Jens-Michael Hoffmann <jmho@c-xx.com>
4 //
5
6 #include "DownloadRegionDialog.h"
7
8 #include <cmath>
9
10 #include <QDialogButtonBox>
11 #include <QGroupBox>
12 #include <QHBoxLayout>
13 #include <QHideEvent>
14 #include <QLabel>
15 #include <QPushButton>
16 #include <QRadioButton>
17 #include <QShowEvent>
18 #include <QVBoxLayout>
19 #include <QSpinBox>
20 #include <QScrollArea>
21 #include <QSet>
22
23 #include "GeoDataLatLonAltBox.h"
24 #include "MarbleDebug.h"
25 #include "MarbleModel.h"
26 #include "MarbleWidget.h"
27 #include "LatLonBoxWidget.h"
28 #include "TextureLayer.h"
29 #include "TileId.h"
30 #include "TileCoordsPyramid.h"
31 #include "TileLevelRangeWidget.h"
32 #include "TileLoaderHelper.h"
33 #include "routing/RoutingManager.h"
34 #include "routing/RoutingModel.h"
35 #include "GeoDataCoordinates.h"
36 #include "GeoDataLineString.h"
37 #include "DownloadRegion.h"
38 #include "GeoSceneDocument.h"
39 #include "GeoSceneMap.h"
40 #include "Route.h"
41
42 namespace Marble
43 {
44
45 int const maxTilesCount = 100000;
46 int const minimumRouteOffset = 0;
47 int const maximumRouteOffset = 10000;
48 int averageTileSize = 13; //The average size of a tile in kilobytes
49
50 class Q_DECL_HIDDEN DownloadRegionDialog::Private
51 {
52 public:
53 Private( MarbleWidget *const widget, QDialog * const dialog );
54 QWidget * createSelectionMethodBox();
55 QLayout * createTilesCounter();
56 QWidget * createOkCancelButtonBox();
57
58 bool hasRoute() const;
59 bool hasTextureLayer() const;
60 QDialog * m_dialog;
61 QRadioButton * m_visibleRegionMethodButton;
62 QRadioButton * m_specifiedRegionMethodButton;
63 LatLonBoxWidget * m_latLonBoxWidget;
64 TileLevelRangeWidget * m_tileLevelRangeWidget;
65 QRadioButton *m_routeDownloadMethodButton;
66 QLabel* m_routeOffsetLabel;
67 QDoubleSpinBox *m_routeOffsetSpinBox;
68 QLabel * m_tilesCountLabel;
69 QLabel * m_tileSizeInfo;
70 QPushButton * m_okButton;
71 QPushButton * m_applyButton;
72 TextureLayer const * m_textureLayer;
73 int m_visibleTileLevel;
74 MarbleModel const*const m_model;
75 MarbleWidget *const m_widget;
76 SelectionMethod m_selectionMethod;
77 GeoDataLatLonAltBox m_visibleRegion;
78 RoutingModel *m_routingModel;
79 DownloadRegion m_downloadRegion;
80 };
81
Private(MarbleWidget * const widget,QDialog * const dialog)82 DownloadRegionDialog::Private::Private( MarbleWidget * const widget,
83 QDialog * const dialog )
84 : m_dialog( dialog ),
85 m_visibleRegionMethodButton( nullptr ),
86 m_specifiedRegionMethodButton( nullptr ),
87 m_latLonBoxWidget( new LatLonBoxWidget ),
88 m_tileLevelRangeWidget( new TileLevelRangeWidget ),
89 m_routeDownloadMethodButton( nullptr ),
90 m_routeOffsetLabel( nullptr ),
91 m_routeOffsetSpinBox( nullptr ),
92 m_tilesCountLabel( nullptr ),
93 m_tileSizeInfo( nullptr ),
94 m_okButton( nullptr ),
95 m_applyButton( nullptr ),
96 m_textureLayer( widget->textureLayer() ),
97 m_visibleTileLevel( m_textureLayer->tileZoomLevel() ),
98 m_model( widget->model() ),
99 m_widget( widget ),
100 m_selectionMethod( VisibleRegionMethod ),
101 m_visibleRegion(),
102 m_routingModel( widget->model()->routingManager()->routingModel() )
103 {
104 m_latLonBoxWidget->setEnabled( false );
105 m_latLonBoxWidget->setLatLonBox( m_visibleRegion );
106 m_tileLevelRangeWidget->setDefaultLevel( m_visibleTileLevel );
107 m_downloadRegion.setMarbleModel( widget->model() );
108 }
109
createSelectionMethodBox()110 QWidget * DownloadRegionDialog::Private::createSelectionMethodBox()
111 {
112 m_visibleRegionMethodButton = new QRadioButton( tr( "Visible region" ) );
113 m_specifiedRegionMethodButton = new QRadioButton( tr( "Specify region" ) );
114
115 m_routeDownloadMethodButton = new QRadioButton( tr( "Download Route" ) );
116 m_routeDownloadMethodButton->setToolTip( tr( "Enabled when a route exists" ) );
117 m_routeDownloadMethodButton->setEnabled( hasRoute() );
118 m_routeDownloadMethodButton->setChecked( hasRoute() );
119 m_routeOffsetSpinBox = new QDoubleSpinBox();
120 m_routeOffsetSpinBox->setEnabled( hasRoute() );
121 m_routeOffsetSpinBox->setRange( minimumRouteOffset, maximumRouteOffset );
122 int defaultOffset = 500;
123 m_routeOffsetSpinBox->setValue( defaultOffset );
124 m_routeOffsetSpinBox->setSingleStep( 100 );
125 m_routeOffsetSpinBox->setSuffix( " m" );
126 m_routeOffsetSpinBox->setDecimals( 0 );
127 m_routeOffsetSpinBox->setAlignment( Qt::AlignRight );
128
129 m_routeOffsetLabel = new QLabel( tr( "Offset from route:" ) );
130 m_routeOffsetLabel->setAlignment( Qt::AlignHCenter );
131
132 connect( m_visibleRegionMethodButton, SIGNAL(toggled(bool)),
133 m_dialog, SLOT(toggleSelectionMethod()) );
134 connect( m_specifiedRegionMethodButton, SIGNAL(toggled(bool)),
135 m_dialog, SLOT(toggleSelectionMethod()));
136 connect( m_routeDownloadMethodButton, SIGNAL(toggled(bool)),
137 m_dialog, SLOT(toggleSelectionMethod()) );
138 connect( m_routingModel, SIGNAL(modelReset()), m_dialog, SLOT(updateRouteDialog()) );
139 connect( m_routingModel, SIGNAL(rowsInserted(QModelIndex,int,int)),
140 m_dialog, SLOT(updateRouteDialog()) );
141 connect( m_routingModel, SIGNAL(rowsRemoved(QModelIndex,int,int)),
142 m_dialog, SLOT(updateRouteDialog()) );
143
144 QHBoxLayout *routeOffsetLayout = new QHBoxLayout;
145 routeOffsetLayout->addWidget( m_routeOffsetLabel );
146 routeOffsetLayout->insertSpacing( 0, 25 );
147 routeOffsetLayout->addWidget( m_routeOffsetSpinBox );
148
149 QVBoxLayout * const routeLayout = new QVBoxLayout;
150 routeLayout->addWidget( m_routeDownloadMethodButton );
151 routeLayout->addLayout( routeOffsetLayout );
152
153 QVBoxLayout * const layout = new QVBoxLayout;
154 layout->addWidget( m_visibleRegionMethodButton );
155 layout->addLayout( routeLayout );
156 layout->addWidget( m_specifiedRegionMethodButton );
157 layout->addWidget( m_latLonBoxWidget );
158
159 bool const smallScreen = MarbleGlobal::getInstance()->profiles() & MarbleGlobal::SmallScreen;
160 m_specifiedRegionMethodButton->setVisible( !smallScreen );
161 m_latLonBoxWidget->setVisible( !smallScreen );
162
163 if ( smallScreen ) {
164 QWidget * const selectionMethodWidget = new QWidget;
165 selectionMethodWidget->setLayout( layout );
166 return selectionMethodWidget;
167 } else {
168 QGroupBox * const selectionMethodBox = new QGroupBox( tr( "Selection Method" ) );
169 selectionMethodBox->setLayout( layout );
170 return selectionMethodBox;
171 }
172 }
173
createTilesCounter()174 QLayout * DownloadRegionDialog::Private::createTilesCounter()
175 {
176 QLabel * const description = new QLabel( tr( "Number of tiles to download:" ) );
177 m_tilesCountLabel = new QLabel;
178 m_tileSizeInfo = new QLabel;
179
180 QHBoxLayout * const tilesCountLayout = new QHBoxLayout;
181 tilesCountLayout->addWidget( description );
182 tilesCountLayout->addWidget( m_tilesCountLabel );
183 //tilesCountLayout->insertSpacing( 0, 5 );
184 QVBoxLayout * const layout = new QVBoxLayout;
185 layout->addLayout( tilesCountLayout );
186 layout->addWidget( m_tileSizeInfo );
187 return layout;
188 }
189
createOkCancelButtonBox()190 QWidget * DownloadRegionDialog::Private::createOkCancelButtonBox()
191 {
192 QDialogButtonBox * const buttonBox = new QDialogButtonBox;
193 m_okButton = buttonBox->addButton( QDialogButtonBox::Ok );
194 m_applyButton = buttonBox->addButton( QDialogButtonBox::Apply );
195 if ( MarbleGlobal::getInstance()->profiles() & MarbleGlobal::SmallScreen ) {
196 buttonBox->removeButton( m_applyButton );
197 m_applyButton->setVisible( false );
198 }
199 buttonBox->addButton( QDialogButtonBox::Cancel );
200 connect( buttonBox, SIGNAL(accepted()), m_dialog, SLOT(accept()) );
201 connect( buttonBox, SIGNAL(rejected()), m_dialog, SLOT(reject()) );
202 connect( m_applyButton, SIGNAL(clicked()), m_dialog, SIGNAL(applied()) );
203 return buttonBox;
204 }
205
hasRoute() const206 bool DownloadRegionDialog::Private::hasRoute() const
207 {
208 return !m_routingModel->route().path().isEmpty();
209 }
210
hasTextureLayer() const211 bool DownloadRegionDialog::Private::hasTextureLayer() const
212 {
213 return m_model->mapTheme()->map()->hasTextureLayers();
214 }
215
DownloadRegionDialog(MarbleWidget * const widget,QWidget * const parent,Qt::WindowFlags const f)216 DownloadRegionDialog::DownloadRegionDialog( MarbleWidget *const widget, QWidget * const parent,
217 Qt::WindowFlags const f )
218 : QDialog( parent, f ),
219 d( new Private( widget, this ))
220 {
221 setWindowTitle( tr( "Download Region" ));
222 QVBoxLayout * const layout = new QVBoxLayout;
223 layout->addWidget( d->createSelectionMethodBox() );
224 layout->addWidget( d->m_tileLevelRangeWidget );
225 layout->addLayout( d->createTilesCounter() );
226
227 if ( MarbleGlobal::getInstance()->profiles() & MarbleGlobal::SmallScreen ) {
228 QWidget* widget = new QWidget( this );
229 widget->setLayout( layout );
230 QScrollArea* scrollArea = new QScrollArea( this );
231 scrollArea->setFrameShape( QFrame::NoFrame );
232 scrollArea->setWidget( widget );
233 QVBoxLayout * const mainLayout = new QVBoxLayout;
234 mainLayout->addWidget( scrollArea );
235 mainLayout->addWidget( d->createOkCancelButtonBox() );
236 setLayout( mainLayout );
237 } else {
238 layout->addWidget( d->createOkCancelButtonBox() );
239 setLayout( layout );
240 }
241
242 connect( d->m_latLonBoxWidget, SIGNAL(valueChanged()), SLOT(updateTilesCount()) );
243 connect( d->m_tileLevelRangeWidget, SIGNAL(topLevelChanged(int)),
244 SLOT(updateTilesCount()) );
245 connect( d->m_tileLevelRangeWidget, SIGNAL(bottomLevelChanged(int)),
246 SLOT(updateTilesCount()) );
247 connect( d->m_routeOffsetSpinBox, SIGNAL(valueChanged(double)), SLOT(updateTilesCount()) );
248 connect( d->m_routeOffsetSpinBox, SIGNAL(valueChanged(double)), SLOT(setOffsetUnit()) );
249 connect( d->m_model, SIGNAL(themeChanged(QString)), SLOT(updateTilesCount()) );
250 }
251
~DownloadRegionDialog()252 DownloadRegionDialog::~DownloadRegionDialog()
253 {
254 delete d;
255 }
256
setAllowedTileLevelRange(int const minimumTileLevel,int const maximumTileLevel)257 void DownloadRegionDialog::setAllowedTileLevelRange( int const minimumTileLevel,
258 int const maximumTileLevel )
259 {
260 d->m_tileLevelRangeWidget->setAllowedLevelRange( minimumTileLevel, maximumTileLevel );
261 }
262
setVisibleTileLevel(int const tileLevel)263 void DownloadRegionDialog::setVisibleTileLevel( int const tileLevel )
264 {
265 d->m_visibleTileLevel = tileLevel;
266 d->m_tileLevelRangeWidget->setDefaultLevel( tileLevel );
267 d->m_downloadRegion.setVisibleTileLevel( tileLevel );
268 }
269
setSelectionMethod(SelectionMethod const selectionMethod)270 void DownloadRegionDialog::setSelectionMethod( SelectionMethod const selectionMethod )
271 {
272 // block signals to prevent infinite recursion:
273 // radioButton->setChecked() -> toggleSelectionMethod() -> setSelectionMethod()
274 // -> radioButton->setChecked() -> ...
275 d->m_visibleRegionMethodButton->blockSignals( true );
276 d->m_specifiedRegionMethodButton->blockSignals( true );
277 d->m_routeDownloadMethodButton->blockSignals( true );
278
279 d->m_selectionMethod = selectionMethod;
280 switch ( selectionMethod ) {
281 case VisibleRegionMethod:
282 d->m_visibleRegionMethodButton->setChecked( true );
283 d->m_routeOffsetLabel->setEnabled( false );
284 d->m_routeOffsetSpinBox->setEnabled( false );
285 d->m_latLonBoxWidget->setEnabled( false );
286 setSpecifiedLatLonAltBox( d->m_visibleRegion );
287 break;
288 case SpecifiedRegionMethod:
289 d->m_specifiedRegionMethodButton->setChecked( true );
290 d->m_routeOffsetLabel->setEnabled( false );
291 d->m_routeOffsetSpinBox->setEnabled( false );
292 d->m_latLonBoxWidget->setEnabled( true );
293 break;
294 case RouteDownloadMethod:
295 d->m_routeDownloadMethodButton->setChecked( true );
296 d->m_routeOffsetLabel->setEnabled( true );
297 d->m_routeOffsetSpinBox->setEnabled( true );
298 d->m_latLonBoxWidget->setEnabled( false );
299 }
300
301 updateTilesCount();
302 d->m_visibleRegionMethodButton->blockSignals( false );
303 d->m_specifiedRegionMethodButton->blockSignals( false );
304 d->m_routeDownloadMethodButton->blockSignals( false );
305 }
306
region() const307 QVector<TileCoordsPyramid> DownloadRegionDialog::region() const
308 {
309 if ( !d->hasTextureLayer() ) {
310 return QVector<TileCoordsPyramid>();
311 }
312
313 d->m_downloadRegion.setTileLevelRange( d->m_tileLevelRangeWidget->topLevel(),
314 d->m_tileLevelRangeWidget->bottomLevel() );
315 d->m_downloadRegion.setVisibleTileLevel( d->m_visibleTileLevel );
316 // check whether "visible region" or "lat/lon region" is selection method
317 GeoDataLatLonAltBox downloadRegion;
318 switch ( d->m_selectionMethod ) {
319 case VisibleRegionMethod:
320 downloadRegion = d->m_visibleRegion;
321 break;
322 case SpecifiedRegionMethod:
323 downloadRegion = GeoDataLatLonAltBox( d->m_latLonBoxWidget->latLonBox(), 0, 0 );
324 break;
325 case RouteDownloadMethod:
326 qreal offset = d->m_routeOffsetSpinBox->value();
327 if (d->m_routeOffsetSpinBox->suffix() == QLatin1String(" km")) {
328 offset *= KM2METER;
329 }
330 const GeoDataLineString waypoints = d->m_model->routingManager()->routingModel()->route().path();
331 return d->m_downloadRegion.fromPath( d->m_textureLayer, offset, waypoints );
332 }
333
334 return d->m_downloadRegion.region( d->m_textureLayer, downloadRegion );
335 }
336
setSpecifiedLatLonAltBox(GeoDataLatLonAltBox const & region)337 void DownloadRegionDialog::setSpecifiedLatLonAltBox( GeoDataLatLonAltBox const & region )
338 {
339 d->m_latLonBoxWidget->setLatLonBox( region );
340 }
341
setVisibleLatLonAltBox(GeoDataLatLonAltBox const & region)342 void DownloadRegionDialog::setVisibleLatLonAltBox( GeoDataLatLonAltBox const & region )
343 {
344 d->m_visibleRegion = region;
345
346 // update lat/lon widget only if not active to prevent that users unintentionally loose
347 // entered values
348 if ( d->m_selectionMethod == VisibleRegionMethod ) {
349 setSpecifiedLatLonAltBox( region );
350 }
351 updateTilesCount();
352 }
353
updateTextureLayer()354 void DownloadRegionDialog::updateTextureLayer()
355 {
356 mDebug() << "DownloadRegionDialog::updateTextureLayer";
357 updateTilesCount();
358 }
359
hideEvent(QHideEvent * event)360 void DownloadRegionDialog::hideEvent( QHideEvent * event )
361 {
362 disconnect( d->m_widget, SIGNAL(visibleLatLonAltBoxChanged(GeoDataLatLonAltBox)),
363 this, SLOT(setVisibleLatLonAltBox(GeoDataLatLonAltBox)) );
364 disconnect( d->m_widget, SIGNAL(themeChanged(QString)),
365 this, SLOT(updateTextureLayer()) );
366
367 emit hidden();
368 event->accept();
369 }
370
showEvent(QShowEvent * event)371 void DownloadRegionDialog::showEvent( QShowEvent * event )
372 {
373 connect( d->m_widget, SIGNAL(visibleLatLonAltBoxChanged(GeoDataLatLonAltBox)),
374 this, SLOT(setVisibleLatLonAltBox(GeoDataLatLonAltBox)) );
375 connect( d->m_widget, SIGNAL(themeChanged(QString)),
376 this, SLOT(updateTextureLayer()) );
377
378 emit shown();
379 event->accept();
380 }
381
toggleSelectionMethod()382 void DownloadRegionDialog::toggleSelectionMethod()
383 {
384 // TODO:QButtonGroup would be easier to handle
385 switch ( d->m_selectionMethod ) {
386 case VisibleRegionMethod:
387 if( d->m_specifiedRegionMethodButton->isChecked() ) {
388 setSelectionMethod( SpecifiedRegionMethod );
389 }
390 else if( d->m_routeDownloadMethodButton->isChecked() ) {
391 setSelectionMethod( RouteDownloadMethod );
392 }
393
394 break;
395 case SpecifiedRegionMethod:
396 if( d->m_visibleRegionMethodButton->isChecked() ) {
397 setSelectionMethod( VisibleRegionMethod );
398 }
399 else if ( d->m_routeDownloadMethodButton->isChecked() ) {
400 setSelectionMethod( RouteDownloadMethod );
401 }
402 break;
403 case RouteDownloadMethod:
404 if( d->m_specifiedRegionMethodButton->isChecked() ) {
405 setSelectionMethod( SpecifiedRegionMethod );
406 }
407 else if ( d->m_visibleRegionMethodButton->isChecked() ) {
408 setSelectionMethod( VisibleRegionMethod );
409 }
410 break;
411
412 }
413 }
414
updateTilesCount()415 void DownloadRegionDialog::updateTilesCount()
416 {
417 if ( !isVisible() || !d->hasTextureLayer() ) {
418 return;
419 }
420
421 qint64 tilesCount = 0;
422 QString themeId( d->m_model->mapThemeId() );
423 QVector<TileCoordsPyramid> const pyramid = region();
424 Q_ASSERT( !pyramid.isEmpty() );
425 if( pyramid.size() == 1 ) {
426 tilesCount = pyramid[0].tilesCount();
427 }
428 else {
429 for( int level = pyramid[0].bottomLevel(); level>= pyramid[0].topLevel(); --level ) {
430 QSet<TileId> tileIdSet;
431 for( int i = 0; i < pyramid.size(); ++i ) {
432 QRect const coords = pyramid[i].coords( level );
433 int x1, y1, x2, y2;
434 coords.getCoords( &x1, &y1, &x2, &y2 );
435 for ( int x = x1; x <= x2; ++x ) {
436 for ( int y = y1; y <= y2; ++y ) {
437 TileId const tileId( 0, level, x, y );
438 tileIdSet.insert( tileId );
439 }
440 }
441 }
442 tilesCount += tileIdSet.count();
443 }
444 }
445
446 if ( tilesCount > maxTilesCount ) {
447 d->m_tileSizeInfo->setToolTip( QString() );
448 //~ singular There is a limit of %n tile to download.
449 //~ plural There is a limit of %n tiles to download.
450 d->m_tileSizeInfo->setText( tr( "There is a limit of %n tile(s) to download.", "",
451 maxTilesCount ) );
452 } else if (themeId == QLatin1String("earth/openstreetmap/openstreetmap.dgml")) {
453 qreal tileDownloadSize = tilesCount * averageTileSize;
454
455 d->m_tileSizeInfo->setToolTip( tr( "Approximate size of the tiles to be downloaded" ) );
456
457 if( tileDownloadSize > 1024 ) {
458 tileDownloadSize = tileDownloadSize / 1024;
459 d->m_tileSizeInfo->setText( tr( "Estimated download size: %1 MB" ).arg( ceil( tileDownloadSize ) ) );
460 }
461 else {
462 d->m_tileSizeInfo->setText( tr( "Estimated download size: %1 kB" ).arg( tileDownloadSize ) );
463 }
464
465 }
466 else {
467 d->m_tileSizeInfo->setToolTip( QString() );
468 d->m_tileSizeInfo->clear();
469 }
470
471 d->m_tilesCountLabel->setText( QString::number( tilesCount ) );
472 bool const tilesCountWithinLimits = tilesCount > 0 && tilesCount <= maxTilesCount;
473 d->m_okButton->setEnabled( tilesCountWithinLimits );
474 d->m_applyButton->setEnabled( tilesCountWithinLimits );
475 }
476
updateRouteDialog()477 void DownloadRegionDialog::updateRouteDialog()
478 {
479 d->m_routeDownloadMethodButton->setEnabled( d->hasRoute() );
480 d->m_routeDownloadMethodButton->setChecked( d->hasRoute() );
481 if( !d->hasRoute() ) {
482 setSelectionMethod( VisibleRegionMethod );
483 }
484 }
485
setOffsetUnit()486 void DownloadRegionDialog::setOffsetUnit()
487 {
488 qreal offset = d->m_routeOffsetSpinBox->value();
489
490 if( offset >= 1100 ) {
491 d->m_routeOffsetSpinBox->setSuffix( " km" );
492 d->m_routeOffsetSpinBox->setRange( minimumRouteOffset * METER2KM, maximumRouteOffset * METER2KM );
493 d->m_routeOffsetSpinBox->setDecimals( 1 );
494 d->m_routeOffsetSpinBox->setValue( offset * METER2KM );
495 d->m_routeOffsetSpinBox->setSingleStep( 0.1 );
496 }
497 else if (offset <= 1 && d->m_routeOffsetSpinBox->suffix() == QLatin1String(" km")) {
498 d->m_routeOffsetSpinBox->setSuffix( " m" );
499 d->m_routeOffsetSpinBox->setRange( minimumRouteOffset, maximumRouteOffset );
500 d->m_routeOffsetSpinBox->setDecimals( 0 );
501 d->m_routeOffsetSpinBox->setValue( offset * KM2METER );
502 d->m_routeOffsetSpinBox->setSingleStep( 100 );
503 }
504 }
505
506 }
507
508 #include "moc_DownloadRegionDialog.cpp"
509