QGIS API Documentation  2.14.0-Essen
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  // QgsDebugMsg( "Entered" );
849  Q_FOREACH ( QgsCptCityDataItem* i, mChildren )
850  {
851  // QgsDebugMsg( QString( "delete child = 0x%0" ).arg(( qlonglong )i, 8, 16, QLatin1Char( '0' ) ) );
852  delete i;
853  }
854 }
855 
857 {
859  QVector< QgsCptCityDataItem* > deleteItems;
860 
861  populate();
862 
863  // recursively add children
864  Q_FOREACH ( QgsCptCityDataItem* childItem, children() )
865  {
866  QgsCptCityCollectionItem* collectionItem = dynamic_cast<QgsCptCityCollectionItem*>( childItem );
867  QgsCptCityColorRampItem* rampItem = dynamic_cast<QgsCptCityColorRampItem*>( childItem );
868  QgsDebugMsgLevel( QString( "child path= %1 coll= %2 ramp = %3" ).arg( childItem->path() ).arg( nullptr != collectionItem ).arg( nullptr != rampItem ), 2 );
869  if ( collectionItem && recursive )
870  {
871  collectionItem->populate();
872  rampItems << collectionItem->childrenRamps( true );
873  }
874  else if ( rampItem )
875  {
876  // init rampItem to get palette and icon, test if is valid after loading file
877  rampItem->init();
878  if ( rampItem->isValid() )
879  rampItems << rampItem;
880  else
881  deleteItems << rampItem;
882  }
883  else
884  {
885  QgsDebugMsg( "invalid item " + childItem->path() );
886  }
887  }
888 
889  // delete invalid items - this is not efficient, but should only happens once
890  Q_FOREACH ( QgsCptCityDataItem* deleteItem, deleteItems )
891  {
892  QgsDebugMsg( QString( "item %1 is invalid, will be deleted" ).arg( deleteItem->path() ) );
893  int i = mChildren.indexOf( deleteItem );
894  if ( i != -1 )
895  mChildren.remove( i );
896  delete deleteItem;
897  }
898 
899  return rampItems;
900 }
901 
902 //-----------------------------------------------------------------------
904  const QString& name, const QString& path )
905  : QgsCptCityCollectionItem( parent, name, path )
906 {
907  mType = Directory;
909  if ( ! mValid )
910  {
911  QgsDebugMsg( "created invalid dir item, path = " + QgsCptCityArchive::defaultBaseDir()
912  + '/' + mPath );
913  }
914 
915  // parse DESC.xml to get mInfo
916  mInfo = "";
917  QString fileName = QgsCptCityArchive::defaultBaseDir() + '/' +
918  mPath + '/' + "DESC.xml";
919  QgsStringMap descMap = QgsCptCityArchive::description( fileName );
920  if ( descMap.contains( "name" ) )
921  mInfo = descMap.value( "name" );
922 
923  // populate();
924 }
925 
927 {
928 }
929 
931 {
932  if ( ! mValid )
934 
936 
937  // add children schemes
939  while ( it.hasNext() )
940  {
941  it.next();
942  // QgsDebugMsg( "schemeName = " + it.key() );
943  QgsCptCityDataItem* item =
944  new QgsCptCityColorRampItem( this, it.key(), it.key(), it.value() );
945  if ( item->isValid() )
946  children << item;
947  else
948  delete item;
949  }
950 
951  // add children dirs
952  Q_FOREACH ( const QString& childPath, dirEntries() )
953  {
954  QgsCptCityDataItem* childItem =
955  QgsCptCityDirectoryItem::dataItem( this, childPath, mPath + '/' + childPath );
956  if ( childItem )
957  children << childItem;
958  }
959 
960  QgsDebugMsg( QString( "name= %1 path= %2 found %3 children" ).arg( mName, mPath ).arg( children.count() ) );
961 
962  return children;
963 }
964 
966 {
967  if ( ! mRampsMap.isEmpty() )
968  return mRampsMap;
969 
970  QString curName, prevName, prevPath, curVariant, curSep, schemeName;
971  QStringList listVariant;
972  QStringList schemeNamesAll, schemeNames;
973  bool prevAdd, curAdd;
974 
976  schemeNamesAll = dir.entryList( QStringList( "*.svg" ), QDir::Files, QDir::Name );
977 
978  // TODO detect if there are duplicate names with different variant counts, combine in 1
979  for ( int i = 0; i < schemeNamesAll.count(); i++ )
980  {
981  // schemeName = QFileInfo( schemeNamesAll[i] ).baseName();
982  schemeName = schemeNamesAll[i];
983  schemeName.chop( 4 );
984  // QgsDebugMsg("=============");
985  // QgsDebugMsg("scheme = "+schemeName);
986  curName = schemeName;
987  curVariant = "";
988 
989  // find if name ends with 1-3 digit number
990  // TODO need to detect if ends with b/c also
991  if ( schemeName.length() > 1 && schemeName.endsWith( 'a' ) && ! listVariant.isEmpty() &&
992  (( prevName + listVariant.last() + 'a' ) == curName ) )
993  {
994  curName = prevName;
995  curVariant = listVariant.last() + 'a';
996  }
997  else
998  {
999  QRegExp rxVariant( "^(.*[^\\d])(\\d{1,3})$" );
1000  int pos = rxVariant.indexIn( schemeName );
1001  if ( pos > -1 )
1002  {
1003  curName = rxVariant.cap( 1 );
1004  curVariant = rxVariant.cap( 2 );
1005  }
1006  }
1007 
1008  curSep = curName.right( 1 );
1009  if ( curSep == "-" || curSep == "_" )
1010  {
1011  curName.chop( 1 );
1012  curVariant = curSep + curVariant;
1013  }
1014 
1015  if ( prevName == "" )
1016  prevName = curName;
1017 
1018  // add element, unless it is empty, or a variant of last element
1019  prevAdd = false;
1020  curAdd = false;
1021  if ( curName == "" )
1022  curName = "__empty__";
1023  // if current is a variant of last, don't add previous and append current variant
1024  if ( curName == prevName )
1025  {
1026  // add current element if it is the last one in the archive
1027  if ( i == schemeNamesAll.count() - 1 )
1028  prevAdd = true;
1029  listVariant << curVariant;
1030  }
1031  else
1032  {
1033  if ( prevName != "" )
1034  {
1035  prevAdd = true;
1036  }
1037  // add current element if it is the last one in the archive
1038  if ( i == schemeNamesAll.count() - 1 )
1039  curAdd = true;
1040  }
1041 
1042  // QgsDebugMsg(QString("prevAdd=%1 curAdd=%2 prevName=%3 curName=%4 count=%5").arg(prevAdd).arg(curAdd).arg(prevName).arg(curName).arg(listVariant.count()));
1043 
1044  if ( prevAdd )
1045  {
1046  // depending on number of variants, make one or more items
1047  if ( listVariant.isEmpty() )
1048  {
1049  // set num colors=-1 to parse file on request only
1050  // mSchemeNumColors[ prevName ] = -1;
1051  schemeNames << prevName;
1052  mRampsMap[ mPath + '/' + prevName ] = QStringList();
1053  }
1054  else if ( listVariant.count() <= 3 )
1055  {
1056  // for 1-2 items, create independent items
1057  for ( int j = 0; j < listVariant.count(); j++ )
1058  {
1059  // mSchemeNumColors[ prevName + listVariant[j] ] = -1;
1060  schemeNames << prevName + listVariant[j];
1061  mRampsMap[ mPath + '/' + prevName + listVariant[j] ] = QStringList();
1062  }
1063  }
1064  else
1065  {
1066  // mSchemeVariants[ path + '/' + prevName ] = listVariant;
1067  mRampsMap[ mPath + '/' + prevName ] = listVariant;
1068  schemeNames << prevName;
1069  }
1070  listVariant.clear();
1071  }
1072  if ( curAdd )
1073  {
1074  if ( curVariant != "" )
1075  curName += curVariant;
1076  schemeNames << curName;
1077  mRampsMap[ mPath + '/' + curName ] = QStringList();
1078  }
1079  // save current to compare next
1080  if ( prevAdd || curAdd )
1081  {
1082  prevName = curName;
1083  if ( curVariant != "" )
1084  listVariant << curVariant;
1085  }
1086 
1087  }
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  return mRampsMap;
1096 }
1097 
1099 {
1101  '/' + mPath ).entryList( QDir::Dirs | QDir::NoDotAndDotDot, QDir::Name );
1102 }
1103 
1105 {
1106  //QgsDebugMsg ( mPath + " x " + other->mPath );
1107  if ( type() != other->type() )
1108  {
1109  return false;
1110  }
1111  return ( path() == other->path() );
1112 }
1113 
1115  const QString& name, const QString& path )
1116 {
1117  QgsDebugMsg( "name= " + name + " path= " + path );
1118 
1119  // first create item with constructor
1120  QgsCptCityDirectoryItem* dirItem = new QgsCptCityDirectoryItem( parent, name, path );
1121  if ( dirItem && ! dirItem->isValid() )
1122  {
1123  delete dirItem;
1124  return nullptr;
1125  }
1126  if ( ! dirItem )
1127  return nullptr;
1128 
1129  // fetch sub-dirs and ramps to know what to do with this item
1130  QStringList theDirEntries = dirItem->dirEntries();
1131  QMap< QString, QStringList > theRampsMap = dirItem->rampsMap();
1132 
1133  QgsDebugMsg( QString( "item has %1 dirs and %2 ramps" ).arg( theDirEntries.count() ).arg( theRampsMap.count() ) );
1134 
1135  // return item if has at least one subdir
1136  if ( !theDirEntries.isEmpty() )
1137  return dirItem;
1138 
1139  // if 0 ramps, delete item
1140  if ( theRampsMap.isEmpty() )
1141  {
1142  delete dirItem;
1143  return nullptr;
1144  }
1145  // if 1 ramp, return this child's item
1146  // so we don't have a directory with just 1 item (with many variants possibly)
1147  else if ( theRampsMap.count() == 1 )
1148  {
1149  delete dirItem;
1150  QgsCptCityColorRampItem* rampItem =
1151  new QgsCptCityColorRampItem( parent, theRampsMap.begin().key(),
1152  theRampsMap.begin().key(), theRampsMap.begin().value() );
1153  if ( ! rampItem->isValid() )
1154  {
1155  delete rampItem;
1156  return nullptr;
1157  }
1158  return rampItem;
1159  }
1160  return dirItem;
1161 }
1162 
1163 
1164 //-----------------------------------------------------------------------
1166  const QString& name, const QString& path )
1167  : QgsCptCityCollectionItem( parent, name, path )
1168 {
1169  mType = Selection;
1170  mValid = ! path.isNull();
1171  if ( mValid )
1172  parseXML();
1173 }
1174 
1176 {
1177 }
1178 
1180 {
1181  if ( ! mValid )
1183 
1184  QgsCptCityDataItem* item = nullptr;
1186 
1187  QgsDebugMsg( "name= " + mName + " path= " + mPath );
1188 
1189  // add children archives
1190  Q_FOREACH ( QString childPath, mSelectionsList )
1191  {
1192  QgsDebugMsg( "childPath = " + childPath + " name= " + QFileInfo( childPath ).baseName() );
1193  if ( childPath.endsWith( '/' ) )
1194  {
1195  childPath.chop( 1 );
1196  QgsCptCityDataItem* childItem =
1197  QgsCptCityDirectoryItem::dataItem( this, childPath, childPath );
1198  if ( childItem )
1199  {
1200  if ( childItem->isValid() )
1201  children << childItem;
1202  else
1203  delete childItem;
1204  }
1205  }
1206  else
1207  {
1208  // init item to test if is valid after loading file
1209  item = new QgsCptCityColorRampItem( this, childPath, childPath, QString(), true );
1210  if ( item->isValid() )
1211  children << item;
1212  else
1213  delete item;
1214  }
1215  }
1216 
1217  QgsDebugMsg( QString( "path= %1 inserted %2 children" ).arg( mPath ).arg( children.count() ) );
1218 
1219  return children;
1220 }
1221 
1223 {
1224  QString filename = QgsCptCityArchive::defaultBaseDir() + '/' + mPath;
1225 
1226  QgsDebugMsg( "reading file " + filename );
1227 
1228  QFile f( filename );
1229  if ( ! f.open( QFile::ReadOnly ) )
1230  {
1231  QgsDebugMsg( filename + " does not exist" );
1232  return;
1233  }
1234 
1235  // parse the document
1236  QString errMsg;
1237  QDomDocument doc( "selection" );
1238  if ( !doc.setContent( &f, &errMsg ) )
1239  {
1240  f.close();
1241  QgsDebugMsg( "Couldn't parse file " + filename + " : " + errMsg );
1242  return;
1243  }
1244  f.close();
1245 
1246  // read description
1247  QDomElement docElem = doc.documentElement();
1248  if ( docElem.tagName() != "selection" )
1249  {
1250  QgsDebugMsg( "Incorrect root tag: " + docElem.tagName() );
1251  return;
1252  }
1253  QDomElement e = docElem.firstChildElement( "name" );
1254  if ( ! e.isNull() && ! e.text().isNull() )
1255  mName = e.text();
1256  mInfo = docElem.firstChildElement( "synopsis" ).text().simplified();
1257 
1258  // get archives
1259  QDomElement collectsElem = docElem.firstChildElement( "seealsocollects" );
1260  e = collectsElem.firstChildElement( "collect" );
1261  while ( ! e.isNull() )
1262  {
1263  if ( ! e.attribute( "dir" ).isNull() )
1264  {
1265  // TODO parse description and use that, instead of default archive name
1266  mSelectionsList << e.attribute( "dir" ) + '/';
1267  }
1268  e = e.nextSiblingElement();
1269  }
1270  // get individual gradients
1271  QDomElement gradientsElem = docElem.firstChildElement( "gradients" );
1272  e = gradientsElem.firstChildElement( "gradient" );
1273  while ( ! e.isNull() )
1274  {
1275  if ( ! e.attribute( "dir" ).isNull() )
1276  {
1277  // QgsDebugMsg( "add " + e.attribute( "dir" ) + '/' + e.attribute( "file" ) + " to " + selname );
1278  // TODO parse description and save elsewhere
1279  mSelectionsList << e.attribute( "dir" ) + '/' + e.attribute( "file" );
1280  }
1281  e = e.nextSiblingElement();
1282  }
1283 }
1284 
1286 {
1287  //QgsDebugMsg ( mPath + " x " + other->mPath );
1288  if ( type() != other->type() )
1289  {
1290  return false;
1291  }
1292  return ( path() == other->path() );
1293 }
1294 
1295 //-----------------------------------------------------------------------
1297  const QString& name, const QVector<QgsCptCityDataItem*>& items )
1298  : QgsCptCityCollectionItem( parent, name, QString() ), mItems( items )
1299 {
1300  mType = AllRamps;
1301  mValid = true;
1302  // populate();
1303 }
1304 
1306 {
1307 }
1308 
1310 {
1311  if ( ! mValid )
1313 
1315 
1316  // add children ramps of each item
1317  Q_FOREACH ( QgsCptCityDataItem* item, mItems )
1318  {
1319  QgsCptCityCollectionItem* colItem = dynamic_cast< QgsCptCityCollectionItem* >( item );
1320  if ( colItem )
1321  children += colItem->childrenRamps( true );
1322  }
1323 
1324  return children;
1325 }
1326 
1327 //-----------------------------------------------------------------------
1328 
1330  QgsCptCityArchive* archive, ViewType viewType )
1331  : QAbstractItemModel( parent ), mArchive( archive ), mViewType( viewType )
1332 {
1333  Q_ASSERT( mArchive );
1334  QgsDebugMsg( "archiveName = " + archive->archiveName() + " viewType=" + static_cast< int >( viewType ) );
1335  // keep iconsize for now, but not effectively used
1336  mIconSize = QSize( 100, 15 );
1337  addRootItems();
1338 }
1339 
1341 {
1342  removeRootItems();
1343 }
1344 
1346 {
1347  if ( mViewType == Authors )
1348  {
1350  }
1351  else if ( mViewType == Selections )
1352  {
1354  }
1355  QgsDebugMsg( QString( "added %1 root items" ).arg( mRootItems.size() ) );
1356 }
1357 
1359 {
1360  // don't remove root items, they belong to the QgsCptCityArchive
1361  // Q_FOREACH ( QgsCptCityDataItem* item, mRootItems )
1362  // {
1363  // delete item;
1364  // }
1365 
1366  mRootItems.clear();
1367 }
1368 
1370 {
1371  if ( !index.isValid() )
1372  return nullptr;
1373 
1374  Qt::ItemFlags flags = Qt::ItemIsEnabled | Qt::ItemIsSelectable;
1375 
1376  return flags;
1377 }
1378 
1380 {
1381  if ( !index.isValid() )
1382  return QVariant();
1383 
1384  QgsCptCityDataItem *item = dataItem( index );
1385 
1386  if ( !item )
1387  {
1388  return QVariant();
1389  }
1390  else if ( role == Qt::DisplayRole )
1391  {
1392  if ( index.column() == 0 )
1393  return item->name();
1394  if ( index.column() == 1 )
1395  {
1396  return item->info();
1397  }
1398  }
1399  else if ( role == Qt::ToolTipRole )
1400  {
1401  if ( item->type() == QgsCptCityDataItem::ColorRamp &&
1402  mViewType == List )
1403  return item->path() + '\n' + item->info();
1404  return item->toolTip();
1405  }
1406  else if ( role == Qt::DecorationRole && index.column() == 1 &&
1407  item->type() == QgsCptCityDataItem::ColorRamp )
1408  {
1409  // keep iconsize for now, but not effectively used
1410  return item->icon( mIconSize );
1411  }
1412  else if ( role == Qt::FontRole &&
1413  dynamic_cast< QgsCptCityCollectionItem* >( item ) )
1414  {
1415  // collectionitems are larger and bold
1416  QFont font;
1417  font.setPointSize( 11 ); //FIXME why is the font so small?
1418  font.setBold( true );
1419  return font;
1420  }
1421  else
1422  {
1423  // unsupported role
1424  return QVariant();
1425  }
1426  return QVariant();
1427 }
1428 
1429 QVariant QgsCptCityBrowserModel::headerData( int section, Qt::Orientation orientation, int role ) const
1430 {
1431  Q_UNUSED( section );
1432  if ( orientation == Qt::Horizontal && role == Qt::DisplayRole )
1433  {
1434  if ( section == 0 )
1435  return QVariant( tr( "Name" ) );
1436  else if ( section == 1 )
1437  return QVariant( tr( "Info" ) );
1438  }
1439  return QVariant();
1440 }
1441 
1443 {
1444  //qDebug("rowCount: idx: (valid %d) %d %d", parent.isValid(), parent.row(), parent.column());
1445 
1446  if ( !parent.isValid() )
1447  {
1448  // root item: its children are top level items
1449  return mRootItems.count(); // mRoot
1450  }
1451  else
1452  {
1453  // ordinary item: number of its children
1454  QgsCptCityDataItem *item = dataItem( parent );
1455  return item ? item->rowCount() : 0;
1456  }
1457 }
1458 
1460 {
1461  if ( !parent.isValid() )
1462  return true; // root item: its children are top level items
1463 
1464  QgsCptCityDataItem *item = dataItem( parent );
1465 
1466  return item && item->hasChildren();
1467 }
1468 
1470 {
1471  Q_UNUSED( parent );
1472  return 2;
1473 }
1474 
1476 {
1477  QModelIndex theIndex; // starting from root
1478  bool foundParent = false, foundChild = true;
1479  QString itemPath;
1480 
1481  QgsDebugMsg( "path = " + path );
1482 
1483  // special case if searching for first item "All Ramps", do not search into tree
1484  if ( path.isEmpty() )
1485  {
1486  for ( int i = 0; i < rowCount( theIndex ); i++ )
1487  {
1488  QModelIndex idx = index( i, 0, theIndex );
1489  QgsCptCityDataItem *item = dataItem( idx );
1490  if ( !item )
1491  return QModelIndex(); // an error occurred
1492 
1493  itemPath = item->path();
1494 
1495  if ( itemPath == path )
1496  {
1497  QgsDebugMsg( "Arrived " + itemPath );
1498  return idx; // we have found the item we have been looking for
1499  }
1500  }
1501  }
1502 
1503  while ( foundChild )
1504  {
1505  foundChild = false; // assume that the next child item will not be found
1506 
1507  int i = 0;
1508  // if root skip first item "All Ramps"
1509  if ( itemPath.isEmpty() )
1510  i = 1;
1511  for ( ; i < rowCount( theIndex ); i++ )
1512  {
1513  QModelIndex idx = index( i, 0, theIndex );
1514  QgsCptCityDataItem *item = dataItem( idx );
1515  if ( !item )
1516  return QModelIndex(); // an error occurred
1517 
1518  itemPath = item->path();
1519 
1520  if ( itemPath == path )
1521  {
1522  QgsDebugMsg( "Arrived " + itemPath );
1523  return idx; // we have found the item we have been looking for
1524  }
1525 
1526  if ( ! itemPath.endsWith( '/' ) )
1527  itemPath += '/';
1528 
1529  foundParent = false;
1530 
1531  // QgsDebugMsg( "path= " + path + " itemPath= " + itemPath );
1532 
1533  // if we are using a selection collection, search for target in the mapping in this group
1534  if ( item->type() == QgsCptCityDataItem::Selection )
1535  {
1536  const QgsCptCitySelectionItem* selItem = dynamic_cast<const QgsCptCitySelectionItem *>( item );
1537  if ( selItem )
1538  {
1539  Q_FOREACH ( QString childPath, selItem->selectionsList() )
1540  {
1541  if ( childPath.endsWith( '/' ) )
1542  childPath.chop( 1 );
1543  // QgsDebugMsg( "childPath= " + childPath );
1544  if ( path.startsWith( childPath ) )
1545  {
1546  foundParent = true;
1547  break;
1548  }
1549  }
1550  }
1551  }
1552  // search for target in parent directory
1553  else if ( path.startsWith( itemPath ) )
1554  {
1555  foundParent = true;
1556  }
1557 
1558  if ( foundParent )
1559  {
1560  QgsDebugMsg( "found parent " + path );
1561  // we have found a preceding item: stop searching on this level and go deeper
1562  foundChild = true;
1563  theIndex = idx;
1564  if ( canFetchMore( theIndex ) )
1565  fetchMore( theIndex );
1566  break;
1567  }
1568  }
1569  }
1570 
1571  return QModelIndex(); // not found
1572 }
1573 
1575 {
1576  beginResetModel();
1577  removeRootItems();
1578  addRootItems();
1579  endResetModel();
1580 }
1581 
1582 /* Refresh dir path */
1584 {
1585  QModelIndex idx = findPath( path );
1586  if ( idx.isValid() )
1587  {
1588  QgsCptCityDataItem* item = dataItem( idx );
1589  if ( item )
1590  item->refresh();
1591  }
1592 }
1593 
1594 QModelIndex QgsCptCityBrowserModel::index( int row, int column, const QModelIndex &parent ) const
1595 {
1596  QgsCptCityDataItem *p = dataItem( parent );
1597  const QVector<QgsCptCityDataItem*> &items = p ? p->children() : mRootItems;
1598  QgsCptCityDataItem *item = items.value( row, nullptr );
1599  return item ? createIndex( row, column, item ) : QModelIndex();
1600 }
1601 
1603 {
1604  QgsCptCityDataItem *item = dataItem( index );
1605  if ( !item )
1606  return QModelIndex();
1607 
1608  return findItem( item->parent() );
1609 }
1610 
1612 {
1613  const QVector<QgsCptCityDataItem*> &items = parent ? parent->children() : mRootItems;
1614 
1615  for ( int i = 0; i < items.size(); i++ )
1616  {
1617  if ( items[i] == item )
1618  return createIndex( i, 0, item );
1619 
1620  QModelIndex childIndex = findItem( item, items[i] );
1621  if ( childIndex.isValid() )
1622  return childIndex;
1623  }
1624 
1625  return QModelIndex();
1626 }
1627 
1628 /* Refresh item */
1630 {
1631  QgsCptCityDataItem *item = dataItem( theIndex );
1632  if ( !item )
1633  return;
1634 
1635  QgsDebugMsg( "Refresh " + item->path() );
1636  item->refresh();
1637 }
1638 
1640 {
1641  QgsDebugMsg( "parent mPath = " + parent->path() );
1642  QModelIndex idx = findItem( parent );
1643  if ( !idx.isValid() )
1644  return;
1645  QgsDebugMsg( "valid" );
1646  beginInsertRows( idx, first, last );
1647  QgsDebugMsg( "end" );
1648 }
1650 {
1651  QgsDebugMsg( "Entered" );
1652  endInsertRows();
1653 }
1655 {
1656  QgsDebugMsg( "parent mPath = " + parent->path() );
1657  QModelIndex idx = findItem( parent );
1658  if ( !idx.isValid() )
1659  return;
1660  beginRemoveRows( idx, first, last );
1661 }
1663 {
1664  QgsDebugMsg( "Entered" );
1665  endRemoveRows();
1666 }
1668 {
1669  connect( item, SIGNAL( beginInsertItems( QgsCptCityDataItem*, int, int ) ),
1670  this, SLOT( beginInsertItems( QgsCptCityDataItem*, int, int ) ) );
1671  connect( item, SIGNAL( endInsertItems() ),
1672  this, SLOT( endInsertItems() ) );
1673  connect( item, SIGNAL( beginRemoveItems( QgsCptCityDataItem*, int, int ) ),
1674  this, SLOT( beginRemoveItems( QgsCptCityDataItem*, int, int ) ) );
1675  connect( item, SIGNAL( endRemoveItems() ),
1676  this, SLOT( endRemoveItems() ) );
1677 }
1678 
1680 {
1681  QgsCptCityDataItem* item = dataItem( parent );
1682  // fetch all items initially so we know which items have children
1683  // (nicer looking and less confusing)
1684 
1685  if ( ! item )
1686  return false;
1687 
1688  // except for "All Ramps" - this is populated when clicked on
1689  if ( item->type() == QgsCptCityDataItem::AllRamps )
1690  return false;
1691 
1692  item->populate();
1693 
1694  return ( ! item->isPopulated() );
1695 }
1696 
1698 {
1699  QgsCptCityDataItem* item = dataItem( parent );
1700  if ( item )
1701  {
1702  item->populate();
1703  QgsDebugMsg( "path = " + item->path() );
1704  }
1705 }
1706 
1707 
1708 #if 0
1710 {
1711  QStringList types;
1712  // In theory the mime type convention is: application/x-vnd.<vendor>.<application>.<type>
1713  // but it seems a bit over formalized. Would be an application/x-qgis-uri better?
1714  types << "application/x-vnd.qgis.qgis.uri";
1715  return types;
1716 }
1717 
1718 QMimeData * QgsCptCityBrowserModel::mimeData( const QModelIndexList &indexes ) const
1719 {
1721  Q_FOREACH ( const QModelIndex &index, indexes )
1722  {
1723  if ( index.isValid() )
1724  {
1726  if ( ptr->type() != QgsCptCityDataItem::Layer ) continue;
1727  QgsLayerItem *layer = ( QgsLayerItem* ) ptr;
1728  lst.append( QgsMimeDataUtils::Uri( ayer ) );
1729  }
1730  }
1731  return QgsMimeDataUtils::encodeUriList( lst );
1732 }
1733 
1734 bool QgsCptCityBrowserModel::dropMimeData( const QMimeData * data, Qt::DropAction action, int row, int column, const QModelIndex & parent )
1735 {
1736  Q_UNUSED( row );
1737  Q_UNUSED( column );
1738 
1739  QgsCptCityDataItem* destItem = dataItem( parent );
1740  if ( !destItem )
1741  {
1742  QgsDebugMsg( "DROP PROBLEM!" );
1743  return false;
1744  }
1745 
1746  return destItem->handleDrop( data, action );
1747 }
1748 #endif
1749 
1751 {
1752  void *v = idx.internalPointer();
1753  QgsCptCityDataItem *d = reinterpret_cast<QgsCptCityDataItem*>( v );
1754  Q_ASSERT( !v || d );
1755  return d;
1756 }
const char * className() const
QObject * child(const char *objName, const char *inheritsClass, bool recursiveSearch) const
void connectItem(QgsCptCityDataItem *item)
QVector< QgsCptCityDataItem * > children() const
void clear()
QDomNodeList elementsByTagName(const QString &tagname) const
void beginInsertItems(QgsCptCityDataItem *parent, int first, int last)
void fetchMore(const QModelIndex &parent) override
virtual QgsCptCityDataItem * removeChildItem(QgsCptCityDataItem *child)
void setPointSize(int pointSize)
QString cap(int nth) const
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
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)
QModelIndex findItem(QgsCptCityDataItem *item, QgsCptCityDataItem *parent=nullptr) const
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.
QgsCptCityDirectoryItem(QgsCptCityDataItem *parent, const QString &name, const QString &path)
void append(const T &value)
void fill(const QColor &color)
QString attribute(const QString &name, const QString &defValue) const
#define QgsDebugMsg(str)
Definition: qgslogger.h:33
int indexOf(const T &value, int from) const
static QString defaultBaseDir()
QString archiveName() const
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...
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.
void setAlpha(int alpha)
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
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)
QVector< QgsCptCityDataItem * > rootItems() const
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
QList< QSize > availableSizes(Mode mode, State state) const
virtual int count() const override
Returns number of defined colors, or -1 if undefined.
static QMap< QString, QgsCptCityArchive * > mArchiveRegistry
QString variantName() const
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 baseDir() const
QStringList variantList() const
QString text() const
QString copyingFileName(const QString &dirName) const
virtual bool handleDrop(const QMimeData *, Qt::DropAction)
QString path() const
QStringList selectionsList() 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)
QStringList dirEntries() const
Base class for all items in the model.
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)
QString descFileName(const QString &dirName) const
virtual bool open(QFlags< QIODevice::OpenModeFlag > mode)
const T & value() const
QVector< QgsCptCityDataItem * > mChildren
bool cdUp()
iterator begin()
QString path() const
QString right(int n) const
static void initDefaultArchive()
QModelIndex createIndex(int row, int column, void *ptr) const
virtual QIcon icon()
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
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
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
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
bool canFetchMore(const QModelIndex &parent) const override
int column() const
int length() const
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:301
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 * > childrenRamps(bool recursive)
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
QgsCptCityAllRampsItem(QgsCptCityDataItem *parent, const QString &name, const QVector< QgsCptCityDataItem * > &items)
QString arg(qlonglong a, int fieldWidth, int base, const QChar &fillChar) const
QString toString() const
static QMap< QString, QgsCptCityArchive * > archiveRegistry()
int count(const Key &key) const
QString info() const
static QgsCptCityDataItem * dataItem(QgsCptCityDataItem *parent, const QString &name, const QString &path)
QString name() const
QVector< QgsCptCityDataItem * > mRootItems
const QgsCptCityColorRampV2 & ramp() const
static QString findFileName(const QString &target, const QString &startDir, const QString &baseDir)
QVector< QgsCptCityDataItem * > selectionItems() const
bool hasNext() 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
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