QGIS API Documentation 3.41.0-Master (cea29feecf2)
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 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, static_cast<int>( QgsBrowserModel::CustomRole::Path ) ).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
180void QgsBrowserTreeView::expandPath( const QString &str, bool selectPath )
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 QgsDirectoryItem *lastItem = nullptr;
293 for ( QgsDirectoryItem *i : std::as_const( pathItems ) )
294 {
295 QModelIndex index = mBrowserModel->findItem( i );
296 if ( QSortFilterProxyModel *proxyModel = qobject_cast<QSortFilterProxyModel *>( model() ) )
297 {
298 index = proxyModel->mapFromSource( index );
299 }
300 expand( index );
301 lastItem = i;
302 }
303
304 if ( selectPath && lastItem )
305 setSelectedItem( lastItem );
306}
307
309{
310 if ( !mBrowserModel )
311 return false;
312
313 QModelIndex index = mBrowserModel->findItem( item );
314 if ( !index.isValid() )
315 return false;
316
317 if ( QSortFilterProxyModel *proxyModel = qobject_cast<QSortFilterProxyModel *>( model() ) )
318 {
319 index = proxyModel->mapFromSource( index );
320 }
321
322 setCurrentIndex( index );
323 return true;
324}
325
326// rowsInserted signal is used to continue in state restoring
327void QgsBrowserTreeView::rowsInserted( const QModelIndex &parentIndex, int start, int end )
328{
329 QTreeView::rowsInserted( parentIndex, start, end );
330
331 if ( !model() )
332 return;
333
334 if ( mExpandPaths.isEmpty() )
335 return;
336
337 QgsDebugMsgLevel( "mExpandPaths = " + mExpandPaths.join( ',' ), 2 );
338
339 const QString parentPath = model()->data( parentIndex, static_cast<int>( QgsBrowserModel::CustomRole::Path ) ).toString();
340 QgsDebugMsgLevel( "parentPath = " + parentPath, 2 );
341
342 // remove parentPath from paths to be expanded
343 mExpandPaths.removeOne( parentPath );
344
345 // Remove the subtree from mExpandPaths if user collapsed the item in the meantime
346 if ( !treeExpanded( parentIndex ) )
347 {
348 const auto constMExpandPaths = mExpandPaths;
349 for ( const QString &path : constMExpandPaths )
350 {
351 if ( path.startsWith( parentPath + '/' ) )
352 mExpandPaths.removeOne( path );
353 }
354 return;
355 }
356
357 for ( int i = start; i <= end; i++ )
358 {
359 const QModelIndex childIndex = model()->index( i, 0, parentIndex );
360 const QString childPath = model()->data( childIndex, static_cast<int>( QgsBrowserModel::CustomRole::Path ) ).toString();
361 const QString escapedChildPath = QRegularExpression::escape( childPath );
362
363 QgsDebugMsgLevel( "childPath = " + childPath + " escapedChildPath = " + escapedChildPath, 2 );
364 if ( mExpandPaths.contains( childPath ) || mExpandPaths.indexOf( QRegularExpression( "^" + escapedChildPath + "/.*" ) ) != -1 )
365 {
366 QgsDebugMsgLevel( QStringLiteral( "-> expand" ), 2 );
367 const QModelIndex modelIndex = browserModel()->findPath( childPath, Qt::MatchExactly );
368 if ( modelIndex.isValid() )
369 {
370 QgsDataItem *ptr = browserModel()->dataItem( modelIndex );
371 if ( !ptr || !( ptr->capabilities2() & Qgis::BrowserItemCapability::Collapse ) )
372 {
373 expand( childIndex );
374 }
375 }
376 }
377 }
378}
379
380QString QgsBrowserTreeView::expandedPathsKey() const
381{
382 return '/' + mSettingsSection + "/expandedPaths";
383}
384
385QStringList QgsBrowserTreeView::expandedPathsList( const QModelIndex &index )
386{
387 QStringList paths;
388
389 if ( !model() )
390 return paths;
391
392 for ( int i = 0; i < model()->rowCount( index ); i++ )
393 {
394 const QModelIndex childIndex = model()->index( i, 0, index );
395 if ( isExpanded( childIndex ) )
396 {
397 const QStringList childrenPaths = expandedPathsList( childIndex );
398 if ( !childrenPaths.isEmpty() )
399 {
400 paths.append( childrenPaths );
401 }
402 else
403 {
404 paths.append( model()->data( childIndex, static_cast<int>( QgsBrowserModel::CustomRole::Path ) ).toString() );
405 }
406 }
407 }
408 return paths;
409}
@ 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 QgsDebugMsgLevel(str, level)
Definition qgslogger.h:39