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