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