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