QGIS API Documentation  3.20.0-Odense (decaadbb31)
qgsdataitem.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgsdataitem.cpp - Data items
3  -------------------
4  begin : 2011-04-01
5  copyright : (C) 2011 Radim Blazek
6  email : radim dot blazek at gmail dot com
7  ***************************************************************************/
8 
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 <QtConcurrentMap>
20 #include <QtConcurrentRun>
21 #include <QDateTime>
22 #include <QElapsedTimer>
23 #include <QDir>
24 #include <QFileInfo>
25 #include <QMenu>
26 #include <QMouseEvent>
27 #include <QTreeWidget>
28 #include <QTreeWidgetItem>
29 #include <QVector>
30 #include <QStyle>
31 #include <QTimer>
32 #include <mutex>
33 #include <QRegularExpression>
34 
35 #include "qgis.h"
36 #include "qgsdataitem.h"
37 #include "qgsapplication.h"
38 #include "qgsdataitemprovider.h"
40 #include "qgsdataprovider.h"
41 #include "qgslogger.h"
42 #include "qgsproviderregistry.h"
43 #include "qgsconfig.h"
44 #include "qgssettings.h"
45 #include "qgsanimatedicon.h"
46 #include "qgsproject.h"
47 #include "qgsvectorlayer.h"
48 #include "qgsprovidermetadata.h"
49 
50 // use GDAL VSI mechanism
51 #define CPL_SUPRESS_CPLUSPLUS //#spellok
52 #include "cpl_vsi.h"
53 #include "cpl_string.h"
54 
55 QgsAnimatedIcon *QgsDataItem::sPopulatingIcon = nullptr;
56 
57 QgsDataItem::QgsDataItem( Qgis::BrowserItemType type, QgsDataItem *parent, const QString &name, const QString &path, const QString &providerKey )
58 // Do not pass parent to QObject, Qt would delete this when parent is deleted
59  : mType( type )
60  , mParent( parent )
61  , mName( name )
62  , mProviderKey( providerKey )
63  , mPath( path )
64 {
65 }
66 
68 {
69  QgsDebugMsgLevel( QStringLiteral( "mName = %1 mPath = %2 mChildren.size() = %3" ).arg( mName, mPath ).arg( mChildren.size() ), 2 );
70  const auto constMChildren = mChildren;
71  for ( QgsDataItem *child : constMChildren )
72  {
73  if ( !child ) // should not happen
74  continue;
75  child->deleteLater();
76  }
77  mChildren.clear();
78 
79  if ( mFutureWatcher && !mFutureWatcher->isFinished() )
80  {
81  // this should not usually happen (until the item was deleted directly when createChildren was running)
82  QgsDebugMsg( QStringLiteral( "mFutureWatcher not finished (should not happen) -> waitForFinished()" ) );
83  mDeferredDelete = true;
84  mFutureWatcher->waitForFinished();
85  }
86 
87  delete mFutureWatcher;
88 }
89 
90 QString QgsDataItem::pathComponent( const QString &string )
91 {
92  return QString( string ).replace( QRegExp( "[\\\\/]" ), QStringLiteral( "|" ) );
93 }
94 
95 QVariant QgsDataItem::sortKey() const
96 {
97  return mSortKey.isValid() ? mSortKey : name();
98 }
99 
100 void QgsDataItem::setSortKey( const QVariant &key )
101 {
102  mSortKey = key;
103 }
104 
106 {
107  QgsDebugMsgLevel( "path = " + path(), 3 );
108  setParent( nullptr ); // also disconnects parent
109  const auto constMChildren = mChildren;
110  for ( QgsDataItem *child : constMChildren )
111  {
112  if ( !child ) // should not happen
113  continue;
114  child->deleteLater();
115  }
116  mChildren.clear();
117 
118  if ( mFutureWatcher && !mFutureWatcher->isFinished() )
119  {
120  QgsDebugMsg( QStringLiteral( "mFutureWatcher not finished -> schedule to delete later" ) );
121  mDeferredDelete = true;
122  }
123  else
124  {
125  QObject::deleteLater();
126  }
127 }
128 
129 void QgsDataItem::deleteLater( QVector<QgsDataItem *> &items )
130 {
131  const auto constItems = items;
132  for ( QgsDataItem *item : constItems )
133  {
134  if ( !item ) // should not happen
135  continue;
136  item->deleteLater();
137  }
138  items.clear();
139 }
140 
141 void QgsDataItem::moveToThread( QThread *targetThread )
142 {
143  // QObject::moveToThread() cannot move objects with parent, but QgsDataItem is not using paren/children from QObject
144  const auto constMChildren = mChildren;
145  for ( QgsDataItem *child : constMChildren )
146  {
147  if ( !child ) // should not happen
148  continue;
149  QgsDebugMsgLevel( "moveToThread child " + child->path(), 3 );
150  child->QObject::setParent( nullptr ); // to be sure
151  child->moveToThread( targetThread );
152  }
153  QObject::moveToThread( targetThread );
154 }
155 
157 {
158  return nullptr;
159 }
160 
162 {
163  if ( state() == Qgis::BrowserItemState::Populating && sPopulatingIcon )
164  return sPopulatingIcon->icon();
165 
166  if ( !mIcon.isNull() )
167  return mIcon;
168 
169  if ( !mIconMap.contains( mIconName ) )
170  {
171  mIconMap.insert( mIconName, mIconName.startsWith( ':' ) ? QIcon( mIconName ) : QgsApplication::getThemeIcon( mIconName ) );
172  }
173 
174  return mIconMap.value( mIconName );
175 }
176 
177 void QgsDataItem::setName( const QString &name )
178 {
179  mName = name;
180  emit dataChanged( this );
181 }
182 
183 QVector<QgsDataItem *> QgsDataItem::createChildren()
184 {
185  return QVector<QgsDataItem *>();
186 }
187 
188 void QgsDataItem::populate( bool foreground )
189 {
191  return;
192 
193  QgsDebugMsgLevel( "mPath = " + mPath, 2 );
194 
195  if ( capabilities2() & Qgis::BrowserItemCapability::Fast || foreground )
196  {
198  }
199  else
200  {
202  // The watcher must not be created with item (in constructor) because the item may be created in thread and the watcher created in thread does not work correctly.
203  if ( !mFutureWatcher )
204  {
205  mFutureWatcher = new QFutureWatcher< QVector <QgsDataItem *> >( this );
206  }
207 
208  connect( mFutureWatcher, &QFutureWatcherBase::finished, this, &QgsDataItem::childrenCreated );
209  mFutureWatcher->setFuture( QtConcurrent::run( runCreateChildren, this ) );
210  }
211 }
212 
213 // This is expected to be run in a separate thread
214 QVector<QgsDataItem *> QgsDataItem::runCreateChildren( QgsDataItem *item )
215 {
216  QgsDebugMsgLevel( "path = " + item->path(), 2 );
217  QElapsedTimer time;
218  time.start();
219  QVector <QgsDataItem *> children = item->createChildren();
220  QgsDebugMsgLevel( QStringLiteral( "%1 children created in %2 ms" ).arg( children.size() ).arg( time.elapsed() ), 3 );
221  // Children objects must be pushed to main thread.
222  const auto constChildren = children;
223  for ( QgsDataItem *child : constChildren )
224  {
225  if ( !child ) // should not happen
226  continue;
227  QgsDebugMsgLevel( "moveToThread child " + child->path(), 2 );
228  if ( qApp )
229  child->moveToThread( qApp->thread() ); // moves also children
230  }
231  QgsDebugMsgLevel( QStringLiteral( "finished path %1: %2 children" ).arg( item->path() ).arg( children.size() ), 3 );
232  return children;
233 }
234 
236 {
237  QgsDebugMsgLevel( QStringLiteral( "path = %1 children.size() = %2" ).arg( path() ).arg( mFutureWatcher->result().size() ), 3 );
238 
239  if ( deferredDelete() )
240  {
241  QgsDebugMsg( QStringLiteral( "Item was scheduled to be deleted later" ) );
242  QObject::deleteLater();
243  return;
244  }
245 
246  if ( mChildren.isEmpty() ) // usually populating but may also be refresh if originally there were no children
247  {
248  populate( mFutureWatcher->result() );
249  }
250  else // refreshing
251  {
252  refresh( mFutureWatcher->result() );
253  }
254  disconnect( mFutureWatcher, &QFutureWatcherBase::finished, this, &QgsDataItem::childrenCreated );
255  emit dataChanged( this ); // to replace loading icon by normal icon
256 }
257 
259 {
260  emit dataChanged( this );
261 }
262 
263 void QgsDataItem::populate( const QVector<QgsDataItem *> &children )
264 {
265  QgsDebugMsgLevel( "mPath = " + mPath, 3 );
266 
267  const auto constChildren = children;
268  for ( QgsDataItem *child : constChildren )
269  {
270  if ( !child ) // should not happen
271  continue;
272  // update after thread finished -> refresh
273  addChildItem( child, true );
274  }
276 }
277 
279 {
280  QgsDebugMsgLevel( "mPath = " + mPath, 3 );
281 
282  const auto constMChildren = mChildren;
283  for ( QgsDataItem *child : constMChildren )
284  {
285  QgsDebugMsgLevel( "remove " + child->path(), 3 );
286  child->depopulate(); // recursive
287  deleteChildItem( child );
288  }
290 }
291 
293 {
295  return;
296 
297  QgsDebugMsgLevel( "mPath = " + mPath, 3 );
298 
300  {
301  refresh( createChildren() );
302  }
303  else
304  {
306  if ( !mFutureWatcher )
307  {
308  mFutureWatcher = new QFutureWatcher< QVector <QgsDataItem *> >( this );
309  }
310  connect( mFutureWatcher, &QFutureWatcherBase::finished, this, &QgsDataItem::childrenCreated );
311  mFutureWatcher->setFuture( QtConcurrent::run( runCreateChildren, this ) );
312  }
313 }
314 
315 void QgsDataItem::refreshConnections( const QString &key )
316 {
317  // Walk up until the root node is reached
318  if ( mParent )
319  {
320  mParent->refreshConnections( key );
321  }
322  else
323  {
324  // if a specific key was specified then we use that -- otherwise we assume the connections
325  // changed belong to the same provider as this item
326  emit connectionsChanged( key.isEmpty() ? providerKey() : key );
327  }
328 }
329 
330 void QgsDataItem::refresh( const QVector<QgsDataItem *> &children )
331 {
332  QgsDebugMsgLevel( "mPath = " + mPath, 2 );
333 
334  // Remove no more present children
335  QVector<QgsDataItem *> remove;
336  const auto constMChildren = mChildren;
337  for ( QgsDataItem *child : constMChildren )
338  {
339  if ( !child ) // should not happen
340  continue;
341  if ( findItem( children, child ) >= 0 )
342  continue;
343  remove.append( child );
344  }
345  const auto constRemove = remove;
346  for ( QgsDataItem *child : constRemove )
347  {
348  QgsDebugMsgLevel( "remove " + child->path(), 3 );
349  deleteChildItem( child );
350  }
351 
352  // Add new children
353  const auto constChildren = children;
354  for ( QgsDataItem *child : constChildren )
355  {
356  if ( !child ) // should not happen
357  continue;
358 
359  int index = findItem( mChildren, child );
360  if ( index >= 0 )
361  {
362  // Refresh recursively (some providers may create more generations of descendants)
363  if ( !( child->capabilities2() & Qgis::BrowserItemCapability::Fertile ) )
364  {
365  // The child cannot createChildren() itself
366  mChildren.value( index )->refresh( child->children() );
367  }
368 
369  child->deleteLater();
370  continue;
371  }
372  addChildItem( child, true );
373  }
375 }
376 
378 {
379  return mProviderKey;
380 }
381 
382 void QgsDataItem::setProviderKey( const QString &value )
383 {
384  mProviderKey = value;
385 }
386 
388 {
389  return mChildren.size();
390 }
392 {
393  return ( state() == Qgis::BrowserItemState::Populated ? !mChildren.isEmpty() : true );
394 }
395 
397 {
398  return false;
399 }
400 
402 {
403  if ( mParent )
404  {
405  disconnect( this, nullptr, mParent, nullptr );
406  }
407  if ( parent )
408  {
415  }
416  mParent = parent;
417 }
418 
419 void QgsDataItem::addChildItem( QgsDataItem *child, bool refresh )
420 {
421  Q_ASSERT( child );
422  QgsDebugMsgLevel( QStringLiteral( "path = %1 add child #%2 - %3 - %4" ).arg( mPath ).arg( mChildren.size() ).arg( child->mName ).arg( qgsEnumValueToKey< Qgis::BrowserItemType >( child->mType ) ), 3 );
423 
424  //calculate position to insert child
425  int i;
427  {
428  for ( i = 0; i < mChildren.size(); i++ )
429  {
430  // sort items by type, so directories are before data items
431  if ( mChildren.at( i )->mType == child->mType &&
432  mChildren.at( i )->mName.localeAwareCompare( child->mName ) > 0 )
433  break;
434  }
435  }
436  else
437  {
438  for ( i = 0; i < mChildren.size(); i++ )
439  {
440  if ( mChildren.at( i )->mName.localeAwareCompare( child->mName ) >= 0 )
441  break;
442  }
443  }
444 
445  if ( refresh )
446  emit beginInsertItems( this, i, i );
447 
448  mChildren.insert( i, child );
449  child->setParent( this );
450 
451  if ( refresh )
452  emit endInsertItems();
453 }
454 
456 {
457  QgsDebugMsgLevel( "mName = " + child->mName, 2 );
458  int i = mChildren.indexOf( child );
459  Q_ASSERT( i >= 0 );
460  emit beginRemoveItems( this, i, i );
461  mChildren.remove( i );
462  child->deleteLater();
463  emit endRemoveItems();
464 }
465 
467 {
468  QgsDebugMsgLevel( "mName = " + child->mName, 2 );
469  int i = mChildren.indexOf( child );
470  Q_ASSERT( i >= 0 );
471  if ( i < 0 )
472  {
473  child->setParent( nullptr );
474  return nullptr;
475  }
476 
477  emit beginRemoveItems( this, i, i );
478  mChildren.remove( i );
479  emit endRemoveItems();
480  return child;
481 }
482 
483 int QgsDataItem::findItem( QVector<QgsDataItem *> items, QgsDataItem *item )
484 {
485  for ( int i = 0; i < items.size(); i++ )
486  {
487  Q_ASSERT_X( items[i], "findItem", QStringLiteral( "item %1 is nullptr" ).arg( i ).toLatin1() );
488  QgsDebugMsgLevel( QString::number( i ) + " : " + items[i]->mPath + " x " + item->mPath, 2 );
489  if ( items[i]->equal( item ) )
490  return i;
491  }
492  return -1;
493 }
494 
495 bool QgsDataItem::equal( const QgsDataItem *other )
496 {
497  return ( metaObject()->className() == other->metaObject()->className() &&
498  mPath == other->path() );
499 }
500 
501 QList<QAction *> QgsDataItem::actions( QWidget *parent )
502 {
503  Q_UNUSED( parent )
504  return QList<QAction *>();
505 }
506 
508 {
509  return false;
510 }
511 
513 {
514  return mimeUris().isEmpty() ? QgsMimeDataUtils::Uri() : mimeUris().first();
515 }
516 
518 {
519  Q_UNUSED( crs )
520  return false;
521 }
522 
523 bool QgsDataItem::rename( const QString & )
524 {
525  return false;
526 }
527 
528 void QgsDataItem::setCapabilities( int capabilities )
529 {
530  setCapabilities( static_cast< Qgis::BrowserItemCapabilities >( capabilities ) );
531 }
532 
534 {
535  return mState;
536 }
537 
539 {
540  QgsDebugMsgLevel( QStringLiteral( "item %1 set state %2 -> %3" ).arg( path() ).arg( qgsEnumValueToKey< Qgis::BrowserItemState >( this->state() ) ).arg( qgsEnumValueToKey< Qgis::BrowserItemState >( state ) ), 3 );
541  if ( state == mState )
542  return;
543 
544  Qgis::BrowserItemState oldState = mState;
545 
546  if ( state == Qgis::BrowserItemState::Populating ) // start loading
547  {
548  if ( !sPopulatingIcon )
549  {
550  // TODO: ensure that QgsAnimatedIcon is created on UI thread only
551  sPopulatingIcon = new QgsAnimatedIcon( QgsApplication::iconPath( QStringLiteral( "/mIconLoading.gif" ) ), QgsApplication::instance() );
552  }
553 
554  sPopulatingIcon->connectFrameChanged( this, &QgsDataItem::updateIcon );
555  }
556  else if ( mState == Qgis::BrowserItemState::Populating && sPopulatingIcon ) // stop loading
557  {
558  sPopulatingIcon->disconnectFrameChanged( this, &QgsDataItem::updateIcon );
559  }
560 
561 
562  mState = state;
563 
564  emit stateChanged( this, oldState );
566  updateIcon();
567 }
568 
569 QList<QMenu *> QgsDataItem::menus( QWidget *parent )
570 {
571  Q_UNUSED( parent )
572  return QList<QMenu *>();
573 }
574 
575 QgsErrorItem::QgsErrorItem( QgsDataItem *parent, const QString &error, const QString &path )
576  : QgsDataItem( Qgis::BrowserItemType::Error, parent, error, path )
577 {
578  mIconName = QStringLiteral( "/mIconDelete.svg" );
579 
580  setState( Qgis::BrowserItemState::Populated ); // no more children
581 }
582 
The Qgis class provides global constants for use throughout the application.
Definition: qgis.h:62
BrowserItemState
Browser item states.
Definition: qgis.h:249
@ NotPopulated
Children not yet created.
@ Populating
Creating children in separate thread (populating or refreshing)
@ Populated
Children created.
@ Fertile
Can create children. Even items without this capability may have children, but cannot create them,...
@ Fast
CreateChildren() is fast enough to be run in main thread when refreshing items, most root items (wms,...
BrowserItemType
Browser item types.
Definition: qgis.h:230
@ Directory
Represents a file directory.
The QgsAbstractDatabaseProviderConnection class provides common functionality for DB based connection...
Animated icon is keeping an animation running if there are listeners connected to frameChanged.
bool disconnectFrameChanged(const typename QtPrivate::FunctionPointer< Func1 >::Object *receiver, Func1 slot)
Convenience function to disconnect the same style that the frame change connection was established.
bool connectFrameChanged(const typename QtPrivate::FunctionPointer< Func1 >::Object *receiver, Func1 slot)
Connect a slot that will be notified repeatedly whenever a frame changes and which should request the...
QIcon icon() const
Gets the icons representation in the current frame.
static QgsApplication * instance()
Returns the singleton instance of the QgsApplication.
static QIcon getThemeIcon(const QString &name, const QColor &fillColor=QColor(), const QColor &strokeColor=QColor())
Helper to get a theme icon.
static QString iconPath(const QString &iconFile)
Returns path to the desired icon file.
This class represents a coordinate reference system (CRS).
Base class for all items in the model.
Definition: qgsdataitem.h:46
void stateChanged(QgsDataItem *item, Qgis::BrowserItemState oldState)
Emitted when an item's state is changed.
void setSortKey(const QVariant &key)
Sets a custom sorting key for the item.
static int findItem(QVector< QgsDataItem * > items, QgsDataItem *item)
QString mName
Definition: qgsdataitem.h:441
Qgis::BrowserItemType mType
Definition: qgsdataitem.h:436
bool hasChildren()
virtual QList< QMenu * > menus(QWidget *parent)
Returns the list of menus available for this item.
virtual void deleteChildItem(QgsDataItem *child)
Removes and deletes a child item, emitting relevant signals to the model.
QVector< QgsDataItem * > mChildren
Definition: qgsdataitem.h:439
virtual QVariant sortKey() const
Returns the sorting key for the item.
Definition: qgsdataitem.cpp:95
Qgis::BrowserItemState mState
Definition: qgsdataitem.h:440
virtual bool handleDoubleClick()
Called when a user double clicks on the item.
void dataChanged(QgsDataItem *item)
bool deferredDelete()
The item is scheduled to be deleted.
Definition: qgsdataitem.h:434
void setParent(QgsDataItem *parent)
Set item parent and connect / disconnect parent to / from item signals.
static void deleteLater(QVector< QgsDataItem * > &items)
void endRemoveItems()
virtual bool layerCollection() const
Returns true if the data item is a collection of layers The default implementation returns false,...
QString mPath
Definition: qgsdataitem.h:447
QVector< QgsDataItem * > children() const
Definition: qgsdataitem.h:329
void beginRemoveItems(QgsDataItem *parent, int first, int last)
QgsDataItem * parent() const
Gets item parent.
Definition: qgsdataitem.h:322
virtual void deleteLater()
Safely delete the item:
virtual QgsAbstractDatabaseProviderConnection * databaseConnection() const
For data items that represent a DB connection or one of its children, this method returns a connectio...
Qgis::BrowserItemType type() const
Definition: qgsdataitem.h:316
QgsDataItem(Qgis::BrowserItemType type, QgsDataItem *parent, const QString &name, const QString &path, const QString &providerKey=QString())
Constructor for QgsDataItem, with the specified parent item.
Definition: qgsdataitem.cpp:57
QString mIconName
Definition: qgsdataitem.h:449
virtual QVector< QgsDataItem * > createChildren()
Create children.
QMap< QString, QIcon > mIconMap
Definition: qgsdataitem.h:451
Qgis::BrowserItemState state() const
virtual void childrenCreated()
virtual QgsDataItem * removeChildItem(QgsDataItem *child)
Removes a child item and returns it without deleting it.
static QString pathComponent(const QString &component)
Create path component replacing path separators.
Definition: qgsdataitem.cpp:90
virtual QList< QAction * > actions(QWidget *parent)
Returns the list of actions available for this item.
QVariant mSortKey
Custom sort key. If invalid, name() will be used for sorting instead.
Definition: qgsdataitem.h:454
void updateIcon()
Will request a repaint of this icon.
virtual Q_DECL_DEPRECATED bool rename(const QString &name)
Sets a new name for the item, and returns true if the item was successfully renamed.
void connectionsChanged(const QString &providerKey=QString())
Emitted when the connections of the provider with the specified providerKey have changed.
virtual Q_DECL_DEPRECATED QgsMimeDataUtils::Uri mimeUri() const
Returns mime URI for the data item.
QgsDataItem * mParent
Definition: qgsdataitem.h:438
QString name() const
Returns the name of the item (the displayed text for the item).
Definition: qgsdataitem.h:337
QString path() const
Definition: qgsdataitem.h:346
virtual QIcon icon()
virtual void setState(Qgis::BrowserItemState state)
Set item state.
void beginInsertItems(QgsDataItem *parent, int first, int last)
virtual void refreshConnections(const QString &providerKey=QString())
Causes a data item provider to refresh all registered connections.
virtual void setCapabilities(Qgis::BrowserItemCapabilities capabilities)
Sets the capabilities for the data item.
Definition: qgsdataitem.h:302
virtual void addChildItem(QgsDataItem *child, bool refresh=false)
Inserts a new child item.
void setName(const QString &name)
Sets the name of the item (the displayed text for the item).
virtual void refresh()
void moveToThread(QThread *targetThread)
Move object and all its descendants to thread.
QString providerKey() const
Returns the provider key that created this item (e.g.
void endInsertItems()
virtual Q_DECL_DEPRECATED bool setCrs(const QgsCoordinateReferenceSystem &crs)
Writes the selected crs into data source.
virtual Qgis::BrowserItemCapabilities capabilities2() const
Returns the capabilities for the data item.
Definition: qgsdataitem.h:295
~QgsDataItem() override
Definition: qgsdataitem.cpp:67
void setProviderKey(const QString &value)
Sets the provider key that created this item (e.g.
QString mProviderKey
Definition: qgsdataitem.h:442
virtual void populate(const QVector< QgsDataItem * > &children)
virtual bool equal(const QgsDataItem *other)
Returns true if this item is equal to another item (by testing item type and path).
virtual QgsMimeDataUtils::UriList mimeUris() const
Returns mime URIs for the data item, most data providers will only return a single URI but some data ...
Definition: qgsdataitem.h:264
virtual void depopulate()
Remove children recursively and set as not populated. This is used when refreshing collapsed items.
QgsErrorItem(QgsDataItem *parent, const QString &error, const QString &path)
#define QgsDebugMsgLevel(str, level)
Definition: qgslogger.h:39
#define QgsDebugMsg(str)
Definition: qgslogger.h:38
const QgsCoordinateReferenceSystem & crs