QGIS API Documentation 3.27.0-Master (9c08adf5ef)
qgsbrowsertreeview.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgsbrowsertreeview.cpp
3 --------------------------------------
4 Date : January 2015
5 Copyright : (C) 2015 by Radim Blazek
7 ***************************************************************************
8 * *
9 * This program is free software; you can redistribute it and/or modify *
10 * it under the terms of the GNU General Public License as published by *
11 * the Free Software Foundation; either version 2 of the License, or *
12 * (at your option) any later version. *
13 * *
14 ***************************************************************************/
15
16#include "qgssettings.h"
17#include "qgsbrowserguimodel.h"
18#include "qgsbrowsertreeview.h"
19#include "qgslogger.h"
20#include "qgsguiutils.h"
21#include "qgsdataitem.h"
22#include "qgsdirectoryitem.h"
23#include "qgsfileutils.h"
24#include "qgsfavoritesitem.h"
25
26#include <QKeyEvent>
27#include <QSortFilterProxyModel>
28#include <QDir>
29#include <QFileInfo>
30
32 : QTreeView( parent )
33 , mSettingsSection( QStringLiteral( "browser" ) )
34{
35 setEditTriggers( QAbstractItemView::EditKeyPressed );
36 setIndentation( QgsGuiUtils::scaleIconSize( 16 ) );
37}
38
39void QgsBrowserTreeView::keyPressEvent( QKeyEvent *event )
40{
41 if ( event->key() == Qt::Key_Return || event->key() == Qt::Key_Enter )
42 emit doubleClicked( currentIndex() );
43 else
44 QTreeView::keyPressEvent( event );
45}
46
47void QgsBrowserTreeView::setModel( QAbstractItemModel *model )
48{
49
50 QTreeView::setModel( model );
51
52 restoreState();
53}
54
56{
57 mBrowserModel = model;
58}
59
60void QgsBrowserTreeView::showEvent( QShowEvent *e )
61{
62 Q_UNUSED( e )
63 if ( model() )
64 restoreState();
65 QTreeView::showEvent( e );
66}
67
68// closeEvent is not called when application is closed
69void QgsBrowserTreeView::hideEvent( QHideEvent *e )
70{
71 Q_UNUSED( e )
72 // hideEvent() may be called (Mac) before showEvent
73 if ( model() )
74 saveState();
75 QTreeView::hideEvent( e );
76}
77
78void QgsBrowserTreeView::saveState()
79{
80 QgsSettings settings;
81 const QStringList expandedPaths = expandedPathsList( QModelIndex() );
82 settings.setValue( expandedPathsKey(), expandedPaths );
83 QgsDebugMsgLevel( "expandedPaths = " + expandedPaths.join( ' ' ), 4 );
84}
85
86void QgsBrowserTreeView::restoreState()
87{
88 const QgsSettings settings;
89 mExpandPaths = settings.value( expandedPathsKey(), QVariant() ).toStringList();
90
91 QgsDebugMsgLevel( "mExpandPaths = " + mExpandPaths.join( ' ' ), 4 );
92 if ( !mExpandPaths.isEmpty() )
93 {
94 QSet<QModelIndex> expandIndexSet;
95 const auto constMExpandPaths = mExpandPaths;
96 for ( const QString &path : constMExpandPaths )
97 {
98 const QModelIndex expandIndex = QgsBrowserGuiModel::findPath( model(), path, Qt::MatchStartsWith );
99 if ( expandIndex.isValid() )
100 {
101 const QModelIndex modelIndex = browserModel()->findPath( path, Qt::MatchExactly );
102 if ( modelIndex.isValid() )
103 {
104 QgsDataItem *ptr = browserModel()->dataItem( modelIndex );
106 {
107 QgsDebugMsgLevel( "do not expand index for path " + path, 4 );
108 const QModelIndex parentIndex = model()->parent( expandIndex );
109 // Still we need to store the parent in order to expand it
110 if ( parentIndex.isValid() )
111 expandIndexSet.insert( parentIndex );
112 }
113 else
114 {
115 expandIndexSet.insert( expandIndex );
116 }
117 }
118 }
119 else
120 {
121 QgsDebugMsgLevel( "index for path " + path + " not found", 4 );
122 }
123 }
124 const auto constExpandIndexSet = expandIndexSet;
125 for ( const QModelIndex &expandIndex : constExpandIndexSet )
126 {
127 expandTree( expandIndex );
128 }
129 }
130
131 // expand root favorites item
132 const QModelIndex index = QgsBrowserGuiModel::findPath( model(), QStringLiteral( "favorites:" ) );
133 expand( index );
134}
135
136void QgsBrowserTreeView::expandTree( const QModelIndex &index )
137{
138 if ( !model() )
139 return;
140
141 QgsDebugMsgLevel( "itemPath = " + model()->data( index, QgsBrowserGuiModel::PathRole ).toString(), 4 );
142
143 expand( index );
144 const QModelIndex parentIndex = model()->parent( index );
145 if ( parentIndex.isValid() )
146 expandTree( parentIndex );
147}
148
149bool QgsBrowserTreeView::treeExpanded( const QModelIndex &index )
150{
151 if ( !model() )
152 return false;
153 if ( !isExpanded( index ) )
154 return false;
155 const QModelIndex parentIndex = model()->parent( index );
156 if ( parentIndex.isValid() )
157 return treeExpanded( parentIndex );
158
159 return true; // root
160}
161
162bool QgsBrowserTreeView::hasExpandedDescendant( const QModelIndex &index ) const
163{
164 if ( !model() || !index.isValid() )
165 return false;
166
167 for ( int i = 0; i < model()->rowCount( index ); i++ )
168 {
169 const QModelIndex childIndex = model()->index( i, 0, index );
170 if ( isExpanded( childIndex ) )
171 return true;
172
173 if ( hasExpandedDescendant( childIndex ) )
174 return true;
175 }
176 return false;
177}
178
180{
181 const QStringList pathParts = QgsFileUtils::splitPathToComponents( str );
182 if ( pathParts.isEmpty() )
183 return;
184
185 // first we build a list of all directory item candidates we could use to start the expansion from
186 QVector< QgsDirectoryItem * > initialDirectoryItemCandidates;
187 const QVector< QgsDataItem * > rootItems = mBrowserModel->rootItems();
188 for ( QgsDataItem *item : rootItems )
189 {
190 if ( QgsDirectoryItem *dirItem = qobject_cast< QgsDirectoryItem * >( item ) )
191 {
192 initialDirectoryItemCandidates << dirItem;
193 }
194 else if ( QgsFavoritesItem *favoritesItem = qobject_cast< QgsFavoritesItem * >( item ) )
195 {
196 const QVector<QgsDataItem * > favoriteChildren = favoritesItem->children();
197 for ( QgsDataItem *favoriteChild : favoriteChildren )
198 {
199 if ( QgsDirectoryItem *dirItem = qobject_cast< QgsDirectoryItem * >( favoriteChild ) )
200 {
201 initialDirectoryItemCandidates << dirItem;
202 }
203 }
204 }
205 }
206
207 QgsDirectoryItem *currentDirectoryItem = nullptr;
208 QString currentCandidatePath;
209 for ( const QString &thisPart : pathParts )
210 {
211 currentCandidatePath += ( currentCandidatePath.isEmpty() || currentCandidatePath.endsWith( '/' ) ? QString() : QStringLiteral( "/" ) ) + thisPart;
212
213 auto it = initialDirectoryItemCandidates.begin();
214 while ( it != initialDirectoryItemCandidates.end() )
215 {
216 if ( !( *it )->dirPath().startsWith( currentCandidatePath ) )
217 {
218 it = initialDirectoryItemCandidates.erase( it );
219 }
220 else
221 {
222 if ( str.startsWith( ( *it )->dirPath() ) )
223 currentDirectoryItem = *it;
224 it++;
225 }
226 }
227 }
228
229 if ( !currentDirectoryItem )
230 return; // should we create a new root drive item automatically??
231
232 QStringList remainingParts = pathParts;
233 auto it = remainingParts.begin();
234 QDir currentDir = *it;
235 while ( it != remainingParts.end() )
236 {
237 if ( currentDirectoryItem->dirPath().startsWith( currentDir.filePath( *it ) ) )
238 {
239 currentDir = QDir( currentDir.filePath( *it ) );
240 it = remainingParts.erase( it );
241 }
242 else
243 {
244 break;
245 }
246 }
247
248 currentDir = QDir( currentDirectoryItem->dirPath() );
249 QList< QgsDirectoryItem * > pathItems;
250
251 pathItems << currentDirectoryItem;
252
253 for ( const QString &currentFolderName : std::as_const( remainingParts ) )
254 {
255 const QString thisPath = currentDir.filePath( currentFolderName );
256
257 if ( !QFile::exists( thisPath ) )
258 break;
259
260 // check if current directory item already has a child for the folder
261 QgsDirectoryItem *existingChild = nullptr;
262 const QVector< QgsDataItem * > children = currentDirectoryItem->children();
263 for ( QgsDataItem *child : children )
264 {
265 if ( QgsDirectoryItem *childDirectoryItem = qobject_cast< QgsDirectoryItem *>( child ) )
266 {
267 if ( childDirectoryItem->dirPath() == thisPath )
268 {
269 existingChild = childDirectoryItem;
270 break;
271 }
272 }
273 }
274
275 if ( existingChild )
276 {
277 pathItems << existingChild;
278 currentDirectoryItem = existingChild;
279 }
280 else
281 {
282 QgsDirectoryItem *newDir = new QgsDirectoryItem( nullptr, currentFolderName, thisPath );
283 pathItems << newDir;
284 currentDirectoryItem ->addChildItem( newDir, true );
285 currentDirectoryItem = newDir;
286 }
287
288 currentDir = QDir( thisPath );
289 }
290
291 for ( QgsDirectoryItem *i : std::as_const( pathItems ) )
292 {
293 QModelIndex index = mBrowserModel->findItem( i );
294 if ( QSortFilterProxyModel *proxyModel = qobject_cast< QSortFilterProxyModel *>( model() ) )
295 {
296 index = proxyModel->mapFromSource( index );
297 }
298 expand( index );
299 }
300}
301
303{
304 if ( !mBrowserModel )
305 return false;
306
307 QModelIndex index = mBrowserModel->findItem( item );
308 if ( !index.isValid() )
309 return false;
310
311 if ( QSortFilterProxyModel *proxyModel = qobject_cast< QSortFilterProxyModel *>( model() ) )
312 {
313 index = proxyModel->mapFromSource( index );
314 }
315
316 setCurrentIndex( index );
317 return true;
318}
319
320// rowsInserted signal is used to continue in state restoring
321void QgsBrowserTreeView::rowsInserted( const QModelIndex &parentIndex, int start, int end )
322{
323 QTreeView::rowsInserted( parentIndex, start, end );
324
325 if ( !model() )
326 return;
327
328 if ( mExpandPaths.isEmpty() )
329 return;
330
331 QgsDebugMsgLevel( "mExpandPaths = " + mExpandPaths.join( ',' ), 2 );
332
333 const QString parentPath = model()->data( parentIndex, QgsBrowserGuiModel::PathRole ).toString();
334 QgsDebugMsgLevel( "parentPath = " + parentPath, 2 );
335
336 // remove parentPath from paths to be expanded
337 mExpandPaths.removeOne( parentPath );
338
339 // Remove the subtree from mExpandPaths if user collapsed the item in the meantime
340 if ( !treeExpanded( parentIndex ) )
341 {
342 const auto constMExpandPaths = mExpandPaths;
343 for ( const QString &path : constMExpandPaths )
344 {
345 if ( path.startsWith( parentPath + '/' ) )
346 mExpandPaths.removeOne( path );
347 }
348 return;
349 }
350
351 for ( int i = start; i <= end; i++ )
352 {
353 const QModelIndex childIndex = model()->index( i, 0, parentIndex );
354 const QString childPath = model()->data( childIndex, QgsBrowserGuiModel::PathRole ).toString();
355 QString escapedChildPath = childPath;
356 escapedChildPath.replace( '|', QLatin1String( "\\|" ) );
357
358 QgsDebugMsgLevel( "childPath = " + childPath + " escapedChildPath = " + escapedChildPath, 2 );
359 if ( mExpandPaths.contains( childPath ) || mExpandPaths.indexOf( QRegExp( "^" + escapedChildPath + "/.*" ) ) != -1 )
360 {
361 QgsDebugMsgLevel( QStringLiteral( "-> expand" ), 2 );
362 const QModelIndex modelIndex = browserModel()->findPath( childPath, Qt::MatchExactly );
363 if ( modelIndex.isValid() )
364 {
365 QgsDataItem *ptr = browserModel()->dataItem( modelIndex );
366 if ( !ptr || !( ptr->capabilities2() & Qgis::BrowserItemCapability::Collapse ) )
367 {
368 expand( childIndex );
369 }
370 }
371 }
372 }
373}
374
375QString QgsBrowserTreeView::expandedPathsKey() const
376{
377 return '/' + mSettingsSection + "/expandedPaths";
378}
379
380QStringList QgsBrowserTreeView::expandedPathsList( const QModelIndex &index )
381{
382 QStringList paths;
383
384 if ( !model() )
385 return paths;
386
387 for ( int i = 0; i < model()->rowCount( index ); i++ )
388 {
389 const QModelIndex childIndex = model()->index( i, 0, index );
390 if ( isExpanded( childIndex ) )
391 {
392 const QStringList childrenPaths = expandedPathsList( childIndex );
393 if ( !childrenPaths.isEmpty() )
394 {
395 paths.append( childrenPaths );
396 }
397 else
398 {
399 paths.append( model()->data( childIndex, QgsBrowserGuiModel::PathRole ).toString() );
400 }
401 }
402 }
403 return paths;
404}
@ Collapse
The collapse/expand status for this items children should be ignored in order to avoid undesired netw...
A model for showing available data sources and other items in a structured tree.
QgsDataItem * dataItem(const QModelIndex &idx) const
Returns the data item at the specified index, or nullptr if no item exists at the index.
QModelIndex findPath(const QString &path, Qt::MatchFlag matchFlag=Qt::MatchExactly)
Returns index of item with given path.
QModelIndex findItem(QgsDataItem *item, QgsDataItem *parent=nullptr) const
Returns the model index corresponding to the specified data item.
@ PathRole
Item path used to access path in the tree, see QgsDataItem::mPath.
QVector< QgsDataItem * > rootItems() const
Returns the root items for the model.
void showEvent(QShowEvent *e) override
QgsBrowserTreeView(QWidget *parent=nullptr)
Constructor for QgsBrowserTreeView.
void keyPressEvent(QKeyEvent *event) override
void setBrowserModel(QgsBrowserGuiModel *model)
Sets the browser model.
void setModel(QAbstractItemModel *model) override
bool setSelectedItem(QgsDataItem *item)
Sets the item currently selected in the view.
void hideEvent(QHideEvent *e) override
void expandPath(const QString &path)
Expands out a file path in the view.
bool hasExpandedDescendant(const QModelIndex &index) const
void rowsInserted(const QModelIndex &parentIndex, int start, int end) override
QgsBrowserGuiModel * browserModel()
Returns the browser model.
Base class for all items in the model.
Definition: qgsdataitem.h:46
QVector< QgsDataItem * > children() const
Definition: qgsdataitem.h:337
virtual void addChildItem(QgsDataItem *child, bool refresh=false)
Inserts a new child item.
virtual Qgis::BrowserItemCapabilities capabilities2() const
Returns the capabilities for the data item.
Definition: qgsdataitem.h:303
A directory: contains subdirectories and layers.
QString dirPath() const
Returns the full path to the directory the item represents.
Contains various Favorites directories.
static QStringList splitPathToComponents(const QString &path)
Given a file path, returns a list of all the components leading to that path.
This class is a composition of two QSettings instances:
Definition: qgssettings.h:62
QVariant value(const QString &key, const QVariant &defaultValue=QVariant(), Section section=NoSection) const
Returns the value for setting key.
void setValue(const QString &key, const QVariant &value, QgsSettings::Section section=QgsSettings::NoSection)
Sets the value of setting key to value.
int scaleIconSize(int standardSize)
Scales an icon size to compensate for display pixel density, making the icon size hi-dpi friendly,...
#define str(x)
Definition: qgis.cpp:37
#define QgsDebugMsgLevel(str, level)
Definition: qgslogger.h:39