QGIS API Documentation 4.1.0-Master (376402f9aeb)
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"
28#include "qgssettingstree.h"
29#include "qgssymbollayerutils.h"
30
31#include <QApplication>
32#include <QDateTime>
33#include <QDir>
34#include <QDomDocument>
35#include <QDomElement>
36#include <QFileInfo>
37#include <QRegularExpression>
38#include <QString>
39#include <QStyle>
40#include <QVector>
41
42#include "moc_qgscptcityarchive.cpp"
43
44using namespace Qt::StringLiterals;
45
47 = new QgsSettingsEntryString( u"cptcity-base-dir"_s, QgsSettingsTree::sTreeColors, QString(), u"Base directory where CPT-City color ramp archives are stored. If empty, the built-in location is used."_s );
49 = new QgsSettingsEntryString( u"cptcity-archive-name"_s, QgsSettingsTree::sTreeColors, QStringLiteral( DEFAULT_CPTCITY_ARCHIVE ), u"Name of the CPT-City archive subdirectory used as the default source of color ramps."_s );
50
51typedef QMap< QString, QgsCptCityArchive * > ArchiveRegistry;
52typedef QMap< QString, QMap< QString, QString > > CopyingInfoMap;
53
54Q_GLOBAL_STATIC( QString, sDefaultArchiveName )
55Q_GLOBAL_STATIC( ArchiveRegistry, sArchiveRegistry )
56Q_GLOBAL_STATIC( CopyingInfoMap, sCopyingInfoMap )
57
59{
60 return *sArchiveRegistry();
61}
62
64 : mArchiveName( archiveName )
65 , mBaseDir( baseDir )
66{
67 QgsDebugMsgLevel( "archiveName = " + archiveName + " baseDir = " + baseDir, 2 );
68
69 // make Author items
70 QgsCptCityDirectoryItem *dirItem = nullptr;
71 const auto constEntryList = QDir( mBaseDir ).entryList( QDir::Dirs | QDir::NoDotAndDotDot, QDir::Name );
72 for ( const QString &path : constEntryList )
73 {
74 if ( path == "selections"_L1 )
75 continue;
76 QgsDebugMsgLevel( "path= " + path, 2 );
77 dirItem = new QgsCptCityDirectoryItem( nullptr, QFileInfo( path ).baseName(), path );
78 if ( dirItem->isValid() )
79 mRootItems << dirItem;
80 else
81 delete dirItem;
82 }
83
84 // make selection items
85 QgsCptCitySelectionItem *selItem = nullptr;
86 const QDir seldir( mBaseDir + '/' + "selections" );
87 QgsDebugMsgLevel( "populating selection from " + seldir.path(), 2 );
88 const QStringList fileList = seldir.entryList( QStringList() << u"*.xml"_s, QDir::Files );
89 for ( const QString &selfile : fileList )
90 {
91 QgsDebugMsgLevel( "file= " + seldir.path() + '/' + selfile, 2 );
92 selItem = new QgsCptCitySelectionItem( nullptr, QFileInfo( selfile ).baseName(), seldir.dirName() + '/' + selfile );
93 //TODO remove item if there are no children (e.g. esri in qgis-sel)
94 if ( selItem->isValid() )
95 mSelectionItems << selItem;
96 else
97 delete selItem;
98 }
99
100 // make "All Ramps items" (which will contain all ramps without hierarchy)
101 QgsCptCityAllRampsItem *allRampsItem = nullptr;
102 allRampsItem = new QgsCptCityAllRampsItem( nullptr, QObject::tr( "All Ramps" ), mRootItems );
103 mRootItems.prepend( allRampsItem );
104 allRampsItem = new QgsCptCityAllRampsItem( nullptr, QObject::tr( "All Ramps" ), mSelectionItems );
105 mSelectionItems.prepend( allRampsItem );
106}
107
109{
110 const auto constMRootItems = mRootItems;
111 for ( QgsCptCityDataItem *item : constMRootItems )
112 delete item;
113 const auto constMSelectionItems = mSelectionItems;
114 for ( QgsCptCityDataItem *item : constMSelectionItems )
115 delete item;
116 mRootItems.clear();
117 mSelectionItems.clear();
118}
119
121{
122 // if was set with setBaseDir, return that value
123 // else return global default
124 if ( !mBaseDir.isNull() )
125 return mBaseDir;
126 else
128}
129
131{
132 // search for matching archive in the registry
133 if ( archiveName.isNull() )
135 if ( QgsCptCityArchive *archive = sArchiveRegistry()->value( archiveName, nullptr ) )
136 return archive->baseDir();
137 else
138 return defaultBaseDir();
139}
140
142{
143 // use CptCity/baseDir setting if set, default is user dir
144 QString baseDir = settingsCptCityBaseDir->value();
145 if ( baseDir.isEmpty() )
146 baseDir = QgsApplication::pkgDataPath() + "/resources";
147 // sub-dir defaults to cpt-city
148 const QString archiveName = settingsCptCityArchiveName->value();
149
150 return baseDir + '/' + archiveName;
151}
152
153
154QString QgsCptCityArchive::findFileName( const QString &target, const QString &startDir, const QString &baseDir )
155{
156 // QgsDebugMsgLevel( "target= " + target + " startDir= " + startDir + " baseDir= " + baseDir, 2 );
157
158 if ( startDir.isEmpty() || !startDir.startsWith( baseDir ) )
159 return QString();
160
161 QDir dir = QDir( startDir );
162 //todo test when
163 while ( !dir.exists( target ) && dir.path() != baseDir )
164 {
165 if ( !dir.cdUp() )
166 break;
167 }
168 if ( !dir.exists( target ) )
169 return QString();
170 else
171 return dir.path() + '/' + target;
172}
173
174
175QString QgsCptCityArchive::copyingFileName( const QString &path ) const
176{
177 return QgsCptCityArchive::findFileName( u"COPYING.xml"_s, baseDir() + '/' + path, baseDir() );
178}
179
180QString QgsCptCityArchive::descFileName( const QString &path ) const
181{
182 return QgsCptCityArchive::findFileName( u"DESC.xml"_s, 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 *sDefaultArchiveName() = settingsCptCityArchiveName->value();
441 if ( sArchiveRegistry()->contains( *sDefaultArchiveName() ) )
442 return sArchiveRegistry()->value( *sDefaultArchiveName() );
443 else
444 return nullptr;
445}
446
447void QgsCptCityArchive::initArchive( const QString &archiveName, const QString &archiveBaseDir )
448{
449 QgsDebugMsgLevel( "archiveName = " + archiveName + " archiveBaseDir = " + archiveBaseDir, 2 );
450 QgsCptCityArchive *archive = new QgsCptCityArchive( archiveName, archiveBaseDir );
451 if ( sArchiveRegistry()->contains( archiveName ) )
452 delete ( *sArchiveRegistry() )[archiveName];
453 ( *sArchiveRegistry() )[archiveName] = archive;
454}
455
457{
458 // use CptCity/baseDir setting if set, default is user dir
459 QString baseDir = settingsCptCityBaseDir->value();
460 if ( baseDir.isEmpty() )
461 baseDir = QgsApplication::pkgDataPath() + "/resources";
462 // sub-dir defaults to
463 const QString defArchiveName = settingsCptCityArchiveName->value();
464
465 if ( !sArchiveRegistry()->contains( defArchiveName ) )
466 initArchive( defArchiveName, baseDir + '/' + defArchiveName );
467}
468
470{
471 QgsStringMap archivesMap;
472 QString baseDir, defArchiveName;
473
474 // use CptCity/baseDir setting if set, default is user dir
476 if ( baseDir.isEmpty() )
477 baseDir = QgsApplication::pkgDataPath() + "/resources";
478 // sub-dir defaults to
479 defArchiveName = settingsCptCityArchiveName->value();
480
481 QgsDebugMsgLevel( "baseDir= " + baseDir + " defArchiveName= " + defArchiveName, 2 );
482 if ( loadAll )
483 {
484 const QDir dir( baseDir );
485 const QStringList fileList = dir.entryList( QStringList() << u"cpt-city*"_s, QDir::Dirs );
486 for ( const QString &entry : fileList )
487 {
488 if ( QFile::exists( baseDir + '/' + entry + "/VERSION.xml" ) )
489 archivesMap[entry] = baseDir + '/' + entry;
490 }
491 }
492 else
493 {
494 archivesMap[defArchiveName] = baseDir + '/' + defArchiveName;
495 }
496
497 for ( QgsStringMap::iterator it = archivesMap.begin(); it != archivesMap.end(); ++it )
498 {
499 if ( QDir( it.value() ).exists() )
500 QgsCptCityArchive::initArchive( it.key(), it.value() );
501 else
502 {
503 QgsDebugError( u"not loading archive [%1] because dir %2 does not exist "_s.arg( it.key(), it.value() ) );
504 }
505 }
506 *sDefaultArchiveName() = defArchiveName;
507}
508
510{
511 qDeleteAll( *sArchiveRegistry() );
512 sArchiveRegistry()->clear();
513}
514
515
516// --------
517
519 // Do not pass parent to QObject, Qt would delete this when parent is deleted
520 : mType( type )
521 , mParent( parent )
522 , mName( name )
523 , mPath( path )
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( u"add child #%1 - %2 - %3"_s.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 && mChildren.at( i )->mName.localeAwareCompare( child->mName ) >= 0 )
592 break;
593 }
594 }
595 else
596 {
597 for ( i = 0; i < mChildren.size(); i++ )
598 {
599 if ( mChildren.at( i )->mName.localeAwareCompare( child->mName ) >= 0 )
600 break;
601 }
602 }
603
604 if ( refresh )
605 emit beginInsertItems( this, i, i );
606
607 mChildren.insert( i, child );
608
613
614 if ( refresh )
615 emit endInsertItems();
616}
618{
619 // QgsDebugMsgLevel( "mName = " + child->mName, 2 );
620 const int i = mChildren.indexOf( child );
621 Q_ASSERT( i >= 0 );
622 emit beginRemoveItems( this, i, i );
623 mChildren.remove( i );
624 delete child;
625 emit endRemoveItems();
626}
627
629{
630 // QgsDebugMsgLevel( "mName = " + child->mName, 2 );
631 const int i = mChildren.indexOf( child );
632 Q_ASSERT( i >= 0 );
633 emit beginRemoveItems( this, i, i );
634 mChildren.remove( i );
635 emit endRemoveItems();
640 child->setParent( nullptr );
641 return child;
642}
643
644int QgsCptCityDataItem::findItem( QVector<QgsCptCityDataItem *> items, QgsCptCityDataItem *item )
645{
646 for ( int i = 0; i < items.size(); i++ )
647 {
648 // QgsDebugMsgLevel( QString::number( i ) + " : " + items[i]->mPath + " x " + item->mPath, 2 );
649 if ( items[i]->equal( item ) )
650 return i;
651 }
652 return -1;
653}
654
656{
657 QgsDebugMsgLevel( "mPath = " + mPath, 2 );
658
659 QApplication::setOverrideCursor( Qt::WaitCursor );
660
661 const QVector<QgsCptCityDataItem *> items = createChildren();
662
663 // Remove no more present items
664 QVector<QgsCptCityDataItem *> remove;
665 const auto constMChildren = mChildren;
666 for ( QgsCptCityDataItem *child : constMChildren )
667 {
668 if ( findItem( items, child ) >= 0 )
669 continue;
670 remove.append( child );
671 }
672 const auto constRemove = remove;
673 for ( QgsCptCityDataItem *child : constRemove )
674 {
675 deleteChildItem( child );
676 }
677
678 // Add new items
679 const auto constItems = items;
680 for ( QgsCptCityDataItem *item : constItems )
681 {
682 // Is it present in children?
683 if ( findItem( mChildren, item ) >= 0 )
684 {
685 delete item;
686 continue;
687 }
688 addChildItem( item, true );
689 }
690
691 QApplication::restoreOverrideCursor();
692}
693
695{
696 return ( metaObject()->className() == other->metaObject()->className() && mPath == other->path() );
697}
698
699// ---------------------------------------------------------------------
700
701QgsCptCityColorRampItem::QgsCptCityColorRampItem( QgsCptCityDataItem *parent, const QString &name, const QString &path, const QString &variantName, bool initialize )
703 , mInitialized( false )
704 , mRamp( path, variantName, false )
705{
706 // QgsDebugMsgLevel( "name= " + name + " path= " + path, 2 );
707 mPopulated = true;
708 if ( initialize )
709 init();
710}
711
712QgsCptCityColorRampItem::QgsCptCityColorRampItem( QgsCptCityDataItem *parent, const QString &name, const QString &path, const QStringList &variantList, bool initialize )
714 , mInitialized( false )
715 , mRamp( path, variantList, QString(), false )
716{
717 // QgsDebugMsgLevel( "name= " + name + " path= " + path, 2 );
718 mPopulated = true;
719 if ( initialize )
720 init();
721}
722
723// TODO only load file when icon is requested...
725{
726 if ( mInitialized )
727 return;
728 mInitialized = true;
729
730 QgsDebugMsgLevel( "path = " + path(), 2 );
731
732 // make preview from variant if exists
733 QStringList variantList = mRamp.variantList();
734 if ( mRamp.variantName().isNull() && !variantList.isEmpty() )
735 mRamp.setVariantName( variantList[variantList.count() / 2] );
736
737 mRamp.loadFile();
738
739 // is this item valid? this might fail when there are variants, check
740 if ( !QFile::exists( mRamp.fileName() ) )
741 mValid = false;
742 else
743 mValid = true;
744
745 // load file and set info
746 if ( mRamp.count() > 0 )
747 {
748 if ( variantList.isEmpty() )
749 {
750 int count = mRamp.count();
751 if ( mRamp.isDiscrete() )
752 count--;
753 mInfo = QString::number( count ) + ' ' + tr( "colors" ) + " - ";
754 if ( mRamp.isDiscrete() )
755 mInfo += tr( "discrete" );
756 else
757 {
758 if ( !mRamp.hasMultiStops() )
759 mInfo += tr( "continuous" );
760 else
761 mInfo += tr( "continuous (multi)" );
762 }
763 mShortInfo = QFileInfo( mName ).fileName();
764 }
765 else
766 {
767 mInfo = QString::number( variantList.count() ) + ' ' + tr( "variants" );
768 // mShortInfo = QFileInfo( mName ).fileName() + " (" + QString::number( variantList.count() ) + ')';
769 mShortInfo = QFileInfo( mName ).fileName();
770 }
771 }
772 else
773 {
774 mInfo.clear();
775 }
776}
777
779{
780 //QgsDebugMsg ( mPath + " x " + other->mPath );
781 if ( type() != other->type() )
782 {
783 return false;
784 }
785 //const QgsCptCityColorRampItem *o = qobject_cast<const QgsCptCityColorRampItem *> ( other );
786 const QgsCptCityColorRampItem *o = qobject_cast<const QgsCptCityColorRampItem *>( other );
787 return o && mPath == o->mPath && mName == o->mName && ramp().variantName() == o->ramp().variantName();
788}
789
791{
792 return icon( QSize( 100, 15 ) );
793}
794
796{
797 const auto constMIcons = mIcons;
798 for ( const QIcon &icon : constMIcons )
799 {
800 if ( icon.availableSizes().contains( size ) )
801 return icon;
802 }
803
804 QIcon icon;
805
806 init();
807
808 if ( mValid && mRamp.count() > 0 )
809 {
811 }
812 else
813 {
814 QPixmap blankPixmap( size );
815 blankPixmap.fill( Qt::white );
816 icon = QIcon( blankPixmap );
817 mInfo.clear();
818 }
819
820 mIcons.append( icon );
821 return icon;
822}
823
824// ---------------------------------------------------------------------
828
833
834QVector< QgsCptCityDataItem * > QgsCptCityCollectionItem::childrenRamps( bool recursive )
835{
836 QVector< QgsCptCityDataItem * > rampItems;
837 QVector< QgsCptCityDataItem * > deleteItems;
838
839 populate();
840
841 // recursively add children
842 const auto constChildren = children();
843 for ( QgsCptCityDataItem *childItem : constChildren )
844 {
845 QgsCptCityCollectionItem *collectionItem = qobject_cast<QgsCptCityCollectionItem *>( childItem );
846 QgsCptCityColorRampItem *rampItem = qobject_cast<QgsCptCityColorRampItem *>( childItem );
847 QgsDebugMsgLevel( u"child path= %1 coll= %2 ramp = %3"_s.arg( childItem->path() ).arg( nullptr != collectionItem ).arg( nullptr != rampItem ), 2 );
848 if ( collectionItem && recursive )
849 {
850 collectionItem->populate();
851 rampItems << collectionItem->childrenRamps( true );
852 }
853 else if ( rampItem )
854 {
855 // init rampItem to get palette and icon, test if is valid after loading file
856 rampItem->init();
857 if ( rampItem->isValid() )
858 rampItems << rampItem;
859 else
860 deleteItems << rampItem;
861 }
862 else
863 {
864 QgsDebugError( "invalid item " + childItem->path() );
865 }
866 }
867
868 // delete invalid items - this is not efficient, but should only happens once
869 const auto constDeleteItems = deleteItems;
870 for ( QgsCptCityDataItem *deleteItem : constDeleteItems )
871 {
872 QgsDebugError( u"item %1 is invalid, will be deleted"_s.arg( deleteItem->path() ) );
873 const int i = mChildren.indexOf( deleteItem );
874 if ( i != -1 )
875 mChildren.remove( i );
876 delete deleteItem;
877 }
878
879 return rampItems;
880}
881
882//-----------------------------------------------------------------------
885{
887 mValid = QDir( QgsCptCityArchive::defaultBaseDir() + '/' + mPath ).exists();
888 if ( !mValid )
889 {
890 QgsDebugError( "created invalid dir item, path = " + QgsCptCityArchive::defaultBaseDir() + '/' + mPath );
891 }
892
893 // parse DESC.xml to get mInfo
894 mInfo.clear();
895 const QString fileName = QgsCptCityArchive::defaultBaseDir() + '/' + mPath + '/' + "DESC.xml";
896 const QgsStringMap descMap = QgsCptCityArchive::description( fileName );
897 if ( descMap.contains( u"name"_s ) )
898 mInfo = descMap.value( u"name"_s );
899
900 // populate();
901}
902
903QVector<QgsCptCityDataItem *> QgsCptCityDirectoryItem::createChildren()
904{
905 if ( !mValid )
906 return QVector<QgsCptCityDataItem *>();
907
908 QVector<QgsCptCityDataItem *> children;
909
910 // add children schemes
911 QMapIterator< QString, QStringList> it( rampsMap() );
912 while ( it.hasNext() )
913 {
914 it.next();
915 // QgsDebugMsgLevel( "schemeName = " + it.key(), 2 );
916 QgsCptCityDataItem *item = new QgsCptCityColorRampItem( this, it.key(), it.key(), it.value() );
917 if ( item->isValid() )
918 children << item;
919 else
920 delete item;
921 }
922
923 // add children dirs
924 const auto constDirEntries = dirEntries();
925 for ( const QString &childPath : constDirEntries )
926 {
927 QgsCptCityDataItem *childItem = QgsCptCityDirectoryItem::dataItem( this, childPath, mPath + '/' + childPath );
928 if ( childItem )
929 children << childItem;
930 }
931
932 QgsDebugMsgLevel( u"name= %1 path= %2 found %3 children"_s.arg( mName, mPath ).arg( children.count() ), 2 );
933
934 return children;
935}
936
937QMap< QString, QStringList > QgsCptCityDirectoryItem::rampsMap()
938{
939 if ( !mRampsMap.isEmpty() )
940 return mRampsMap;
941
942 QString curName, prevName, curVariant, curSep, schemeName;
943 QStringList listVariant;
944 QStringList schemeNamesAll, schemeNames;
945 bool prevAdd, curAdd;
946
947 const QDir dir( QgsCptCityArchive::defaultBaseDir() + '/' + mPath );
948 schemeNamesAll = dir.entryList( QStringList( u"*.svg"_s ), QDir::Files, QDir::Name );
949
950 // TODO detect if there are duplicate names with different variant counts, combine in 1
951 for ( int i = 0; i < schemeNamesAll.count(); i++ )
952 {
953 // schemeName = QFileInfo( schemeNamesAll[i] ).baseName();
954 schemeName = schemeNamesAll[i];
955 schemeName.chop( 4 );
956 // QgsDebugMsgLevel("scheme = "+schemeName, 2);
957 curName = schemeName;
958 curVariant.clear();
959
960 // find if name ends with 1-3 digit number
961 // TODO need to detect if ends with b/c also
962 if ( schemeName.length() > 1 && schemeName.endsWith( 'a' ) && !listVariant.isEmpty() && ( ( prevName + listVariant.last() + 'a' ) == curName ) )
963 {
964 curName = prevName;
965 curVariant = listVariant.last() + 'a';
966 }
967 else
968 {
969 const thread_local QRegularExpression rxVariant( "^(.*[^\\d])(\\d{1,3})$" );
970 const QRegularExpressionMatch match = rxVariant.match( schemeName );
971 if ( match.hasMatch() )
972 {
973 curName = match.captured( 1 );
974 curVariant = match.captured( 2 );
975 }
976 }
977
978 curSep = curName.right( 1 );
979 if ( curSep == "-"_L1 || curSep == "_"_L1 )
980 {
981 curName.chop( 1 );
982 curVariant = curSep + curVariant;
983 }
984
985 if ( prevName.isEmpty() )
986 prevName = curName;
987
988 // add element, unless it is empty, or a variant of last element
989 prevAdd = false;
990 curAdd = false;
991 if ( curName.isEmpty() )
992 curName = u"__empty__"_s;
993 // if current is a variant of last, don't add previous and append current variant
994 if ( curName == prevName )
995 {
996 // add current element if it is the last one in the archive
997 if ( i == schemeNamesAll.count() - 1 )
998 prevAdd = true;
999 listVariant << curVariant;
1000 }
1001 else
1002 {
1003 if ( !prevName.isEmpty() )
1004 {
1005 prevAdd = true;
1006 }
1007 // add current element if it is the last one in the archive
1008 if ( i == schemeNamesAll.count() - 1 )
1009 curAdd = true;
1010 }
1011
1012 // QgsDebugMsgLevel(QString("prevAdd=%1 curAdd=%2 prevName=%3 curName=%4 count=%5").arg(prevAdd).arg(curAdd).arg(prevName).arg(curName).arg(listVariant.count()), 2);
1013
1014 if ( prevAdd )
1015 {
1016 // depending on number of variants, make one or more items
1017 if ( listVariant.isEmpty() )
1018 {
1019 // set num colors=-1 to parse file on request only
1020 // mSchemeNumColors[ prevName ] = -1;
1021 schemeNames << prevName;
1022 mRampsMap[mPath + '/' + prevName] = QStringList();
1023 }
1024 else if ( listVariant.count() <= 3 )
1025 {
1026 // for 1-2 items, create independent items
1027 for ( int j = 0; j < listVariant.count(); j++ )
1028 {
1029 // mSchemeNumColors[ prevName + listVariant[j] ] = -1;
1030 schemeNames << prevName + listVariant[j];
1031 mRampsMap[mPath + '/' + prevName + listVariant[j]] = QStringList();
1032 }
1033 }
1034 else
1035 {
1036 // mSchemeVariants[ path + '/' + prevName ] = listVariant;
1037 mRampsMap[mPath + '/' + prevName] = listVariant;
1038 schemeNames << prevName;
1039 }
1040 listVariant.clear();
1041 }
1042 if ( curAdd )
1043 {
1044 if ( !curVariant.isEmpty() )
1045 curName += curVariant;
1046 schemeNames << curName;
1047 mRampsMap[mPath + '/' + curName] = QStringList();
1048 }
1049 // save current to compare next
1050 if ( prevAdd || curAdd )
1051 {
1052 prevName = curName;
1053 if ( !curVariant.isEmpty() )
1054 listVariant << curVariant;
1055 }
1056 }
1057#if 0
1058 //TODO what to do with other vars? e.g. schemeNames
1059 // add schemes to archive
1060 mSchemeMap[ path ] = schemeNames;
1061 schemeCount += schemeName.count();
1062 schemeNames.clear();
1063 listVariant.clear();
1064 prevName = "";
1065#endif
1066 return mRampsMap;
1067}
1068
1070{
1071 return QDir( QgsCptCityArchive::defaultBaseDir() + '/' + mPath ).entryList( QDir::Dirs | QDir::NoDotAndDotDot, QDir::Name );
1072}
1073
1075{
1076 //QgsDebugMsg ( mPath + " x " + other->mPath );
1077 if ( type() != other->type() )
1078 {
1079 return false;
1080 }
1081 return ( path() == other->path() );
1082}
1083
1085{
1086 QgsDebugMsgLevel( "name= " + name + " path= " + path, 2 );
1087
1088 // first create item with constructor
1090 if ( dirItem && !dirItem->isValid() )
1091 {
1092 delete dirItem;
1093 return nullptr;
1094 }
1095 if ( !dirItem )
1096 return nullptr;
1097
1098 // fetch sub-dirs and ramps to know what to do with this item
1099 const QStringList dirEntries = dirItem->dirEntries();
1100 QMap< QString, QStringList > rampsMap = dirItem->rampsMap();
1101
1102 QgsDebugMsgLevel( u"item has %1 dirs and %2 ramps"_s.arg( dirEntries.count() ).arg( rampsMap.count() ), 2 );
1103
1104 // return item if has at least one subdir
1105 if ( !dirEntries.isEmpty() )
1106 return dirItem;
1107
1108 // if 0 ramps, delete item
1109 if ( rampsMap.isEmpty() )
1110 {
1111 delete dirItem;
1112 return nullptr;
1113 }
1114 // if 1 ramp, return this child's item
1115 // so we don't have a directory with just 1 item (with many variants possibly)
1116 else if ( rampsMap.count() == 1 )
1117 {
1118 delete dirItem;
1119 QgsCptCityColorRampItem *rampItem = new QgsCptCityColorRampItem( parent, rampsMap.begin().key(), rampsMap.begin().key(), rampsMap.begin().value() );
1120 if ( !rampItem->isValid() )
1121 {
1122 delete rampItem;
1123 return nullptr;
1124 }
1125 return rampItem;
1126 }
1127 return dirItem;
1128}
1129
1130
1131//-----------------------------------------------------------------------
1134{
1135 mType = Selection;
1136 mValid = !path.isNull();
1137 if ( mValid )
1138 parseXml();
1139}
1140
1141QVector<QgsCptCityDataItem *> QgsCptCitySelectionItem::createChildren()
1142{
1143 if ( !mValid )
1144 return QVector<QgsCptCityDataItem *>();
1145
1146 QgsCptCityDataItem *item = nullptr;
1147 QVector<QgsCptCityDataItem *> children;
1148
1149 QgsDebugMsgLevel( "name= " + mName + " path= " + mPath, 2 );
1150
1151 // add children archives
1152 for ( QString childPath : std::as_const( mSelectionsList ) )
1153 {
1154 QgsDebugMsgLevel( "childPath = " + childPath + " name= " + QFileInfo( childPath ).baseName(), 2 );
1155 if ( childPath.endsWith( '/' ) )
1156 {
1157 childPath.chop( 1 );
1158 QgsCptCityDataItem *childItem = QgsCptCityDirectoryItem::dataItem( this, childPath, childPath );
1159 if ( childItem )
1160 {
1161 if ( childItem->isValid() )
1162 children << childItem;
1163 else
1164 delete childItem;
1165 }
1166 }
1167 else
1168 {
1169 const QString fileName = QgsCptCityColorRamp::fileNameForVariant( childPath, QString() );
1170 if ( !QFile::exists( fileName ) )
1171 {
1172 continue;
1173 }
1174
1175 item = new QgsCptCityColorRampItem( this, childPath, childPath, QString(), true );
1176 if ( item->isValid() )
1177 children << item;
1178 else
1179 delete item;
1180 }
1181 }
1182
1183 QgsDebugMsgLevel( u"path= %1 inserted %2 children"_s.arg( mPath ).arg( children.count() ), 2 );
1184
1185 return children;
1186}
1187
1189{
1190 const QString filename = QgsCptCityArchive::defaultBaseDir() + '/' + mPath;
1191
1192 QgsDebugMsgLevel( "reading file " + filename, 2 );
1193
1194 QFile f( filename );
1195 if ( !f.open( QFile::ReadOnly ) )
1196 {
1197 QgsDebugError( filename + " does not exist" );
1198 return;
1199 }
1200
1201 // parse the document
1202 QString errMsg;
1203 QDomDocument doc( u"selection"_s );
1204 if ( !doc.setContent( &f, &errMsg ) )
1205 {
1206 f.close();
1207 QgsDebugError( "Couldn't parse file " + filename + " : " + errMsg );
1208 return;
1209 }
1210 f.close();
1211
1212 // read description
1213 const QDomElement docElem = doc.documentElement();
1214 if ( docElem.tagName() != "selection"_L1 )
1215 {
1216 QgsDebugError( "Incorrect root tag: " + docElem.tagName() );
1217 return;
1218 }
1219 QDomElement e = docElem.firstChildElement( u"name"_s );
1220 if ( !e.isNull() && !e.text().isNull() )
1221 mName = e.text();
1222 mInfo = docElem.firstChildElement( u"synopsis"_s ).text().simplified();
1223
1224 // get archives
1225 const QDomElement collectsElem = docElem.firstChildElement( u"seealsocollects"_s );
1226 e = collectsElem.firstChildElement( u"collect"_s );
1227 while ( !e.isNull() )
1228 {
1229 if ( !e.attribute( u"dir"_s ).isNull() )
1230 {
1231 // TODO parse description and use that, instead of default archive name
1232 const QString dir = e.attribute( u"dir"_s ) + '/';
1233 if ( QFile::exists( QgsCptCityArchive::defaultBaseDir() + '/' + dir ) )
1234 {
1235 mSelectionsList << dir;
1236 }
1237 }
1238 e = e.nextSiblingElement();
1239 }
1240 // get individual gradients
1241 const QDomElement gradientsElem = docElem.firstChildElement( u"gradients"_s );
1242 e = gradientsElem.firstChildElement( u"gradient"_s );
1243 while ( !e.isNull() )
1244 {
1245 if ( !e.attribute( u"dir"_s ).isNull() )
1246 {
1247 // QgsDebugMsgLevel( "add " + e.attribute( "dir" ) + '/' + e.attribute( "file" ) + " to " + selname, 2 );
1248 // TODO parse description and save elsewhere
1249 const QString dir = e.attribute( u"dir"_s );
1250 if ( QFile::exists( QgsCptCityArchive::defaultBaseDir() + '/' + dir ) )
1251 {
1252 mSelectionsList << dir + '/' + e.attribute( u"file"_s );
1253 }
1254 }
1255 e = e.nextSiblingElement();
1256 }
1257}
1258
1260{
1261 //QgsDebugMsgLevel( mPath + " x " + other->mPath, 2 );
1262 if ( type() != other->type() )
1263 {
1264 return false;
1265 }
1266 return ( path() == other->path() );
1267}
1268
1269//-----------------------------------------------------------------------
1270QgsCptCityAllRampsItem::QgsCptCityAllRampsItem( QgsCptCityDataItem *parent, const QString &name, const QVector<QgsCptCityDataItem *> &items )
1271 : QgsCptCityCollectionItem( parent, name, QString() )
1272 , mItems( items )
1273{
1274 mType = AllRamps;
1275 mValid = true;
1276 // populate();
1277}
1278
1279QVector<QgsCptCityDataItem *> QgsCptCityAllRampsItem::createChildren()
1280{
1281 if ( !mValid )
1282 return QVector<QgsCptCityDataItem *>();
1283
1284 QVector<QgsCptCityDataItem *> children;
1285
1286 // add children ramps of each item
1287 const auto constMItems = mItems;
1288 for ( QgsCptCityDataItem *item : constMItems )
1289 {
1290 QgsCptCityCollectionItem *colItem = qobject_cast< QgsCptCityCollectionItem * >( item );
1291 if ( colItem )
1292 children += colItem->childrenRamps( true );
1293 }
1294
1295 return children;
1296}
1297
1298//-----------------------------------------------------------------------
1299
1301 : QAbstractItemModel( parent )
1302 , mArchive( archive )
1303 , mViewType( viewType )
1304{
1305 Q_ASSERT( mArchive );
1306 QgsDebugMsgLevel( "archiveName = "_L1 + archive->archiveName() + " viewType=" + QString::number( static_cast< int >( viewType ) ), 2 );
1307 // keep iconsize for now, but not effectively used
1308 mIconSize = QSize( 100, 15 );
1309 addRootItems();
1310}
1311
1316
1318{
1319 if ( mViewType == Authors )
1320 {
1321 mRootItems = mArchive->rootItems();
1322 }
1323 else if ( mViewType == Selections )
1324 {
1325 mRootItems = mArchive->selectionItems();
1326 }
1327 QgsDebugMsgLevel( u"added %1 root items"_s.arg( mRootItems.size() ), 2 );
1328}
1329
1334
1335Qt::ItemFlags QgsCptCityBrowserModel::flags( const QModelIndex &index ) const
1336{
1337 if ( !index.isValid() )
1338 return Qt::ItemFlags();
1339
1340 Qt::ItemFlags flags = Qt::ItemIsEnabled | Qt::ItemIsSelectable;
1341
1342 return flags;
1343}
1344
1345QVariant QgsCptCityBrowserModel::data( const QModelIndex &index, int role ) const
1346{
1347 if ( !index.isValid() )
1348 return QVariant();
1349
1351
1352 if ( !item )
1353 {
1354 return QVariant();
1355 }
1356 else if ( role == Qt::DisplayRole )
1357 {
1358 if ( index.column() == 0 )
1359 return item->name();
1360 if ( index.column() == 1 )
1361 {
1362 return item->info();
1363 }
1364 }
1365 else if ( role == Qt::ToolTipRole )
1366 {
1367 if ( item->type() == QgsCptCityDataItem::ColorRamp && mViewType == List )
1368 return QString( item->path() + '\n' + item->info() );
1369 return item->toolTip();
1370 }
1371 else if ( role == Qt::DecorationRole && index.column() == 1 && item->type() == QgsCptCityDataItem::ColorRamp )
1372 {
1373 // keep iconsize for now, but not effectively used
1374 return item->icon( mIconSize );
1375 }
1376 else if ( role == Qt::FontRole && qobject_cast< QgsCptCityCollectionItem * >( item ) )
1377 {
1378 // collectionitems are larger and bold
1379 QFont font;
1380 font.setPointSize( 11 ); //FIXME why is the font so small?
1381 font.setBold( true );
1382 return font;
1383 }
1384 else
1385 {
1386 // unsupported role
1387 return QVariant();
1388 }
1389 return QVariant();
1390}
1391
1392QVariant QgsCptCityBrowserModel::headerData( int section, Qt::Orientation orientation, int role ) const
1393{
1394 Q_UNUSED( section )
1395 if ( orientation == Qt::Horizontal && role == Qt::DisplayRole )
1396 {
1397 if ( section == 0 )
1398 return QVariant( tr( "Name" ) );
1399 else if ( section == 1 )
1400 return QVariant( tr( "Info" ) );
1401 }
1402 return QVariant();
1403}
1404
1405int QgsCptCityBrowserModel::rowCount( const QModelIndex &parent ) const
1406{
1407 //qDebug("rowCount: idx: (valid %d) %d %d", parent.isValid(), parent.row(), parent.column());
1408
1409 if ( !parent.isValid() )
1410 {
1411 // root item: its children are top level items
1412 return mRootItems.count(); // mRoot
1413 }
1414 else
1415 {
1416 // ordinary item: number of its children
1418 return item ? item->rowCount() : 0;
1419 }
1420}
1421
1422bool QgsCptCityBrowserModel::hasChildren( const QModelIndex &parent ) const
1423{
1424 if ( !parent.isValid() )
1425 return true; // root item: its children are top level items
1426
1428
1429 return item && item->hasChildren();
1430}
1431
1432int QgsCptCityBrowserModel::columnCount( const QModelIndex &parent ) const
1433{
1434 Q_UNUSED( parent )
1435 return 2;
1436}
1437
1438QModelIndex QgsCptCityBrowserModel::findPath( const QString &path )
1439{
1440 QModelIndex rootIndex; // starting from root
1441 bool foundParent = false, foundChild = true;
1442 QString itemPath;
1443
1444 QgsDebugMsgLevel( "path = " + path, 2 );
1445
1446 // special case if searching for first item "All Ramps", do not search into tree
1447 if ( path.isEmpty() )
1448 {
1449 for ( int i = 0; i < rowCount( rootIndex ); i++ )
1450 {
1451 QModelIndex idx = index( i, 0, rootIndex );
1452 QgsCptCityDataItem *item = dataItem( idx );
1453 if ( !item )
1454 return QModelIndex(); // an error occurred
1455
1456 itemPath = item->path();
1457
1458 if ( itemPath == path )
1459 {
1460 QgsDebugMsgLevel( "Arrived " + itemPath, 2 );
1461 return idx; // we have found the item we have been looking for
1462 }
1463 }
1464 }
1465
1466 while ( foundChild )
1467 {
1468 foundChild = false; // assume that the next child item will not be found
1469
1470 int i = 0;
1471 // if root skip first item "All Ramps"
1472 if ( itemPath.isEmpty() )
1473 i = 1;
1474 for ( ; i < rowCount( rootIndex ); i++ )
1475 {
1476 QModelIndex idx = index( i, 0, rootIndex );
1477 QgsCptCityDataItem *item = dataItem( idx );
1478 if ( !item )
1479 return QModelIndex(); // an error occurred
1480
1481 itemPath = item->path();
1482
1483 if ( itemPath == path )
1484 {
1485 QgsDebugMsgLevel( "Arrived " + itemPath, 2 );
1486 return idx; // we have found the item we have been looking for
1487 }
1488
1489 if ( !itemPath.endsWith( '/' ) )
1490 itemPath += '/';
1491
1492 foundParent = false;
1493
1494 // QgsDebugMsgLevel( "path= " + path + " itemPath= " + itemPath, 2 );
1495
1496 // if we are using a selection collection, search for target in the mapping in this group
1497 if ( item->type() == QgsCptCityDataItem::Selection )
1498 {
1499 const QgsCptCitySelectionItem *selItem = qobject_cast<const QgsCptCitySelectionItem *>( item );
1500 if ( selItem )
1501 {
1502 const auto constSelectionsList = selItem->selectionsList();
1503 for ( QString childPath : constSelectionsList )
1504 {
1505 if ( childPath.endsWith( '/' ) )
1506 childPath.chop( 1 );
1507 // QgsDebugMsgLevel( "childPath= " + childPath, 2 );
1508 if ( path.startsWith( childPath ) )
1509 {
1510 foundParent = true;
1511 break;
1512 }
1513 }
1514 }
1515 }
1516 // search for target in parent directory
1517 else if ( path.startsWith( itemPath ) )
1518 {
1519 foundParent = true;
1520 }
1521
1522 if ( foundParent )
1523 {
1524 QgsDebugMsgLevel( "found parent " + path, 2 );
1525 // we have found a preceding item: stop searching on this level and go deeper
1526 foundChild = true;
1527 rootIndex = idx;
1528 if ( canFetchMore( rootIndex ) )
1529 fetchMore( rootIndex );
1530 break;
1531 }
1532 }
1533 }
1534
1535 return QModelIndex(); // not found
1536}
1537
1539{
1540 beginResetModel();
1542 addRootItems();
1543 endResetModel();
1544}
1545
1546/* Refresh dir path */
1547void QgsCptCityBrowserModel::refresh( const QString &path )
1548{
1549 const QModelIndex idx = findPath( path );
1550 if ( idx.isValid() )
1551 {
1552 QgsCptCityDataItem *item = dataItem( idx );
1553 if ( item )
1554 item->refresh();
1555 }
1556}
1557
1558QModelIndex QgsCptCityBrowserModel::index( int row, int column, const QModelIndex &parent ) const
1559{
1561 const QVector<QgsCptCityDataItem *> &items = p ? p->children() : mRootItems;
1562 QgsCptCityDataItem *item = items.value( row, nullptr );
1563 return item ? createIndex( row, column, item ) : QModelIndex();
1564}
1565
1566QModelIndex QgsCptCityBrowserModel::parent( const QModelIndex &index ) const
1567{
1569 if ( !item )
1570 return QModelIndex();
1571
1572 return findItem( item->parent() );
1573}
1574
1576{
1577 const QVector<QgsCptCityDataItem *> &items = parent ? parent->children() : mRootItems;
1578
1579 for ( int i = 0; i < items.size(); i++ )
1580 {
1581 if ( items[i] == item )
1582 return createIndex( i, 0, item );
1583
1584 QModelIndex childIndex = findItem( item, items[i] );
1585 if ( childIndex.isValid() )
1586 return childIndex;
1587 }
1588
1589 return QModelIndex();
1590}
1591
1592/* Refresh item */
1593void QgsCptCityBrowserModel::refresh( const QModelIndex &index )
1594{
1596 if ( !item )
1597 return;
1598
1599 QgsDebugMsgLevel( "Refresh " + item->path(), 2 );
1600 item->refresh();
1601}
1602
1604{
1605 QgsDebugMsgLevel( "parent mPath = " + parent->path(), 2 );
1606 const QModelIndex idx = findItem( parent );
1607 if ( !idx.isValid() )
1608 return;
1609 QgsDebugMsgLevel( u"valid"_s, 2 );
1610 beginInsertRows( idx, first, last );
1611 QgsDebugMsgLevel( u"end"_s, 2 );
1612}
1614{
1615 endInsertRows();
1616}
1618{
1619 QgsDebugMsgLevel( "parent mPath = " + parent->path(), 2 );
1620 const QModelIndex idx = findItem( parent );
1621 if ( !idx.isValid() )
1622 return;
1623 beginRemoveRows( idx, first, last );
1624}
1626{
1627 endRemoveRows();
1628}
1636
1637bool QgsCptCityBrowserModel::canFetchMore( const QModelIndex &parent ) const
1638{
1640 // fetch all items initially so we know which items have children
1641 // (nicer looking and less confusing)
1642
1643 if ( !item )
1644 return false;
1645
1646 // except for "All Ramps" - this is populated when clicked on
1647 if ( item->type() == QgsCptCityDataItem::AllRamps )
1648 return false;
1649
1650 item->populate();
1651
1652 return ( !item->isPopulated() );
1653}
1654
1656{
1658 if ( item )
1659 {
1660 item->populate();
1661 QgsDebugMsgLevel( "path = " + item->path(), 2 );
1662 }
1663}
1664
1666{
1667 void *v = idx.internalPointer();
1668 QgsCptCityDataItem *d = reinterpret_cast<QgsCptCityDataItem *>( v );
1669 Q_ASSERT( !v || d );
1670 return d;
1671}
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)
static const QgsSettingsEntryString * settingsCptCityArchiveName
QString archiveName() const
static QgsCptCityArchive * defaultArchive()
QString copyingFileName(const QString &dirName) const
bool isEmpty() const
Returns true if archive is empty.
static const QgsSettingsEntryString * settingsCptCityBaseDir
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
A string settings entry.
static QgsSettingsTreeNode * sTreeColors
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:7649
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