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