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