QGIS API Documentation  2.99.0-Master (37c43df)
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 <QSettings>
29 #include <QDomDocument>
30 #include <QDomElement>
31 
32 #include "qgscptcityarchive.h"
33 #include "qgis.h"
34 
35 #include "qgsdataprovider.h"
36 #include "qgslogger.h"
37 #include "qgsconfig.h"
38 #include "qgsmimedatautils.h"
39 #include "qgsapplication.h"
40 #include "qgssymbollayerutils.h"
41 
43 QMap< QString, QgsCptCityArchive* > QgsCptCityArchive::mArchiveRegistry;
44 QMap< QString, QgsCptCityArchive* > QgsCptCityArchive::archiveRegistry() { return mArchiveRegistry; }
45 QMap< QString, QMap< QString, QString > > QgsCptCityArchive::mCopyingInfoMap;
46 
47 QgsCptCityArchive::QgsCptCityArchive( const QString& archiveName, const QString& baseDir )
48  : mArchiveName( archiveName )
49  , mBaseDir( baseDir )
50 {
51  QgsDebugMsg( "archiveName = " + archiveName + " baseDir = " + baseDir );
52 
53  // make Author items
54  QgsCptCityDirectoryItem* dirItem = nullptr;
55  Q_FOREACH ( const QString& path, QDir( mBaseDir ).entryList( QDir::Dirs | QDir::NoDotAndDotDot, QDir::Name ) )
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  Q_FOREACH ( const QString& selfile, seldir.entryList( QStringList( "*.xml" ), QDir::Files ) )
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;
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 = mArchiveRegistry.value( archiveName, nullptr ) )
119  return archive->baseDir();
120  else
121  return defaultBaseDir();
122 }
123 
125 {
126  QString baseDir, archiveName;
127  QSettings 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 == QLatin1String( "" ) || ! 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::mCopyingInfoMap.contains( fileName ) )
180  {
181  QgsDebugMsg( "found copying info in copyingInfoMap, file = " + fileName );
182  return QgsCptCityArchive::mCopyingInfoMap.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( "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( "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( "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::mCopyingInfoMap[ 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( "name tag missing" );
310  }
311  descMap[ QStringLiteral( "name" )] = e.text().simplified();
312  e = docElem.firstChildElement( QStringLiteral( "full" ) );
313  if ( e.isNull() )
314  {
315  QgsDebugMsg( "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( "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" ), QLatin1String( "" ) ); // 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( QString( "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  QSettings settings;
419  mDefaultArchiveName = 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 ( mArchiveRegistry.contains( archiveName ) )
431  delete mArchiveRegistry[ archiveName ];
432  mArchiveRegistry[ archiveName ] = archive;
433 }
434 
436 {
437  QSettings 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 ( ! mArchiveRegistry.contains( defArchiveName ) )
445  initArchive( defArchiveName, baseDir + '/' + defArchiveName );
446 }
447 
449 {
450  QgsStringMap archivesMap;
451  QString baseDir, defArchiveName;
452  QSettings 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  Q_FOREACH ( const QString& entry, dir.entryList( QStringList( "cpt-city*" ), QDir::Dirs ) )
465  {
466  if ( QFile::exists( baseDir + '/' + entry + "/VERSION.xml" ) )
467  archivesMap[ entry ] = baseDir + '/' + entry;
468  }
469  }
470  else
471  {
472  archivesMap[ defArchiveName ] = baseDir + '/' + defArchiveName;
473  }
474 
475  for ( QgsStringMap::iterator it = archivesMap.begin();
476  it != archivesMap.end(); ++it )
477  {
478  if ( QDir( it.value() ).exists() )
479  QgsCptCityArchive::initArchive( it.key(), it.value() );
480  else
481  {
482  QgsDebugMsg( QString( "not loading archive [%1] because dir %2 does not exist " ).arg( it.key(), it.value() ) );
483  }
484  }
485  mDefaultArchiveName = defArchiveName;
486 }
487 
489 {
490  qDeleteAll( mArchiveRegistry );
491  mArchiveRegistry.clear();
492 }
493 
494 
495 // --------
496 
498  const QString& name, const QString& path )
499 // Do not pass parent to QObject, Qt would delete this when parent is deleted
500  : QObject()
501  , mType( type ), mParent( parent ), mPopulated( false )
502  , mName( name ), mPath( path ), mValid( true )
503 {
504 }
505 
507 {
508  // QgsDebugMsg( "mName = " + mName + " mPath = " + mPath );
509 }
510 
511 QVector<QgsCptCityDataItem*> QgsCptCityDataItem::createChildren()
512 {
513  QVector<QgsCptCityDataItem*> children;
514  return children;
515 }
516 
518 {
519  if ( mPopulated )
520  return;
521 
522  QgsDebugMsg( "mPath = " + mPath );
523 
524  QApplication::setOverrideCursor( Qt::WaitCursor );
525 
526  QVector<QgsCptCityDataItem*> children = createChildren();
527  Q_FOREACH ( QgsCptCityDataItem *child, children )
528  {
529  // initialization, do not refresh! That would result in infinite loop (beginInsertItems->rowCount->populate)
530  addChildItem( child );
531  }
532  mPopulated = true;
533 
534  QApplication::restoreOverrideCursor();
535 }
536 
538 {
539  // if ( !mPopulated )
540  // populate();
541  return mChildren.size();
542 }
543 
545 {
546  if ( !mPopulated )
547  return 0;
548 
549  int count = 0;
550  Q_FOREACH ( QgsCptCityDataItem *child, mChildren )
551  {
552  if ( child )
553  count += child->leafCount();
554  }
555  return count;
556 }
557 
558 
560 {
561  return ( mPopulated ? !mChildren.isEmpty() : true );
562 }
563 
565 {
566  QgsDebugMsg( QString( "add child #%1 - %2 - %3" ).arg( mChildren.size() ).arg( child->mName ).arg( child->mType ) );
567 
568  int i;
569  if ( type() == ColorRamp )
570  {
571  for ( i = 0; i < mChildren.size(); i++ )
572  {
573  // sort items by type, so directories are after data items
574  if ( mChildren.at( i )->mType == child->mType &&
575  mChildren.at( i )->mName.localeAwareCompare( child->mName ) >= 0 )
576  break;
577  }
578  }
579  else
580  {
581  for ( i = 0; i < mChildren.size(); i++ )
582  {
583  if ( mChildren.at( i )->mName.localeAwareCompare( child->mName ) >= 0 )
584  break;
585  }
586  }
587 
588  if ( refresh )
589  emit beginInsertItems( this, i, i );
590 
591  mChildren.insert( i, child );
592 
597 
598  if ( refresh )
599  emit endInsertItems();
600 }
602 {
603  // QgsDebugMsg( "mName = " + child->mName );
604  int i = mChildren.indexOf( child );
605  Q_ASSERT( i >= 0 );
606  emit beginRemoveItems( this, i, i );
607  mChildren.remove( i );
608  delete child;
609  emit endRemoveItems();
610 }
611 
613 {
614  // QgsDebugMsg( "mName = " + child->mName );
615  int i = mChildren.indexOf( child );
616  Q_ASSERT( i >= 0 );
617  emit beginRemoveItems( this, i, i );
618  mChildren.remove( i );
619  emit endRemoveItems();
624  child->setParent( nullptr );
625  return child;
626 }
627 
628 int QgsCptCityDataItem::findItem( QVector<QgsCptCityDataItem*> items, QgsCptCityDataItem * item )
629 {
630  for ( int i = 0; i < items.size(); i++ )
631  {
632  // QgsDebugMsg( QString::number( i ) + " : " + items[i]->mPath + " x " + item->mPath );
633  if ( items[i]->equal( item ) )
634  return i;
635  }
636  return -1;
637 }
638 
640 {
641  QgsDebugMsg( "mPath = " + mPath );
642 
643  QApplication::setOverrideCursor( Qt::WaitCursor );
644 
645  QVector<QgsCptCityDataItem*> items = createChildren();
646 
647  // Remove no more present items
648  QVector<QgsCptCityDataItem*> remove;
649  Q_FOREACH ( QgsCptCityDataItem *child, mChildren )
650  {
651  if ( findItem( items, child ) >= 0 )
652  continue;
653  remove.append( child );
654  }
655  Q_FOREACH ( QgsCptCityDataItem *child, remove )
656  {
657  deleteChildItem( child );
658  }
659 
660  // Add new items
661  Q_FOREACH ( QgsCptCityDataItem *item, items )
662  {
663  // Is it present in childs?
664  if ( findItem( mChildren, item ) >= 0 )
665  {
666  delete item;
667  continue;
668  }
669  addChildItem( item, true );
670  }
671 
672  QApplication::restoreOverrideCursor();
673 }
674 
676 {
677  if ( metaObject()->className() == other->metaObject()->className() &&
678  mPath == other->path() )
679  {
680  return true;
681  }
682  return false;
683 }
684 
685 // ---------------------------------------------------------------------
686 
688  const QString& name, const QString& path, const QString& variantName, bool initialize )
689  : QgsCptCityDataItem( ColorRamp, parent, name, path )
690  , mInitialised( false )
691  , mRamp( path, variantName, false )
692 {
693  // QgsDebugMsg( "name= " + name + " path= " + path );
694  mPopulated = true;
695  if ( initialize )
696  init();
697 }
698 
700  const QString& name, const QString& path, const QStringList& variantList, bool initialize )
701  : QgsCptCityDataItem( ColorRamp, parent, name, path )
702  , mInitialised( false )
703  , mRamp( path, variantList, QString(), false )
704 {
705  // QgsDebugMsg( "name= " + name + " path= " + path );
706  mPopulated = true;
707  if ( initialize )
708  init();
709 }
710 
711 // TODO only load file when icon is requested...
713 {
714  if ( mInitialised )
715  return;
716  mInitialised = true;
717 
718  QgsDebugMsg( "path = " + path() );
719 
720  // make preview from variant if exists
721  QStringList variantList = mRamp.variantList();
722  if ( mRamp.variantName().isNull() && ! variantList.isEmpty() )
723  mRamp.setVariantName( variantList[ variantList.count() / 2 ] );
724 
725  mRamp.loadFile();
726 
727  // is this item valid? this might fail when there are variants, check
728  if ( ! QFile::exists( mRamp.fileName() ) )
729  mValid = false;
730  else
731  mValid = true;
732 
733  // load file and set info
734  if ( mRamp.count() > 0 )
735  {
736  if ( variantList.isEmpty() )
737  {
738  int count = mRamp.count();
739  if ( mRamp.isDiscrete() )
740  count--;
741  mInfo = QString::number( count ) + ' ' + tr( "colors" ) + " - ";
742  if ( mRamp.isDiscrete() )
743  mInfo += tr( "discrete" );
744  else
745  {
746  if ( !mRamp.hasMultiStops() )
747  mInfo += tr( "continuous" );
748  else
749  mInfo += tr( "continuous (multi)" );
750  }
751  mShortInfo = QFileInfo( mName ).fileName();
752  }
753  else
754  {
755  mInfo = QString::number( variantList.count() ) + ' ' + tr( "variants" );
756  // mShortInfo = QFileInfo( mName ).fileName() + " (" + QString::number( variantList.count() ) + ')';
757  mShortInfo = QFileInfo( mName ).fileName();
758  }
759  }
760  else
761  {
762  mInfo = QLatin1String( "" );
763  }
764 
765 }
766 
768 {
769  //QgsDebugMsg ( mPath + " x " + other->mPath );
770  if ( type() != other->type() )
771  {
772  return false;
773  }
774  //const QgsCptCityColorRampItem *o = qobject_cast<const QgsCptCityColorRampItem *> ( other );
775  const QgsCptCityColorRampItem *o = dynamic_cast<const QgsCptCityColorRampItem *>( other );
776  return o &&
777  mPath == o->mPath &&
778  mName == o->mName &&
779  ramp().variantName() == o->ramp().variantName();
780 }
781 
783 {
784  return icon( QSize( 100, 15 ) );
785 }
786 
787 QIcon QgsCptCityColorRampItem::icon( QSize size )
788 {
789  Q_FOREACH ( const QIcon& icon, mIcons )
790  {
791  if ( icon.availableSizes().contains( size ) )
792  return icon;
793  }
794 
795  QIcon icon;
796 
797  init();
798 
799  if ( mValid && mRamp.count() > 0 )
800  {
802  }
803  else
804  {
805  QPixmap blankPixmap( size );
806  blankPixmap.fill( Qt::white );
807  icon = QIcon( blankPixmap );
808  mInfo = QLatin1String( "" );
809  }
810 
811  mIcons.append( icon );
812  return icon;
813 }
814 
815 // ---------------------------------------------------------------------
817  const QString& name, const QString& path )
818  : QgsCptCityDataItem( Collection, parent, name, path )
819  , mPopulatedRamps( false )
820 {
821 }
822 
824 {
825  Q_FOREACH ( QgsCptCityDataItem* i, mChildren )
826  {
827  // QgsDebugMsg( QString( "delete child = 0x%0" ).arg(( qlonglong )i, 8, 16, QLatin1Char( '0' ) ) );
828  delete i;
829  }
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  Q_FOREACH ( QgsCptCityDataItem* childItem, children() )
841  {
842  QgsCptCityCollectionItem* collectionItem = dynamic_cast<QgsCptCityCollectionItem*>( childItem );
843  QgsCptCityColorRampItem* rampItem = dynamic_cast<QgsCptCityColorRampItem*>( childItem );
844  QgsDebugMsgLevel( QString( "child path= %1 coll= %2 ramp = %3" ).arg( childItem->path() ).arg( nullptr != collectionItem ).arg( nullptr != rampItem ), 2 );
845  if ( collectionItem && recursive )
846  {
847  collectionItem->populate();
848  rampItems << collectionItem->childrenRamps( true );
849  }
850  else if ( rampItem )
851  {
852  // init rampItem to get palette and icon, test if is valid after loading file
853  rampItem->init();
854  if ( rampItem->isValid() )
855  rampItems << rampItem;
856  else
857  deleteItems << rampItem;
858  }
859  else
860  {
861  QgsDebugMsg( "invalid item " + childItem->path() );
862  }
863  }
864 
865  // delete invalid items - this is not efficient, but should only happens once
866  Q_FOREACH ( QgsCptCityDataItem* deleteItem, deleteItems )
867  {
868  QgsDebugMsg( QString( "item %1 is invalid, will be deleted" ).arg( deleteItem->path() ) );
869  int i = mChildren.indexOf( deleteItem );
870  if ( i != -1 )
871  mChildren.remove( i );
872  delete deleteItem;
873  }
874 
875  return rampItems;
876 }
877 
878 //-----------------------------------------------------------------------
880  const QString& name, const QString& path )
881  : QgsCptCityCollectionItem( parent, name, path )
882 {
883  mType = Directory;
884  mValid = QDir( QgsCptCityArchive::defaultBaseDir() + '/' + mPath ).exists();
885  if ( ! mValid )
886  {
887  QgsDebugMsg( "created invalid dir item, path = " + QgsCptCityArchive::defaultBaseDir()
888  + '/' + mPath );
889  }
890 
891  // parse DESC.xml to get mInfo
892  mInfo = QLatin1String( "" );
893  QString fileName = QgsCptCityArchive::defaultBaseDir() + '/' +
894  mPath + '/' + "DESC.xml";
895  QgsStringMap descMap = QgsCptCityArchive::description( fileName );
896  if ( descMap.contains( QStringLiteral( "name" ) ) )
897  mInfo = descMap.value( QStringLiteral( "name" ) );
898 
899  // populate();
900 }
901 
903 {
904 }
905 
906 QVector<QgsCptCityDataItem*> QgsCptCityDirectoryItem::createChildren()
907 {
908  if ( ! mValid )
909  return QVector<QgsCptCityDataItem*>();
910 
911  QVector<QgsCptCityDataItem*> children;
912 
913  // add children schemes
914  QMapIterator< QString, QStringList> it( rampsMap() );
915  while ( it.hasNext() )
916  {
917  it.next();
918  // QgsDebugMsg( "schemeName = " + it.key() );
919  QgsCptCityDataItem* item =
920  new QgsCptCityColorRampItem( this, it.key(), it.key(), it.value() );
921  if ( item->isValid() )
922  children << item;
923  else
924  delete item;
925  }
926 
927  // add children dirs
928  Q_FOREACH ( const QString& childPath, dirEntries() )
929  {
930  QgsCptCityDataItem* childItem =
931  QgsCptCityDirectoryItem::dataItem( this, childPath, mPath + '/' + childPath );
932  if ( childItem )
933  children << childItem;
934  }
935 
936  QgsDebugMsg( QString( "name= %1 path= %2 found %3 children" ).arg( mName, mPath ).arg( children.count() ) );
937 
938  return children;
939 }
940 
941 QMap< QString, QStringList > QgsCptCityDirectoryItem::rampsMap()
942 {
943  if ( ! mRampsMap.isEmpty() )
944  return mRampsMap;
945 
946  QString curName, prevName, curVariant, curSep, schemeName;
947  QStringList listVariant;
948  QStringList schemeNamesAll, schemeNames;
949  bool prevAdd, curAdd;
950 
951  QDir dir( QgsCptCityArchive::defaultBaseDir() + '/' + mPath );
952  schemeNamesAll = dir.entryList( QStringList( QStringLiteral( "*.svg" ) ), QDir::Files, QDir::Name );
953 
954  // TODO detect if there are duplicate names with different variant counts, combine in 1
955  for ( int i = 0; i < schemeNamesAll.count(); i++ )
956  {
957  // schemeName = QFileInfo( schemeNamesAll[i] ).baseName();
958  schemeName = schemeNamesAll[i];
959  schemeName.chop( 4 );
960  // QgsDebugMsg("=============");
961  // QgsDebugMsg("scheme = "+schemeName);
962  curName = schemeName;
963  curVariant = QLatin1String( "" );
964 
965  // find if name ends with 1-3 digit number
966  // TODO need to detect if ends with b/c also
967  if ( schemeName.length() > 1 && schemeName.endsWith( 'a' ) && ! listVariant.isEmpty() &&
968  (( prevName + listVariant.last() + 'a' ) == curName ) )
969  {
970  curName = prevName;
971  curVariant = listVariant.last() + 'a';
972  }
973  else
974  {
975  QRegExp rxVariant( "^(.*[^\\d])(\\d{1,3})$" );
976  int pos = rxVariant.indexIn( schemeName );
977  if ( pos > -1 )
978  {
979  curName = rxVariant.cap( 1 );
980  curVariant = rxVariant.cap( 2 );
981  }
982  }
983 
984  curSep = curName.right( 1 );
985  if ( curSep == QLatin1String( "-" ) || curSep == QLatin1String( "_" ) )
986  {
987  curName.chop( 1 );
988  curVariant = curSep + curVariant;
989  }
990 
991  if ( prevName == QLatin1String( "" ) )
992  prevName = curName;
993 
994  // add element, unless it is empty, or a variant of last element
995  prevAdd = false;
996  curAdd = false;
997  if ( curName == QLatin1String( "" ) )
998  curName = QStringLiteral( "__empty__" );
999  // if current is a variant of last, don't add previous and append current variant
1000  if ( curName == prevName )
1001  {
1002  // add current element if it is the last one in the archive
1003  if ( i == schemeNamesAll.count() - 1 )
1004  prevAdd = true;
1005  listVariant << curVariant;
1006  }
1007  else
1008  {
1009  if ( prevName != QLatin1String( "" ) )
1010  {
1011  prevAdd = true;
1012  }
1013  // add current element if it is the last one in the archive
1014  if ( i == schemeNamesAll.count() - 1 )
1015  curAdd = true;
1016  }
1017 
1018  // QgsDebugMsg(QString("prevAdd=%1 curAdd=%2 prevName=%3 curName=%4 count=%5").arg(prevAdd).arg(curAdd).arg(prevName).arg(curName).arg(listVariant.count()));
1019 
1020  if ( prevAdd )
1021  {
1022  // depending on number of variants, make one or more items
1023  if ( listVariant.isEmpty() )
1024  {
1025  // set num colors=-1 to parse file on request only
1026  // mSchemeNumColors[ prevName ] = -1;
1027  schemeNames << prevName;
1028  mRampsMap[ mPath + '/' + prevName ] = QStringList();
1029  }
1030  else if ( listVariant.count() <= 3 )
1031  {
1032  // for 1-2 items, create independent items
1033  for ( int j = 0; j < listVariant.count(); j++ )
1034  {
1035  // mSchemeNumColors[ prevName + listVariant[j] ] = -1;
1036  schemeNames << prevName + listVariant[j];
1037  mRampsMap[ mPath + '/' + prevName + listVariant[j] ] = QStringList();
1038  }
1039  }
1040  else
1041  {
1042  // mSchemeVariants[ path + '/' + prevName ] = listVariant;
1043  mRampsMap[ mPath + '/' + prevName ] = listVariant;
1044  schemeNames << prevName;
1045  }
1046  listVariant.clear();
1047  }
1048  if ( curAdd )
1049  {
1050  if ( curVariant != QLatin1String( "" ) )
1051  curName += curVariant;
1052  schemeNames << curName;
1053  mRampsMap[ mPath + '/' + curName ] = QStringList();
1054  }
1055  // save current to compare next
1056  if ( prevAdd || curAdd )
1057  {
1058  prevName = curName;
1059  if ( curVariant != QLatin1String( "" ) )
1060  listVariant << curVariant;
1061  }
1062 
1063  }
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  return mRampsMap;
1072 }
1073 
1075 {
1076  return QDir( QgsCptCityArchive::defaultBaseDir() +
1077  '/' + mPath ).entryList( QDir::Dirs | QDir::NoDotAndDotDot, QDir::Name );
1078 }
1079 
1081 {
1082  //QgsDebugMsg ( mPath + " x " + other->mPath );
1083  if ( type() != other->type() )
1084  {
1085  return false;
1086  }
1087  return ( path() == other->path() );
1088 }
1089 
1091  const QString& name, const QString& path )
1092 {
1093  QgsDebugMsg( "name= " + name + " path= " + path );
1094 
1095  // first create item with constructor
1096  QgsCptCityDirectoryItem* dirItem = new QgsCptCityDirectoryItem( parent, name, path );
1097  if ( dirItem && ! dirItem->isValid() )
1098  {
1099  delete dirItem;
1100  return nullptr;
1101  }
1102  if ( ! dirItem )
1103  return nullptr;
1104 
1105  // fetch sub-dirs and ramps to know what to do with this item
1106  QStringList theDirEntries = dirItem->dirEntries();
1107  QMap< QString, QStringList > theRampsMap = dirItem->rampsMap();
1108 
1109  QgsDebugMsg( QString( "item has %1 dirs and %2 ramps" ).arg( theDirEntries.count() ).arg( theRampsMap.count() ) );
1110 
1111  // return item if has at least one subdir
1112  if ( !theDirEntries.isEmpty() )
1113  return dirItem;
1114 
1115  // if 0 ramps, delete item
1116  if ( theRampsMap.isEmpty() )
1117  {
1118  delete dirItem;
1119  return nullptr;
1120  }
1121  // if 1 ramp, return this child's item
1122  // so we don't have a directory with just 1 item (with many variants possibly)
1123  else if ( theRampsMap.count() == 1 )
1124  {
1125  delete dirItem;
1126  QgsCptCityColorRampItem* rampItem =
1127  new QgsCptCityColorRampItem( parent, theRampsMap.begin().key(),
1128  theRampsMap.begin().key(), theRampsMap.begin().value() );
1129  if ( ! rampItem->isValid() )
1130  {
1131  delete rampItem;
1132  return nullptr;
1133  }
1134  return rampItem;
1135  }
1136  return dirItem;
1137 }
1138 
1139 
1140 //-----------------------------------------------------------------------
1142  const QString& name, const QString& path )
1143  : QgsCptCityCollectionItem( parent, name, path )
1144 {
1145  mType = Selection;
1146  mValid = ! path.isNull();
1147  if ( mValid )
1148  parseXml();
1149 }
1150 
1152 {
1153 }
1154 
1155 QVector<QgsCptCityDataItem*> QgsCptCitySelectionItem::createChildren()
1156 {
1157  if ( ! mValid )
1158  return QVector<QgsCptCityDataItem*>();
1159 
1160  QgsCptCityDataItem* item = nullptr;
1161  QVector<QgsCptCityDataItem*> children;
1162 
1163  QgsDebugMsg( "name= " + mName + " path= " + mPath );
1164 
1165  // add children archives
1166  Q_FOREACH ( QString childPath, mSelectionsList )
1167  {
1168  QgsDebugMsg( "childPath = " + childPath + " name= " + QFileInfo( childPath ).baseName() );
1169  if ( childPath.endsWith( '/' ) )
1170  {
1171  childPath.chop( 1 );
1172  QgsCptCityDataItem* childItem =
1173  QgsCptCityDirectoryItem::dataItem( this, childPath, childPath );
1174  if ( childItem )
1175  {
1176  if ( childItem->isValid() )
1177  children << childItem;
1178  else
1179  delete childItem;
1180  }
1181  }
1182  else
1183  {
1184  // init item to test if is valid after loading file
1185  item = new QgsCptCityColorRampItem( this, childPath, childPath, QString(), true );
1186  if ( item->isValid() )
1187  children << item;
1188  else
1189  delete item;
1190  }
1191  }
1192 
1193  QgsDebugMsg( QString( "path= %1 inserted %2 children" ).arg( mPath ).arg( children.count() ) );
1194 
1195  return children;
1196 }
1197 
1199 {
1200  QString filename = QgsCptCityArchive::defaultBaseDir() + '/' + mPath;
1201 
1202  QgsDebugMsg( "reading file " + filename );
1203 
1204  QFile f( filename );
1205  if ( ! f.open( QFile::ReadOnly ) )
1206  {
1207  QgsDebugMsg( filename + " does not exist" );
1208  return;
1209  }
1210 
1211  // parse the document
1212  QString errMsg;
1213  QDomDocument doc( QStringLiteral( "selection" ) );
1214  if ( !doc.setContent( &f, &errMsg ) )
1215  {
1216  f.close();
1217  QgsDebugMsg( "Couldn't parse file " + filename + " : " + errMsg );
1218  return;
1219  }
1220  f.close();
1221 
1222  // read description
1223  QDomElement docElem = doc.documentElement();
1224  if ( docElem.tagName() != QLatin1String( "selection" ) )
1225  {
1226  QgsDebugMsg( "Incorrect root tag: " + docElem.tagName() );
1227  return;
1228  }
1229  QDomElement e = docElem.firstChildElement( QStringLiteral( "name" ) );
1230  if ( ! e.isNull() && ! e.text().isNull() )
1231  mName = e.text();
1232  mInfo = docElem.firstChildElement( QStringLiteral( "synopsis" ) ).text().simplified();
1233 
1234  // get archives
1235  QDomElement collectsElem = docElem.firstChildElement( QStringLiteral( "seealsocollects" ) );
1236  e = collectsElem.firstChildElement( QStringLiteral( "collect" ) );
1237  while ( ! e.isNull() )
1238  {
1239  if ( ! e.attribute( QStringLiteral( "dir" ) ).isNull() )
1240  {
1241  // TODO parse description and use that, instead of default archive name
1242  mSelectionsList << e.attribute( QStringLiteral( "dir" ) ) + '/';
1243  }
1244  e = e.nextSiblingElement();
1245  }
1246  // get individual gradients
1247  QDomElement gradientsElem = docElem.firstChildElement( QStringLiteral( "gradients" ) );
1248  e = gradientsElem.firstChildElement( QStringLiteral( "gradient" ) );
1249  while ( ! e.isNull() )
1250  {
1251  if ( ! e.attribute( QStringLiteral( "dir" ) ).isNull() )
1252  {
1253  // QgsDebugMsg( "add " + e.attribute( "dir" ) + '/' + e.attribute( "file" ) + " to " + selname );
1254  // TODO parse description and save elsewhere
1255  mSelectionsList << e.attribute( QStringLiteral( "dir" ) ) + '/' + e.attribute( QStringLiteral( "file" ) );
1256  }
1257  e = e.nextSiblingElement();
1258  }
1259 }
1260 
1262 {
1263  //QgsDebugMsg ( mPath + " x " + other->mPath );
1264  if ( type() != other->type() )
1265  {
1266  return false;
1267  }
1268  return ( path() == other->path() );
1269 }
1270 
1271 //-----------------------------------------------------------------------
1273  const QString& name, const QVector<QgsCptCityDataItem*>& items )
1274  : QgsCptCityCollectionItem( parent, name, QString() )
1275  , mItems( items )
1276 {
1277  mType = AllRamps;
1278  mValid = true;
1279  // populate();
1280 }
1281 
1283 {
1284 }
1285 
1286 QVector<QgsCptCityDataItem*> QgsCptCityAllRampsItem::createChildren()
1287 {
1288  if ( ! mValid )
1289  return QVector<QgsCptCityDataItem*>();
1290 
1291  QVector<QgsCptCityDataItem*> children;
1292 
1293  // add children ramps of each item
1294  Q_FOREACH ( QgsCptCityDataItem* item, mItems )
1295  {
1296  QgsCptCityCollectionItem* colItem = dynamic_cast< QgsCptCityCollectionItem* >( item );
1297  if ( colItem )
1298  children += colItem->childrenRamps( true );
1299  }
1300 
1301  return children;
1302 }
1303 
1304 //-----------------------------------------------------------------------
1305 
1307  QgsCptCityArchive* archive, ViewType viewType )
1308  : QAbstractItemModel( parent )
1309  , mArchive( archive )
1310  , mViewType( viewType )
1311 {
1312  Q_ASSERT( mArchive );
1313  QgsDebugMsg( "archiveName = " + archive->archiveName() + " viewType=" + static_cast< int >( viewType ) );
1314  // keep iconsize for now, but not effectively used
1315  mIconSize = QSize( 100, 15 );
1316  addRootItems();
1317 }
1318 
1320 {
1321  removeRootItems();
1322 }
1323 
1325 {
1326  if ( mViewType == Authors )
1327  {
1329  }
1330  else if ( mViewType == Selections )
1331  {
1333  }
1334  QgsDebugMsg( QString( "added %1 root items" ).arg( mRootItems.size() ) );
1335 }
1336 
1338 {
1339  // don't remove root items, they belong to the QgsCptCityArchive
1340  // Q_FOREACH ( QgsCptCityDataItem* item, mRootItems )
1341  // {
1342  // delete item;
1343  // }
1344 
1345  mRootItems.clear();
1346 }
1347 
1348 Qt::ItemFlags QgsCptCityBrowserModel::flags( const QModelIndex & index ) const
1349 {
1350  if ( !index.isValid() )
1351  return Qt::ItemFlags();
1352 
1353  Qt::ItemFlags flags = Qt::ItemIsEnabled | Qt::ItemIsSelectable;
1354 
1355  return flags;
1356 }
1357 
1358 QVariant QgsCptCityBrowserModel::data( const QModelIndex &index, int role ) const
1359 {
1360  if ( !index.isValid() )
1361  return QVariant();
1362 
1363  QgsCptCityDataItem *item = dataItem( index );
1364 
1365  if ( !item )
1366  {
1367  return QVariant();
1368  }
1369  else if ( role == Qt::DisplayRole )
1370  {
1371  if ( index.column() == 0 )
1372  return item->name();
1373  if ( index.column() == 1 )
1374  {
1375  return item->info();
1376  }
1377  }
1378  else if ( role == Qt::ToolTipRole )
1379  {
1380  if ( item->type() == QgsCptCityDataItem::ColorRamp &&
1381  mViewType == List )
1382  return item->path() + '\n' + item->info();
1383  return item->toolTip();
1384  }
1385  else if ( role == Qt::DecorationRole && index.column() == 1 &&
1386  item->type() == QgsCptCityDataItem::ColorRamp )
1387  {
1388  // keep iconsize for now, but not effectively used
1389  return item->icon( mIconSize );
1390  }
1391  else if ( role == Qt::FontRole &&
1392  dynamic_cast< QgsCptCityCollectionItem* >( item ) )
1393  {
1394  // collectionitems are larger and bold
1395  QFont font;
1396  font.setPointSize( 11 ); //FIXME why is the font so small?
1397  font.setBold( true );
1398  return font;
1399  }
1400  else
1401  {
1402  // unsupported role
1403  return QVariant();
1404  }
1405  return QVariant();
1406 }
1407 
1408 QVariant QgsCptCityBrowserModel::headerData( int section, Qt::Orientation orientation, int role ) const
1409 {
1410  Q_UNUSED( section );
1411  if ( orientation == Qt::Horizontal && role == Qt::DisplayRole )
1412  {
1413  if ( section == 0 )
1414  return QVariant( tr( "Name" ) );
1415  else if ( section == 1 )
1416  return QVariant( tr( "Info" ) );
1417  }
1418  return QVariant();
1419 }
1420 
1421 int QgsCptCityBrowserModel::rowCount( const QModelIndex &parent ) const
1422 {
1423  //qDebug("rowCount: idx: (valid %d) %d %d", parent.isValid(), parent.row(), parent.column());
1424 
1425  if ( !parent.isValid() )
1426  {
1427  // root item: its children are top level items
1428  return mRootItems.count(); // mRoot
1429  }
1430  else
1431  {
1432  // ordinary item: number of its children
1433  QgsCptCityDataItem *item = dataItem( parent );
1434  return item ? item->rowCount() : 0;
1435  }
1436 }
1437 
1438 bool QgsCptCityBrowserModel::hasChildren( const QModelIndex &parent ) const
1439 {
1440  if ( !parent.isValid() )
1441  return true; // root item: its children are top level items
1442 
1443  QgsCptCityDataItem *item = dataItem( parent );
1444 
1445  return item && item->hasChildren();
1446 }
1447 
1448 int QgsCptCityBrowserModel::columnCount( const QModelIndex &parent ) const
1449 {
1450  Q_UNUSED( parent );
1451  return 2;
1452 }
1453 
1454 QModelIndex QgsCptCityBrowserModel::findPath( const QString& path )
1455 {
1456  QModelIndex theIndex; // starting from root
1457  bool foundParent = false, foundChild = true;
1458  QString itemPath;
1459 
1460  QgsDebugMsg( "path = " + path );
1461 
1462  // special case if searching for first item "All Ramps", do not search into tree
1463  if ( path.isEmpty() )
1464  {
1465  for ( int i = 0; i < rowCount( theIndex ); i++ )
1466  {
1467  QModelIndex idx = index( i, 0, theIndex );
1468  QgsCptCityDataItem *item = dataItem( idx );
1469  if ( !item )
1470  return QModelIndex(); // an error occurred
1471 
1472  itemPath = item->path();
1473 
1474  if ( itemPath == path )
1475  {
1476  QgsDebugMsg( "Arrived " + itemPath );
1477  return idx; // we have found the item we have been looking for
1478  }
1479  }
1480  }
1481 
1482  while ( foundChild )
1483  {
1484  foundChild = false; // assume that the next child item will not be found
1485 
1486  int i = 0;
1487  // if root skip first item "All Ramps"
1488  if ( itemPath.isEmpty() )
1489  i = 1;
1490  for ( ; i < rowCount( theIndex ); i++ )
1491  {
1492  QModelIndex idx = index( i, 0, theIndex );
1493  QgsCptCityDataItem *item = dataItem( idx );
1494  if ( !item )
1495  return QModelIndex(); // an error occurred
1496 
1497  itemPath = item->path();
1498 
1499  if ( itemPath == path )
1500  {
1501  QgsDebugMsg( "Arrived " + itemPath );
1502  return idx; // we have found the item we have been looking for
1503  }
1504 
1505  if ( ! itemPath.endsWith( '/' ) )
1506  itemPath += '/';
1507 
1508  foundParent = false;
1509 
1510  // QgsDebugMsg( "path= " + path + " itemPath= " + itemPath );
1511 
1512  // if we are using a selection collection, search for target in the mapping in this group
1513  if ( item->type() == QgsCptCityDataItem::Selection )
1514  {
1515  const QgsCptCitySelectionItem* selItem = dynamic_cast<const QgsCptCitySelectionItem *>( item );
1516  if ( selItem )
1517  {
1518  Q_FOREACH ( QString childPath, selItem->selectionsList() )
1519  {
1520  if ( childPath.endsWith( '/' ) )
1521  childPath.chop( 1 );
1522  // QgsDebugMsg( "childPath= " + childPath );
1523  if ( path.startsWith( childPath ) )
1524  {
1525  foundParent = true;
1526  break;
1527  }
1528  }
1529  }
1530  }
1531  // search for target in parent directory
1532  else if ( path.startsWith( itemPath ) )
1533  {
1534  foundParent = true;
1535  }
1536 
1537  if ( foundParent )
1538  {
1539  QgsDebugMsg( "found parent " + path );
1540  // we have found a preceding item: stop searching on this level and go deeper
1541  foundChild = true;
1542  theIndex = idx;
1543  if ( canFetchMore( theIndex ) )
1544  fetchMore( theIndex );
1545  break;
1546  }
1547  }
1548  }
1549 
1550  return QModelIndex(); // not found
1551 }
1552 
1554 {
1555  beginResetModel();
1556  removeRootItems();
1557  addRootItems();
1558  endResetModel();
1559 }
1560 
1561 /* Refresh dir path */
1562 void QgsCptCityBrowserModel::refresh( const QString& path )
1563 {
1564  QModelIndex idx = findPath( path );
1565  if ( idx.isValid() )
1566  {
1567  QgsCptCityDataItem* item = dataItem( idx );
1568  if ( item )
1569  item->refresh();
1570  }
1571 }
1572 
1573 QModelIndex QgsCptCityBrowserModel::index( int row, int column, const QModelIndex &parent ) const
1574 {
1575  QgsCptCityDataItem *p = dataItem( parent );
1576  const QVector<QgsCptCityDataItem*> &items = p ? p->children() : mRootItems;
1577  QgsCptCityDataItem *item = items.value( row, nullptr );
1578  return item ? createIndex( row, column, item ) : QModelIndex();
1579 }
1580 
1581 QModelIndex QgsCptCityBrowserModel::parent( const QModelIndex &index ) const
1582 {
1583  QgsCptCityDataItem *item = dataItem( index );
1584  if ( !item )
1585  return QModelIndex();
1586 
1587  return findItem( item->parent() );
1588 }
1589 
1591 {
1592  const QVector<QgsCptCityDataItem*> &items = parent ? parent->children() : mRootItems;
1593 
1594  for ( int i = 0; i < items.size(); i++ )
1595  {
1596  if ( items[i] == item )
1597  return createIndex( i, 0, item );
1598 
1599  QModelIndex childIndex = findItem( item, items[i] );
1600  if ( childIndex.isValid() )
1601  return childIndex;
1602  }
1603 
1604  return QModelIndex();
1605 }
1606 
1607 /* Refresh item */
1608 void QgsCptCityBrowserModel::refresh( const QModelIndex& theIndex )
1609 {
1610  QgsCptCityDataItem *item = dataItem( theIndex );
1611  if ( !item )
1612  return;
1613 
1614  QgsDebugMsg( "Refresh " + item->path() );
1615  item->refresh();
1616 }
1617 
1619 {
1620  QgsDebugMsg( "parent mPath = " + parent->path() );
1621  QModelIndex idx = findItem( parent );
1622  if ( !idx.isValid() )
1623  return;
1624  QgsDebugMsg( "valid" );
1625  beginInsertRows( idx, first, last );
1626  QgsDebugMsg( "end" );
1627 }
1629 {
1630  endInsertRows();
1631 }
1633 {
1634  QgsDebugMsg( "parent mPath = " + parent->path() );
1635  QModelIndex idx = findItem( parent );
1636  if ( !idx.isValid() )
1637  return;
1638  beginRemoveRows( idx, first, last );
1639 }
1641 {
1642  endRemoveRows();
1643 }
1645 {
1650 }
1651 
1652 bool QgsCptCityBrowserModel::canFetchMore( const QModelIndex & parent ) const
1653 {
1654  QgsCptCityDataItem* item = dataItem( parent );
1655  // fetch all items initially so we know which items have children
1656  // (nicer looking and less confusing)
1657 
1658  if ( ! item )
1659  return false;
1660 
1661  // except for "All Ramps" - this is populated when clicked on
1662  if ( item->type() == QgsCptCityDataItem::AllRamps )
1663  return false;
1664 
1665  item->populate();
1666 
1667  return ( ! item->isPopulated() );
1668 }
1669 
1670 void QgsCptCityBrowserModel::fetchMore( const QModelIndex & parent )
1671 {
1672  QgsCptCityDataItem* item = dataItem( parent );
1673  if ( item )
1674  {
1675  item->populate();
1676  QgsDebugMsg( "path = " + item->path() );
1677  }
1678 }
1679 
1680 
1681 #if 0
1682 QStringList QgsCptCityBrowserModel::mimeTypes() const
1683 {
1684  QStringList types;
1685  // In theory the mime type convention is: application/x-vnd.<vendor>.<application>.<type>
1686  // but it seems a bit over formalized. Would be an application/x-qgis-uri better?
1687  types << "application/x-vnd.qgis.qgis.uri";
1688  return types;
1689 }
1690 
1691 QMimeData * QgsCptCityBrowserModel::mimeData( const QModelIndexList &indexes ) const
1692 {
1694  Q_FOREACH ( const QModelIndex &index, indexes )
1695  {
1696  if ( index.isValid() )
1697  {
1698  QgsCptCityDataItem* ptr = ( QgsCptCityDataItem* ) index.internalPointer();
1699  if ( ptr->type() != QgsCptCityDataItem::Layer ) continue;
1700  QgsLayerItem *layer = ( QgsLayerItem* ) ptr;
1701  lst.append( QgsMimeDataUtils::Uri( ayer ) );
1702  }
1703  }
1704  return QgsMimeDataUtils::encodeUriList( lst );
1705 }
1706 
1707 bool QgsCptCityBrowserModel::dropMimeData( const QMimeData * data, Qt::DropAction action, int row, int column, const QModelIndex & parent )
1708 {
1709  Q_UNUSED( row );
1710  Q_UNUSED( column );
1711 
1712  QgsCptCityDataItem* destItem = dataItem( parent );
1713  if ( !destItem )
1714  {
1715  QgsDebugMsg( "DROP PROBLEM!" );
1716  return false;
1717  }
1718 
1719  return destItem->handleDrop( data, action );
1720 }
1721 #endif
1722 
1724 {
1725  void *v = idx.internalPointer();
1726  QgsCptCityDataItem *d = reinterpret_cast<QgsCptCityDataItem*>( v );
1727  Q_ASSERT( !v || d );
1728  return d;
1729 }
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)
QVector< QgsCptCityDataItem * > mItems
An "All ramps item", which contains all items in a flat hierarchy.
virtual Qt::ItemFlags flags(const QModelIndex &index) const override
QgsCptCityDataItem * dataItem(const QModelIndex &idx) const
Returns a list of mime that can describe model indexes.
static QgsCptCityArchive * defaultArchive()
void beginRemoveItems(QgsCptCityDataItem *parent, int first, int last)
virtual int columnCount(const QModelIndex &parent=QModelIndex()) const override
QVector< QgsCptCityDataItem * > children() const
QgsCptCityDirectoryItem(QgsCptCityDataItem *parent, const QString &name, const QString &path)
static QMap< QString, QgsCptCityArchive *> mArchiveRegistry
QgsCptCityColorRamp mRamp
#define QgsDebugMsg(str)
Definition: qgslogger.h:33
QString name() const
static QString defaultBaseDir()
QgsCptCityAllRampsItem(QgsCptCityDataItem *parent, const QString &name, const QVector< QgsCptCityDataItem *> &items)
QMap< QString, QStringList > rampsMap()
virtual 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)
void setVariantName(const QString &variantName)
Definition: qgscolorramp.h:565
QgsCptCitySelectionItem(QgsCptCityDataItem *parent, const QString &name, const QString &path)
QModelIndex findPath(const QString &path)
return index of a path
virtual void addChildItem(QgsCptCityDataItem *child, bool refresh=false)
QMap< QString, QString > QgsStringMap
Definition: qgis.h:328
QgsCptCityArchive(const QString &archiveName=DEFAULT_CPTCITY_ARCHIVE, const QString &baseDir=QString())
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)
static void initArchives(bool loadAll=false)
QVector< QgsCptCityDataItem * > createChildren() override
virtual bool equal(const QgsCptCityDataItem *other) override
QVector< QgsCptCityDataItem *> selectionItems() const
const QgsCptCityColorRamp & ramp() const
virtual 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:34
virtual QModelIndex parent(const QModelIndex &index) const override
virtual 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
QVector< QgsCptCityDataItem * > mSelectionItems
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:165
A Collection: logical collection of subcollections and color ramps.
static void initArchive(const QString &archiveName, const QString &archiveBaseDir)
QVector< QgsCptCityDataItem * > mChildren
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
virtual int count() const override
Returns number of defined colors, or -1 if undefined.
Definition: qgscolorramp.h:127
static void clearArchives()
virtual void deleteChildItem(QgsCptCityDataItem *child)
static QMap< QString, QMap< QString, QString > > mCopyingInfoMap
void refresh(const QString &path)
virtual 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 QString mDefaultArchiveName
bool hasMultiStops() const
Definition: qgscolorramp.h:571
virtual bool equal(const QgsCptCityDataItem *other) override
void beginInsertItems(QgsCptCityDataItem *parent, int first, int last)
QVector< QgsCptCityDataItem * > mRootItems
QString info() const
static int findItem(QVector< QgsCptCityDataItem *> items, QgsCptCityDataItem *item)
bool canFetchMore(const QModelIndex &parent) const override
QString baseDir() const
QVector< QgsCptCityDataItem * > createChildren() override
Item that represents a layer that can be opened with one of the providers.
Definition: qgsdataitem.h:321
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 *> rootItems() const
A selection: contains subdirectories and color ramps.
QgsCptCityDataItem * parent() const
QList< Uri > UriList
QStringList variantList() const
Definition: qgscolorramp.h:561
QVector< QgsCptCityDataItem * > childrenRamps(bool recursive)
QModelIndex findItem(QgsCptCityDataItem *item, QgsCptCityDataItem *parent=nullptr) const
virtual int leafCount() const
static QgsCptCityDataItem * dataItem(QgsCptCityDataItem *parent, const QString &name, const QString &path)
QString variantName() const
Definition: qgscolorramp.h:560
QVector< QgsCptCityDataItem *> mRootItems
static QString findFileName(const QString &target, const QString &startDir, const QString &baseDir)
QString copyingFileName(const QString &dirName) const
QStringList dirEntries() const
static QMap< QString, QgsCptCityArchive *> archiveRegistry()
virtual QVariant headerData(int section, Qt::Orientation orientation, int role=Qt::DisplayRole) const override