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