Marble

MapThemeManager.cpp
1// SPDX-License-Identifier: LGPL-2.1-or-later
2//
3// SPDX-FileCopyrightText: 2008 Torsten Rahn <tackat@kde.org>
4// SPDX-FileCopyrightText: 2008 Jens-Michael Hoffmann <jensmh@gmx.de>
5//
6
7
8// Own
9#include "MapThemeManager.h"
10
11// Qt
12#include <QDir>
13#include <QFile>
14#include <QFileInfo>
15#include <QFileSystemWatcher>
16#include <QScopedPointer>
17#include <QString>
18#include <QStringList>
19#include <QStandardItemModel>
20
21// Local dir
22#include "GeoDataPhotoOverlay.h"
23#include "GeoSceneDocument.h"
24#include "GeoSceneMap.h"
25#include "GeoSceneHead.h"
26#include "GeoSceneIcon.h"
27#include "GeoSceneParser.h"
28#include "GeoSceneLayer.h"
29#include "GeoSceneTileDataset.h"
30#include "GeoSceneTextureTileDataset.h"
31#include "GeoSceneProperty.h"
32#include "GeoSceneZoom.h"
33#include "GeoSceneSettings.h"
34#include "MarbleDebug.h"
35#include "MarbleDirs.h"
36#include "Planet.h"
37#include "PlanetFactory.h"
38
39// Std
40#include <limits>
41
42namespace
43{
44 static const QString mapDirName = "maps";
45 static const int columnRelativePath = 1;
46}
47
48namespace Marble
49{
50
51class Q_DECL_HIDDEN MapThemeManager::Private
52{
53public:
54 Private( MapThemeManager *parent );
55 ~Private();
56
57 void directoryChanged( const QString& path );
58 void fileChanged( const QString & path );
59
60 /**
61 * @brief Updates the map theme model on request.
62 *
63 * This method should usually get invoked on startup or
64 * by a QFileSystemWatcher instance.
65 */
66 void updateMapThemeModel();
67
68 void watchPaths();
69
70 /**
71 * @brief Adds directory paths and .dgml file paths to the given QStringList.
72 */
73 static void addMapThemePaths( const QString& mapPathName, QStringList& result );
74
75 /**
76 * @brief Helper method for findMapThemes(). Searches for .dgml files below
77 * given directory path.
78 */
79 static QStringList findMapThemes( const QString& basePath );
80
81 /**
82 * @brief Searches for .dgml files below local and system map directory.
83 */
84 static QStringList findMapThemes();
85
86 static GeoSceneDocument* loadMapThemeFile( const QString& mapThemeId );
87
88 /**
89 * @brief Helper method for updateMapThemeModel().
90 */
91 static QList<QStandardItem *> createMapThemeRow( const QString& mapThemeID );
92
93 /**
94 * @brief Deletes any directory with its contents.
95 * @param directory Path to directory
96 * WARNING: Please do not raise this method's visibility in future, keep it private.
97 */
98 static bool deleteDirectory( const QString &directory );
99
100 MapThemeManager *const q;
101 QStandardItemModel m_mapThemeModel;
102 QStandardItemModel m_celestialList;
103 QFileSystemWatcher m_fileSystemWatcher;
104 bool m_isInitialized;
105
106private:
107 /**
108 * @brief Returns all directory paths and .dgml file paths below local and
109 * system map directory.
110 */
111 static QStringList pathsToWatch();
112};
113
114MapThemeManager::Private::Private( MapThemeManager *parent )
115 : q( parent ),
116 m_mapThemeModel( 0, 3 ),
117 m_celestialList(),
118 m_fileSystemWatcher(),
119 m_isInitialized( false )
120{
121}
122
123MapThemeManager::Private::~Private()
124{
125}
126
127
129 : QObject( parent ),
130 d( new Private( this ) )
131{
132 d->watchPaths();
133 connect( &d->m_fileSystemWatcher, SIGNAL(directoryChanged(QString)),
134 this, SLOT(directoryChanged(QString)));
135 connect( &d->m_fileSystemWatcher, SIGNAL(fileChanged(QString)),
136 this, SLOT(fileChanged(QString)));
137}
138
139MapThemeManager::~MapThemeManager()
140{
141 delete d;
142}
143
145{
146 QStringList result;
147
148 if ( !d->m_isInitialized ) {
149 d->updateMapThemeModel();
150 d->m_isInitialized = true;
151 }
152
153 const int mapThemeIdCount = d->m_mapThemeModel.rowCount();
154 result.reserve(mapThemeIdCount);
155 for (int i = 0; i < mapThemeIdCount; ++i) {
156 const QString id = d->m_mapThemeModel.data( d->m_mapThemeModel.index( i, 0 ), Qt::UserRole + 1 ).toString();
157 result << id;
158 }
159
160 return result;
161}
162
163GeoSceneDocument* MapThemeManager::loadMapTheme( const QString& mapThemeStringID )
164{
165 if ( mapThemeStringID.isEmpty() )
166 return nullptr;
167
168 return Private::loadMapThemeFile( mapThemeStringID );
169}
170
171void MapThemeManager::deleteMapTheme( const QString &mapThemeId )
172{
173 const QString dgmlPath = MarbleDirs::localPath() + QLatin1String("/maps/") + mapThemeId;
175
176 QString themeDir = dgmlFile.dir().absolutePath();
177 Private::deleteDirectory( themeDir );
178}
179
180bool MapThemeManager::Private::deleteDirectory( const QString& directory )
181{
182 QDir dir( directory );
183 bool result = true;
184
185 if ( dir.exists() ) {
186 for( const QFileInfo &info: dir.entryInfoList(
187 QDir::NoDotAndDotDot | QDir::System | QDir::Hidden |
188 QDir::AllDirs | QDir::Files,
189 QDir::DirsFirst ) ) {
190
191 if ( info.isDir() ) {
192 result = deleteDirectory( info.absoluteFilePath() );
193 } else {
194 result = QFile::remove( info.absoluteFilePath() );
195 }
196
197 if ( !result ) {
198 return result;
199 }
200 }
201
202 result = dir.rmdir( directory );
203
204 if( !result ) {
205 return result;
206 }
207 }
208
209 return result;
210}
211
212GeoSceneDocument* MapThemeManager::Private::loadMapThemeFile( const QString& mapThemeStringID )
213{
214 const QString mapThemePath = mapDirName + QLatin1Char('/') + mapThemeStringID;
215 const QString dgmlPath = MarbleDirs::path( mapThemePath );
216
217 // Check whether file exists
218 QFile file( dgmlPath );
219 if ( !file.exists() ) {
220 qWarning() << "Map theme file does not exist:" << dgmlPath;
221 return nullptr;
222 }
223
224 // Open file in right mode
225 const bool fileReadable = file.open( QIODevice::ReadOnly );
226
227 if ( !fileReadable ) {
228 qWarning() << "Map theme file not readable:" << dgmlPath;
229 return nullptr;
230 }
231
232 GeoSceneParser parser( GeoScene_DGML );
233
234 if ( !parser.read( &file )) {
235 qWarning() << "Map theme file not well-formed:" << dgmlPath;
236 return nullptr;
237 }
238
239 mDebug() << "Map theme file successfully loaded:" << dgmlPath;
240
241 // Get result document
242 GeoSceneDocument* document = static_cast<GeoSceneDocument*>( parser.releaseDocument() );
243 Q_ASSERT( document );
244 return document;
245}
246
247QStringList MapThemeManager::Private::pathsToWatch()
248{
249 QStringList result;
250 const QString localMapPathName = MarbleDirs::localPath() + QLatin1Char('/') + mapDirName;
251 const QString systemMapPathName = MarbleDirs::systemPath() + QLatin1Char('/') + mapDirName;
252
253 if( !QDir().exists( localMapPathName ) ) {
254 QDir().mkpath( localMapPathName );
255 }
256
257 result << localMapPathName;
258 result << systemMapPathName;
259 addMapThemePaths( localMapPathName, result );
260 addMapThemePaths( systemMapPathName, result );
261 return result;
262}
263
264QStringList MapThemeManager::Private::findMapThemes( const QString& basePath )
265{
266 const QString mapPathName = basePath + QLatin1Char('/') + mapDirName;
267
268 QDir paths = QDir( mapPathName );
269
270 QStringList mapPaths = paths.entryList( QStringList( "*" ),
274 QStringList mapDirs;
275
276 for ( int planet = 0; planet < mapPaths.size(); ++planet ) {
277 QDir themeDir = QDir(mapPathName + QLatin1Char('/') + mapPaths.at(planet));
278 QStringList themeMapPaths = themeDir.entryList(
279 QStringList( "*" ),
283 for ( int theme = 0; theme < themeMapPaths.size(); ++theme ) {
284 mapDirs << mapPathName + QLatin1Char('/') + mapPaths.at(planet) + QLatin1Char('/')
285 + themeMapPaths.at( theme );
286 }
287 }
288
289 QStringList mapFiles;
290 QStringListIterator it( mapDirs );
291 while ( it.hasNext() ) {
292 QString themeDir = it.next() + QLatin1Char('/');
293 QString themeDirName = QDir(themeDir).path().section(QLatin1Char('/'), -2, -1);
294 QStringList tmp = QDir( themeDir ).entryList( QStringList( "*.dgml" ),
296 if ( !tmp.isEmpty() ) {
297 QStringListIterator k( tmp );
298 while ( k.hasNext() ) {
299 QString themeXml = k.next();
300 mapFiles << themeDirName + QLatin1Char('/') + themeXml;
301 }
302 }
303 }
304
305 return mapFiles;
306}
307
308QStringList MapThemeManager::Private::findMapThemes()
309{
310 QStringList mapFilesLocal = findMapThemes( MarbleDirs::localPath() );
311 QStringList mapFilesSystem = findMapThemes( MarbleDirs::systemPath() );
312 QStringList allMapFiles( mapFilesLocal );
313 allMapFiles << mapFilesSystem;
314
315 // remove duplicate entries
316 allMapFiles.sort();
317 for ( int i = 1; i < allMapFiles.size(); ++i ) {
318 if ( allMapFiles.at(i) == allMapFiles.at( i-1 ) ) {
319 allMapFiles.removeAt( i );
320 --i;
321 }
322 }
323
324 return allMapFiles;
325}
326
327QStandardItemModel* MapThemeManager::mapThemeModel()
328{
329 if ( !d->m_isInitialized ) {
330 d->updateMapThemeModel();
331 d->m_isInitialized = true;
332 }
333 return &d->m_mapThemeModel;
334}
335
336QStandardItemModel *MapThemeManager::celestialBodiesModel()
337{
338 if ( !d->m_isInitialized ) {
339 d->updateMapThemeModel();
340 d->m_isInitialized = true;
341 }
342
343 return &d->m_celestialList;
344}
345
346QList<QStandardItem *> MapThemeManager::Private::createMapThemeRow( QString const& mapThemeID )
347{
348 QList<QStandardItem *> itemList;
349
350 QScopedPointer<GeoSceneDocument> mapTheme( loadMapThemeFile( mapThemeID ) );
351 if ( !mapTheme || !mapTheme->head()->visible() ) {
352 return itemList;
353 }
354
355 QPixmap themeIconPixmap;
356
357 QString relativePath = mapDirName + QLatin1Char('/')
358 + mapTheme->head()->target() + QLatin1Char('/') + mapTheme->head()->theme() + QLatin1Char('/')
359 + mapTheme->head()->icon()->pixmap();
360 themeIconPixmap.load( MarbleDirs::path( relativePath ) );
361
362 if ( themeIconPixmap.isNull() ) {
363 relativePath = "svg/application-x-marble-gray.png";
364 themeIconPixmap.load( MarbleDirs::path( relativePath ) );
365 }
366 else {
367 // Make sure we don't keep excessively large previews in memory
368 // TODO: Scale the icon down to the default icon size in MarbleSelectView.
369 // For now maxIconSize already equals what's expected by the listview.
370 QSize maxIconSize( 136, 136 );
371 if ( themeIconPixmap.size() != maxIconSize ) {
372 mDebug() << "Smooth scaling theme icon";
373 themeIconPixmap = themeIconPixmap.scaled( maxIconSize,
376 }
377 }
378
379 QIcon mapThemeIcon = QIcon( themeIconPixmap );
380
381 QString name = mapTheme->head()->name();
382 const QString translatedDescription = QCoreApplication::translate("DGML", mapTheme->head()->description().toUtf8().constData());
383 const QString toolTip = QLatin1String("<span style=\" max-width: 150 px;\"> ") + translatedDescription + QLatin1String(" </span>");
384
385 QStandardItem *item = new QStandardItem( name );
387 item->setData( mapThemeIcon, Qt::DecorationRole );
388 item->setData(toolTip, Qt::ToolTipRole);
389 item->setData( mapThemeID, Qt::UserRole + 1 );
390 item->setData(translatedDescription, Qt::UserRole + 2);
391
392 itemList << item;
393
394 return itemList;
395}
396
397void MapThemeManager::Private::updateMapThemeModel()
398{
399 mDebug();
400 m_mapThemeModel.clear();
401
402 m_mapThemeModel.setHeaderData(0, Qt::Horizontal, QObject::tr("Name"));
403
404 QStringList stringlist = findMapThemes();
405 QStringListIterator it( stringlist );
406
407 while ( it.hasNext() ) {
408 QString mapThemeID = it.next();
409
410 QList<QStandardItem *> itemList = createMapThemeRow( mapThemeID );
411 if ( !itemList.empty() ) {
412 m_mapThemeModel.appendRow( itemList );
413 }
414 }
415
416 for ( const QString &mapThemeId: stringlist ) {
417 const QString celestialBodyId = mapThemeId.section(QLatin1Char('/'), 0, 0);
418 QString celestialBodyName = PlanetFactory::localizedName( celestialBodyId );
419
420 QList<QStandardItem*> matchingItems = m_celestialList.findItems( celestialBodyId, Qt::MatchExactly, 1 );
421 if ( matchingItems.isEmpty() ) {
422 m_celestialList.appendRow( QList<QStandardItem*>()
423 << new QStandardItem( celestialBodyName )
424 << new QStandardItem( celestialBodyId ) );
425 }
426 }
427}
428
429void MapThemeManager::Private::watchPaths()
430{
431 QStringList const paths = pathsToWatch();
432 QStringList const files = m_fileSystemWatcher.files();
433 QStringList const directories = m_fileSystemWatcher.directories();
434 // Check each resource to add that it is not being watched already,
435 // otherwise some qWarning appears
436 for( const QString &resource: paths ) {
437 if ( !directories.contains( resource ) && !files.contains( resource ) ) {
438 m_fileSystemWatcher.addPath( resource );
439 }
440 }
441}
442
443void MapThemeManager::Private::directoryChanged( const QString& path )
444{
445 mDebug() << "directoryChanged:" << path;
446 watchPaths();
447
448 mDebug() << "Emitting themesChanged()";
449 updateMapThemeModel();
450 emit q->themesChanged();
451}
452
453void MapThemeManager::Private::fileChanged( const QString& path )
454{
455 mDebug() << "fileChanged:" << path;
456
457 // 1. if the file does not (anymore) exist, it got deleted and we
458 // have to delete the corresponding item from the model
459 // 2. if the file exists it is changed and we have to replace
460 // the item with a new one.
461
462 const QString mapThemeId = path.section(QLatin1Char('/'), -3);
463 mDebug() << "mapThemeId:" << mapThemeId;
464 QList<QStandardItem *> matchingItems = m_mapThemeModel.findItems( mapThemeId,
467 columnRelativePath );
468 mDebug() << "matchingItems:" << matchingItems.size();
469 Q_ASSERT( matchingItems.size() <= 1 );
470 int insertAtRow = 0;
471
472 if ( matchingItems.size() == 1 ) {
473 const int row = matchingItems.front()->row();
474 insertAtRow = row;
475 QList<QStandardItem *> toBeDeleted = m_mapThemeModel.takeRow( row );
476 while ( !toBeDeleted.isEmpty() ) {
477 delete toBeDeleted.takeFirst();
478 }
479 }
480
481 QFileInfo fileInfo( path );
482 if ( fileInfo.exists() ) {
483 QList<QStandardItem *> newMapThemeRow = createMapThemeRow( mapThemeId );
484 if ( !newMapThemeRow.empty() ) {
485 m_mapThemeModel.insertRow( insertAtRow, newMapThemeRow );
486 }
487 }
488
489 emit q->themesChanged();
490}
491
492//
493// <mapPathName>/<orbDirName>/<themeDirName>
494//
495void MapThemeManager::Private::addMapThemePaths( const QString& mapPathName, QStringList& result )
496{
497 QDir mapPath( mapPathName );
498 QStringList orbDirNames = mapPath.entryList( QStringList( "*" ),
502 QStringListIterator itOrb( orbDirNames );
503 while ( itOrb.hasNext() ) {
504 const QString orbPathName = mapPathName + QLatin1Char('/') + itOrb.next();
505 result << orbPathName;
506
507 QDir orbPath( orbPathName );
508 QStringList themeDirNames = orbPath.entryList( QStringList( "*" ),
512 QStringListIterator itThemeDir( themeDirNames );
513 while ( itThemeDir.hasNext() ) {
514 const QString themePathName = orbPathName + QLatin1Char('/') + itThemeDir.next();
515 result << themePathName;
516
517 QDir themePath( themePathName );
518 QStringList themeFileNames = themePath.entryList( QStringList( "*.dgml" ),
521 QStringListIterator itThemeFile( themeFileNames );
522 while ( itThemeFile.hasNext() ) {
523 const QString themeFilePathName = themePathName + QLatin1Char('/') + itThemeFile.next();
524 result << themeFilePathName;
525 }
526 }
527 }
528}
529
530GeoSceneDocument *MapThemeManager::createMapThemeFromOverlay( const GeoDataPhotoOverlay *overlayData )
531{
532 GeoSceneDocument * document = new GeoSceneDocument();
533 document->head()->setDescription( overlayData->description() );
534 document->head()->setName( overlayData->name() );
535 document->head()->setTheme( "photo" );
536 document->head()->setTarget( "panorama" );
537 document->head()->setRadius(36000);
538 document->head()->setVisible(true);
539
540 document->head()->zoom()->setMaximum(3500);
541 document->head()->zoom()->setMinimum(900);
542 document->head()->zoom()->setDiscrete(false);
543
544 GeoSceneLayer * layer = new GeoSceneLayer( "photo" );
545 layer->setBackend("texture");
546
547 GeoSceneTextureTileDataset * texture = new GeoSceneTextureTileDataset( "map" );
548 texture->setExpire(std::numeric_limits<int>::max());
549
550 QString fileName = overlayData->absoluteIconFile();
551 QFileInfo fileInfo( fileName );
552 fileName = fileInfo.fileName();
553
554 QString sourceDir = fileInfo.absoluteDir().path();
555
556 QString extension = fileInfo.suffix();
557
558 texture->setSourceDir( sourceDir );
559 texture->setFileFormat( extension );
560 texture->setInstallMap( fileName );
561 texture->setTileProjection(GeoSceneAbstractTileProjection::Equirectangular);
562
563 layer->addDataset(texture);
564
565 document->map()->addLayer(layer);
566
567 GeoSceneSettings *settings = document->settings();
568
569 GeoSceneProperty *gridProperty = new GeoSceneProperty( "coordinate-grid" );
570 gridProperty->setValue( false );
571 gridProperty->setAvailable( false );
572 settings->addProperty( gridProperty );
573
574 GeoSceneProperty *overviewmap = new GeoSceneProperty( "overviewmap" );
575 overviewmap->setValue( false );
576 overviewmap->setAvailable( false );
577 settings->addProperty( overviewmap );
578
579 GeoSceneProperty *compass = new GeoSceneProperty( "compass" );
580 compass->setValue( false );
581 compass->setAvailable( false );
582 settings->addProperty( compass );
583
584 GeoSceneProperty *scalebar = new GeoSceneProperty( "scalebar" );
585 scalebar->setValue( true );
586 scalebar->setAvailable( true );
587 settings->addProperty( scalebar );
588
589 return document;
590}
591
592}
593
594#include "moc_MapThemeManager.cpp"
Provides access to all map themes installed locally.
QStringList mapThemeIds() const
A list of all installed map theme ids, each entry has the form "planet/themeid/themeid....
MapThemeManager(QObject *parent=nullptr)
Constructor.
A container for features parsed from the DGML file.
Layer of a GeoScene document.
void addDataset(GeoSceneAbstractDataset *)
Add a data set to the legend.
void addLayer(GeoSceneLayer *)
Add a new layer to the map.
Settings property within a GeoScene document.
Settings of a GeoScene document.
void addProperty(GeoSceneProperty *property)
Add a property to the settings.
QString path(const QString &relativePath)
KIOCORE_EXPORT QString dir(const QString &fileClass)
QString name(StandardShortcut id)
Binds a QML item to a specific geodetic location in screen coordinates.
const char * constData() const const
QString translate(const char *context, const char *sourceText, const char *disambiguation, int n)
QStringList entryList(Filters filters, SortFlags sort) const const
bool mkpath(const QString &dirPath) const const
QString path() const const
bool remove()
QDir absoluteDir() const const
QString fileName() const const
QString suffix() const const
const_reference at(qsizetype i) const const
bool empty() const const
reference front()
bool isEmpty() const const
void reserve(qsizetype size)
qsizetype size() const const
value_type takeFirst()
T qobject_cast(QObject *object)
QString tr(const char *sourceText, const char *disambiguation, int n)
bool isNull() const const
bool load(const QString &fileName, const char *format, Qt::ImageConversionFlags flags)
QPixmap scaled(const QSize &size, Qt::AspectRatioMode aspectRatioMode, Qt::TransformationMode transformMode) const const
QSize size() const const
virtual void setData(const QVariant &value, int role)
QChar * data()
QString section(QChar sep, qsizetype start, qsizetype end, SectionFlags flags) const const
QByteArray toUtf8() const const
bool contains(QLatin1StringView str, Qt::CaseSensitivity cs) const const
void sort(Qt::CaseSensitivity cs)
KeepAspectRatio
UserRole
MatchExactly
Horizontal
SmoothTransformation
QFuture< ArgsType< Signal > > connect(Sender *sender, Signal signal)
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Tue Mar 26 2024 11:18:17 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.