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