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