QGIS API Documentation 3.99.0-Master (2fe06baccd8)
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 , mName( name )
523 , mPath( path )
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 )
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 )
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// ---------------------------------------------------------------------
839
844
845QVector< QgsCptCityDataItem * > QgsCptCityCollectionItem::childrenRamps( bool recursive )
846{
847 QVector< QgsCptCityDataItem * > rampItems;
848 QVector< QgsCptCityDataItem * > deleteItems;
849
850 populate();
851
852 // recursively add children
853 const auto constChildren = children();
854 for ( QgsCptCityDataItem *childItem : constChildren )
855 {
856 QgsCptCityCollectionItem *collectionItem = qobject_cast<QgsCptCityCollectionItem *>( childItem );
857 QgsCptCityColorRampItem *rampItem = qobject_cast<QgsCptCityColorRampItem *>( childItem );
858 QgsDebugMsgLevel( QStringLiteral( "child path= %1 coll= %2 ramp = %3" ).arg( childItem->path() ).arg( nullptr != collectionItem ).arg( nullptr != rampItem ), 2 );
859 if ( collectionItem && recursive )
860 {
861 collectionItem->populate();
862 rampItems << collectionItem->childrenRamps( true );
863 }
864 else if ( rampItem )
865 {
866 // init rampItem to get palette and icon, test if is valid after loading file
867 rampItem->init();
868 if ( rampItem->isValid() )
869 rampItems << rampItem;
870 else
871 deleteItems << rampItem;
872 }
873 else
874 {
875 QgsDebugError( "invalid item " + childItem->path() );
876 }
877 }
878
879 // delete invalid items - this is not efficient, but should only happens once
880 const auto constDeleteItems = deleteItems;
881 for ( QgsCptCityDataItem *deleteItem : constDeleteItems )
882 {
883 QgsDebugError( QStringLiteral( "item %1 is invalid, will be deleted" ).arg( deleteItem->path() ) );
884 const int i = mChildren.indexOf( deleteItem );
885 if ( i != -1 )
886 mChildren.remove( i );
887 delete deleteItem;
888 }
889
890 return rampItems;
891}
892
893//-----------------------------------------------------------------------
895 const QString &name, const QString &path )
897{
899 mValid = QDir( QgsCptCityArchive::defaultBaseDir() + '/' + mPath ).exists();
900 if ( ! mValid )
901 {
902 QgsDebugError( "created invalid dir item, path = " + QgsCptCityArchive::defaultBaseDir()
903 + '/' + mPath );
904 }
905
906 // parse DESC.xml to get mInfo
907 mInfo.clear();
908 const QString fileName = QgsCptCityArchive::defaultBaseDir() + '/' +
909 mPath + '/' + "DESC.xml";
910 const QgsStringMap descMap = QgsCptCityArchive::description( fileName );
911 if ( descMap.contains( QStringLiteral( "name" ) ) )
912 mInfo = descMap.value( QStringLiteral( "name" ) );
913
914 // populate();
915}
916
917QVector<QgsCptCityDataItem *> QgsCptCityDirectoryItem::createChildren()
918{
919 if ( ! mValid )
920 return QVector<QgsCptCityDataItem *>();
921
922 QVector<QgsCptCityDataItem *> children;
923
924 // add children schemes
925 QMapIterator< QString, QStringList> it( rampsMap() );
926 while ( it.hasNext() )
927 {
928 it.next();
929 // QgsDebugMsgLevel( "schemeName = " + it.key(), 2 );
930 QgsCptCityDataItem *item =
931 new QgsCptCityColorRampItem( this, it.key(), it.key(), it.value() );
932 if ( item->isValid() )
933 children << item;
934 else
935 delete item;
936 }
937
938 // add children dirs
939 const auto constDirEntries = dirEntries();
940 for ( const QString &childPath : constDirEntries )
941 {
942 QgsCptCityDataItem *childItem =
943 QgsCptCityDirectoryItem::dataItem( this, childPath, mPath + '/' + childPath );
944 if ( childItem )
945 children << childItem;
946 }
947
948 QgsDebugMsgLevel( QStringLiteral( "name= %1 path= %2 found %3 children" ).arg( mName, mPath ).arg( children.count() ), 2 );
949
950 return children;
951}
952
953QMap< QString, QStringList > QgsCptCityDirectoryItem::rampsMap()
954{
955 if ( ! mRampsMap.isEmpty() )
956 return mRampsMap;
957
958 QString curName, prevName, curVariant, curSep, schemeName;
959 QStringList listVariant;
960 QStringList schemeNamesAll, schemeNames;
961 bool prevAdd, curAdd;
962
963 const QDir dir( QgsCptCityArchive::defaultBaseDir() + '/' + mPath );
964 schemeNamesAll = dir.entryList( QStringList( QStringLiteral( "*.svg" ) ), QDir::Files, QDir::Name );
965
966 // TODO detect if there are duplicate names with different variant counts, combine in 1
967 for ( int i = 0; i < schemeNamesAll.count(); i++ )
968 {
969 // schemeName = QFileInfo( schemeNamesAll[i] ).baseName();
970 schemeName = schemeNamesAll[i];
971 schemeName.chop( 4 );
972 // QgsDebugMsgLevel("scheme = "+schemeName, 2);
973 curName = schemeName;
974 curVariant.clear();
975
976 // find if name ends with 1-3 digit number
977 // TODO need to detect if ends with b/c also
978 if ( schemeName.length() > 1 && schemeName.endsWith( 'a' ) && ! listVariant.isEmpty() &&
979 ( ( prevName + listVariant.last() + 'a' ) == curName ) )
980 {
981 curName = prevName;
982 curVariant = listVariant.last() + 'a';
983 }
984 else
985 {
986 const thread_local QRegularExpression rxVariant( "^(.*[^\\d])(\\d{1,3})$" );
987 const QRegularExpressionMatch match = rxVariant.match( schemeName );
988 if ( match.hasMatch() )
989 {
990 curName = match.captured( 1 );
991 curVariant = match.captured( 2 );
992 }
993 }
994
995 curSep = curName.right( 1 );
996 if ( curSep == QLatin1String( "-" ) || curSep == QLatin1String( "_" ) )
997 {
998 curName.chop( 1 );
999 curVariant = curSep + curVariant;
1000 }
1001
1002 if ( prevName.isEmpty() )
1003 prevName = curName;
1004
1005 // add element, unless it is empty, or a variant of last element
1006 prevAdd = false;
1007 curAdd = false;
1008 if ( curName.isEmpty() )
1009 curName = QStringLiteral( "__empty__" );
1010 // if current is a variant of last, don't add previous and append current variant
1011 if ( curName == prevName )
1012 {
1013 // add current element if it is the last one in the archive
1014 if ( i == schemeNamesAll.count() - 1 )
1015 prevAdd = true;
1016 listVariant << curVariant;
1017 }
1018 else
1019 {
1020 if ( !prevName.isEmpty() )
1021 {
1022 prevAdd = true;
1023 }
1024 // add current element if it is the last one in the archive
1025 if ( i == schemeNamesAll.count() - 1 )
1026 curAdd = true;
1027 }
1028
1029 // QgsDebugMsgLevel(QString("prevAdd=%1 curAdd=%2 prevName=%3 curName=%4 count=%5").arg(prevAdd).arg(curAdd).arg(prevName).arg(curName).arg(listVariant.count()), 2);
1030
1031 if ( prevAdd )
1032 {
1033 // depending on number of variants, make one or more items
1034 if ( listVariant.isEmpty() )
1035 {
1036 // set num colors=-1 to parse file on request only
1037 // mSchemeNumColors[ prevName ] = -1;
1038 schemeNames << prevName;
1039 mRampsMap[ mPath + '/' + prevName ] = QStringList();
1040 }
1041 else if ( listVariant.count() <= 3 )
1042 {
1043 // for 1-2 items, create independent items
1044 for ( int j = 0; j < listVariant.count(); j++ )
1045 {
1046 // mSchemeNumColors[ prevName + listVariant[j] ] = -1;
1047 schemeNames << prevName + listVariant[j];
1048 mRampsMap[ mPath + '/' + prevName + listVariant[j] ] = QStringList();
1049 }
1050 }
1051 else
1052 {
1053 // mSchemeVariants[ path + '/' + prevName ] = listVariant;
1054 mRampsMap[ mPath + '/' + prevName ] = listVariant;
1055 schemeNames << prevName;
1056 }
1057 listVariant.clear();
1058 }
1059 if ( curAdd )
1060 {
1061 if ( !curVariant.isEmpty() )
1062 curName += curVariant;
1063 schemeNames << curName;
1064 mRampsMap[ mPath + '/' + curName ] = QStringList();
1065 }
1066 // save current to compare next
1067 if ( prevAdd || curAdd )
1068 {
1069 prevName = curName;
1070 if ( !curVariant.isEmpty() )
1071 listVariant << curVariant;
1072 }
1073
1074 }
1075#if 0
1076 //TODO what to do with other vars? e.g. schemeNames
1077 // add schemes to archive
1078 mSchemeMap[ path ] = schemeNames;
1079 schemeCount += schemeName.count();
1080 schemeNames.clear();
1081 listVariant.clear();
1082 prevName = "";
1083#endif
1084 return mRampsMap;
1085}
1086
1088{
1089 return QDir( QgsCptCityArchive::defaultBaseDir() +
1090 '/' + mPath ).entryList( QDir::Dirs | QDir::NoDotAndDotDot, QDir::Name );
1091}
1092
1094{
1095 //QgsDebugMsg ( mPath + " x " + other->mPath );
1096 if ( type() != other->type() )
1097 {
1098 return false;
1099 }
1100 return ( path() == other->path() );
1101}
1102
1104 const QString &name, const QString &path )
1105{
1106 QgsDebugMsgLevel( "name= " + name + " path= " + path, 2 );
1107
1108 // first create item with constructor
1110 if ( dirItem && ! dirItem->isValid() )
1111 {
1112 delete dirItem;
1113 return nullptr;
1114 }
1115 if ( ! dirItem )
1116 return nullptr;
1117
1118 // fetch sub-dirs and ramps to know what to do with this item
1119 const QStringList dirEntries = dirItem->dirEntries();
1120 QMap< QString, QStringList > rampsMap = dirItem->rampsMap();
1121
1122 QgsDebugMsgLevel( QStringLiteral( "item has %1 dirs and %2 ramps" ).arg( dirEntries.count() ).arg( rampsMap.count() ), 2 );
1123
1124 // return item if has at least one subdir
1125 if ( !dirEntries.isEmpty() )
1126 return dirItem;
1127
1128 // if 0 ramps, delete item
1129 if ( rampsMap.isEmpty() )
1130 {
1131 delete dirItem;
1132 return nullptr;
1133 }
1134 // if 1 ramp, return this child's item
1135 // so we don't have a directory with just 1 item (with many variants possibly)
1136 else if ( rampsMap.count() == 1 )
1137 {
1138 delete dirItem;
1139 QgsCptCityColorRampItem *rampItem =
1140 new QgsCptCityColorRampItem( parent, rampsMap.begin().key(),
1141 rampsMap.begin().key(), rampsMap.begin().value() );
1142 if ( ! rampItem->isValid() )
1143 {
1144 delete rampItem;
1145 return nullptr;
1146 }
1147 return rampItem;
1148 }
1149 return dirItem;
1150}
1151
1152
1153//-----------------------------------------------------------------------
1155 const QString &name, const QString &path )
1157{
1158 mType = Selection;
1159 mValid = ! path.isNull();
1160 if ( mValid )
1161 parseXml();
1162}
1163
1164QVector<QgsCptCityDataItem *> QgsCptCitySelectionItem::createChildren()
1165{
1166 if ( ! mValid )
1167 return QVector<QgsCptCityDataItem *>();
1168
1169 QgsCptCityDataItem *item = nullptr;
1170 QVector<QgsCptCityDataItem *> children;
1171
1172 QgsDebugMsgLevel( "name= " + mName + " path= " + mPath, 2 );
1173
1174 // add children archives
1175 for ( QString childPath : std::as_const( mSelectionsList ) )
1176 {
1177 QgsDebugMsgLevel( "childPath = " + childPath + " name= " + QFileInfo( childPath ).baseName(), 2 );
1178 if ( childPath.endsWith( '/' ) )
1179 {
1180 childPath.chop( 1 );
1181 QgsCptCityDataItem *childItem =
1182 QgsCptCityDirectoryItem::dataItem( this, childPath, childPath );
1183 if ( childItem )
1184 {
1185 if ( childItem->isValid() )
1186 children << childItem;
1187 else
1188 delete childItem;
1189 }
1190 }
1191 else
1192 {
1193 const QString fileName = QgsCptCityColorRamp::fileNameForVariant( childPath, QString() );
1194 if ( !QFile::exists( fileName ) )
1195 {
1196 continue;
1197 }
1198
1199 item = new QgsCptCityColorRampItem( this, childPath, childPath, QString(), true );
1200 if ( item->isValid() )
1201 children << item;
1202 else
1203 delete item;
1204 }
1205 }
1206
1207 QgsDebugMsgLevel( QStringLiteral( "path= %1 inserted %2 children" ).arg( mPath ).arg( children.count() ), 2 );
1208
1209 return children;
1210}
1211
1213{
1214 const QString filename = QgsCptCityArchive::defaultBaseDir() + '/' + mPath;
1215
1216 QgsDebugMsgLevel( "reading file " + filename, 2 );
1217
1218 QFile f( filename );
1219 if ( ! f.open( QFile::ReadOnly ) )
1220 {
1221 QgsDebugError( filename + " does not exist" );
1222 return;
1223 }
1224
1225 // parse the document
1226 QString errMsg;
1227 QDomDocument doc( QStringLiteral( "selection" ) );
1228 if ( !doc.setContent( &f, &errMsg ) )
1229 {
1230 f.close();
1231 QgsDebugError( "Couldn't parse file " + filename + " : " + errMsg );
1232 return;
1233 }
1234 f.close();
1235
1236 // read description
1237 const QDomElement docElem = doc.documentElement();
1238 if ( docElem.tagName() != QLatin1String( "selection" ) )
1239 {
1240 QgsDebugError( "Incorrect root tag: " + docElem.tagName() );
1241 return;
1242 }
1243 QDomElement e = docElem.firstChildElement( QStringLiteral( "name" ) );
1244 if ( ! e.isNull() && ! e.text().isNull() )
1245 mName = e.text();
1246 mInfo = docElem.firstChildElement( QStringLiteral( "synopsis" ) ).text().simplified();
1247
1248 // get archives
1249 const QDomElement collectsElem = docElem.firstChildElement( QStringLiteral( "seealsocollects" ) );
1250 e = collectsElem.firstChildElement( QStringLiteral( "collect" ) );
1251 while ( ! e.isNull() )
1252 {
1253 if ( ! e.attribute( QStringLiteral( "dir" ) ).isNull() )
1254 {
1255 // TODO parse description and use that, instead of default archive name
1256 const QString dir = e.attribute( QStringLiteral( "dir" ) ) + '/';
1257 if ( QFile::exists( QgsCptCityArchive::defaultBaseDir() + '/' + dir ) )
1258 {
1259 mSelectionsList << dir;
1260 }
1261 }
1262 e = e.nextSiblingElement();
1263 }
1264 // get individual gradients
1265 const QDomElement gradientsElem = docElem.firstChildElement( QStringLiteral( "gradients" ) );
1266 e = gradientsElem.firstChildElement( QStringLiteral( "gradient" ) );
1267 while ( ! e.isNull() )
1268 {
1269 if ( ! e.attribute( QStringLiteral( "dir" ) ).isNull() )
1270 {
1271 // QgsDebugMsgLevel( "add " + e.attribute( "dir" ) + '/' + e.attribute( "file" ) + " to " + selname, 2 );
1272 // TODO parse description and save elsewhere
1273 const QString dir = e.attribute( QStringLiteral( "dir" ) );
1274 if ( QFile::exists( QgsCptCityArchive::defaultBaseDir() + '/' + dir ) )
1275 {
1276 mSelectionsList << dir + '/' + e.attribute( QStringLiteral( "file" ) );
1277 }
1278 }
1279 e = e.nextSiblingElement();
1280 }
1281}
1282
1284{
1285 //QgsDebugMsgLevel( mPath + " x " + other->mPath, 2 );
1286 if ( type() != other->type() )
1287 {
1288 return false;
1289 }
1290 return ( path() == other->path() );
1291}
1292
1293//-----------------------------------------------------------------------
1295 const QString &name, const QVector<QgsCptCityDataItem *> &items )
1296 : QgsCptCityCollectionItem( parent, name, QString() )
1297 , mItems( items )
1298{
1299 mType = AllRamps;
1300 mValid = true;
1301 // populate();
1302}
1303
1304QVector<QgsCptCityDataItem *> QgsCptCityAllRampsItem::createChildren()
1305{
1306 if ( ! mValid )
1307 return QVector<QgsCptCityDataItem *>();
1308
1309 QVector<QgsCptCityDataItem *> children;
1310
1311 // add children ramps of each item
1312 const auto constMItems = mItems;
1313 for ( QgsCptCityDataItem *item : constMItems )
1314 {
1315 QgsCptCityCollectionItem *colItem = qobject_cast< QgsCptCityCollectionItem * >( item );
1316 if ( colItem )
1317 children += colItem->childrenRamps( true );
1318 }
1319
1320 return children;
1321}
1322
1323//-----------------------------------------------------------------------
1324
1326 QgsCptCityArchive *archive, ViewType viewType )
1327 : QAbstractItemModel( parent )
1328 , mArchive( archive )
1329 , mViewType( viewType )
1330{
1331 Q_ASSERT( mArchive );
1332 QgsDebugMsgLevel( QLatin1String( "archiveName = " ) + archive->archiveName() + " viewType=" + QString::number( static_cast< int >( viewType ) ), 2 );
1333 // keep iconsize for now, but not effectively used
1334 mIconSize = QSize( 100, 15 );
1335 addRootItems();
1336}
1337
1342
1344{
1345 if ( mViewType == Authors )
1346 {
1347 mRootItems = mArchive->rootItems();
1348 }
1349 else if ( mViewType == Selections )
1350 {
1351 mRootItems = mArchive->selectionItems();
1352 }
1353 QgsDebugMsgLevel( QStringLiteral( "added %1 root items" ).arg( mRootItems.size() ), 2 );
1354}
1355
1360
1361Qt::ItemFlags QgsCptCityBrowserModel::flags( const QModelIndex &index ) const
1362{
1363 if ( !index.isValid() )
1364 return Qt::ItemFlags();
1365
1366 Qt::ItemFlags flags = Qt::ItemIsEnabled | Qt::ItemIsSelectable;
1367
1368 return flags;
1369}
1370
1371QVariant QgsCptCityBrowserModel::data( const QModelIndex &index, int role ) const
1372{
1373 if ( !index.isValid() )
1374 return QVariant();
1375
1377
1378 if ( !item )
1379 {
1380 return QVariant();
1381 }
1382 else if ( role == Qt::DisplayRole )
1383 {
1384 if ( index.column() == 0 )
1385 return item->name();
1386 if ( index.column() == 1 )
1387 {
1388 return item->info();
1389 }
1390 }
1391 else if ( role == Qt::ToolTipRole )
1392 {
1393 if ( item->type() == QgsCptCityDataItem::ColorRamp &&
1394 mViewType == List )
1395 return QString( item->path() + '\n' + item->info() );
1396 return item->toolTip();
1397 }
1398 else if ( role == Qt::DecorationRole && index.column() == 1 &&
1400 {
1401 // keep iconsize for now, but not effectively used
1402 return item->icon( mIconSize );
1403 }
1404 else if ( role == Qt::FontRole &&
1405 qobject_cast< QgsCptCityCollectionItem * >( item ) )
1406 {
1407 // collectionitems are larger and bold
1408 QFont font;
1409 font.setPointSize( 11 ); //FIXME why is the font so small?
1410 font.setBold( true );
1411 return font;
1412 }
1413 else
1414 {
1415 // unsupported role
1416 return QVariant();
1417 }
1418 return QVariant();
1419}
1420
1421QVariant QgsCptCityBrowserModel::headerData( int section, Qt::Orientation orientation, int role ) const
1422{
1423 Q_UNUSED( section )
1424 if ( orientation == Qt::Horizontal && role == Qt::DisplayRole )
1425 {
1426 if ( section == 0 )
1427 return QVariant( tr( "Name" ) );
1428 else if ( section == 1 )
1429 return QVariant( tr( "Info" ) );
1430 }
1431 return QVariant();
1432}
1433
1434int QgsCptCityBrowserModel::rowCount( const QModelIndex &parent ) const
1435{
1436 //qDebug("rowCount: idx: (valid %d) %d %d", parent.isValid(), parent.row(), parent.column());
1437
1438 if ( !parent.isValid() )
1439 {
1440 // root item: its children are top level items
1441 return mRootItems.count(); // mRoot
1442 }
1443 else
1444 {
1445 // ordinary item: number of its children
1447 return item ? item->rowCount() : 0;
1448 }
1449}
1450
1451bool QgsCptCityBrowserModel::hasChildren( const QModelIndex &parent ) const
1452{
1453 if ( !parent.isValid() )
1454 return true; // root item: its children are top level items
1455
1457
1458 return item && item->hasChildren();
1459}
1460
1461int QgsCptCityBrowserModel::columnCount( const QModelIndex &parent ) const
1462{
1463 Q_UNUSED( parent )
1464 return 2;
1465}
1466
1467QModelIndex QgsCptCityBrowserModel::findPath( const QString &path )
1468{
1469 QModelIndex rootIndex; // starting from root
1470 bool foundParent = false, foundChild = true;
1471 QString itemPath;
1472
1473 QgsDebugMsgLevel( "path = " + path, 2 );
1474
1475 // special case if searching for first item "All Ramps", do not search into tree
1476 if ( path.isEmpty() )
1477 {
1478 for ( int i = 0; i < rowCount( rootIndex ); i++ )
1479 {
1480 QModelIndex idx = index( i, 0, rootIndex );
1481 QgsCptCityDataItem *item = dataItem( idx );
1482 if ( !item )
1483 return QModelIndex(); // an error occurred
1484
1485 itemPath = item->path();
1486
1487 if ( itemPath == path )
1488 {
1489 QgsDebugMsgLevel( "Arrived " + itemPath, 2 );
1490 return idx; // we have found the item we have been looking for
1491 }
1492 }
1493 }
1494
1495 while ( foundChild )
1496 {
1497 foundChild = false; // assume that the next child item will not be found
1498
1499 int i = 0;
1500 // if root skip first item "All Ramps"
1501 if ( itemPath.isEmpty() )
1502 i = 1;
1503 for ( ; i < rowCount( rootIndex ); i++ )
1504 {
1505 QModelIndex idx = index( i, 0, rootIndex );
1506 QgsCptCityDataItem *item = dataItem( idx );
1507 if ( !item )
1508 return QModelIndex(); // an error occurred
1509
1510 itemPath = item->path();
1511
1512 if ( itemPath == path )
1513 {
1514 QgsDebugMsgLevel( "Arrived " + itemPath, 2 );
1515 return idx; // we have found the item we have been looking for
1516 }
1517
1518 if ( ! itemPath.endsWith( '/' ) )
1519 itemPath += '/';
1520
1521 foundParent = false;
1522
1523 // QgsDebugMsgLevel( "path= " + path + " itemPath= " + itemPath, 2 );
1524
1525 // if we are using a selection collection, search for target in the mapping in this group
1526 if ( item->type() == QgsCptCityDataItem::Selection )
1527 {
1528 const QgsCptCitySelectionItem *selItem = qobject_cast<const QgsCptCitySelectionItem *>( item );
1529 if ( selItem )
1530 {
1531 const auto constSelectionsList = selItem->selectionsList();
1532 for ( QString childPath : constSelectionsList )
1533 {
1534 if ( childPath.endsWith( '/' ) )
1535 childPath.chop( 1 );
1536 // QgsDebugMsgLevel( "childPath= " + childPath, 2 );
1537 if ( path.startsWith( childPath ) )
1538 {
1539 foundParent = true;
1540 break;
1541 }
1542 }
1543 }
1544 }
1545 // search for target in parent directory
1546 else if ( path.startsWith( itemPath ) )
1547 {
1548 foundParent = true;
1549 }
1550
1551 if ( foundParent )
1552 {
1553 QgsDebugMsgLevel( "found parent " + path, 2 );
1554 // we have found a preceding item: stop searching on this level and go deeper
1555 foundChild = true;
1556 rootIndex = idx;
1557 if ( canFetchMore( rootIndex ) )
1558 fetchMore( rootIndex );
1559 break;
1560 }
1561 }
1562 }
1563
1564 return QModelIndex(); // not found
1565}
1566
1568{
1569 beginResetModel();
1571 addRootItems();
1572 endResetModel();
1573}
1574
1575/* Refresh dir path */
1576void QgsCptCityBrowserModel::refresh( const QString &path )
1577{
1578 const QModelIndex idx = findPath( path );
1579 if ( idx.isValid() )
1580 {
1581 QgsCptCityDataItem *item = dataItem( idx );
1582 if ( item )
1583 item->refresh();
1584 }
1585}
1586
1587QModelIndex QgsCptCityBrowserModel::index( int row, int column, const QModelIndex &parent ) const
1588{
1590 const QVector<QgsCptCityDataItem *> &items = p ? p->children() : mRootItems;
1591 QgsCptCityDataItem *item = items.value( row, nullptr );
1592 return item ? createIndex( row, column, item ) : QModelIndex();
1593}
1594
1595QModelIndex QgsCptCityBrowserModel::parent( const QModelIndex &index ) const
1596{
1598 if ( !item )
1599 return QModelIndex();
1600
1601 return findItem( item->parent() );
1602}
1603
1605{
1606 const QVector<QgsCptCityDataItem *> &items = parent ? parent->children() : mRootItems;
1607
1608 for ( int i = 0; i < items.size(); i++ )
1609 {
1610 if ( items[i] == item )
1611 return createIndex( i, 0, item );
1612
1613 QModelIndex childIndex = findItem( item, items[i] );
1614 if ( childIndex.isValid() )
1615 return childIndex;
1616 }
1617
1618 return QModelIndex();
1619}
1620
1621/* Refresh item */
1622void QgsCptCityBrowserModel::refresh( const QModelIndex &index )
1623{
1625 if ( !item )
1626 return;
1627
1628 QgsDebugMsgLevel( "Refresh " + item->path(), 2 );
1629 item->refresh();
1630}
1631
1633{
1634 QgsDebugMsgLevel( "parent mPath = " + parent->path(), 2 );
1635 const QModelIndex idx = findItem( parent );
1636 if ( !idx.isValid() )
1637 return;
1638 QgsDebugMsgLevel( QStringLiteral( "valid" ), 2 );
1639 beginInsertRows( idx, first, last );
1640 QgsDebugMsgLevel( QStringLiteral( "end" ), 2 );
1641}
1643{
1644 endInsertRows();
1645}
1647{
1648 QgsDebugMsgLevel( "parent mPath = " + parent->path(), 2 );
1649 const QModelIndex idx = findItem( parent );
1650 if ( !idx.isValid() )
1651 return;
1652 beginRemoveRows( idx, first, last );
1653}
1655{
1656 endRemoveRows();
1657}
1665
1666bool QgsCptCityBrowserModel::canFetchMore( const QModelIndex &parent ) const
1667{
1669 // fetch all items initially so we know which items have children
1670 // (nicer looking and less confusing)
1671
1672 if ( ! item )
1673 return false;
1674
1675 // except for "All Ramps" - this is populated when clicked on
1676 if ( item->type() == QgsCptCityDataItem::AllRamps )
1677 return false;
1678
1679 item->populate();
1680
1681 return ( ! item->isPopulated() );
1682}
1683
1685{
1687 if ( item )
1688 {
1689 item->populate();
1690 QgsDebugMsgLevel( "path = " + item->path(), 2 );
1691 }
1692}
1693
1695{
1696 void *v = idx.internalPointer();
1697 QgsCptCityDataItem *d = reinterpret_cast<QgsCptCityDataItem *>( v );
1698 Q_ASSERT( !v || d );
1699 return d;
1700}
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