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