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