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