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