QGIS API Documentation  3.21.0-Master (5b68dc587e)
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
Definition: qgscolorramp.h:726
QString fileName() const
void setVariantName(const QString &variantName)
Definition: qgscolorramp.h:730
bool hasMultiStops() const
Definition: qgscolorramp.h:736
QString variantName() const
Definition: qgscolorramp.h:725
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...
Definition: qgscolorramp.h:221
int count() const override
Returns number of defined colors, or -1 if undefined.
Definition: qgscolorramp.h:170
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.
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:1691
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