QGIS API Documentation  2.0.1-Dufour
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros Groups Pages
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 
41 QMap< QString, QgsCptCityArchive* > QgsCptCityArchive::mArchiveRegistry;
42 QMap< QString, QgsCptCityArchive* > QgsCptCityArchive::archiveRegistry() { return mArchiveRegistry; }
43 QMap< QString, QMap< QString, QString > > QgsCptCityArchive::mCopyingInfoMap;
44 
45 QgsCptCityArchive::QgsCptCityArchive( QString archiveName, QString baseDir )
46  : mArchiveName( archiveName ), mBaseDir( baseDir )
47 {
48  QgsDebugMsg( "archiveName = " + archiveName + " baseDir = " + baseDir );
49 
50  // make Author items
51  QgsCptCityDirectoryItem* dirItem = 0;
52  foreach ( QString path, QDir( mBaseDir ).entryList( QDir::Dirs | QDir::NoDotAndDotDot, QDir::Name ) )
53  {
54  if ( path == "selections" )
55  continue;
56  QgsDebugMsg( "path= " + path );
57  dirItem = new QgsCptCityDirectoryItem( NULL, QFileInfo( path ).baseName(), path );
58  if ( dirItem->isValid() )
59  mRootItems << dirItem;
60  else
61  delete dirItem;
62  }
63 
64  // make selection items
65  QgsCptCitySelectionItem* selItem = 0;
66  QDir seldir( mBaseDir + QDir::separator() + "selections" );
67  QgsDebugMsg( "populating selection from " + seldir.path() );
68  foreach ( QString selfile, seldir.entryList( QStringList( "*.xml" ), QDir::Files ) )
69  {
70  QgsDebugMsg( "file= " + seldir.path() + "/" + selfile );
71  selItem = new QgsCptCitySelectionItem( NULL, QFileInfo( selfile ).baseName(),
72  seldir.dirName() + QDir::separator() + selfile );
73  //TODO remove item if there are no children (e.g. esri in qgis-sel)
74  if ( selItem->isValid() )
75  mSelectionItems << selItem;
76  else
77  delete selItem;
78  }
79 
80  // make "All Ramps items" (which will contain all ramps without hierarchy)
81  QgsCptCityAllRampsItem* allRampsItem;
82  allRampsItem = new QgsCptCityAllRampsItem( NULL, QObject::tr( "All Ramps" ),
83  mRootItems );
84  mRootItems.prepend( allRampsItem );
85  allRampsItem = new QgsCptCityAllRampsItem( NULL, QObject::tr( "All Ramps" ),
87  mSelectionItems.prepend( allRampsItem );
88 }
89 
91 {
92  foreach ( QgsCptCityDataItem* item, mRootItems )
93  delete item;
94  foreach ( QgsCptCityDataItem* item, mSelectionItems )
95  delete item;
96  mRootItems.clear();
97  mSelectionItems.clear();
98 }
99 
101 {
102  // if was set with setBaseDir, return that value
103  // else return global default
104  if ( ! mBaseDir.isNull() )
105  return mBaseDir;
106  else
108 }
109 
110 QString QgsCptCityArchive::baseDir( QString archiveName )
111 {
112  // search for matching archive in the registry
113  if ( archiveName.isNull() )
114  archiveName = DEFAULT_CPTCITY_ARCHIVE;
115  if ( mArchiveRegistry.contains( archiveName ) )
116  return mArchiveRegistry.value( archiveName )->baseDir();
117  else
118  return defaultBaseDir();
119 }
120 
122 {
123  QString baseDir, archiveName;
124  QSettings settings;
125 
126  // use CptCity/baseDir setting if set, default is user dir
127  baseDir = settings.value( "CptCity/baseDir",
128  QgsApplication::pkgDataPath() + "/resources" ).toString();
129  // sub-dir defaults to cpt-city
130  archiveName = settings.value( "CptCity/archiveName", DEFAULT_CPTCITY_ARCHIVE ).toString();
131 
132  return baseDir + QDir::separator() + archiveName;
133 }
134 
135 
136 QString QgsCptCityArchive::findFileName( const QString & target, const QString & startDir, const QString & baseDir )
137 {
138  // QgsDebugMsg( "target= " + target + " startDir= " + startDir + " baseDir= " + baseDir );
139 
140  if ( startDir == "" || ! startDir.startsWith( baseDir ) )
141  return QString();
142 
143  QDir dir = QDir( startDir );
144  //todo test when
145  while ( ! dir.exists( target ) && dir.path() != baseDir )
146  {
147  if ( ! dir.cdUp() )
148  break;
149  }
150  if ( ! dir.exists( target ) )
151  return QString();
152  else
153  return dir.path() + QDir::separator() + target;
154 }
155 
156 
157 QString QgsCptCityArchive::copyingFileName( const QString& path ) const
158 {
159  return QgsCptCityArchive::findFileName( "COPYING.xml",
160  baseDir() + QDir::separator() + path, baseDir() );
161 }
162 
163 QString QgsCptCityArchive::descFileName( const QString& path ) const
164 {
165  return QgsCptCityArchive::findFileName( "DESC.xml",
166  baseDir() + QDir::separator() + path, baseDir() );
167 }
168 
170 {
171  QgsStringMap copyingMap;
172 
173  if ( fileName.isNull() )
174  return copyingMap;
175 
176  if ( QgsCptCityArchive::mCopyingInfoMap.contains( fileName ) )
177  {
178  QgsDebugMsg( "found copying info in copyingInfoMap, file = " + fileName );
179  return QgsCptCityArchive::mCopyingInfoMap.value( fileName );
180  }
181 
182  QgsDebugMsg( "fileName = " + fileName );
183 
184  // import xml file
185  QFile f( fileName );
186  if ( !f.open( QFile::ReadOnly ) )
187  {
188  QgsDebugMsg( "Couldn't open xml file: " + fileName );
189  return copyingMap;
190  }
191 
192  // parse the document
193  QDomDocument doc( "license" );
194  if ( !doc.setContent( &f ) )
195  {
196  f.close();
197  QgsDebugMsg( "Couldn't parse xml file: " + fileName );
198  return copyingMap;
199  }
200  f.close();
201 
202  // get root element
203  QDomElement docElem = doc.documentElement();
204  if ( docElem.tagName() != "copying" )
205  {
206  QgsDebugMsg( "Incorrect root tag: " + docElem.tagName() );
207  return copyingMap;
208  }
209 
210  // load author information
211  QDomElement authorsElement = docElem.firstChildElement( "authors" );
212  if ( authorsElement.isNull() )
213  {
214  QgsDebugMsg( "authors tag missing" );
215  }
216  else
217  {
218  QDomElement e = authorsElement.firstChildElement();
219  QStringList authors;
220  while ( ! e.isNull() )
221  {
222  if ( e.tagName() == "author" )
223  {
224  if ( ! e.firstChildElement( "name" ).isNull() )
225  authors << e.firstChildElement( "name" ).text().simplified();
226  // org???
227  }
228  e = e.nextSiblingElement();
229  }
230  copyingMap[ "authors" ] = authors.join( ", " );
231  }
232 
233  // load license information
234  QDomElement licenseElement = docElem.firstChildElement( "license" );
235  if ( licenseElement.isNull() )
236  {
237  QgsDebugMsg( "license tag missing" );
238  }
239  else
240  {
241  QDomElement e = licenseElement.firstChildElement( "informal" );
242  if ( ! e.isNull() )
243  copyingMap[ "license/informal" ] = e.text().simplified();
244  e = licenseElement.firstChildElement( "year" );
245  if ( ! e.isNull() )
246  copyingMap[ "license/year" ] = e.text().simplified();
247  e = licenseElement.firstChildElement( "text" );
248  if ( ! e.isNull() && e.attribute( "href" ) != QString() )
249  copyingMap[ "license/url" ] = e.attribute( "href" );
250  }
251 
252  // load src information
253  QDomElement element = docElem.firstChildElement( "src" );
254  if ( element.isNull() )
255  {
256  QgsDebugMsg( "src tag missing" );
257  }
258  else
259  {
260  QDomElement e = element.firstChildElement( "link" );
261  if ( ! e.isNull() && e.attribute( "href" ) != QString() )
262  copyingMap[ "src/link" ] = e.attribute( "href" );
263  }
264 
265  // save copyingMap for further access
266  QgsCptCityArchive::mCopyingInfoMap[ fileName ] = copyingMap;
267  return copyingMap;
268 }
269 
271 {
272  QgsStringMap descMap;
273 
274  QgsDebugMsg( "description fileName = " + fileName );
275 
276  QFile f( fileName );
277  if ( ! f.open( QFile::ReadOnly ) )
278  {
279  QgsDebugMsg( "description file " + fileName + " ] does not exist" );
280  return descMap;
281  }
282 
283  // parse the document
284  QString errMsg;
285  QDomDocument doc( "description" );
286  if ( !doc.setContent( &f, &errMsg ) )
287  {
288  f.close();
289  QgsDebugMsg( "Couldn't parse file " + fileName + " : " + errMsg );
290  return descMap;
291  }
292  f.close();
293 
294  // read description
295  QDomElement docElem = doc.documentElement();
296  if ( docElem.tagName() != "description" )
297  {
298  QgsDebugMsg( "Incorrect root tag: " + docElem.tagName() );
299  return descMap;
300  }
301  // should we make sure the <dir> tag is ok?
302 
303  QDomElement e = docElem.firstChildElement( "name" );
304  if ( e.isNull() )
305  {
306  QgsDebugMsg( "name tag missing" );
307  }
308  descMap[ "name" ] = e.text().simplified();
309  e = docElem.firstChildElement( "full" );
310  if ( e.isNull() )
311  {
312  QgsDebugMsg( "full tag missing" );
313  }
314  descMap[ "full" ] = e.text().simplified();
315 
316  return descMap;
317 }
318 
319 QMap< double, QPair<QColor, QColor> >QgsCptCityArchive::gradientColorMap( const QString& fileName )
320 {
321  QMap< double, QPair<QColor, QColor> > colorMap;
322 
323  // import xml file
324  QFile f( fileName );
325  if ( !f.open( QFile::ReadOnly ) )
326  {
327  QgsDebugMsg( "Couldn't open SVG file: " + fileName );
328  return colorMap;
329  }
330 
331  // parse the document
332  QDomDocument doc( "gradient" );
333  if ( !doc.setContent( &f ) )
334  {
335  f.close();
336  QgsDebugMsg( "Couldn't parse SVG file: " + fileName );
337  return colorMap;
338  }
339  f.close();
340 
341  QDomElement docElem = doc.documentElement();
342 
343  if ( docElem.tagName() != "svg" )
344  {
345  QgsDebugMsg( "Incorrect root tag: " + docElem.tagName() );
346  return colorMap;
347  }
348 
349  // load color ramp from first linearGradient node
350  QDomElement rampsElement = docElem.firstChildElement( "linearGradient" );
351  if ( rampsElement.isNull() )
352  {
353  QDomNodeList nodeList = docElem.elementsByTagName( "linearGradient" );
354  if ( ! nodeList.isEmpty() )
355  rampsElement = nodeList.at( 0 ).toElement();
356  }
357  if ( rampsElement.isNull() )
358  {
359  QgsDebugMsg( "linearGradient tag missing" );
360  return colorMap;
361  }
362 
363  // loop for all stop tags
364  QDomElement e = rampsElement.firstChildElement();
365 
366  while ( !e.isNull() )
367  {
368  if ( e.tagName() == "stop" )
369  {
370  //todo integrate this into symbollayerutils, keep here for now...
371  double offset;
372  QString offsetStr = e.attribute( "offset" ); // offset="50.00%" | offset="0.5"
373  QString colorStr = e.attribute( "stop-color", "" ); // stop-color="rgb(222,235,247)"
374  QString opacityStr = e.attribute( "stop-opacity", "1.0" ); // stop-opacity="1.0000"
375  if ( offsetStr.endsWith( "%" ) )
376  offset = offsetStr.remove( offsetStr.size() - 1, 1 ).toDouble() / 100.0;
377  else
378  offset = offsetStr.toDouble();
379 
380  // QColor color( 255, 0, 0 ); // red color as a warning :)
381  QColor color = QgsSymbolLayerV2Utils::parseColor( colorStr );
382  if ( color != QColor() )
383  {
384  int alpha = opacityStr.toDouble() * 255; // test
385  color.setAlpha( alpha );
386  if ( colorMap.contains( offset ) )
387  colorMap[offset].second = color;
388  else
389  colorMap[offset] = qMakePair( color, color );
390  }
391  else
392  {
393  QgsDebugMsg( QString( "at offset=%1 invalid color" ).arg( offset ) );
394  }
395  }
396  else
397  {
398  QgsDebugMsg( "unknown tag: " + e.tagName() );
399  }
400 
401  e = e.nextSiblingElement();
402  }
403 
404  return colorMap;
405 }
406 
408 {
409  return ( mRootItems.isEmpty() );
410 }
411 
412 
414 {
415  QSettings settings;
416  mDefaultArchiveName = settings.value( "CptCity/archiveName", DEFAULT_CPTCITY_ARCHIVE ).toString();
419  else
420  return NULL;
421 }
422 
423 void QgsCptCityArchive::initArchive( QString archiveName, QString archiveBaseDir )
424 {
425  QgsDebugMsg( "archiveName = " + archiveName + " archiveBaseDir = " + archiveBaseDir );
426  QgsCptCityArchive *archive = new QgsCptCityArchive( archiveName, archiveBaseDir );
427  if ( mArchiveRegistry.contains( archiveName ) )
428  delete mArchiveRegistry[ archiveName ];
429  mArchiveRegistry[ archiveName ] = archive;
430 }
431 
433 {
434  QSettings settings;
435  // use CptCity/baseDir setting if set, default is user dir
436  QString baseDir = settings.value( "CptCity/baseDir",
437  QgsApplication::pkgDataPath() + "/resources" ).toString();
438  // sub-dir defaults to
439  QString defArchiveName = settings.value( "CptCity/archiveName", DEFAULT_CPTCITY_ARCHIVE ).toString();
440 
441  if ( ! mArchiveRegistry.contains( defArchiveName ) )
442  initArchive( defArchiveName, baseDir + QDir::separator() + defArchiveName );
443 }
444 
446 {
447  QgsStringMap archivesMap;
448  QString baseDir, defArchiveName;
449  QSettings settings;
450 
451  // use CptCity/baseDir setting if set, default is user dir
452  baseDir = settings.value( "CptCity/baseDir",
453  QgsApplication::pkgDataPath() + "/resources" ).toString();
454  // sub-dir defaults to
455  defArchiveName = settings.value( "CptCity/archiveName", DEFAULT_CPTCITY_ARCHIVE ).toString();
456 
457  QgsDebugMsg( "baseDir= " + baseDir + " defArchiveName= " + defArchiveName );
458  if ( loadAll )
459  {
460  QDir dir( baseDir );
461  foreach ( QString entry, dir.entryList( QStringList( "cpt-city*" ), QDir::Dirs ) )
462  {
463  if ( QFile::exists( baseDir + QDir::separator() + entry + "/VERSION.xml" ) )
464  archivesMap[ entry ] = baseDir + QDir::separator() + entry;
465  }
466  }
467  else
468  {
469  archivesMap[ defArchiveName ] = baseDir + QDir::separator() + defArchiveName;
470  }
471 
472  for ( QgsStringMap::iterator it = archivesMap.begin();
473  it != archivesMap.end(); ++it )
474  {
475  if ( QDir( it.value() ).exists() )
476  QgsCptCityArchive::initArchive( it.key(), it.value() );
477  else
478  {
479  QgsDebugMsg( QString( "not loading archive [%1] because dir %2 does not exist " ).arg( it.key() ).arg( it.value() ) );
480  }
481  }
482  mDefaultArchiveName = defArchiveName;
483 }
484 
486 {
487  for ( QMap< QString, QgsCptCityArchive* >::iterator it = mArchiveRegistry.begin();
488  it != mArchiveRegistry.end(); ++it )
489  delete it.value();
490  mArchiveRegistry.clear();
491 }
492 
493 
494 // --------
495 
497  QString name, QString path )
498 // Do not pass parent to QObject, Qt would delete this when parent is deleted
499  : QObject(), 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 
526 QVector<QgsCptCityDataItem*> QgsCptCityDataItem::createChildren()
527 {
528  QVector<QgsCptCityDataItem*> children;
529  return children;
530 }
531 
533 {
534  if ( mPopulated )
535  return;
536 
537  QgsDebugMsg( "mPath = " + mPath );
538 
539  QApplication::setOverrideCursor( Qt::WaitCursor );
540 
541  QVector<QgsCptCityDataItem*> children = createChildren();
542  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 
549  QApplication::restoreOverrideCursor();
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  foreach ( QgsCptCityDataItem *child, mChildren )
566  {
567  if ( child )
568  count += child->leafCount();
569  }
570  return count;
571 }
572 
573 
575 {
576  return ( mPopulated ? mChildren.count() > 0 : 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[i]->mType == child->mType &&
590  mChildren[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[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( 0 );
648  return child;
649 }
650 
651 int QgsCptCityDataItem::findItem( QVector<QgsCptCityDataItem*> items, QgsCptCityDataItem * item )
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 
668  QVector<QgsCptCityDataItem*> items = createChildren( );
669 
670  // Remove no more present items
671  QVector<QgsCptCityDataItem*> remove;
672  foreach ( QgsCptCityDataItem *child, mChildren )
673  {
674  if ( findItem( items, child ) >= 0 )
675  continue;
676  remove.append( child );
677  }
678  foreach ( QgsCptCityDataItem *child, remove )
679  {
680  deleteChildItem( child );
681  }
682 
683  // Add new items
684  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 
695  QApplication::restoreOverrideCursor();
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  QString name, QString path, QString variantName, bool initialize )
712  : QgsCptCityDataItem( ColorRamp, parent, name, path ),
713  mInitialised( false ), mRamp( path, variantName, false )
714 {
715  // QgsDebugMsg( "name= " + name + " path= " + path );
716  mPopulated = true;
717  if ( initialize )
718  init();
719 }
720 
722  QString name, QString path, QStringList variantList, bool initialize )
723  : QgsCptCityDataItem( ColorRamp, parent, name, path ),
724  mInitialised( false ), mRamp( path, variantList, QString(), false )
725 {
726  // QgsDebugMsg( "name= " + name + " path= " + path );
727  mPopulated = true;
728  if ( initialize )
729  init();
730 }
731 
732 // TODO only load file when icon is requested...
734 {
735  if ( mInitialised )
736  return;
737  mInitialised = true;
738 
739  QgsDebugMsg( "path = " + path() );
740 
741  // make preview from variant if exists
742  QStringList variantList = mRamp.variantList();
743  if ( mRamp.variantName().isNull() && ! variantList.isEmpty() )
744  mRamp.setVariantName( variantList[ variantList.count() / 2 ] );
745 
746  mRamp.loadFile();
747 
748  // is this item valid? this might fail when there are variants, check
749  if ( ! QFile::exists( mRamp.fileName() ) )
750  mValid = false;
751  else
752  mValid = true;
753 
754  // load file and set info
755  if ( mRamp.count() > 0 )
756  {
757  if ( variantList.isEmpty() )
758  {
759  int count = mRamp.count();
760  if ( mRamp.isDiscrete() )
761  count--;
762  mInfo = QString::number( count ) + " " + tr( "colors" ) + " - ";
763  if ( mRamp.isDiscrete() )
764  mInfo += tr( "discrete" );
765  else
766  {
767  if ( !mRamp.hasMultiStops() )
768  mInfo += tr( "continuous" );
769  else
770  mInfo += tr( "continuous (multi)" );
771  }
772  mShortInfo = QFileInfo( mName ).fileName();
773  }
774  else
775  {
776  mInfo = QString::number( variantList.count() ) + " " + tr( "variants" );
777  // mShortInfo = QFileInfo( mName ).fileName() + " (" + QString::number( variantList.count() ) + ")";
778  mShortInfo = QFileInfo( mName ).fileName();
779  }
780  }
781  else
782  {
783  mInfo = "";
784  }
785 
786 }
787 
789 {
790  //QgsDebugMsg ( mPath + " x " + other->mPath );
791  if ( type() != other->type() )
792  {
793  return false;
794  }
795  //const QgsCptCityColorRampItem *o = qobject_cast<const QgsCptCityColorRampItem *> ( other );
796  const QgsCptCityColorRampItem *o = dynamic_cast<const QgsCptCityColorRampItem *>( other );
797  return ( mPath == o->mPath && mName == o->mName &&
798  ramp().variantName() == o->ramp().variantName() );
799 }
800 
802 {
803  return icon( QSize( 100, 15 ) );
804 }
805 
806 QIcon QgsCptCityColorRampItem::icon( const QSize& size )
807 {
808  foreach ( QIcon icon, mIcons )
809  {
810  if ( icon.availableSizes().contains( size ) )
811  return icon;
812  }
813 
814  QIcon icon( size );
815 
816  init();
817 
818  if ( mValid && mRamp.count() > 0 )
819  {
821  }
822  else
823  {
824  QPixmap blankPixmap( size );
825  blankPixmap.fill( Qt::white );
826  icon = QIcon( blankPixmap );
827  mInfo = "";
828  }
829 
830  mIcons.append( icon );
831  return icon;
832 }
833 
834 // ---------------------------------------------------------------------
836  QString name, QString path )
837  : QgsCptCityDataItem( Collection, parent, name, path ), mPopulatedRamps( false )
838 {
839 }
840 
842 {
843  // QgsDebugMsg( "Entered" );
844  foreach ( QgsCptCityDataItem* i, mChildren )
845  {
846  // QgsDebugMsg( QString( "delete child = 0x%0" ).arg(( qlonglong )i, 8, 16, QLatin1Char( '0' ) ) );
847  delete i;
848  }
849 }
850 
851 QVector< QgsCptCityDataItem* > QgsCptCityCollectionItem::childrenRamps( bool recursive )
852 {
853  QVector< QgsCptCityDataItem* > rampItems;
854  QVector< QgsCptCityDataItem* > deleteItems;
855 
856  populate();
857 
858  // recursively add children
859  foreach ( QgsCptCityDataItem* childItem, children() )
860  {
861  QgsCptCityCollectionItem* collectionItem = dynamic_cast<QgsCptCityCollectionItem*>( childItem );
862  QgsCptCityColorRampItem* rampItem = dynamic_cast<QgsCptCityColorRampItem*>( childItem );
863  QgsDebugMsg( QString( "child path= %1 coll= %2 ramp = %3" ).arg( childItem->path() ).arg( collectionItem != 0 ).arg( rampItem != 0 ) );
864  if ( collectionItem && recursive )
865  {
866  collectionItem->populate();
867  rampItems << collectionItem->childrenRamps( true );
868  }
869  else if ( rampItem )
870  {
871  // init rampItem to get palette and icon, test if is valid after loading file
872  rampItem->init();
873  if ( rampItem->isValid() )
874  rampItems << rampItem;
875  else
876  deleteItems << rampItem;
877  }
878  else
879  {
880  QgsDebugMsg( "invalid item " + childItem->path() );
881  }
882  }
883 
884  // delete invalid items - this is not efficient, but should only happens once
885  foreach ( QgsCptCityDataItem* deleteItem, deleteItems )
886  {
887  QgsDebugMsg( QString( "item %1 is invalid, will be deleted" ).arg( deleteItem->path() ) );
888  int i = mChildren.indexOf( deleteItem );
889  if ( i != -1 )
890  mChildren.remove( i );
891  delete deleteItem;
892  }
893 
894  return rampItems;
895 }
896 
897 //-----------------------------------------------------------------------
899  QString name, QString path )
900  : QgsCptCityCollectionItem( parent, name, path )
901 {
902  mType = Directory;
903  mValid = QDir( QgsCptCityArchive::defaultBaseDir() + QDir::separator() + mPath ).exists();
904  if ( ! mValid )
905  {
906  QgsDebugMsg( "created invalid dir item, path = " + QgsCptCityArchive::defaultBaseDir()
907  + QDir::separator() + mPath );
908  }
909 
910  // parse DESC.xml to get mInfo
911  mInfo = "";
912  QString fileName = QgsCptCityArchive::defaultBaseDir() + QDir::separator() + \
913  mPath + QDir::separator() + "DESC.xml";
914  QgsStringMap descMap = QgsCptCityArchive::description( fileName );
915  if ( descMap.contains( "name" ) )
916  mInfo = descMap.value( "name" );
917 
918  // populate();
919 }
920 
922 {
923 }
924 
925 QVector<QgsCptCityDataItem*> QgsCptCityDirectoryItem::createChildren()
926 {
927  if ( ! mValid )
928  return QVector<QgsCptCityDataItem*>();
929 
930  QVector<QgsCptCityDataItem*> children;
931 
932  // add children schemes
933  QMapIterator< QString, QStringList> it( rampsMap() );
934  while ( it.hasNext() )
935  {
936  it.next();
937  // QgsDebugMsg( "schemeName = " + it.key() );
938  QgsCptCityDataItem* item =
939  new QgsCptCityColorRampItem( this, it.key(), it.key(), it.value() );
940  if ( item->isValid() )
941  children << item;
942  else
943  delete item;
944  }
945 
946  // add children dirs
947  foreach ( QString childPath, dirEntries() )
948  {
949  QgsCptCityDataItem* childItem =
950  QgsCptCityDirectoryItem::dataItem( this, childPath, mPath + QDir::separator() + childPath );
951  if ( childItem )
952  children << childItem;
953  }
954 
955  QgsDebugMsg( QString( "name= %1 path= %2 found %3 children" ).arg( mName ).arg( mPath ).arg( children.count() ) );
956 
957  return children;
958 }
959 
960 QMap< QString, QStringList > QgsCptCityDirectoryItem::rampsMap()
961 {
962  if ( ! mRampsMap.isEmpty() )
963  return mRampsMap;
964 
965  QString curName, prevName, prevPath, curVariant, curSep, schemeName;
966  QStringList listVariant;
967  QStringList schemeNamesAll, schemeNames;
968  int num;
969  bool ok, prevAdd, curAdd;
970 
971  QDir dir( QgsCptCityArchive::defaultBaseDir() + QDir::separator() + mPath );
972  schemeNamesAll = dir.entryList( QStringList( "*.svg" ), QDir::Files, QDir::Name );
973 
974  // TODO detect if there are duplicate names with different variant counts, combine in 1
975  for ( int i = 0; i < schemeNamesAll.count(); i++ )
976  {
977  // schemeName = QFileInfo( schemeNamesAll[i] ).baseName();
978  schemeName = schemeNamesAll[i];
979  schemeName.chop( 4 );
980  // QgsDebugMsg("=============");
981  // QgsDebugMsg("scheme = "+schemeName);
982  curName = schemeName;
983  curVariant = "";
984 
985  // stupid code to find if name ends with 1-3 digit number - should use regexp
986  // TODO need to detect if ends with b/c also
987  if ( schemeName.length() > 1 && schemeName.endsWith( "a" ) && ! listVariant.isEmpty() &&
988  (( prevName + listVariant.last() + "a" ) == curName ) )
989  {
990  curName = prevName;
991  curVariant = listVariant.last() + "a";
992  }
993  else
994  {
995  num = schemeName.right( 3 ).toInt( &ok );
996  Q_UNUSED( num );
997  if ( ok )
998  {
999  curName = schemeName.left( schemeName.size() - 3 );
1000  curVariant = schemeName.right( 3 );
1001  }
1002  else
1003  {
1004  num = schemeName.right( 2 ).toInt( &ok );
1005  if ( ok )
1006  {
1007  curName = schemeName.left( schemeName.size() - 2 );
1008  curVariant = schemeName.right( 2 );
1009  }
1010  else
1011  {
1012  num = schemeName.right( 1 ).toInt( &ok );
1013  if ( ok )
1014  {
1015  curName = schemeName.left( schemeName.size() - 1 );
1016  curVariant = schemeName.right( 1 );
1017  }
1018  }
1019  }
1020  }
1021  curSep = curName.right( 1 );
1022  if ( curSep == "-" || curSep == "_" )
1023  {
1024  curName.chop( 1 );
1025  curVariant = curSep + curVariant;
1026  }
1027 
1028  if ( prevName == "" )
1029  prevName = curName;
1030 
1031  // add element, unless it is empty, or a variant of last element
1032  prevAdd = false;
1033  curAdd = false;
1034  if ( curName == "" )
1035  curName = "__empty__";
1036  // if current is a variant of last, don't add previous and append current variant
1037  if ( curName == prevName )
1038  {
1039  // add current element if it is the last one in the archive
1040  if ( i == schemeNamesAll.count() - 1 )
1041  prevAdd = true;
1042  listVariant << curVariant;
1043  }
1044  else
1045  {
1046  if ( prevName != "" )
1047  {
1048  prevAdd = true;
1049  }
1050  // add current element if it is the last one in the archive
1051  if ( i == schemeNamesAll.count() - 1 )
1052  curAdd = true;
1053  }
1054 
1055  // QgsDebugMsg(QString("prevAdd=%1 curAdd=%2 prevName=%3 curName=%4 count=%5").arg(prevAdd).arg(curAdd).arg(prevName).arg(curName).arg(listVariant.count()));
1056 
1057  if ( prevAdd )
1058  {
1059  // depending on number of variants, make one or more items
1060  if ( listVariant.count() == 0 )
1061  {
1062  // set num colors=-1 to parse file on request only
1063  // mSchemeNumColors[ prevName ] = -1;
1064  schemeNames << prevName;
1065  mRampsMap[ mPath + QDir::separator() + prevName ] = QStringList();
1066  }
1067  else if ( listVariant.count() <= 3 )
1068  {
1069  // for 1-2 items, create independent items
1070  for ( int j = 0; j < listVariant.count(); j++ )
1071  {
1072  // mSchemeNumColors[ prevName + listVariant[j] ] = -1;
1073  schemeNames << prevName + listVariant[j];
1074  mRampsMap[ mPath + QDir::separator() + prevName + listVariant[j] ] = QStringList();
1075  }
1076  }
1077  else
1078  {
1079  // mSchemeVariants[ path + QDir::separator() + prevName ] = listVariant;
1080  mRampsMap[ mPath + QDir::separator() + prevName ] = listVariant;
1081  schemeNames << prevName;
1082  }
1083  listVariant.clear();
1084  }
1085  if ( curAdd )
1086  {
1087  if ( curVariant != "" )
1088  curName += curVariant;
1089  schemeNames << curName;
1090  mRampsMap[ mPath + QDir::separator() + curName ] = QStringList();
1091  }
1092  // save current to compare next
1093  if ( prevAdd || curAdd )
1094  {
1095  prevName = curName;
1096  if ( curVariant != "" )
1097  listVariant << curVariant;
1098  }
1099 
1100  }
1101  //TODO what to do with other vars? e.g. schemeNames
1102  // // add schemes to archive
1103  // mSchemeMap[ path ] = schemeNames;
1104  // schemeCount += schemeName.count();
1105  // schemeNames.clear();
1106  // listVariant.clear();
1107  // prevName = "";
1108  return mRampsMap;
1109 }
1110 
1112 {
1113  return QDir( QgsCptCityArchive::defaultBaseDir() + \
1114  QDir::separator() + mPath ).entryList( QDir::Dirs | QDir::NoDotAndDotDot, QDir::Name );
1115 }
1116 
1118 {
1119  //QgsDebugMsg ( mPath + " x " + other->mPath );
1120  if ( type() != other->type() )
1121  {
1122  return false;
1123  }
1124  return ( path() == other->path() );
1125 }
1126 
1128  QString name, QString path )
1129 {
1130  QgsDebugMsg( "name= " + name + " path= " + path );
1131 
1132  // first create item with constructor
1133  QgsCptCityDirectoryItem* dirItem = new QgsCptCityDirectoryItem( parent, name, path );
1134  if ( dirItem && ! dirItem->isValid() )
1135  {
1136  delete dirItem;
1137  return 0;
1138  }
1139  if ( ! dirItem )
1140  return 0;
1141 
1142  // fetch sub-dirs and ramps to know what to do with this item
1143  QStringList theDirEntries = dirItem->dirEntries();
1144  QMap< QString, QStringList > theRampsMap = dirItem->rampsMap();
1145 
1146  QgsDebugMsg( QString( "item has %1 dirs and %2 ramps" ).arg( theDirEntries.count() ).arg( theRampsMap.count() ) );
1147 
1148  // return item if has at least one subdir
1149  if ( theDirEntries.count() > 0 )
1150  return dirItem;
1151 
1152  // if 0 ramps, delete item
1153  if ( theRampsMap.count() == 0 )
1154  {
1155  delete dirItem;
1156  return 0;
1157  }
1158  // if 1 ramp, return this child's item
1159  // so we don't have a directory with just 1 item (with many variants possibly)
1160  else if ( theRampsMap.count() == 1 )
1161  {
1162  delete dirItem;
1163  QgsCptCityColorRampItem* rampItem =
1164  new QgsCptCityColorRampItem( parent, theRampsMap.begin().key(),
1165  theRampsMap.begin().key(), theRampsMap.begin().value() );
1166  if ( ! rampItem->isValid() )
1167  {
1168  delete rampItem;
1169  return 0;
1170  }
1171  return rampItem;
1172  }
1173  return dirItem;
1174 }
1175 
1176 
1177 //-----------------------------------------------------------------------
1179  QString name, QString path )
1180  : QgsCptCityCollectionItem( parent, name, path )
1181 {
1182  mType = Selection;
1183  mValid = ! path.isNull();
1184  if ( mValid )
1185  parseXML();
1186 }
1187 
1189 {
1190 }
1191 
1192 QVector<QgsCptCityDataItem*> QgsCptCitySelectionItem::createChildren()
1193 {
1194  if ( ! mValid )
1195  return QVector<QgsCptCityDataItem*>();
1196 
1197  QgsCptCityDataItem* item = 0;
1198  QVector<QgsCptCityDataItem*> children;
1199 
1200  QgsDebugMsg( "name= " + mName + " path= " + mPath );
1201 
1202  // add children archives
1203  foreach ( QString childPath, mSelectionsList )
1204  {
1205  QgsDebugMsg( "childPath = " + childPath + " name= " + QFileInfo( childPath ).baseName() );
1206  if ( childPath.endsWith( "/" ) )
1207  {
1208  childPath.chop( 1 );
1209  QgsCptCityDataItem* childItem =
1210  QgsCptCityDirectoryItem::dataItem( this, childPath, childPath );
1211  if ( childItem )
1212  {
1213  if ( childItem->isValid() )
1214  children << childItem;
1215  else
1216  delete childItem;
1217  }
1218  }
1219  else
1220  {
1221  // init item to test if is valid after loading file
1222  item = new QgsCptCityColorRampItem( this, childPath, childPath, QString(), true );
1223  if ( item->isValid() )
1224  children << item;
1225  else
1226  delete item;
1227  }
1228  }
1229 
1230  QgsDebugMsg( QString( "path= %1 inserted %2 children" ).arg( mPath ).arg( children.count() ) );
1231 
1232  return children;
1233 }
1234 
1236 {
1237  QString filename = QgsCptCityArchive::defaultBaseDir() + QDir::separator() + mPath;
1238 
1239  QgsDebugMsg( "reading file " + filename );
1240 
1241  QFile f( filename );
1242  if ( ! f.open( QFile::ReadOnly ) )
1243  {
1244  QgsDebugMsg( filename + " does not exist" );
1245  return;
1246  }
1247 
1248  // parse the document
1249  QString errMsg;
1250  QDomDocument doc( "selection" );
1251  if ( !doc.setContent( &f, &errMsg ) )
1252  {
1253  f.close();
1254  QgsDebugMsg( "Couldn't parse file " + filename + " : " + errMsg );
1255  return;
1256  }
1257  f.close();
1258 
1259  // read description
1260  QDomElement docElem = doc.documentElement();
1261  if ( docElem.tagName() != "selection" )
1262  {
1263  QgsDebugMsg( "Incorrect root tag: " + docElem.tagName() );
1264  return;
1265  }
1266  QDomElement e = docElem.firstChildElement( "name" );
1267  if ( ! e.isNull() && ! e.text().isNull() )
1268  mName = e.text();
1269  mInfo = docElem.firstChildElement( "synopsis" ).text().simplified();
1270 
1271  // get archives
1272  QDomElement collectsElem = docElem.firstChildElement( "seealsocollects" );
1273  e = collectsElem.firstChildElement( "collect" );
1274  while ( ! e.isNull() )
1275  {
1276  if ( ! e.attribute( "dir" ).isNull() )
1277  {
1278  // TODO parse description and use that, instead of default archive name
1279  mSelectionsList << e.attribute( "dir" ) + "/";
1280  }
1281  e = e.nextSiblingElement();
1282  }
1283  // get individual gradients
1284  QDomElement gradientsElem = docElem.firstChildElement( "gradients" );
1285  e = gradientsElem.firstChildElement( "gradient" );
1286  while ( ! e.isNull() )
1287  {
1288  if ( ! e.attribute( "dir" ).isNull() )
1289  {
1290  // QgsDebugMsg( "add " + e.attribute( "dir" ) + "/" + e.attribute( "file" ) + " to " + selname );
1291  // TODO parse description and save elsewhere
1292  mSelectionsList << e.attribute( "dir" ) + "/" + e.attribute( "file" );
1293  }
1294  e = e.nextSiblingElement();
1295  }
1296 }
1297 
1299 {
1300  //QgsDebugMsg ( mPath + " x " + other->mPath );
1301  if ( type() != other->type() )
1302  {
1303  return false;
1304  }
1305  return ( path() == other->path() );
1306 }
1307 
1308 //-----------------------------------------------------------------------
1310  QString name, QVector<QgsCptCityDataItem*> items )
1311  : QgsCptCityCollectionItem( parent, name, QString() ), mItems( items )
1312 {
1313  mType = AllRamps;
1314  mValid = true;
1315  // populate();
1316 }
1317 
1319 {
1320 }
1321 
1322 QVector<QgsCptCityDataItem*> QgsCptCityAllRampsItem::createChildren()
1323 {
1324  if ( ! mValid )
1325  return QVector<QgsCptCityDataItem*>();
1326 
1327  QVector<QgsCptCityDataItem*> children;
1328 
1329  // add children ramps of each item
1330  foreach ( QgsCptCityDataItem* item, mItems )
1331  {
1332  QgsCptCityCollectionItem* colItem = dynamic_cast< QgsCptCityCollectionItem* >( item );
1333  if ( colItem )
1334  children += colItem->childrenRamps( true );
1335  }
1336 
1337  return children;
1338 }
1339 
1340 //-----------------------------------------------------------------------
1341 
1343  QgsCptCityArchive* archive, ViewType viewType )
1344  : QAbstractItemModel( parent ), mArchive( archive ), mViewType( viewType )
1345 {
1346  Q_ASSERT( mArchive != NULL );
1347  QgsDebugMsg( "archiveName = " + archive->archiveName() + " viewType=" + ( int ) viewType );
1348  // keep iconsize for now, but not effectively used
1349  mIconSize = QSize( 100, 15 );
1350  addRootItems();
1351 }
1352 
1354 {
1355  removeRootItems();
1356 }
1357 
1359 {
1360  if ( mViewType == Authors )
1361  {
1363  }
1364  else if ( mViewType == Selections )
1365  {
1367  }
1368  QgsDebugMsg( QString( "added %1 root items" ).arg( mRootItems.size() ) );
1369 }
1370 
1372 {
1373  // don't remove root items, they belong to the QgsCptCityArchive
1374  // foreach ( QgsCptCityDataItem* item, mRootItems )
1375  // {
1376  // delete item;
1377  // }
1378 
1379  mRootItems.clear();
1380 }
1381 
1382 Qt::ItemFlags QgsCptCityBrowserModel::flags( const QModelIndex & index ) const
1383 {
1384  if ( !index.isValid() )
1385  return 0;
1386 
1387  Qt::ItemFlags flags = Qt::ItemIsEnabled | Qt::ItemIsSelectable;
1388 
1389  return flags;
1390 }
1391 
1392 QVariant QgsCptCityBrowserModel::data( const QModelIndex &index, int role ) const
1393 {
1394  if ( !index.isValid() )
1395  return QVariant();
1396 
1397  QgsCptCityDataItem *item = dataItem( index );
1398 
1399  if ( !item )
1400  {
1401  return QVariant();
1402  }
1403  else if ( role == Qt::DisplayRole )
1404  {
1405  if ( index.column() == 0 )
1406  return item->name();
1407  if ( index.column() == 1 )
1408  {
1409  return item->info();
1410  }
1411  }
1412  else if ( role == Qt::ToolTipRole )
1413  {
1414  if ( item->type() == QgsCptCityDataItem::ColorRamp &&
1415  mViewType == List )
1416  return item->path() + "\n" + item->info();
1417  return item->toolTip();
1418  }
1419  else if ( role == Qt::DecorationRole && index.column() == 1 &&
1420  item->type() == QgsCptCityDataItem::ColorRamp )
1421  {
1422  // keep iconsize for now, but not effectively used
1423  return item->icon( mIconSize );
1424  }
1425  else if ( role == Qt::FontRole &&
1426  ( dynamic_cast< QgsCptCityCollectionItem* >( item ) != 0 ) )
1427  {
1428  // collectionitems are larger and bold
1429  QFont font;
1430  font.setPointSize( 11 ); //FIXME why is the font so small?
1431  font.setBold( true );
1432  return font;
1433  }
1434  else
1435  {
1436  // unsupported role
1437  return QVariant();
1438  }
1439  return QVariant();
1440 }
1441 
1442 QVariant QgsCptCityBrowserModel::headerData( int section, Qt::Orientation orientation, int role ) const
1443 {
1444  Q_UNUSED( section );
1445  if ( orientation == Qt::Horizontal && role == Qt::DisplayRole )
1446  {
1447  if ( section == 0 )
1448  return QVariant( tr( "Name" ) );
1449  else if ( section == 1 )
1450  return QVariant( tr( "Info" ) );
1451  }
1452  return QVariant();
1453 }
1454 
1455 int QgsCptCityBrowserModel::rowCount( const QModelIndex &parent ) const
1456 {
1457  //qDebug("rowCount: idx: (valid %d) %d %d", parent.isValid(), parent.row(), parent.column());
1458 
1459  if ( !parent.isValid() )
1460  {
1461  // root item: its children are top level items
1462  return mRootItems.count(); // mRoot
1463  }
1464  else
1465  {
1466  // ordinary item: number of its children
1467  QgsCptCityDataItem *item = dataItem( parent );
1468  return item ? item->rowCount() : 0;
1469  }
1470 }
1471 
1472 bool QgsCptCityBrowserModel::hasChildren( const QModelIndex &parent ) const
1473 {
1474  if ( !parent.isValid() )
1475  return true; // root item: its children are top level items
1476 
1477  QgsCptCityDataItem *item = dataItem( parent );
1478 
1479  return item && item->hasChildren();
1480 }
1481 
1482 int QgsCptCityBrowserModel::columnCount( const QModelIndex &parent ) const
1483 {
1484  Q_UNUSED( parent );
1485  return 2;
1486 }
1487 
1488 QModelIndex QgsCptCityBrowserModel::findPath( QString path )
1489 {
1490  QModelIndex theIndex; // starting from root
1491  bool foundParent = false, foundChild = true;
1492  QString itemPath;
1493 
1494  QgsDebugMsg( "path = " + path );
1495 
1496  while ( foundChild )
1497  {
1498  foundChild = false; // assume that the next child item will not be found
1499 
1500  int i = 0;
1501  // if root skip first item "All Ramps"
1502  if ( itemPath.isEmpty() )
1503  i = 1;
1504  for ( ; i < rowCount( theIndex ); i++ )
1505  {
1506  QModelIndex idx = index( i, 0, theIndex );
1507  QgsCptCityDataItem *item = dataItem( idx );
1508  if ( !item )
1509  return QModelIndex(); // an error occurred
1510 
1511  itemPath = item->path();
1512 
1513  if ( itemPath == path )
1514  {
1515  QgsDebugMsg( "Arrived " + itemPath );
1516  return idx; // we have found the item we have been looking for
1517  }
1518 
1519  if ( ! itemPath.endsWith( "/" ) )
1520  itemPath += "/";
1521 
1522  foundParent = false;
1523 
1524  // QgsDebugMsg( "path= " + path + " itemPath= " + itemPath );
1525 
1526  // if we are using a selection collection, search for target in the mapping in this group
1527  if ( item->type() == QgsCptCityDataItem::Selection )
1528  {
1529  const QgsCptCitySelectionItem* selItem = dynamic_cast<const QgsCptCitySelectionItem *>( item );
1530  foreach ( QString childPath, selItem->selectionsList() )
1531  {
1532  if ( childPath.endsWith( "/" ) )
1533  childPath.chop( 1 );
1534  // QgsDebugMsg( "childPath= " + childPath );
1535  if ( path.startsWith( childPath ) )
1536  {
1537  foundParent = true;
1538  break;
1539  }
1540  }
1541  }
1542  // search for target in parent directory
1543  else if ( path.startsWith( itemPath ) )
1544  {
1545  foundParent = true;
1546  }
1547 
1548  if ( foundParent )
1549  {
1550  QgsDebugMsg( "found parent " + path );
1551  // we have found a preceding item: stop searching on this level and go deeper
1552  foundChild = true;
1553  theIndex = idx;
1554  if ( canFetchMore( theIndex ) )
1555  fetchMore( theIndex );
1556  break;
1557  }
1558  }
1559  }
1560 
1561  return QModelIndex(); // not found
1562 }
1563 
1565 {
1566  removeRootItems();
1567  addRootItems();
1568  reset(); // Qt4.6 brings better methods beginResetModel + endResetModel
1569 }
1570 
1571 /* Refresh dir path */
1573 {
1574  QModelIndex idx = findPath( path );
1575  if ( idx.isValid() )
1576  {
1577  QgsCptCityDataItem* item = dataItem( idx );
1578  if ( item )
1579  item->refresh();
1580  }
1581 }
1582 
1583 QModelIndex QgsCptCityBrowserModel::index( int row, int column, const QModelIndex &parent ) const
1584 {
1585  QgsCptCityDataItem *p = dataItem( parent );
1586  const QVector<QgsCptCityDataItem*> &items = p ? p->children() : mRootItems;
1587  QgsCptCityDataItem *item = items.value( row, 0 );
1588  return item ? createIndex( row, column, item ) : QModelIndex();
1589 }
1590 
1591 QModelIndex QgsCptCityBrowserModel::parent( const QModelIndex &index ) const
1592 {
1593  QgsCptCityDataItem *item = dataItem( index );
1594  if ( !item )
1595  return QModelIndex();
1596 
1597  return findItem( item->parent() );
1598 }
1599 
1601 {
1602  const QVector<QgsCptCityDataItem*> &items = parent ? parent->children() : mRootItems;
1603 
1604  for ( int i = 0; i < items.size(); i++ )
1605  {
1606  if ( items[i] == item )
1607  return createIndex( i, 0, item );
1608 
1609  QModelIndex childIndex = findItem( item, items[i] );
1610  if ( childIndex.isValid() )
1611  return childIndex;
1612  }
1613 
1614  return QModelIndex();
1615 }
1616 
1617 /* Refresh item */
1618 void QgsCptCityBrowserModel::refresh( const QModelIndex& theIndex )
1619 {
1620  QgsCptCityDataItem *item = dataItem( theIndex );
1621  if ( !item )
1622  return;
1623 
1624  QgsDebugMsg( "Refresh " + item->path() );
1625  item->refresh();
1626 }
1627 
1629 {
1630  QgsDebugMsg( "parent mPath = " + parent->path() );
1631  QModelIndex idx = findItem( parent );
1632  if ( !idx.isValid() )
1633  return;
1634  QgsDebugMsg( "valid" );
1635  beginInsertRows( idx, first, last );
1636  QgsDebugMsg( "end" );
1637 }
1639 {
1640  QgsDebugMsg( "Entered" );
1641  endInsertRows();
1642 }
1644 {
1645  QgsDebugMsg( "parent mPath = " + parent->path() );
1646  QModelIndex idx = findItem( parent );
1647  if ( !idx.isValid() )
1648  return;
1649  beginRemoveRows( idx, first, last );
1650 }
1652 {
1653  QgsDebugMsg( "Entered" );
1654  endRemoveRows();
1655 }
1657 {
1658  connect( item, SIGNAL( beginInsertItems( QgsCptCityDataItem*, int, int ) ),
1659  this, SLOT( beginInsertItems( QgsCptCityDataItem*, int, int ) ) );
1660  connect( item, SIGNAL( endInsertItems() ),
1661  this, SLOT( endInsertItems() ) );
1662  connect( item, SIGNAL( beginRemoveItems( QgsCptCityDataItem*, int, int ) ),
1663  this, SLOT( beginRemoveItems( QgsCptCityDataItem*, int, int ) ) );
1664  connect( item, SIGNAL( endRemoveItems() ),
1665  this, SLOT( endRemoveItems() ) );
1666 }
1667 
1668 bool QgsCptCityBrowserModel::canFetchMore( const QModelIndex & parent ) const
1669 {
1670  QgsCptCityDataItem* item = dataItem( parent );
1671  // fetch all items initially so we know which items have children
1672  // (nicer looking and less confusing)
1673 
1674  if ( ! item )
1675  return false;
1676 
1677  // except for "All Ramps" - this is populated when clicked on
1678  if ( item->type() == QgsCptCityDataItem::AllRamps )
1679  return false;
1680 
1681  item->populate();
1682 
1683  return ( ! item->isPopulated() );
1684 }
1685 
1686 void QgsCptCityBrowserModel::fetchMore( const QModelIndex & parent )
1687 {
1688  QgsCptCityDataItem* item = dataItem( parent );
1689  if ( item )
1690  item->populate();
1691  QgsDebugMsg( "path = " + item->path() );
1692 }
1693 
1694 
1695 #if 0
1696 QStringList QgsCptCityBrowserModel::mimeTypes() const
1697 {
1698  QStringList types;
1699  // In theory the mime type convention is: application/x-vnd.<vendor>.<application>.<type>
1700  // but it seems a bit over formalized. Would be an application/x-qgis-uri better?
1701  types << "application/x-vnd.qgis.qgis.uri";
1702  return types;
1703 }
1704 
1705 QMimeData * QgsCptCityBrowserModel::mimeData( const QModelIndexList &indexes ) const
1706 {
1708  foreach ( const QModelIndex &index, indexes )
1709  {
1710  if ( index.isValid() )
1711  {
1712  QgsCptCityDataItem* ptr = ( QgsCptCityDataItem* ) index.internalPointer();
1713  if ( ptr->type() != QgsCptCityDataItem::Layer ) continue;
1714  QgsLayerItem *layer = ( QgsLayerItem* ) ptr;
1715  lst.append( QgsMimeDataUtils::Uri( ayer ) );
1716  }
1717  }
1718  return QgsMimeDataUtils::encodeUriList( lst );
1719 }
1720 
1721 bool QgsCptCityBrowserModel::dropMimeData( const QMimeData * data, Qt::DropAction action, int row, int column, const QModelIndex & parent )
1722 {
1723  Q_UNUSED( row );
1724  Q_UNUSED( column );
1725 
1726  QgsCptCityDataItem* destItem = dataItem( parent );
1727  if ( !destItem )
1728  {
1729  QgsDebugMsg( "DROP PROBLEM!" );
1730  return false;
1731  }
1732 
1733  return destItem->handleDrop( data, action );
1734 }
1735 #endif
1736 
1738 {
1739  void *v = idx.internalPointer();
1740  QgsCptCityDataItem *d = reinterpret_cast<QgsCptCityDataItem*>( v );
1741  Q_ASSERT( !v || d );
1742  return d;
1743 }