QGIS API Documentation 3.29.0-Master (19d7edcfed)
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#include <QRegularExpression>
31
33 : QTreeView( parent )
34 , mSettingsSection( QStringLiteral( "browser" ) )
35{
36 setEditTriggers( QAbstractItemView::EditKeyPressed );
37 setIndentation( QgsGuiUtils::scaleIconSize( 16 ) );
38}
39
40void QgsBrowserTreeView::keyPressEvent( QKeyEvent *event )
41{
42 if ( event->key() == Qt::Key_Return || event->key() == Qt::Key_Enter )
43 emit doubleClicked( currentIndex() );
44 else
45 QTreeView::keyPressEvent( event );
46}
47
48void QgsBrowserTreeView::setModel( QAbstractItemModel *model )
49{
50
51 QTreeView::setModel( model );
52
53 restoreState();
54}
55
57{
58 mBrowserModel = model;
59}
60
61void QgsBrowserTreeView::showEvent( QShowEvent *e )
62{
63 Q_UNUSED( e )
64 if ( model() )
65 restoreState();
66 QTreeView::showEvent( e );
67}
68
69// closeEvent is not called when application is closed
70void QgsBrowserTreeView::hideEvent( QHideEvent *e )
71{
72 Q_UNUSED( e )
73 // hideEvent() may be called (Mac) before showEvent
74 if ( model() )
75 saveState();
76 QTreeView::hideEvent( e );
77}
78
79void QgsBrowserTreeView::saveState()
80{
81 QgsSettings settings;
82 const QStringList expandedPaths = expandedPathsList( QModelIndex() );
83 settings.setValue( expandedPathsKey(), expandedPaths );
84 QgsDebugMsgLevel( "expandedPaths = " + expandedPaths.join( ' ' ), 4 );
85}
86
87void QgsBrowserTreeView::restoreState()
88{
89 const QgsSettings settings;
90 mExpandPaths = settings.value( expandedPathsKey(), QVariant() ).toStringList();
91
92 QgsDebugMsgLevel( "mExpandPaths = " + mExpandPaths.join( ' ' ), 4 );
93 if ( !mExpandPaths.isEmpty() )
94 {
95 QSet<QModelIndex> expandIndexSet;
96 const auto constMExpandPaths = mExpandPaths;
97 for ( const QString &path : constMExpandPaths )
98 {
99 const QModelIndex expandIndex = QgsBrowserGuiModel::findPath( model(), path, Qt::MatchStartsWith );
100 if ( expandIndex.isValid() )
101 {
102 const QModelIndex modelIndex = browserModel()->findPath( path, Qt::MatchExactly );
103 if ( modelIndex.isValid() )
104 {
105 QgsDataItem *ptr = browserModel()->dataItem( modelIndex );
107 {
108 QgsDebugMsgLevel( "do not expand index for path " + path, 4 );
109 const QModelIndex parentIndex = model()->parent( expandIndex );
110 // Still we need to store the parent in order to expand it
111 if ( parentIndex.isValid() )
112 expandIndexSet.insert( parentIndex );
113 }
114 else
115 {
116 expandIndexSet.insert( expandIndex );
117 }
118 }
119 }
120 else
121 {
122 QgsDebugMsgLevel( "index for path " + path + " not found", 4 );
123 }
124 }
125 const auto constExpandIndexSet = expandIndexSet;
126 for ( const QModelIndex &expandIndex : constExpandIndexSet )
127 {
128 expandTree( expandIndex );
129 }
130 }
131
132 // expand root favorites item
133 const QModelIndex index = QgsBrowserGuiModel::findPath( model(), QStringLiteral( "favorites:" ) );
134 expand( index );
135}
136
137void QgsBrowserTreeView::expandTree( const QModelIndex &index )
138{
139 if ( !model() )
140 return;
141
142 QgsDebugMsgLevel( "itemPath = " + model()->data( index, QgsBrowserGuiModel::PathRole ).toString(), 4 );
143
144 expand( index );
145 const QModelIndex parentIndex = model()->parent( index );
146 if ( parentIndex.isValid() )
147 expandTree( parentIndex );
148}
149
150bool QgsBrowserTreeView::treeExpanded( const QModelIndex &index )
151{
152 if ( !model() )
153 return false;
154 if ( !isExpanded( index ) )
155 return false;
156 const QModelIndex parentIndex = model()->parent( index );
157 if ( parentIndex.isValid() )
158 return treeExpanded( parentIndex );
159
160 return true; // root
161}
162
163bool QgsBrowserTreeView::hasExpandedDescendant( const QModelIndex &index ) const
164{
165 if ( !model() || !index.isValid() )
166 return false;
167
168 for ( int i = 0; i < model()->rowCount( index ); i++ )
169 {
170 const QModelIndex childIndex = model()->index( i, 0, index );
171 if ( isExpanded( childIndex ) )
172 return true;
173
174 if ( hasExpandedDescendant( childIndex ) )
175 return true;
176 }
177 return false;
178}
179
181{
182 const QStringList pathParts = QgsFileUtils::splitPathToComponents( str );
183 if ( pathParts.isEmpty() )
184 return;
185
186 // first we build a list of all directory item candidates we could use to start the expansion from
187 QVector< QgsDirectoryItem * > initialDirectoryItemCandidates;
188 const QVector< QgsDataItem * > rootItems = mBrowserModel->rootItems();
189 for ( QgsDataItem *item : rootItems )
190 {
191 if ( QgsDirectoryItem *dirItem = qobject_cast< QgsDirectoryItem * >( item ) )
192 {
193 initialDirectoryItemCandidates << dirItem;
194 }
195 else if ( QgsFavoritesItem *favoritesItem = qobject_cast< QgsFavoritesItem * >( item ) )
196 {
197 const QVector<QgsDataItem * > favoriteChildren = favoritesItem->children();
198 for ( QgsDataItem *favoriteChild : favoriteChildren )
199 {
200 if ( QgsDirectoryItem *dirItem = qobject_cast< QgsDirectoryItem * >( favoriteChild ) )
201 {
202 initialDirectoryItemCandidates << dirItem;
203 }
204 }
205 }
206 }
207
208 QgsDirectoryItem *currentDirectoryItem = nullptr;
209 QString currentCandidatePath;
210 for ( const QString &thisPart : pathParts )
211 {
212 currentCandidatePath += ( currentCandidatePath.isEmpty() || currentCandidatePath.endsWith( '/' ) ? QString() : QStringLiteral( "/" ) ) + thisPart;
213
214 auto it = initialDirectoryItemCandidates.begin();
215 while ( it != initialDirectoryItemCandidates.end() )
216 {
217 if ( !( *it )->dirPath().startsWith( currentCandidatePath ) )
218 {
219 it = initialDirectoryItemCandidates.erase( it );
220 }
221 else
222 {
223 if ( str.startsWith( ( *it )->dirPath() ) )
224 currentDirectoryItem = *it;
225 it++;
226 }
227 }
228 }
229
230 if ( !currentDirectoryItem )
231 return; // should we create a new root drive item automatically??
232
233 QStringList remainingParts = pathParts;
234 auto it = remainingParts.begin();
235 QDir currentDir = *it;
236 while ( it != remainingParts.end() )
237 {
238 if ( currentDirectoryItem->dirPath().startsWith( currentDir.filePath( *it ) ) )
239 {
240 currentDir = QDir( currentDir.filePath( *it ) );
241 it = remainingParts.erase( it );
242 }
243 else
244 {
245 break;
246 }
247 }
248
249 currentDir = QDir( currentDirectoryItem->dirPath() );
250 QList< QgsDirectoryItem * > pathItems;
251
252 pathItems << currentDirectoryItem;
253
254 for ( const QString &currentFolderName : std::as_const( remainingParts ) )
255 {
256 const QString thisPath = currentDir.filePath( currentFolderName );
257
258 if ( !QFile::exists( thisPath ) )
259 break;
260
261 // check if current directory item already has a child for the folder
262 QgsDirectoryItem *existingChild = nullptr;
263 const QVector< QgsDataItem * > children = currentDirectoryItem->children();
264 for ( QgsDataItem *child : children )
265 {
266 if ( QgsDirectoryItem *childDirectoryItem = qobject_cast< QgsDirectoryItem *>( child ) )
267 {
268 if ( childDirectoryItem->dirPath() == thisPath )
269 {
270 existingChild = childDirectoryItem;
271 break;
272 }
273 }
274 }
275
276 if ( existingChild )
277 {
278 pathItems << existingChild;
279 currentDirectoryItem = existingChild;
280 }
281 else
282 {
283 QgsDirectoryItem *newDir = new QgsDirectoryItem( nullptr, currentFolderName, thisPath );
284 pathItems << newDir;
285 currentDirectoryItem ->addChildItem( newDir, true );
286 currentDirectoryItem = newDir;
287 }
288
289 currentDir = QDir( thisPath );
290 }
291
292 for ( QgsDirectoryItem *i : std::as_const( pathItems ) )
293 {
294 QModelIndex index = mBrowserModel->findItem( i );
295 if ( QSortFilterProxyModel *proxyModel = qobject_cast< QSortFilterProxyModel *>( model() ) )
296 {
297 index = proxyModel->mapFromSource( index );
298 }
299 expand( index );
300 }
301}
302
304{
305 if ( !mBrowserModel )
306 return false;
307
308 QModelIndex index = mBrowserModel->findItem( item );
309 if ( !index.isValid() )
310 return false;
311
312 if ( QSortFilterProxyModel *proxyModel = qobject_cast< QSortFilterProxyModel *>( model() ) )
313 {
314 index = proxyModel->mapFromSource( index );
315 }
316
317 setCurrentIndex( index );
318 return true;
319}
320
321// rowsInserted signal is used to continue in state restoring
322void QgsBrowserTreeView::rowsInserted( const QModelIndex &parentIndex, int start, int end )
323{
324 QTreeView::rowsInserted( parentIndex, start, end );
325
326 if ( !model() )
327 return;
328
329 if ( mExpandPaths.isEmpty() )
330 return;
331
332 QgsDebugMsgLevel( "mExpandPaths = " + mExpandPaths.join( ',' ), 2 );
333
334 const QString parentPath = model()->data( parentIndex, QgsBrowserGuiModel::PathRole ).toString();
335 QgsDebugMsgLevel( "parentPath = " + parentPath, 2 );
336
337 // remove parentPath from paths to be expanded
338 mExpandPaths.removeOne( parentPath );
339
340 // Remove the subtree from mExpandPaths if user collapsed the item in the meantime
341 if ( !treeExpanded( parentIndex ) )
342 {
343 const auto constMExpandPaths = mExpandPaths;
344 for ( const QString &path : constMExpandPaths )
345 {
346 if ( path.startsWith( parentPath + '/' ) )
347 mExpandPaths.removeOne( path );
348 }
349 return;
350 }
351
352 for ( int i = start; i <= end; i++ )
353 {
354 const QModelIndex childIndex = model()->index( i, 0, parentIndex );
355 const QString childPath = model()->data( childIndex, QgsBrowserGuiModel::PathRole ).toString();
356 const QString escapedChildPath = QRegularExpression::escape( childPath );
357
358 QgsDebugMsgLevel( "childPath = " + childPath + " escapedChildPath = " + escapedChildPath, 2 );
359 if ( mExpandPaths.contains( childPath ) || mExpandPaths.indexOf( QRegularExpression( "^" + 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:63
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