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