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