1 /***************************************************************************
2 qgsbrowserdockwidget.cpp
3 ---------------------
4 begin : July 2011
5 copyright : (C) 2011 by Martin Dobias
6 email : wonder dot sk at gmail dot com
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 #include "qgsbrowserdockwidget.h"
16 #include "qgsbrowserdockwidget_p.h"
17
18 #include <QAbstractTextDocumentLayout>
19 #include <QHeaderView>
20 #include <QTreeView>
21 #include <QMenu>
22 #include <QToolButton>
23 #include <QFileDialog>
24 #include <QPlainTextDocumentLayout>
25 #include <QSortFilterProxyModel>
26
27 #include "qgsbrowserguimodel.h"
28 #include "qgsbrowsertreeview.h"
29 #include "qgslogger.h"
30 #include "qgsrasterlayer.h"
31 #include "qgsvectorlayer.h"
32 #include "qgsproject.h"
33 #include "qgssettings.h"
34 #include "qgsnewnamedialog.h"
35 #include "qgsbrowserproxymodel.h"
36 #include "qgsgui.h"
37 #include "qgswindowmanagerinterface.h"
38 #include "qgsnative.h"
39 #include "qgsdataitemguiproviderregistry.h"
40 #include "qgsdataitemguiprovider.h"
41
42 // browser layer properties dialog
43 #include "qgsapplication.h"
44 #include "qgsmapcanvas.h"
45
46 #include <QDragEnterEvent>
47
QgsBrowserDockWidget(const QString & name,QgsBrowserGuiModel * browserModel,QWidget * parent)48 QgsBrowserDockWidget::QgsBrowserDockWidget( const QString &name, QgsBrowserGuiModel *browserModel, QWidget *parent )
49 : QgsDockWidget( parent )
50 , mModel( browserModel )
51 , mPropertiesWidgetEnabled( false )
52 , mPropertiesWidgetHeight( 0 )
53 {
54 setupUi( this );
55
56 mContents->layout()->setContentsMargins( 0, 0, 0, 0 );
57 static_cast< QVBoxLayout * >( mContents->layout() )->setSpacing( 0 );
58
59 setWindowTitle( name );
60
61 mBrowserView = new QgsDockBrowserTreeView( this );
62 mLayoutBrowser->addWidget( mBrowserView );
63
64 mWidgetFilter->hide();
65 mLeFilter->setPlaceholderText( tr( "Type here to filter visible items…" ) );
66 // icons from http://www.fatcow.com/free-icons License: CC Attribution 3.0
67
68 QMenu *menu = new QMenu( this );
69 menu->setSeparatorsCollapsible( false );
70 mBtnFilterOptions->setMenu( menu );
71 QAction *action = new QAction( tr( "Case Sensitive" ), menu );
72 action->setData( "case" );
73 action->setCheckable( true );
74 action->setChecked( false );
75 connect( action, &QAction::toggled, this, &QgsBrowserDockWidget::setCaseSensitive );
76 menu->addAction( action );
77 QActionGroup *group = new QActionGroup( menu );
78 action = new QAction( tr( "Filter Pattern Syntax" ), group );
79 action->setSeparator( true );
80 menu->addAction( action );
81 action = new QAction( tr( "Normal" ), group );
82 action->setData( QgsBrowserProxyModel::Normal );
83 action->setCheckable( true );
84 action->setChecked( true );
85 menu->addAction( action );
86 action = new QAction( tr( "Wildcard(s)" ), group );
87 action->setData( QgsBrowserProxyModel::Wildcards );
88 action->setCheckable( true );
89 menu->addAction( action );
90 action = new QAction( tr( "Regular Expression" ), group );
91 action->setData( QgsBrowserProxyModel::RegularExpression );
92 action->setCheckable( true );
93 menu->addAction( action );
94
95 mBrowserView->setExpandsOnDoubleClick( false );
96
97 connect( mActionRefresh, &QAction::triggered, this, &QgsBrowserDockWidget::refresh );
98 connect( mActionAddLayers, &QAction::triggered, this, &QgsBrowserDockWidget::addSelectedLayers );
99 connect( mActionCollapse, &QAction::triggered, mBrowserView, &QgsDockBrowserTreeView::collapseAll );
100 connect( mActionShowFilter, &QAction::triggered, this, &QgsBrowserDockWidget::showFilterWidget );
101 connect( mActionPropertiesWidget, &QAction::triggered, this, &QgsBrowserDockWidget::enablePropertiesWidget );
102 connect( mLeFilter, &QgsFilterLineEdit::returnPressed, this, &QgsBrowserDockWidget::setFilter );
103 connect( mLeFilter, &QgsFilterLineEdit::cleared, this, &QgsBrowserDockWidget::setFilter );
104 connect( mLeFilter, &QgsFilterLineEdit::textChanged, this, &QgsBrowserDockWidget::setFilter );
105 connect( group, &QActionGroup::triggered, this, &QgsBrowserDockWidget::setFilterSyntax );
106 connect( mBrowserView, &QgsDockBrowserTreeView::customContextMenuRequested, this, &QgsBrowserDockWidget::showContextMenu );
107 connect( mBrowserView, &QgsDockBrowserTreeView::doubleClicked, this, &QgsBrowserDockWidget::itemDoubleClicked );
108 connect( mSplitter, &QSplitter::splitterMoved, this, &QgsBrowserDockWidget::splitterMoved );
109 }
110
~QgsBrowserDockWidget()111 QgsBrowserDockWidget::~QgsBrowserDockWidget()
112 {
113 QgsSettings settings;
114 settings.setValue( settingsSection() + "/propertiesWidgetEnabled", mPropertiesWidgetEnabled );
115 //settings.setValue(settingsSection() + "/propertiesWidgetHeight", mPropertiesWidget->size().height() );
116 settings.setValue( settingsSection() + "/propertiesWidgetHeight", mPropertiesWidgetHeight );
117 }
118
showEvent(QShowEvent * e)119 void QgsBrowserDockWidget::showEvent( QShowEvent *e )
120 {
121 // delayed initialization of the model
122 if ( !mModel->initialized( ) )
123 {
124 mModel->initialize();
125 }
126 if ( ! mProxyModel )
127 {
128 mProxyModel = new QgsBrowserProxyModel( this );
129 mProxyModel->setBrowserModel( mModel );
130 mProxyModel->setHiddenDataItemProviderKeyFilter( mDisabledDataItemsKeys );
131 mBrowserView->setSettingsSection( objectName().toLower() ); // to distinguish 2 or more instances of the browser
132 mBrowserView->setBrowserModel( mModel );
133 mBrowserView->setModel( mProxyModel );
134 mBrowserView->setSortingEnabled( true );
135 mBrowserView->sortByColumn( 0, Qt::AscendingOrder );
136 // provide a horizontal scroll bar instead of using ellipse (...) for longer items
137 mBrowserView->setTextElideMode( Qt::ElideNone );
138 mBrowserView->header()->setSectionResizeMode( 0, QHeaderView::ResizeToContents );
139 mBrowserView->header()->setStretchLastSection( false );
140
141 // selectionModel is created when model is set on tree
142 connect( mBrowserView->selectionModel(), &QItemSelectionModel::selectionChanged,
143 this, &QgsBrowserDockWidget::selectionChanged );
144
145 // Forward the model changed signals to the widget
146 connect( mModel, &QgsBrowserModel::connectionsChanged,
147 this, &QgsBrowserDockWidget::connectionsChanged );
148
149
150 // objectName used by settingsSection() is not yet set in constructor
151 QgsSettings settings;
152 mPropertiesWidgetEnabled = settings.value( settingsSection() + "/propertiesWidgetEnabled", false ).toBool();
153 mActionPropertiesWidget->setChecked( mPropertiesWidgetEnabled );
154 mPropertiesWidget->setVisible( false ); // false until item is selected
155
156 mPropertiesWidgetHeight = settings.value( settingsSection() + "/propertiesWidgetHeight" ).toFloat();
157 QList<int> sizes = mSplitter->sizes();
158 int total = sizes.value( 0 ) + sizes.value( 1 );
159 int height = static_cast<int>( total * mPropertiesWidgetHeight );
160 sizes.clear();
161 sizes << total - height << height;
162 mSplitter->setSizes( sizes );
163 }
164
165 QgsDockWidget::showEvent( e );
166 }
167
itemDoubleClicked(const QModelIndex & index)168 void QgsBrowserDockWidget::itemDoubleClicked( const QModelIndex &index )
169 {
170 QgsDataItem *item = mModel->dataItem( mProxyModel->mapToSource( index ) );
171 if ( !item )
172 return;
173
174 QgsDataItemGuiContext context = createContext();
175
176 const QList< QgsDataItemGuiProvider * > providers = QgsGui::instance()->dataItemGuiProviderRegistry()->providers();
177 for ( QgsDataItemGuiProvider *provider : providers )
178 {
179 if ( provider->handleDoubleClick( item, context ) )
180 return;
181 }
182
183 // if no providers overrode the double-click handling for this item, we give the item itself a chance
184 if ( !item->handleDoubleClick() )
185 {
186 // double-click not handled by browser model, so use as default view expand behavior
187 if ( mBrowserView->isExpanded( index ) )
188 mBrowserView->collapse( index );
189 else
190 mBrowserView->expand( index );
191 }
192 }
193
showContextMenu(QPoint pt)194 void QgsBrowserDockWidget::showContextMenu( QPoint pt )
195 {
196 QModelIndex index = mProxyModel->mapToSource( mBrowserView->indexAt( pt ) );
197 QgsDataItem *item = mModel->dataItem( index );
198 if ( !item )
199 return;
200
201 const QModelIndexList selection = mBrowserView->selectionModel()->selectedIndexes();
202 QList< QgsDataItem * > selectedItems;
203 selectedItems.reserve( selection.size() );
204 for ( const QModelIndex &selectedIndex : selection )
205 {
206 QgsDataItem *selectedItem = mProxyModel->dataItem( selectedIndex );
207 if ( selectedItem )
208 selectedItems << selectedItem;
209 }
210
211 QMenu *menu = new QMenu( this );
212
213 const QList<QMenu *> menus = item->menus( menu );
214 QList<QAction *> actions = item->actions( menu );
215
216 if ( !menus.isEmpty() )
217 {
218 for ( QMenu *mn : menus )
219 {
220 menu->addMenu( mn );
221 }
222 }
223
224 if ( !actions.isEmpty() )
225 {
226 if ( !menu->actions().isEmpty() )
227 menu->addSeparator();
228 // add action to the menu
229 menu->addActions( actions );
230 }
231
232 QgsDataItemGuiContext context = createContext();
233
234 const QList< QgsDataItemGuiProvider * > providers = QgsGui::instance()->dataItemGuiProviderRegistry()->providers();
235 for ( QgsDataItemGuiProvider *provider : providers )
236 {
237 provider->populateContextMenu( item, menu, selectedItems, context );
238 }
239
240 if ( menu->actions().isEmpty() )
241 {
242 delete menu;
243 return;
244 }
245
246 menu->popup( mBrowserView->mapToGlobal( pt ) );
247 }
248
addFavorite()249 void QgsBrowserDockWidget::addFavorite()
250 {
251 QModelIndex index = mProxyModel->mapToSource( mBrowserView->currentIndex() );
252 QgsDataItem *item = mModel->dataItem( index );
253 if ( !item )
254 return;
255
256 QgsDirectoryItem *dirItem = qobject_cast<QgsDirectoryItem *>( item );
257 if ( !dirItem )
258 return;
259
260 Q_NOWARN_DEPRECATED_PUSH
261 addFavoriteDirectory( dirItem->dirPath() );
262 Q_NOWARN_DEPRECATED_POP
263 }
264
addFavoriteDirectory()265 void QgsBrowserDockWidget::addFavoriteDirectory()
266 {
267 QString directory = QFileDialog::getExistingDirectory( this, tr( "Add directory to favorites" ) );
268 if ( !directory.isEmpty() )
269 {
270 Q_NOWARN_DEPRECATED_PUSH
271 addFavoriteDirectory( directory );
272 Q_NOWARN_DEPRECATED_POP
273 }
274 }
275
addFavoriteDirectory(const QString & favDir,const QString & name)276 void QgsBrowserDockWidget::addFavoriteDirectory( const QString &favDir, const QString &name )
277 {
278 mModel->addFavoriteDirectory( favDir, name );
279 }
280
setMessageBar(QgsMessageBar * bar)281 void QgsBrowserDockWidget::setMessageBar( QgsMessageBar *bar )
282 {
283 mMessageBar = bar;
284 mModel->setMessageBar( bar );
285 }
286
messageBar()287 QgsMessageBar *QgsBrowserDockWidget::messageBar()
288 {
289 return mMessageBar;
290 }
291
setDisabledDataItemsKeys(const QStringList & filter)292 void QgsBrowserDockWidget::setDisabledDataItemsKeys( const QStringList &filter )
293 {
294 mDisabledDataItemsKeys = filter;
295
296 if ( !mProxyModel )
297 return;
298
299 mProxyModel->setHiddenDataItemProviderKeyFilter( mDisabledDataItemsKeys );
300 }
301
removeFavorite()302 void QgsBrowserDockWidget::removeFavorite()
303 {
304 mModel->removeFavorite( mProxyModel->mapToSource( mBrowserView->currentIndex() ) );
305 }
306
refresh()307 void QgsBrowserDockWidget::refresh()
308 {
309 refreshModel( QModelIndex() );
310 }
311
refreshModel(const QModelIndex & index)312 void QgsBrowserDockWidget::refreshModel( const QModelIndex &index )
313 {
314 if ( mModel && mProxyModel )
315 {
316 QgsDataItem *item = mModel->dataItem( index );
317 if ( item )
318 {
319 QgsDebugMsgLevel( "path = " + item->path(), 4 );
320 }
321 else
322 {
323 QgsDebugMsgLevel( QStringLiteral( "invalid item" ), 4 );
324 }
325
326 if ( item && ( item->capabilities2() & QgsDataItem::Fertile ) )
327 {
328 mModel->refresh( index );
329 }
330
331 for ( int i = 0; i < mModel->rowCount( index ); i++ )
332 {
333 QModelIndex idx = mModel->index( i, 0, index );
334 QModelIndex proxyIdx = mProxyModel->mapFromSource( idx );
335 QgsDataItem *child = mModel->dataItem( idx );
336
337 // Check also expanded descendants so that the whole expanded path does not get collapsed if one item is collapsed.
338 // Fast items (usually root items) are refreshed so that when collapsed, it is obvious they are if empty (no expand symbol).
339 if ( mBrowserView->isExpanded( proxyIdx ) || mBrowserView->hasExpandedDescendant( proxyIdx ) || ( child && child->capabilities2() & QgsDataItem::Fast ) )
340 {
341 refreshModel( idx );
342 }
343 else
344 {
345 if ( child && ( child->capabilities2() & QgsDataItem::Fertile ) )
346 {
347 child->depopulate();
348 }
349 }
350 }
351 }
352 }
353
addLayer(QgsLayerItem * layerItem)354 void QgsBrowserDockWidget::addLayer( QgsLayerItem *layerItem )
355 {
356 if ( !layerItem )
357 return;
358
359 QgsMimeDataUtils::UriList list;
360 list << layerItem->mimeUri();
361 emit handleDropUriList( list );
362 }
363
addLayerAtIndex(const QModelIndex & index)364 bool QgsBrowserDockWidget::addLayerAtIndex( const QModelIndex &index )
365 {
366 QgsDebugMsg( QStringLiteral( "rowCount() = %1" ).arg( mModel->rowCount( mProxyModel->mapToSource( index ) ) ) );
367 QgsDataItem *item = mModel->dataItem( mProxyModel->mapToSource( index ) );
368
369 if ( item && item->type() == QgsDataItem::Project )
370 {
371 QgsProjectItem *projectItem = qobject_cast<QgsProjectItem *>( item );
372 if ( projectItem )
373 {
374 QApplication::setOverrideCursor( Qt::WaitCursor );
375 emit openFile( projectItem->path(), QStringLiteral( "project" ) );
376 QApplication::restoreOverrideCursor();
377 }
378 return true;
379 }
380 else if ( item && item->type() == QgsDataItem::Layer )
381 {
382 QgsLayerItem *layerItem = qobject_cast<QgsLayerItem *>( item );
383 if ( layerItem )
384 {
385 QApplication::setOverrideCursor( Qt::WaitCursor );
386 addLayer( layerItem );
387 QApplication::restoreOverrideCursor();
388 }
389 return true;
390 }
391 return false;
392 }
393
addSelectedLayers()394 void QgsBrowserDockWidget::addSelectedLayers()
395 {
396 QApplication::setOverrideCursor( Qt::WaitCursor );
397
398 // get a sorted list of selected indexes
399 QModelIndexList list = mBrowserView->selectionModel()->selectedIndexes();
400 std::sort( list.begin(), list.end() );
401
402 // If any of the layer items are QGIS we just open and exit the loop
403 const auto constList = list;
404 for ( const QModelIndex &index : constList )
405 {
406 QgsDataItem *item = mModel->dataItem( mProxyModel->mapToSource( index ) );
407 if ( item && item->type() == QgsDataItem::Project )
408 {
409 QgsProjectItem *projectItem = qobject_cast<QgsProjectItem *>( item );
410 if ( projectItem )
411 emit openFile( projectItem->path(), QStringLiteral( "project" ) );
412
413 QApplication::restoreOverrideCursor();
414 return;
415 }
416 }
417
418 // add items in reverse order so they are in correct order in the layers dock
419 for ( int i = list.size() - 1; i >= 0; i-- )
420 {
421 QgsDataItem *item = mModel->dataItem( mProxyModel->mapToSource( list[i] ) );
422 if ( item && item->type() == QgsDataItem::Layer )
423 {
424 QgsLayerItem *layerItem = qobject_cast<QgsLayerItem *>( item );
425 if ( layerItem )
426 addLayer( layerItem );
427 }
428 }
429
430 QApplication::restoreOverrideCursor();
431 }
432
hideItem()433 void QgsBrowserDockWidget::hideItem()
434 {
435 QModelIndex index = mProxyModel->mapToSource( mBrowserView->currentIndex() );
436 QgsDataItem *item = mModel->dataItem( index );
437 if ( ! item )
438 return;
439
440 if ( item->type() == QgsDataItem::Directory )
441 {
442 mModel->hidePath( item );
443 }
444 }
445
showProperties()446 void QgsBrowserDockWidget::showProperties()
447 {
448 QModelIndex index = mProxyModel->mapToSource( mBrowserView->currentIndex() );
449 QgsDataItem *item = mModel->dataItem( index );
450 if ( ! item )
451 return;
452
453 if ( item->type() == QgsDataItem::Layer || item->type() == QgsDataItem::Directory )
454 {
455 QgsBrowserPropertiesDialog *dialog = new QgsBrowserPropertiesDialog( settingsSection(), this );
456 dialog->setAttribute( Qt::WA_DeleteOnClose );
457 dialog->setItem( item, createContext() );
458 dialog->show();
459 }
460 }
461
toggleFastScan()462 void QgsBrowserDockWidget::toggleFastScan()
463 {
464 QModelIndex index = mProxyModel->mapToSource( mBrowserView->currentIndex() );
465 QgsDataItem *item = mModel->dataItem( index );
466 if ( ! item )
467 return;
468
469 if ( item->type() == QgsDataItem::Directory )
470 {
471 QgsSettings settings;
472 QStringList fastScanDirs = settings.value( QStringLiteral( "qgis/scanItemsFastScanUris" ),
473 QStringList() ).toStringList();
474 int idx = fastScanDirs.indexOf( item->path() );
475 if ( idx != -1 )
476 {
477 fastScanDirs.removeAt( idx );
478 }
479 else
480 {
481 fastScanDirs << item->path();
482 }
483 settings.setValue( QStringLiteral( "qgis/scanItemsFastScanUris" ), fastScanDirs );
484 }
485 }
486
showFilterWidget(bool visible)487 void QgsBrowserDockWidget::showFilterWidget( bool visible )
488 {
489 mWidgetFilter->setVisible( visible );
490 if ( ! visible )
491 {
492 mLeFilter->setText( QString() );
493 setFilter();
494 }
495 else
496 {
497 mLeFilter->setFocus();
498 }
499 }
500
setFilter()501 void QgsBrowserDockWidget::setFilter()
502 {
503 QString filter = mLeFilter->text();
504 if ( mProxyModel )
505 mProxyModel->setFilterString( filter );
506 }
507
updateProjectHome()508 void QgsBrowserDockWidget::updateProjectHome()
509 {
510 if ( mModel )
511 mModel->updateProjectHome();
512 }
513
setFilterSyntax(QAction * action)514 void QgsBrowserDockWidget::setFilterSyntax( QAction *action )
515 {
516 if ( !action || ! mProxyModel )
517 return;
518
519 mProxyModel->setFilterSyntax( static_cast< QgsBrowserProxyModel::FilterSyntax >( action->data().toInt() ) );
520 }
521
setCaseSensitive(bool caseSensitive)522 void QgsBrowserDockWidget::setCaseSensitive( bool caseSensitive )
523 {
524 if ( ! mProxyModel )
525 return;
526 mProxyModel->setFilterCaseSensitivity( caseSensitive ? Qt::CaseSensitive : Qt::CaseInsensitive );
527 }
528
selectedItemsCount()529 int QgsBrowserDockWidget::selectedItemsCount()
530 {
531 QItemSelectionModel *selectionModel = mBrowserView->selectionModel();
532 if ( selectionModel )
533 {
534 return selectionModel->selectedIndexes().size();
535 }
536 return 0;
537 }
538
createContext()539 QgsDataItemGuiContext QgsBrowserDockWidget::createContext()
540 {
541 QgsDataItemGuiContext context;
542 context.setMessageBar( mMessageBar );
543 return context;
544 }
545
selectionChanged(const QItemSelection & selected,const QItemSelection & deselected)546 void QgsBrowserDockWidget::selectionChanged( const QItemSelection &selected, const QItemSelection &deselected )
547 {
548 Q_UNUSED( selected )
549 Q_UNUSED( deselected )
550 if ( mPropertiesWidgetEnabled )
551 {
552 setPropertiesWidget();
553 }
554 }
555
clearPropertiesWidget()556 void QgsBrowserDockWidget::clearPropertiesWidget()
557 {
558 while ( mPropertiesLayout->count() > 0 )
559 {
560 delete mPropertiesLayout->itemAt( 0 )->widget();
561 }
562 mPropertiesWidget->setVisible( false );
563 }
564
setPropertiesWidget()565 void QgsBrowserDockWidget::setPropertiesWidget()
566 {
567 clearPropertiesWidget();
568 QItemSelectionModel *selectionModel = mBrowserView->selectionModel();
569 if ( selectionModel )
570 {
571 QModelIndexList indexes = selectionModel->selectedIndexes();
572 if ( indexes.size() == 1 )
573 {
574 QModelIndex index = mProxyModel->mapToSource( indexes.value( 0 ) );
575 QgsDataItem *item = mModel->dataItem( index );
576 QgsDataItemGuiContext context = createContext();
577 QgsBrowserPropertiesWidget *propertiesWidget = QgsBrowserPropertiesWidget::createWidget( item, context, mPropertiesWidget );
578 if ( propertiesWidget )
579 {
580 propertiesWidget->setCondensedMode( true );
581 mPropertiesLayout->addWidget( propertiesWidget );
582 }
583 }
584 }
585 mPropertiesWidget->setVisible( mPropertiesLayout->count() > 0 );
586 }
587
enablePropertiesWidget(bool enable)588 void QgsBrowserDockWidget::enablePropertiesWidget( bool enable )
589 {
590 mPropertiesWidgetEnabled = enable;
591 if ( enable && selectedItemsCount() == 1 )
592 {
593 setPropertiesWidget();
594 }
595 else
596 {
597 clearPropertiesWidget();
598 }
599 }
600
setActiveIndex(const QModelIndex & index)601 void QgsBrowserDockWidget::setActiveIndex( const QModelIndex &index )
602 {
603 if ( index.isValid() )
604 {
605 QModelIndex proxyIndex = mProxyModel->mapFromSource( index );
606 mBrowserView->expand( proxyIndex );
607 mBrowserView->setCurrentIndex( proxyIndex );
608 }
609 }
610
splitterMoved()611 void QgsBrowserDockWidget::splitterMoved()
612 {
613 QList<int> sizes = mSplitter->sizes();
614 float total = sizes.value( 0 ) + sizes.value( 1 );
615 mPropertiesWidgetHeight = total > 0 ? sizes.value( 1 ) / total : 0;
616 }
617