QGIS API Documentation 3.37.0-Master (fdefdf9c27f)
qgscptcityarchive.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgscptcityarchive.cpp
3 ---------------------
4 begin : August 2012
5 copyright : (C) 2009 by Martin Dobias
6 copyright : (C) 2011 Radim Blazek
7 copyright : (C) 2012 by Etienne Tourigny
8 email : etourigny.dev at gmail.com
9 ***************************************************************************
10 * *
11 * This program is free software; you can redistribute it and/or modify *
12 * it under the terms of the GNU General Public License as published by *
13 * the Free Software Foundation; either version 2 of the License, or *
14 * (at your option) any later version. *
15 * *
16 ***************************************************************************/
17
18#include "qgssettings.h"
19#include "qgscptcityarchive.h"
20#include "qgis.h"
21#include "qgsdataprovider.h"
22#include "qgslogger.h"
23#include "qgsconfig.h"
24#include "qgsmimedatautils.h"
25#include "qgsapplication.h"
26#include "qgssymbollayerutils.h"
27
28#include <QApplication>
29#include <QDateTime>
30#include <QDir>
31#include <QFileInfo>
32#include <QVector>
33#include <QStyle>
34#include <QDomDocument>
35#include <QDomElement>
36#include <QRegularExpression>
37
38typedef QMap< QString, QgsCptCityArchive * > ArchiveRegistry;
39typedef QMap< QString, QMap< QString, QString > > CopyingInfoMap;
40
41Q_GLOBAL_STATIC( QString, sDefaultArchiveName )
42Q_GLOBAL_STATIC( ArchiveRegistry, sArchiveRegistry )
43Q_GLOBAL_STATIC( CopyingInfoMap, sCopyingInfoMap )
44
45QMap< QString, QgsCptCityArchive * > QgsCptCityArchive::archiveRegistry()
46{
47 return *sArchiveRegistry();
48}
49
50QgsCptCityArchive::QgsCptCityArchive( const QString &archiveName, const QString &baseDir )
51 : mArchiveName( archiveName )
52 , mBaseDir( baseDir )
53{
54 QgsDebugMsgLevel( "archiveName = " + archiveName + " baseDir = " + baseDir, 2 );
55
56 // make Author items
57 QgsCptCityDirectoryItem *dirItem = nullptr;
58 const auto constEntryList = QDir( mBaseDir ).entryList( QDir::Dirs | QDir::NoDotAndDotDot, QDir::Name );
59 for ( const QString &path : constEntryList )
60 {
61 if ( path == QLatin1String( "selections" ) )
62 continue;
63 QgsDebugMsgLevel( "path= " + path, 2 );
64 dirItem = new QgsCptCityDirectoryItem( nullptr, QFileInfo( path ).baseName(), path );
65 if ( dirItem->isValid() )
66 mRootItems << dirItem;
67 else
68 delete dirItem;
69 }
70
71 // make selection items
72 QgsCptCitySelectionItem *selItem = nullptr;
73 const QDir seldir( mBaseDir + '/' + "selections" );
74 QgsDebugMsgLevel( "populating selection from " + seldir.path(), 2 );
75 const QStringList fileList = seldir.entryList( QStringList() << QStringLiteral( "*.xml" ), QDir::Files );
76 for ( const QString &selfile : fileList )
77 {
78 QgsDebugMsgLevel( "file= " + seldir.path() + '/' + selfile, 2 );
79 selItem = new QgsCptCitySelectionItem( nullptr, QFileInfo( selfile ).baseName(),
80 seldir.dirName() + '/' + selfile );
81 //TODO remove item if there are no children (e.g. esri in qgis-sel)
82 if ( selItem->isValid() )
83 mSelectionItems << selItem;
84 else
85 delete selItem;
86 }
87
88 // make "All Ramps items" (which will contain all ramps without hierarchy)
89 QgsCptCityAllRampsItem *allRampsItem = nullptr;
90 allRampsItem = new QgsCptCityAllRampsItem( nullptr, QObject::tr( "All Ramps" ),
91 mRootItems );
92 mRootItems.prepend( allRampsItem );
93 allRampsItem = new QgsCptCityAllRampsItem( nullptr, QObject::tr( "All Ramps" ),
94 mSelectionItems );
95 mSelectionItems.prepend( allRampsItem );
96}
97
99{
100 const auto constMRootItems = mRootItems;
101 for ( QgsCptCityDataItem *item : constMRootItems )
102 delete item;
103 const auto constMSelectionItems = mSelectionItems;
104 for ( QgsCptCityDataItem *item : constMSelectionItems )
105 delete item;
106 mRootItems.clear();
107 mSelectionItems.clear();
108}
109
111{
112 // if was set with setBaseDir, return that value
113 // else return global default
114 if ( ! mBaseDir.isNull() )
115 return mBaseDir;
116 else
118}
119
120QString QgsCptCityArchive::baseDir( QString archiveName )
121{
122 // search for matching archive in the registry
123 if ( archiveName.isNull() )
125 if ( QgsCptCityArchive *archive = sArchiveRegistry()->value( archiveName, nullptr ) )
126 return archive->baseDir();
127 else
128 return defaultBaseDir();
129}
130
132{
133 QString baseDir, archiveName;
134 const QgsSettings settings;
135
136 // use CptCity/baseDir setting if set, default is user dir
137 baseDir = settings.value( QStringLiteral( "CptCity/baseDir" ),
138 QString( QgsApplication::pkgDataPath() + "/resources" ) ).toString();
139 // sub-dir defaults to cpt-city
140 archiveName = settings.value( QStringLiteral( "CptCity/archiveName" ), DEFAULT_CPTCITY_ARCHIVE ).toString();
141
142 return baseDir + '/' + archiveName;
143}
144
145
146QString QgsCptCityArchive::findFileName( const QString &target, const QString &startDir, const QString &baseDir )
147{
148 // QgsDebugMsgLevel( "target= " + target + " startDir= " + startDir + " baseDir= " + baseDir, 2 );
149
150 if ( startDir.isEmpty() || ! startDir.startsWith( baseDir ) )
151 return QString();
152
153 QDir dir = QDir( startDir );
154 //todo test when
155 while ( ! dir.exists( target ) && dir.path() != baseDir )
156 {
157 if ( ! dir.cdUp() )
158 break;
159 }
160 if ( ! dir.exists( target ) )
161 return QString();
162 else
163 return dir.path() + '/' + target;
164}
165
166
167QString QgsCptCityArchive::copyingFileName( const QString &path ) const
168{
169 return QgsCptCityArchive::findFileName( QStringLiteral( "COPYING.xml" ),
170 baseDir() + '/' + path, baseDir() );
171}
172
173QString QgsCptCityArchive::descFileName( const QString &path ) const
174{
175 return QgsCptCityArchive::findFileName( QStringLiteral( "DESC.xml" ),
176 baseDir() + '/' + path, baseDir() );
177}
178
180{
181 QgsStringMap copyingMap;
182
183 if ( fileName.isNull() )
184 return copyingMap;
185
186 if ( sCopyingInfoMap()->contains( fileName ) )
187 {
188 QgsDebugMsgLevel( "found copying info in copyingInfoMap, file = " + fileName, 2 );
189 return sCopyingInfoMap()->value( fileName );
190 }
191
192 QgsDebugMsgLevel( "fileName = " + fileName, 2 );
193
194 // import xml file
195 QFile f( fileName );
196 if ( !f.open( QFile::ReadOnly ) )
197 {
198 QgsDebugError( "Couldn't open xml file: " + fileName );
199 return copyingMap;
200 }
201
202 // parse the document
203 QDomDocument doc( QStringLiteral( "license" ) );
204 if ( !doc.setContent( &f ) )
205 {
206 f.close();
207 QgsDebugError( "Couldn't parse xml file: " + fileName );
208 return copyingMap;
209 }
210 f.close();
211
212 // get root element
213 const QDomElement docElem = doc.documentElement();
214 if ( docElem.tagName() != QLatin1String( "copying" ) )
215 {
216 QgsDebugError( "Incorrect root tag: " + docElem.tagName() );
217 return copyingMap;
218 }
219
220 // load author information
221 const QDomElement authorsElement = docElem.firstChildElement( QStringLiteral( "authors" ) );
222 if ( authorsElement.isNull() )
223 {
224 QgsDebugMsgLevel( QStringLiteral( "authors tag missing" ), 2 );
225 }
226 else
227 {
228 QDomElement e = authorsElement.firstChildElement();
229 QStringList authors;
230 while ( ! e.isNull() )
231 {
232 if ( e.tagName() == QLatin1String( "author" ) )
233 {
234 if ( ! e.firstChildElement( QStringLiteral( "name" ) ).isNull() )
235 authors << e.firstChildElement( QStringLiteral( "name" ) ).text().simplified();
236 // org???
237 }
238 e = e.nextSiblingElement();
239 }
240 copyingMap[ QStringLiteral( "authors" )] = authors.join( QLatin1String( ", " ) );
241 }
242
243 // load license information
244 const QDomElement licenseElement = docElem.firstChildElement( QStringLiteral( "license" ) );
245 if ( licenseElement.isNull() )
246 {
247 QgsDebugMsgLevel( QStringLiteral( "license tag missing" ), 2 );
248 }
249 else
250 {
251 QDomElement e = licenseElement.firstChildElement( QStringLiteral( "informal" ) );
252 if ( ! e.isNull() )
253 copyingMap[ QStringLiteral( "license/informal" )] = e.text().simplified();
254 e = licenseElement.firstChildElement( QStringLiteral( "year" ) );
255 if ( ! e.isNull() )
256 copyingMap[ QStringLiteral( "license/year" )] = e.text().simplified();
257 e = licenseElement.firstChildElement( QStringLiteral( "text" ) );
258 if ( ! e.isNull() && e.attribute( QStringLiteral( "href" ) ) != QString() )
259 copyingMap[ QStringLiteral( "license/url" )] = e.attribute( QStringLiteral( "href" ) );
260 }
261
262 // load src information
263 const QDomElement element = docElem.firstChildElement( QStringLiteral( "src" ) );
264 if ( element.isNull() )
265 {
266 QgsDebugMsgLevel( QStringLiteral( "src tag missing" ), 2 );
267 }
268 else
269 {
270 const QDomElement e = element.firstChildElement( QStringLiteral( "link" ) );
271 if ( ! e.isNull() && e.attribute( QStringLiteral( "href" ) ) != QString() )
272 copyingMap[ QStringLiteral( "src/link" )] = e.attribute( QStringLiteral( "href" ) );
273 }
274
275 // save copyingMap for further access
276 ( *sCopyingInfoMap() )[ fileName ] = copyingMap;
277 return copyingMap;
278}
279
281{
282 QgsStringMap descMap;
283
284 QgsDebugMsgLevel( "description fileName = " + fileName, 2 );
285
286 QFile f( fileName );
287 if ( ! f.open( QFile::ReadOnly ) )
288 {
289 QgsDebugMsgLevel( "description file " + fileName + " ] does not exist", 2 );
290 return descMap;
291 }
292
293 // parse the document
294 QString errMsg;
295 QDomDocument doc( QStringLiteral( "description" ) );
296 if ( !doc.setContent( &f, &errMsg ) )
297 {
298 f.close();
299 QgsDebugError( "Couldn't parse file " + fileName + " : " + errMsg );
300 return descMap;
301 }
302 f.close();
303
304 // read description
305 const QDomElement docElem = doc.documentElement();
306 if ( docElem.tagName() != QLatin1String( "description" ) )
307 {
308 QgsDebugError( "Incorrect root tag: " + docElem.tagName() );
309 return descMap;
310 }
311 // should we make sure the <dir> tag is OK?
312
313 QDomElement e = docElem.firstChildElement( QStringLiteral( "name" ) );
314 if ( e.isNull() )
315 {
316 QgsDebugMsgLevel( QStringLiteral( "name tag missing" ), 2 );
317 }
318 descMap[ QStringLiteral( "name" )] = e.text().simplified();
319 e = docElem.firstChildElement( QStringLiteral( "full" ) );
320 if ( e.isNull() )
321 {
322 QgsDebugMsgLevel( QStringLiteral( "full tag missing" ), 2 );
323 }
324 descMap[ QStringLiteral( "full" )] = e.text().simplified();
325
326 return descMap;
327}
328
329QMap< double, QPair<QColor, QColor> >QgsCptCityArchive::gradientColorMap( const QString &fileName )
330{
331 QMap< double, QPair<QColor, QColor> > colorMap;
332
333 // import xml file
334 QFile f( fileName );
335 if ( !f.open( QFile::ReadOnly ) )
336 {
337 QgsDebugError( "Couldn't open SVG file: " + fileName );
338 return colorMap;
339 }
340
341 // parse the document
342 QDomDocument doc( QStringLiteral( "gradient" ) );
343 if ( !doc.setContent( &f ) )
344 {
345 f.close();
346 QgsDebugError( "Couldn't parse SVG file: " + fileName );
347 return colorMap;
348 }
349 f.close();
350
351 const QDomElement docElem = doc.documentElement();
352
353 if ( docElem.tagName() != QLatin1String( "svg" ) )
354 {
355 QgsDebugError( "Incorrect root tag: " + docElem.tagName() );
356 return colorMap;
357 }
358
359 // load color ramp from first linearGradient node
360 QDomElement rampsElement = docElem.firstChildElement( QStringLiteral( "linearGradient" ) );
361 if ( rampsElement.isNull() )
362 {
363 const QDomNodeList nodeList = docElem.elementsByTagName( QStringLiteral( "linearGradient" ) );
364 if ( ! nodeList.isEmpty() )
365 rampsElement = nodeList.at( 0 ).toElement();
366 }
367 if ( rampsElement.isNull() )
368 {
369 QgsDebugError( QStringLiteral( "linearGradient tag missing" ) );
370 return colorMap;
371 }
372
373 // loop for all stop tags
374 QDomElement e = rampsElement.firstChildElement();
375
376 while ( !e.isNull() )
377 {
378 if ( e.tagName() == QLatin1String( "stop" ) )
379 {
380 //todo integrate this into symbollayerutils, keep here for now...
381 double offset;
382 QString offsetStr = e.attribute( QStringLiteral( "offset" ) ); // offset="50.00%" | offset="0.5"
383 const QString colorStr = e.attribute( QStringLiteral( "stop-color" ), QString() ); // stop-color="rgb(222,235,247)"
384 const QString opacityStr = e.attribute( QStringLiteral( "stop-opacity" ), QStringLiteral( "1.0" ) ); // stop-opacity="1.0000"
385 if ( offsetStr.endsWith( '%' ) )
386 offset = offsetStr.remove( offsetStr.size() - 1, 1 ).toDouble() / 100.0;
387 else
388 offset = offsetStr.toDouble();
389
390 QColor color;
391 if ( colorStr.isEmpty() )
392 {
393 // SVG spec says that stops without color default to black!
394 color = QColor( 0, 0, 0 );
395 }
396 else
397 {
398 color = QgsSymbolLayerUtils::parseColor( colorStr );
399 }
400
401 if ( color.isValid() )
402 {
403 const int alpha = opacityStr.toDouble() * 255; // test
404 color.setAlpha( alpha );
405 if ( colorMap.contains( offset ) )
406 colorMap[offset].second = color;
407 else
408 colorMap[offset] = qMakePair( color, color );
409 }
410 else
411 {
412 QgsDebugError( QStringLiteral( "at offset=%1 invalid color \"%2\"" ).arg( offset ).arg( colorStr ) );
413 }
414 }
415 else
416 {
417 QgsDebugError( "unknown tag: " + e.tagName() );
418 }
419
420 e = e.nextSiblingElement();
421 }
422
423 return colorMap;
424}
425
427{
428 return ( mRootItems.isEmpty() );
429}
430
431
433{
434 const QgsSettings settings;
435 *sDefaultArchiveName() = settings.value( QStringLiteral( "CptCity/archiveName" ), DEFAULT_CPTCITY_ARCHIVE ).toString();
436 if ( sArchiveRegistry()->contains( *sDefaultArchiveName() ) )
437 return sArchiveRegistry()->value( *sDefaultArchiveName() );
438 else
439 return nullptr;
440}
441
442void QgsCptCityArchive::initArchive( const QString &archiveName, const QString &archiveBaseDir )
443{
444 QgsDebugMsgLevel( "archiveName = " + archiveName + " archiveBaseDir = " + archiveBaseDir, 2 );
445 QgsCptCityArchive *archive = new QgsCptCityArchive( archiveName, archiveBaseDir );
446 if ( sArchiveRegistry()->contains( archiveName ) )
447 delete ( *sArchiveRegistry() )[ archiveName ];
448 ( *sArchiveRegistry() )[ archiveName ] = archive;
449}
450
452{
453 const QgsSettings settings;
454 // use CptCity/baseDir setting if set, default is user dir
455 const QString baseDir = settings.value( QStringLiteral( "CptCity/baseDir" ),
456 QString( QgsApplication::pkgDataPath() + "/resources" ) ).toString();
457 // sub-dir defaults to
458 const QString defArchiveName = settings.value( QStringLiteral( "CptCity/archiveName" ), DEFAULT_CPTCITY_ARCHIVE ).toString();
459
460 if ( ! sArchiveRegistry()->contains( defArchiveName ) )
461 initArchive( defArchiveName, baseDir + '/' + defArchiveName );
462}
463
465{
466 QgsStringMap archivesMap;
467 QString baseDir, defArchiveName;
468 const QgsSettings settings;
469
470 // use CptCity/baseDir setting if set, default is user dir
471 baseDir = settings.value( QStringLiteral( "CptCity/baseDir" ),
472 QString( QgsApplication::pkgDataPath() + "/resources" ) ).toString();
473 // sub-dir defaults to
474 defArchiveName = settings.value( QStringLiteral( "CptCity/archiveName" ), DEFAULT_CPTCITY_ARCHIVE ).toString();
475
476 QgsDebugMsgLevel( "baseDir= " + baseDir + " defArchiveName= " + defArchiveName, 2 );
477 if ( loadAll )
478 {
479 const QDir dir( baseDir );
480 const QStringList fileList = dir.entryList( QStringList() << QStringLiteral( "cpt-city*" ), QDir::Dirs );
481 for ( const QString &entry : fileList )
482 {
483 if ( QFile::exists( baseDir + '/' + entry + "/VERSION.xml" ) )
484 archivesMap[ entry ] = baseDir + '/' + entry;
485 }
486 }
487 else
488 {
489 archivesMap[ defArchiveName ] = baseDir + '/' + defArchiveName;
490 }
491
492 for ( QgsStringMap::iterator it = archivesMap.begin();
493 it != archivesMap.end(); ++it )
494 {
495 if ( QDir( it.value() ).exists() )
496 QgsCptCityArchive::initArchive( it.key(), it.value() );
497 else
498 {
499 QgsDebugError( QStringLiteral( "not loading archive [%1] because dir %2 does not exist " ).arg( it.key(), it.value() ) );
500 }
501 }
502 *sDefaultArchiveName() = defArchiveName;
503}
504
506{
507 qDeleteAll( *sArchiveRegistry() );
508 sArchiveRegistry()->clear();
509}
510
511
512// --------
513
515 const QString &name, const QString &path )
516// Do not pass parent to QObject, Qt would delete this when parent is deleted
517 : mType( type )
518 , mParent( parent )
519 , mPopulated( false )
520 , mName( name )
521 , mPath( path )
522 , mValid( true )
523{
524}
525
526QVector<QgsCptCityDataItem *> QgsCptCityDataItem::createChildren()
527{
528 QVector<QgsCptCityDataItem *> children;
529 return children;
530}
531
533{
534 if ( mPopulated )
535 return;
536
537 QgsDebugMsgLevel( "mPath = " + mPath, 2 );
538
539 QApplication::setOverrideCursor( Qt::WaitCursor );
540
541 const QVector<QgsCptCityDataItem *> children = createChildren();
542 const auto constChildren = children;
543 for ( QgsCptCityDataItem *child : constChildren )
544 {
545 // initialization, do not refresh! That would result in infinite loop (beginInsertItems->rowCount->populate)
546 addChildItem( child );
547 }
548 mPopulated = true;
549
550 QApplication::restoreOverrideCursor();
551}
552
554{
555 // if ( !mPopulated )
556 // populate();
557 return mChildren.size();
558}
559
561{
562 if ( !mPopulated )
563 return 0;
564
565 int count = 0;
566 const auto constMChildren = mChildren;
567 for ( QgsCptCityDataItem *child : constMChildren )
568 {
569 if ( child )
570 count += child->leafCount();
571 }
572 return count;
573}
574
575
577{
578 return ( mPopulated ? !mChildren.isEmpty() : true );
579}
580
582{
583 QgsDebugMsgLevel( QStringLiteral( "add child #%1 - %2 - %3" ).arg( mChildren.size() ).arg( child->mName ).arg( child->mType ), 2 );
584
585 int i;
586 if ( type() == ColorRamp )
587 {
588 for ( i = 0; i < mChildren.size(); i++ )
589 {
590 // sort items by type, so directories are after data items
591 if ( mChildren.at( i )->mType == child->mType &&
592 mChildren.at( i )->mName.localeAwareCompare( child->mName ) >= 0 )
593 break;
594 }
595 }
596 else
597 {
598 for ( i = 0; i < mChildren.size(); i++ )
599 {
600 if ( mChildren.at( i )->mName.localeAwareCompare( child->mName ) >= 0 )
601 break;
602 }
603 }
604
605 if ( refresh )
606 emit beginInsertItems( this, i, i );
607
608 mChildren.insert( i, child );
609
614
615 if ( refresh )
616 emit endInsertItems();
617}
619{
620 // QgsDebugMsgLevel( "mName = " + child->mName, 2 );
621 const int i = mChildren.indexOf( child );
622 Q_ASSERT( i >= 0 );
623 emit beginRemoveItems( this, i, i );
624 mChildren.remove( i );
625 delete child;
626 emit endRemoveItems();
627}
628
630{
631 // QgsDebugMsgLevel( "mName = " + child->mName, 2 );
632 const int i = mChildren.indexOf( child );
633 Q_ASSERT( i >= 0 );
634 emit beginRemoveItems( this, i, i );
635 mChildren.remove( i );
636 emit endRemoveItems();
641 child->setParent( nullptr );
642 return child;
643}
644
645int QgsCptCityDataItem::findItem( QVector<QgsCptCityDataItem *> items, QgsCptCityDataItem *item )
646{
647 for ( int i = 0; i < items.size(); i++ )
648 {
649 // QgsDebugMsgLevel( QString::number( i ) + " : " + items[i]->mPath + " x " + item->mPath, 2 );
650 if ( items[i]->equal( item ) )
651 return i;
652 }
653 return -1;
654}
655
657{
658 QgsDebugMsgLevel( "mPath = " + mPath, 2 );
659
660 QApplication::setOverrideCursor( Qt::WaitCursor );
661
662 const QVector<QgsCptCityDataItem *> items = createChildren();
663
664 // Remove no more present items
665 QVector<QgsCptCityDataItem *> remove;
666 const auto constMChildren = mChildren;
667 for ( QgsCptCityDataItem *child : constMChildren )
668 {
669 if ( findItem( items, child ) >= 0 )
670 continue;
671 remove.append( child );
672 }
673 const auto constRemove = remove;
674 for ( QgsCptCityDataItem *child : constRemove )
675 {
676 deleteChildItem( child );
677 }
678
679 // Add new items
680 const auto constItems = items;
681 for ( QgsCptCityDataItem *item : constItems )
682 {
683 // Is it present in children?
684 if ( findItem( mChildren, item ) >= 0 )
685 {
686 delete item;
687 continue;
688 }
689 addChildItem( item, true );
690 }
691
692 QApplication::restoreOverrideCursor();
693}
694
696{
697 return ( metaObject()->className() == other->metaObject()->className() &&
698 mPath == other->path() );
699}
700
701// ---------------------------------------------------------------------
702
704 const QString &name, const QString &path, const QString &variantName, bool initialize )
705 : QgsCptCityDataItem( ColorRamp, parent, name, path )
706 , mInitialized( false )
707 , mRamp( path, variantName, false )
708{
709 // QgsDebugMsgLevel( "name= " + name + " path= " + path, 2 );
710 mPopulated = true;
711 if ( initialize )
712 init();
713}
714
716 const QString &name, const QString &path, const QStringList &variantList, bool initialize )
717 : QgsCptCityDataItem( ColorRamp, parent, name, path )
718 , mInitialized( false )
719 , mRamp( path, variantList, QString(), false )
720{
721 // QgsDebugMsgLevel( "name= " + name + " path= " + path, 2 );
722 mPopulated = true;
723 if ( initialize )
724 init();
725}
726
727// TODO only load file when icon is requested...
729{
730 if ( mInitialized )
731 return;
732 mInitialized = true;
733
734 QgsDebugMsgLevel( "path = " + path(), 2 );
735
736 // make preview from variant if exists
737 QStringList variantList = mRamp.variantList();
738 if ( mRamp.variantName().isNull() && ! variantList.isEmpty() )
739 mRamp.setVariantName( variantList[ variantList.count() / 2 ] );
740
741 mRamp.loadFile();
742
743 // is this item valid? this might fail when there are variants, check
744 if ( ! QFile::exists( mRamp.fileName() ) )
745 mValid = false;
746 else
747 mValid = true;
748
749 // load file and set info
750 if ( mRamp.count() > 0 )
751 {
752 if ( variantList.isEmpty() )
753 {
754 int count = mRamp.count();
755 if ( mRamp.isDiscrete() )
756 count--;
757 mInfo = QString::number( count ) + ' ' + tr( "colors" ) + " - ";
758 if ( mRamp.isDiscrete() )
759 mInfo += tr( "discrete" );
760 else
761 {
762 if ( !mRamp.hasMultiStops() )
763 mInfo += tr( "continuous" );
764 else
765 mInfo += tr( "continuous (multi)" );
766 }
767 mShortInfo = QFileInfo( mName ).fileName();
768 }
769 else
770 {
771 mInfo = QString::number( variantList.count() ) + ' ' + tr( "variants" );
772 // mShortInfo = QFileInfo( mName ).fileName() + " (" + QString::number( variantList.count() ) + ')';
773 mShortInfo = QFileInfo( mName ).fileName();
774 }
775 }
776 else
777 {
778 mInfo.clear();
779 }
780
781}
782
784{
785 //QgsDebugMsg ( mPath + " x " + other->mPath );
786 if ( type() != other->type() )
787 {
788 return false;
789 }
790 //const QgsCptCityColorRampItem *o = qobject_cast<const QgsCptCityColorRampItem *> ( other );
791 const QgsCptCityColorRampItem *o = qobject_cast<const QgsCptCityColorRampItem *>( other );
792 return o &&
793 mPath == o->mPath &&
794 mName == o->mName &&
795 ramp().variantName() == o->ramp().variantName();
796}
797
799{
800 return icon( QSize( 100, 15 ) );
801}
802
804{
805 const auto constMIcons = mIcons;
806 for ( const QIcon &icon : constMIcons )
807 {
808 if ( icon.availableSizes().contains( size ) )
809 return icon;
810 }
811
812 QIcon icon;
813
814 init();
815
816 if ( mValid && mRamp.count() > 0 )
817 {
819 }
820 else
821 {
822 QPixmap blankPixmap( size );
823 blankPixmap.fill( Qt::white );
824 icon = QIcon( blankPixmap );
825 mInfo.clear();
826 }
827
828 mIcons.append( icon );
829 return icon;
830}
831
832// ---------------------------------------------------------------------
834 const QString &name, const QString &path )
835 : QgsCptCityDataItem( Collection, parent, name, path )
836 , mPopulatedRamps( false )
837{
838}
839
841{
842 qDeleteAll( mChildren );
843}
844
845QVector< QgsCptCityDataItem * > QgsCptCityCollectionItem::childrenRamps( bool recursive )
846{
847 QVector< QgsCptCityDataItem * > rampItems;
848 QVector< QgsCptCityDataItem * > deleteItems;
849
850 populate();
851
852 // recursively add children
853 const auto constChildren = children();
854 for ( QgsCptCityDataItem *childItem : constChildren )
855 {
856 QgsCptCityCollectionItem *collectionItem = qobject_cast<QgsCptCityCollectionItem *>( childItem );
857 QgsCptCityColorRampItem *rampItem = qobject_cast<QgsCptCityColorRampItem *>( childItem );
858 QgsDebugMsgLevel( QStringLiteral( "child path= %1 coll= %2 ramp = %3" ).arg( childItem->path() ).arg( nullptr != collectionItem ).arg( nullptr != rampItem ), 2 );
859 if ( collectionItem && recursive )
860 {
861 collectionItem->populate();
862 rampItems << collectionItem->childrenRamps( true );
863 }
864 else if ( rampItem )
865 {
866 // init rampItem to get palette and icon, test if is valid after loading file
867 rampItem->init();
868 if ( rampItem->isValid() )
869 rampItems << rampItem;
870 else
871 deleteItems << rampItem;
872 }
873 else
874 {
875 QgsDebugError( "invalid item " + childItem->path() );
876 }
877 }
878
879 // delete invalid items - this is not efficient, but should only happens once
880 const auto constDeleteItems = deleteItems;
881 for ( QgsCptCityDataItem *deleteItem : constDeleteItems )
882 {
883 QgsDebugError( QStringLiteral( "item %1 is invalid, will be deleted" ).arg( deleteItem->path() ) );
884 const int i = mChildren.indexOf( deleteItem );
885 if ( i != -1 )
886 mChildren.remove( i );
887 delete deleteItem;
888 }
889
890 return rampItems;
891}
892
893//-----------------------------------------------------------------------
895 const QString &name, const QString &path )
896 : QgsCptCityCollectionItem( parent, name, path )
897{
899 mValid = QDir( QgsCptCityArchive::defaultBaseDir() + '/' + mPath ).exists();
900 if ( ! mValid )
901 {
902 QgsDebugError( "created invalid dir item, path = " + QgsCptCityArchive::defaultBaseDir()
903 + '/' + mPath );
904 }
905
906 // parse DESC.xml to get mInfo
907 mInfo.clear();
908 const QString fileName = QgsCptCityArchive::defaultBaseDir() + '/' +
909 mPath + '/' + "DESC.xml";
910 const QgsStringMap descMap = QgsCptCityArchive::description( fileName );
911 if ( descMap.contains( QStringLiteral( "name" ) ) )
912 mInfo = descMap.value( QStringLiteral( "name" ) );
913
914 // populate();
915}
916
917QVector<QgsCptCityDataItem *> QgsCptCityDirectoryItem::createChildren()
918{
919 if ( ! mValid )
920 return QVector<QgsCptCityDataItem *>();
921
922 QVector<QgsCptCityDataItem *> children;
923
924 // add children schemes
925 QMapIterator< QString, QStringList> it( rampsMap() );
926 while ( it.hasNext() )
927 {
928 it.next();
929 // QgsDebugMsgLevel( "schemeName = " + it.key(), 2 );
930 QgsCptCityDataItem *item =
931 new QgsCptCityColorRampItem( this, it.key(), it.key(), it.value() );
932 if ( item->isValid() )
933 children << item;
934 else
935 delete item;
936 }
937
938 // add children dirs
939 const auto constDirEntries = dirEntries();
940 for ( const QString &childPath : constDirEntries )
941 {
942 QgsCptCityDataItem *childItem =
943 QgsCptCityDirectoryItem::dataItem( this, childPath, mPath + '/' + childPath );
944 if ( childItem )
945 children << childItem;
946 }
947
948 QgsDebugMsgLevel( QStringLiteral( "name= %1 path= %2 found %3 children" ).arg( mName, mPath ).arg( children.count() ), 2 );
949
950 return children;
951}
952
953QMap< QString, QStringList > QgsCptCityDirectoryItem::rampsMap()
954{
955 if ( ! mRampsMap.isEmpty() )
956 return mRampsMap;
957
958 QString curName, prevName, curVariant, curSep, schemeName;
959 QStringList listVariant;
960 QStringList schemeNamesAll, schemeNames;
961 bool prevAdd, curAdd;
962
963 const QDir dir( QgsCptCityArchive::defaultBaseDir() + '/' + mPath );
964 schemeNamesAll = dir.entryList( QStringList( QStringLiteral( "*.svg" ) ), QDir::Files, QDir::Name );
965
966 // TODO detect if there are duplicate names with different variant counts, combine in 1
967 for ( int i = 0; i < schemeNamesAll.count(); i++ )
968 {
969 // schemeName = QFileInfo( schemeNamesAll[i] ).baseName();
970 schemeName = schemeNamesAll[i];
971 schemeName.chop( 4 );
972 // QgsDebugMsgLevel("scheme = "+schemeName, 2);
973 curName = schemeName;
974 curVariant.clear();
975
976 // find if name ends with 1-3 digit number
977 // TODO need to detect if ends with b/c also
978 if ( schemeName.length() > 1 && schemeName.endsWith( 'a' ) && ! listVariant.isEmpty() &&
979 ( ( prevName + listVariant.last() + 'a' ) == curName ) )
980 {
981 curName = prevName;
982 curVariant = listVariant.last() + 'a';
983 }
984 else
985 {
986 const thread_local QRegularExpression rxVariant( "^(.*[^\\d])(\\d{1,3})$" );
987 const QRegularExpressionMatch match = rxVariant.match( schemeName );
988 if ( match.hasMatch() )
989 {
990 curName = match.captured( 1 );
991 curVariant = match.captured( 2 );
992 }
993 }
994
995 curSep = curName.right( 1 );
996 if ( curSep == QLatin1String( "-" ) || curSep == QLatin1String( "_" ) )
997 {
998 curName.chop( 1 );
999 curVariant = curSep + curVariant;
1000 }
1001
1002 if ( prevName.isEmpty() )
1003 prevName = curName;
1004
1005 // add element, unless it is empty, or a variant of last element
1006 prevAdd = false;
1007 curAdd = false;
1008 if ( curName.isEmpty() )
1009 curName = QStringLiteral( "__empty__" );
1010 // if current is a variant of last, don't add previous and append current variant
1011 if ( curName == prevName )
1012 {
1013 // add current element if it is the last one in the archive
1014 if ( i == schemeNamesAll.count() - 1 )
1015 prevAdd = true;
1016 listVariant << curVariant;
1017 }
1018 else
1019 {
1020 if ( !prevName.isEmpty() )
1021 {
1022 prevAdd = true;
1023 }
1024 // add current element if it is the last one in the archive
1025 if ( i == schemeNamesAll.count() - 1 )
1026 curAdd = true;
1027 }
1028
1029 // QgsDebugMsgLevel(QString("prevAdd=%1 curAdd=%2 prevName=%3 curName=%4 count=%5").arg(prevAdd).arg(curAdd).arg(prevName).arg(curName).arg(listVariant.count()), 2);
1030
1031 if ( prevAdd )
1032 {
1033 // depending on number of variants, make one or more items
1034 if ( listVariant.isEmpty() )
1035 {
1036 // set num colors=-1 to parse file on request only
1037 // mSchemeNumColors[ prevName ] = -1;
1038 schemeNames << prevName;
1039 mRampsMap[ mPath + '/' + prevName ] = QStringList();
1040 }
1041 else if ( listVariant.count() <= 3 )
1042 {
1043 // for 1-2 items, create independent items
1044 for ( int j = 0; j < listVariant.count(); j++ )
1045 {
1046 // mSchemeNumColors[ prevName + listVariant[j] ] = -1;
1047 schemeNames << prevName + listVariant[j];
1048 mRampsMap[ mPath + '/' + prevName + listVariant[j] ] = QStringList();
1049 }
1050 }
1051 else
1052 {
1053 // mSchemeVariants[ path + '/' + prevName ] = listVariant;
1054 mRampsMap[ mPath + '/' + prevName ] = listVariant;
1055 schemeNames << prevName;
1056 }
1057 listVariant.clear();
1058 }
1059 if ( curAdd )
1060 {
1061 if ( !curVariant.isEmpty() )
1062 curName += curVariant;
1063 schemeNames << curName;
1064 mRampsMap[ mPath + '/' + curName ] = QStringList();
1065 }
1066 // save current to compare next
1067 if ( prevAdd || curAdd )
1068 {
1069 prevName = curName;
1070 if ( !curVariant.isEmpty() )
1071 listVariant << curVariant;
1072 }
1073
1074 }
1075#if 0
1076 //TODO what to do with other vars? e.g. schemeNames
1077 // add schemes to archive
1078 mSchemeMap[ path ] = schemeNames;
1079 schemeCount += schemeName.count();
1080 schemeNames.clear();
1081 listVariant.clear();
1082 prevName = "";
1083#endif
1084 return mRampsMap;
1085}
1086
1088{
1089 return QDir( QgsCptCityArchive::defaultBaseDir() +
1090 '/' + mPath ).entryList( QDir::Dirs | QDir::NoDotAndDotDot, QDir::Name );
1091}
1092
1094{
1095 //QgsDebugMsg ( mPath + " x " + other->mPath );
1096 if ( type() != other->type() )
1097 {
1098 return false;
1099 }
1100 return ( path() == other->path() );
1101}
1102
1104 const QString &name, const QString &path )
1105{
1106 QgsDebugMsgLevel( "name= " + name + " path= " + path, 2 );
1107
1108 // first create item with constructor
1110 if ( dirItem && ! dirItem->isValid() )
1111 {
1112 delete dirItem;
1113 return nullptr;
1114 }
1115 if ( ! dirItem )
1116 return nullptr;
1117
1118 // fetch sub-dirs and ramps to know what to do with this item
1119 const QStringList dirEntries = dirItem->dirEntries();
1120 QMap< QString, QStringList > rampsMap = dirItem->rampsMap();
1121
1122 QgsDebugMsgLevel( QStringLiteral( "item has %1 dirs and %2 ramps" ).arg( dirEntries.count() ).arg( rampsMap.count() ), 2 );
1123
1124 // return item if has at least one subdir
1125 if ( !dirEntries.isEmpty() )
1126 return dirItem;
1127
1128 // if 0 ramps, delete item
1129 if ( rampsMap.isEmpty() )
1130 {
1131 delete dirItem;
1132 return nullptr;
1133 }
1134 // if 1 ramp, return this child's item
1135 // so we don't have a directory with just 1 item (with many variants possibly)
1136 else if ( rampsMap.count() == 1 )
1137 {
1138 delete dirItem;
1139 QgsCptCityColorRampItem *rampItem =
1140 new QgsCptCityColorRampItem( parent, rampsMap.begin().key(),
1141 rampsMap.begin().key(), rampsMap.begin().value() );
1142 if ( ! rampItem->isValid() )
1143 {
1144 delete rampItem;
1145 return nullptr;
1146 }
1147 return rampItem;
1148 }
1149 return dirItem;
1150}
1151
1152
1153//-----------------------------------------------------------------------
1155 const QString &name, const QString &path )
1156 : QgsCptCityCollectionItem( parent, name, path )
1157{
1158 mType = Selection;
1159 mValid = ! path.isNull();
1160 if ( mValid )
1161 parseXml();
1162}
1163
1164QVector<QgsCptCityDataItem *> QgsCptCitySelectionItem::createChildren()
1165{
1166 if ( ! mValid )
1167 return QVector<QgsCptCityDataItem *>();
1168
1169 QgsCptCityDataItem *item = nullptr;
1170 QVector<QgsCptCityDataItem *> children;
1171
1172 QgsDebugMsgLevel( "name= " + mName + " path= " + mPath, 2 );
1173
1174 // add children archives
1175 for ( QString childPath : std::as_const( mSelectionsList ) )
1176 {
1177 QgsDebugMsgLevel( "childPath = " + childPath + " name= " + QFileInfo( childPath ).baseName(), 2 );
1178 if ( childPath.endsWith( '/' ) )
1179 {
1180 childPath.chop( 1 );
1181 QgsCptCityDataItem *childItem =
1182 QgsCptCityDirectoryItem::dataItem( this, childPath, childPath );
1183 if ( childItem )
1184 {
1185 if ( childItem->isValid() )
1186 children << childItem;
1187 else
1188 delete childItem;
1189 }
1190 }
1191 else
1192 {
1193 const QString fileName = QgsCptCityColorRamp::fileNameForVariant( childPath, QString() );
1194 if ( !QFile::exists( fileName ) )
1195 {
1196 continue;
1197 }
1198
1199 item = new QgsCptCityColorRampItem( this, childPath, childPath, QString(), true );
1200 if ( item->isValid() )
1201 children << item;
1202 else
1203 delete item;
1204 }
1205 }
1206
1207 QgsDebugMsgLevel( QStringLiteral( "path= %1 inserted %2 children" ).arg( mPath ).arg( children.count() ), 2 );
1208
1209 return children;
1210}
1211
1213{
1214 const QString filename = QgsCptCityArchive::defaultBaseDir() + '/' + mPath;
1215
1216 QgsDebugMsgLevel( "reading file " + filename, 2 );
1217
1218 QFile f( filename );
1219 if ( ! f.open( QFile::ReadOnly ) )
1220 {
1221 QgsDebugError( filename + " does not exist" );
1222 return;
1223 }
1224
1225 // parse the document
1226 QString errMsg;
1227 QDomDocument doc( QStringLiteral( "selection" ) );
1228 if ( !doc.setContent( &f, &errMsg ) )
1229 {
1230 f.close();
1231 QgsDebugError( "Couldn't parse file " + filename + " : " + errMsg );
1232 return;
1233 }
1234 f.close();
1235
1236 // read description
1237 const QDomElement docElem = doc.documentElement();
1238 if ( docElem.tagName() != QLatin1String( "selection" ) )
1239 {
1240 QgsDebugError( "Incorrect root tag: " + docElem.tagName() );
1241 return;
1242 }
1243 QDomElement e = docElem.firstChildElement( QStringLiteral( "name" ) );
1244 if ( ! e.isNull() && ! e.text().isNull() )
1245 mName = e.text();
1246 mInfo = docElem.firstChildElement( QStringLiteral( "synopsis" ) ).text().simplified();
1247
1248 // get archives
1249 const QDomElement collectsElem = docElem.firstChildElement( QStringLiteral( "seealsocollects" ) );
1250 e = collectsElem.firstChildElement( QStringLiteral( "collect" ) );
1251 while ( ! e.isNull() )
1252 {
1253 if ( ! e.attribute( QStringLiteral( "dir" ) ).isNull() )
1254 {
1255 // TODO parse description and use that, instead of default archive name
1256 const QString dir = e.attribute( QStringLiteral( "dir" ) ) + '/';
1257 if ( QFile::exists( QgsCptCityArchive::defaultBaseDir() + '/' + dir ) )
1258 {
1259 mSelectionsList << dir;
1260 }
1261 }
1262 e = e.nextSiblingElement();
1263 }
1264 // get individual gradients
1265 const QDomElement gradientsElem = docElem.firstChildElement( QStringLiteral( "gradients" ) );
1266 e = gradientsElem.firstChildElement( QStringLiteral( "gradient" ) );
1267 while ( ! e.isNull() )
1268 {
1269 if ( ! e.attribute( QStringLiteral( "dir" ) ).isNull() )
1270 {
1271 // QgsDebugMsgLevel( "add " + e.attribute( "dir" ) + '/' + e.attribute( "file" ) + " to " + selname, 2 );
1272 // TODO parse description and save elsewhere
1273 const QString dir = e.attribute( QStringLiteral( "dir" ) );
1274 if ( QFile::exists( QgsCptCityArchive::defaultBaseDir() + '/' + dir ) )
1275 {
1276 mSelectionsList << dir + '/' + e.attribute( QStringLiteral( "file" ) );
1277 }
1278 }
1279 e = e.nextSiblingElement();
1280 }
1281}
1282
1284{
1285 //QgsDebugMsgLevel( mPath + " x " + other->mPath, 2 );
1286 if ( type() != other->type() )
1287 {
1288 return false;
1289 }
1290 return ( path() == other->path() );
1291}
1292
1293//-----------------------------------------------------------------------
1295 const QString &name, const QVector<QgsCptCityDataItem *> &items )
1296 : QgsCptCityCollectionItem( parent, name, QString() )
1297 , mItems( items )
1298{
1299 mType = AllRamps;
1300 mValid = true;
1301 // populate();
1302}
1303
1304QVector<QgsCptCityDataItem *> QgsCptCityAllRampsItem::createChildren()
1305{
1306 if ( ! mValid )
1307 return QVector<QgsCptCityDataItem *>();
1308
1309 QVector<QgsCptCityDataItem *> children;
1310
1311 // add children ramps of each item
1312 const auto constMItems = mItems;
1313 for ( QgsCptCityDataItem *item : constMItems )
1314 {
1315 QgsCptCityCollectionItem *colItem = qobject_cast< QgsCptCityCollectionItem * >( item );
1316 if ( colItem )
1317 children += colItem->childrenRamps( true );
1318 }
1319
1320 return children;
1321}
1322
1323//-----------------------------------------------------------------------
1324
1326 QgsCptCityArchive *archive, ViewType viewType )
1327 : QAbstractItemModel( parent )
1328 , mArchive( archive )
1329 , mViewType( viewType )
1330{
1331 Q_ASSERT( mArchive );
1332 QgsDebugMsgLevel( QLatin1String( "archiveName = " ) + archive->archiveName() + " viewType=" + QString::number( static_cast< int >( viewType ) ), 2 );
1333 // keep iconsize for now, but not effectively used
1334 mIconSize = QSize( 100, 15 );
1335 addRootItems();
1336}
1337
1339{
1341}
1342
1344{
1345 if ( mViewType == Authors )
1346 {
1348 }
1349 else if ( mViewType == Selections )
1350 {
1352 }
1353 QgsDebugMsgLevel( QStringLiteral( "added %1 root items" ).arg( mRootItems.size() ), 2 );
1354}
1355
1357{
1358 mRootItems.clear();
1359}
1360
1361Qt::ItemFlags QgsCptCityBrowserModel::flags( const QModelIndex &index ) const
1362{
1363 if ( !index.isValid() )
1364 return Qt::ItemFlags();
1365
1366 Qt::ItemFlags flags = Qt::ItemIsEnabled | Qt::ItemIsSelectable;
1367
1368 return flags;
1369}
1370
1371QVariant QgsCptCityBrowserModel::data( const QModelIndex &index, int role ) const
1372{
1373 if ( !index.isValid() )
1374 return QVariant();
1375
1377
1378 if ( !item )
1379 {
1380 return QVariant();
1381 }
1382 else if ( role == Qt::DisplayRole )
1383 {
1384 if ( index.column() == 0 )
1385 return item->name();
1386 if ( index.column() == 1 )
1387 {
1388 return item->info();
1389 }
1390 }
1391 else if ( role == Qt::ToolTipRole )
1392 {
1393 if ( item->type() == QgsCptCityDataItem::ColorRamp &&
1394 mViewType == List )
1395 return QString( item->path() + '\n' + item->info() );
1396 return item->toolTip();
1397 }
1398 else if ( role == Qt::DecorationRole && index.column() == 1 &&
1400 {
1401 // keep iconsize for now, but not effectively used
1402 return item->icon( mIconSize );
1403 }
1404 else if ( role == Qt::FontRole &&
1405 qobject_cast< QgsCptCityCollectionItem * >( item ) )
1406 {
1407 // collectionitems are larger and bold
1408 QFont font;
1409 font.setPointSize( 11 ); //FIXME why is the font so small?
1410 font.setBold( true );
1411 return font;
1412 }
1413 else
1414 {
1415 // unsupported role
1416 return QVariant();
1417 }
1418 return QVariant();
1419}
1420
1421QVariant QgsCptCityBrowserModel::headerData( int section, Qt::Orientation orientation, int role ) const
1422{
1423 Q_UNUSED( section )
1424 if ( orientation == Qt::Horizontal && role == Qt::DisplayRole )
1425 {
1426 if ( section == 0 )
1427 return QVariant( tr( "Name" ) );
1428 else if ( section == 1 )
1429 return QVariant( tr( "Info" ) );
1430 }
1431 return QVariant();
1432}
1433
1434int QgsCptCityBrowserModel::rowCount( const QModelIndex &parent ) const
1435{
1436 //qDebug("rowCount: idx: (valid %d) %d %d", parent.isValid(), parent.row(), parent.column());
1437
1438 if ( !parent.isValid() )
1439 {
1440 // root item: its children are top level items
1441 return mRootItems.count(); // mRoot
1442 }
1443 else
1444 {
1445 // ordinary item: number of its children
1447 return item ? item->rowCount() : 0;
1448 }
1449}
1450
1451bool QgsCptCityBrowserModel::hasChildren( const QModelIndex &parent ) const
1452{
1453 if ( !parent.isValid() )
1454 return true; // root item: its children are top level items
1455
1457
1458 return item && item->hasChildren();
1459}
1460
1461int QgsCptCityBrowserModel::columnCount( const QModelIndex &parent ) const
1462{
1463 Q_UNUSED( parent )
1464 return 2;
1465}
1466
1467QModelIndex QgsCptCityBrowserModel::findPath( const QString &path )
1468{
1469 QModelIndex rootIndex; // starting from root
1470 bool foundParent = false, foundChild = true;
1471 QString itemPath;
1472
1473 QgsDebugMsgLevel( "path = " + path, 2 );
1474
1475 // special case if searching for first item "All Ramps", do not search into tree
1476 if ( path.isEmpty() )
1477 {
1478 for ( int i = 0; i < rowCount( rootIndex ); i++ )
1479 {
1480 QModelIndex idx = index( i, 0, rootIndex );
1481 QgsCptCityDataItem *item = dataItem( idx );
1482 if ( !item )
1483 return QModelIndex(); // an error occurred
1484
1485 itemPath = item->path();
1486
1487 if ( itemPath == path )
1488 {
1489 QgsDebugMsgLevel( "Arrived " + itemPath, 2 );
1490 return idx; // we have found the item we have been looking for
1491 }
1492 }
1493 }
1494
1495 while ( foundChild )
1496 {
1497 foundChild = false; // assume that the next child item will not be found
1498
1499 int i = 0;
1500 // if root skip first item "All Ramps"
1501 if ( itemPath.isEmpty() )
1502 i = 1;
1503 for ( ; i < rowCount( rootIndex ); i++ )
1504 {
1505 QModelIndex idx = index( i, 0, rootIndex );
1506 QgsCptCityDataItem *item = dataItem( idx );
1507 if ( !item )
1508 return QModelIndex(); // an error occurred
1509
1510 itemPath = item->path();
1511
1512 if ( itemPath == path )
1513 {
1514 QgsDebugMsgLevel( "Arrived " + itemPath, 2 );
1515 return idx; // we have found the item we have been looking for
1516 }
1517
1518 if ( ! itemPath.endsWith( '/' ) )
1519 itemPath += '/';
1520
1521 foundParent = false;
1522
1523 // QgsDebugMsgLevel( "path= " + path + " itemPath= " + itemPath, 2 );
1524
1525 // if we are using a selection collection, search for target in the mapping in this group
1526 if ( item->type() == QgsCptCityDataItem::Selection )
1527 {
1528 const QgsCptCitySelectionItem *selItem = qobject_cast<const QgsCptCitySelectionItem *>( item );
1529 if ( selItem )
1530 {
1531 const auto constSelectionsList = selItem->selectionsList();
1532 for ( QString childPath : constSelectionsList )
1533 {
1534 if ( childPath.endsWith( '/' ) )
1535 childPath.chop( 1 );
1536 // QgsDebugMsgLevel( "childPath= " + childPath, 2 );
1537 if ( path.startsWith( childPath ) )
1538 {
1539 foundParent = true;
1540 break;
1541 }
1542 }
1543 }
1544 }
1545 // search for target in parent directory
1546 else if ( path.startsWith( itemPath ) )
1547 {
1548 foundParent = true;
1549 }
1550
1551 if ( foundParent )
1552 {
1553 QgsDebugMsgLevel( "found parent " + path, 2 );
1554 // we have found a preceding item: stop searching on this level and go deeper
1555 foundChild = true;
1556 rootIndex = idx;
1557 if ( canFetchMore( rootIndex ) )
1558 fetchMore( rootIndex );
1559 break;
1560 }
1561 }
1562 }
1563
1564 return QModelIndex(); // not found
1565}
1566
1568{
1569 beginResetModel();
1571 addRootItems();
1572 endResetModel();
1573}
1574
1575/* Refresh dir path */
1576void QgsCptCityBrowserModel::refresh( const QString &path )
1577{
1578 const QModelIndex idx = findPath( path );
1579 if ( idx.isValid() )
1580 {
1581 QgsCptCityDataItem *item = dataItem( idx );
1582 if ( item )
1583 item->refresh();
1584 }
1585}
1586
1587QModelIndex QgsCptCityBrowserModel::index( int row, int column, const QModelIndex &parent ) const
1588{
1590 const QVector<QgsCptCityDataItem *> &items = p ? p->children() : mRootItems;
1591 QgsCptCityDataItem *item = items.value( row, nullptr );
1592 return item ? createIndex( row, column, item ) : QModelIndex();
1593}
1594
1595QModelIndex QgsCptCityBrowserModel::parent( const QModelIndex &index ) const
1596{
1598 if ( !item )
1599 return QModelIndex();
1600
1601 return findItem( item->parent() );
1602}
1603
1605{
1606 const QVector<QgsCptCityDataItem *> &items = parent ? parent->children() : mRootItems;
1607
1608 for ( int i = 0; i < items.size(); i++ )
1609 {
1610 if ( items[i] == item )
1611 return createIndex( i, 0, item );
1612
1613 QModelIndex childIndex = findItem( item, items[i] );
1614 if ( childIndex.isValid() )
1615 return childIndex;
1616 }
1617
1618 return QModelIndex();
1619}
1620
1621/* Refresh item */
1622void QgsCptCityBrowserModel::refresh( const QModelIndex &index )
1623{
1625 if ( !item )
1626 return;
1627
1628 QgsDebugMsgLevel( "Refresh " + item->path(), 2 );
1629 item->refresh();
1630}
1631
1633{
1634 QgsDebugMsgLevel( "parent mPath = " + parent->path(), 2 );
1635 const QModelIndex idx = findItem( parent );
1636 if ( !idx.isValid() )
1637 return;
1638 QgsDebugMsgLevel( QStringLiteral( "valid" ), 2 );
1639 beginInsertRows( idx, first, last );
1640 QgsDebugMsgLevel( QStringLiteral( "end" ), 2 );
1641}
1643{
1644 endInsertRows();
1645}
1647{
1648 QgsDebugMsgLevel( "parent mPath = " + parent->path(), 2 );
1649 const QModelIndex idx = findItem( parent );
1650 if ( !idx.isValid() )
1651 return;
1652 beginRemoveRows( idx, first, last );
1653}
1655{
1656 endRemoveRows();
1657}
1659{
1664}
1665
1666bool QgsCptCityBrowserModel::canFetchMore( const QModelIndex &parent ) const
1667{
1669 // fetch all items initially so we know which items have children
1670 // (nicer looking and less confusing)
1671
1672 if ( ! item )
1673 return false;
1674
1675 // except for "All Ramps" - this is populated when clicked on
1676 if ( item->type() == QgsCptCityDataItem::AllRamps )
1677 return false;
1678
1679 item->populate();
1680
1681 return ( ! item->isPopulated() );
1682}
1683
1684void QgsCptCityBrowserModel::fetchMore( const QModelIndex &parent )
1685{
1687 if ( item )
1688 {
1689 item->populate();
1690 QgsDebugMsgLevel( "path = " + item->path(), 2 );
1691 }
1692}
1693
1694
1695#if 0
1696QStringList QgsCptCityBrowserModel::mimeTypes() const
1697{
1698 QStringList types;
1699 // In theory the mime type convention is: application/x-vnd.<vendor>.<application>.<type>
1700 // but it seems a bit over formalized. Would be an application/x-qgis-uri better?
1701 types << "application/x-vnd.qgis.qgis.uri";
1702 return types;
1703}
1704
1705QMimeData *QgsCptCityBrowserModel::mimeData( const QModelIndexList &indexes ) const
1706{
1708 const auto constIndexes = indexes;
1709 for ( const QModelIndex &index : constIndexes )
1710 {
1711 if ( index.isValid() )
1712 {
1713 QgsCptCityDataItem *ptr = ( QgsCptCityDataItem * ) index.internalPointer();
1714 if ( ptr->type() != QgsCptCityDataItem::Layer ) continue;
1715 QgsLayerItem *layer = ( QgsLayerItem * ) ptr;
1716 lst.append( QgsMimeDataUtils::Uri( ayer ) );
1717 }
1718 }
1719 return QgsMimeDataUtils::encodeUriList( lst );
1720}
1721
1722bool QgsCptCityBrowserModel::dropMimeData( const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent )
1723{
1724 Q_UNUSED( row )
1725 Q_UNUSED( column )
1726
1727 QgsCptCityDataItem *destItem = dataItem( parent );
1728 if ( !destItem )
1729 {
1730 QgsDebugError( QStringLiteral( "DROP PROBLEM!" ) );
1731 return false;
1732 }
1733
1734 return destItem->handleDrop( data, action );
1735}
1736#endif
1737
1739{
1740 void *v = idx.internalPointer();
1741 QgsCptCityDataItem *d = reinterpret_cast<QgsCptCityDataItem *>( v );
1742 Q_ASSERT( !v || d );
1743 return d;
1744}
static QString pkgDataPath()
Returns the common root path of all application data directories.
An "All ramps item", which contains all items in a flat hierarchy.
QgsCptCityAllRampsItem(QgsCptCityDataItem *parent, const QString &name, const QVector< QgsCptCityDataItem * > &items)
QVector< QgsCptCityDataItem * > mItems
QVector< QgsCptCityDataItem * > createChildren() override
static void clearArchives()
static void initArchive(const QString &archiveName, const QString &archiveBaseDir)
QString descFileName(const QString &dirName) const
QString baseDir() const
static QString defaultBaseDir()
static QMap< QString, QString > copyingInfo(const QString &fileName)
QVector< QgsCptCityDataItem * > rootItems() const
QString archiveName() const
static QgsCptCityArchive * defaultArchive()
QString copyingFileName(const QString &dirName) const
static void initDefaultArchive()
QgsCptCityArchive(const QString &archiveName=DEFAULT_CPTCITY_ARCHIVE, const QString &baseDir=QString())
static QString findFileName(const QString &target, const QString &startDir, const QString &baseDir)
static QMap< QString, QString > description(const QString &fileName)
static void initArchives(bool loadAll=false)
static QMap< double, QPair< QColor, QColor > > gradientColorMap(const QString &fileName)
QVector< QgsCptCityDataItem * > selectionItems() const
int columnCount(const QModelIndex &parent=QModelIndex()) const override
void refresh(const QString &path)
void beginRemoveItems(QgsCptCityDataItem *parent, int first, int last)
QModelIndex findPath(const QString &path)
Returns index of a path.
QVariant data(const QModelIndex &index, int role=Qt::DisplayRole) const override
QModelIndex index(int row, int column, const QModelIndex &parent=QModelIndex()) const override
QgsCptCityDataItem * dataItem(const QModelIndex &idx) const
Returns a list of mime that can describe model indexes.
void connectItem(QgsCptCityDataItem *item)
QgsCptCityArchive * mArchive
QModelIndex parent(const QModelIndex &index) const override
Qt::ItemFlags flags(const QModelIndex &index) const override
QVariant headerData(int section, Qt::Orientation orientation, int role=Qt::DisplayRole) const override
QgsCptCityBrowserModel(QObject *parent=nullptr, QgsCptCityArchive *archive=QgsCptCityArchive::defaultArchive(), ViewType Type=Authors)
void beginInsertItems(QgsCptCityDataItem *parent, int first, int last)
void fetchMore(const QModelIndex &parent) override
int rowCount(const QModelIndex &parent=QModelIndex()) const override
bool hasChildren(const QModelIndex &parent=QModelIndex()) const override
bool canFetchMore(const QModelIndex &parent) const override
QModelIndex findItem(QgsCptCityDataItem *item, QgsCptCityDataItem *parent=nullptr) const
QVector< QgsCptCityDataItem * > mRootItems
A Collection: logical collection of subcollections and color ramps.
QgsCptCityCollectionItem(QgsCptCityDataItem *parent, const QString &name, const QString &path)
QVector< QgsCptCityDataItem * > childrenRamps(bool recursive)
Item that represents a layer that can be opened with one of the providers.
const QgsCptCityColorRamp & ramp() const
bool equal(const QgsCptCityDataItem *other) override
QgsCptCityColorRampItem(QgsCptCityDataItem *parent, const QString &name, const QString &path, const QString &variantName=QString(), bool initialize=false)
QgsCptCityColorRamp mRamp
QStringList variantList() const
QString fileName() const
static QString fileNameForVariant(const QString &schema, const QString &variant)
Returns the source file name for a CPT schema and variant.
void setVariantName(const QString &variantName)
bool hasMultiStops() const
QString variantName() const
Base class for all items in the model.
QgsCptCityDataItem * parent() const
QString toolTip() const
virtual void addChildItem(QgsCptCityDataItem *child, bool refresh=false)
void beginRemoveItems(QgsCptCityDataItem *parent, int first, int last)
QString info() const
virtual QVector< QgsCptCityDataItem * > createChildren()
virtual QgsCptCityDataItem * removeChildItem(QgsCptCityDataItem *child)
static int findItem(QVector< QgsCptCityDataItem * > items, QgsCptCityDataItem *item)
QVector< QgsCptCityDataItem * > mChildren
void setParent(QgsCptCityDataItem *parent)
QString name() const
virtual void deleteChildItem(QgsCptCityDataItem *child)
QString path() const
virtual bool equal(const QgsCptCityDataItem *other)
QgsCptCityDataItem(QgsCptCityDataItem::Type type, QgsCptCityDataItem *parent, const QString &name, const QString &path)
virtual QIcon icon()
QVector< QgsCptCityDataItem * > children() const
void beginInsertItems(QgsCptCityDataItem *parent, int first, int last)
virtual int leafCount() const
virtual bool handleDrop(const QMimeData *, Qt::DropAction)
A directory: contains subdirectories and color ramps.
QStringList dirEntries() const
static QgsCptCityDataItem * dataItem(QgsCptCityDataItem *parent, const QString &name, const QString &path)
QVector< QgsCptCityDataItem * > createChildren() override
QgsCptCityDirectoryItem(QgsCptCityDataItem *parent, const QString &name, const QString &path)
QMap< QString, QStringList > mRampsMap
bool equal(const QgsCptCityDataItem *other) override
QMap< QString, QStringList > rampsMap()
A selection: contains subdirectories and color ramps.
QVector< QgsCptCityDataItem * > createChildren() override
QgsCptCitySelectionItem(QgsCptCityDataItem *parent, const QString &name, const QString &path)
bool equal(const QgsCptCityDataItem *other) override
QStringList selectionsList() const
bool isDiscrete() const
Returns true if the gradient is using discrete interpolation, rather than smoothly interpolating betw...
int count() const override
Returns number of defined colors, or -1 if undefined.
Item that represents a layer that can be opened with one of the providers.
Definition: qgslayeritem.h:31
QList< QgsMimeDataUtils::Uri > UriList
static QMimeData * encodeUriList(const UriList &layers)
Encodes a URI list to a new QMimeData object.
This class is a composition of two QSettings instances:
Definition: qgssettings.h:64
QVariant value(const QString &key, const QVariant &defaultValue=QVariant(), Section section=NoSection) const
Returns the value for setting key.
static QColor parseColor(const QString &colorStr, bool strictEval=false)
Attempts to parse a string as a color using a variety of common formats, including hex codes,...
static QIcon colorRampPreviewIcon(QgsColorRamp *ramp, QSize size, int padding=0)
Returns an icon preview for a color ramp.
QMap< QString, QString > QgsStringMap
Definition: qgis.h:5737
QMap< QString, QMap< QString, QString > > CopyingInfoMap
QMap< QString, QgsCptCityArchive * > ArchiveRegistry
#define DEFAULT_CPTCITY_ARCHIVE
Q_GLOBAL_STATIC(QReadWriteLock, sDefinitionCacheLock)
#define QgsDebugMsgLevel(str, level)
Definition: qgslogger.h:39
#define QgsDebugError(str)
Definition: qgslogger.h:38