1 /***************************************************************************
2 qgsdatasourceselectdialog.cpp - QgsDataSourceSelectDialog
3
4 ---------------------
5 begin : 1.11.2018
6 copyright : (C) 2018 by Alessandro Pasotti
7 email : elpaso@itopen.it
8 ***************************************************************************
9 * *
10 * This program is free software; you can redistribute it and/or modify *
11 * it under the terms of the GNU General Public License as published by *
12 * the Free Software Foundation; either version 2 of the License, or *
13 * (at your option) any later version. *
14 * *
15 ***************************************************************************/
16
17 #include "qgsdatasourceselectdialog.h"
18
19 #include "qgis.h"
20 #include "qgsbrowsermodel.h"
21 #include "qgsgui.h"
22 #include "qgsguiutils.h"
23 #include "qgssettings.h"
24 #include "qgsnative.h"
25 #include "qgslayeritem.h"
26
27 #include <QPushButton>
28 #include <QMenu>
29 #include <QDesktopServices>
30 #include <QDialogButtonBox>
31 #include <QFileInfo>
32 #include <QUrl>
33 #include <QActionGroup>
34
QgsDataSourceSelectWidget(QgsBrowserGuiModel * browserModel,bool setFilterByLayerType,QgsMapLayerType layerType,QWidget * parent)35 QgsDataSourceSelectWidget::QgsDataSourceSelectWidget(
36 QgsBrowserGuiModel *browserModel,
37 bool setFilterByLayerType,
38 QgsMapLayerType layerType,
39 QWidget *parent )
40 : QgsPanelWidget( parent )
41 {
42 if ( ! browserModel )
43 {
44 mBrowserModel = new QgsBrowserGuiModel( this );
45 mBrowserModel->initialize();
46 }
47 else
48 {
49 mBrowserModel = browserModel;
50 mBrowserModel->initialize();
51 }
52
53 setupUi( this );
54
55 mBrowserProxyModel.setBrowserModel( mBrowserModel );
56 mBrowserTreeView->setHeaderHidden( true );
57
58 if ( setFilterByLayerType )
59 {
60 // This will also set the (proxy) model
61 setLayerTypeFilter( layerType );
62 }
63 else
64 {
65 mBrowserTreeView->setModel( &mBrowserProxyModel );
66 setValid( false );
67 }
68
69 mBrowserTreeView->setBrowserModel( mBrowserModel );
70
71 mWidgetFilter->hide();
72 mLeFilter->setPlaceholderText( tr( "Type here to filter visible items…" ) );
73 // icons from http://www.fatcow.com/free-icons License: CC Attribution 3.0
74
75 QMenu *menu = new QMenu( this );
76 menu->setSeparatorsCollapsible( false );
77 mBtnFilterOptions->setMenu( menu );
78 QAction *action = new QAction( tr( "Case Sensitive" ), menu );
79 action->setData( "case" );
80 action->setCheckable( true );
81 action->setChecked( false );
82 connect( action, &QAction::toggled, this, &QgsDataSourceSelectWidget::setCaseSensitive );
83 menu->addAction( action );
84 QActionGroup *group = new QActionGroup( menu );
85 action = new QAction( tr( "Filter Pattern Syntax" ), group );
86 action->setSeparator( true );
87 menu->addAction( action );
88 action = new QAction( tr( "Normal" ), group );
89 action->setData( QgsBrowserProxyModel::Normal );
90 action->setCheckable( true );
91 action->setChecked( true );
92 menu->addAction( action );
93 action = new QAction( tr( "Wildcard(s)" ), group );
94 action->setData( QgsBrowserProxyModel::Wildcards );
95 action->setCheckable( true );
96 menu->addAction( action );
97 action = new QAction( tr( "Regular Expression" ), group );
98 action->setData( QgsBrowserProxyModel::RegularExpression );
99 action->setCheckable( true );
100 menu->addAction( action );
101
102 connect( mActionRefresh, &QAction::triggered, this, [ = ] { refreshModel( QModelIndex() ); } );
103 connect( mBrowserTreeView, &QgsBrowserTreeView::clicked, this, &QgsDataSourceSelectWidget::onLayerSelected );
104 connect( mBrowserTreeView, &QgsBrowserTreeView::doubleClicked, this, &QgsDataSourceSelectWidget::itemDoubleClicked );
105 connect( mActionCollapse, &QAction::triggered, mBrowserTreeView, &QgsBrowserTreeView::collapseAll );
106 connect( mActionShowFilter, &QAction::triggered, this, &QgsDataSourceSelectWidget::showFilterWidget );
107 connect( mLeFilter, &QgsFilterLineEdit::returnPressed, this, &QgsDataSourceSelectWidget::setFilter );
108 connect( mLeFilter, &QgsFilterLineEdit::cleared, this, &QgsDataSourceSelectWidget::setFilter );
109 connect( mLeFilter, &QgsFilterLineEdit::textChanged, this, &QgsDataSourceSelectWidget::setFilter );
110 connect( group, &QActionGroup::triggered, this, &QgsDataSourceSelectWidget::setFilterSyntax );
111
112 mBrowserToolbar->setIconSize( QgsGuiUtils::iconSize( true ) );
113
114 if ( QgsSettings().value( QStringLiteral( "datasourceSelectFilterVisible" ), false, QgsSettings::Section::Gui ).toBool() )
115 {
116 mActionShowFilter->trigger();
117 }
118 }
119
120 QgsDataSourceSelectWidget::~QgsDataSourceSelectWidget() = default;
121
showEvent(QShowEvent * e)122 void QgsDataSourceSelectWidget::showEvent( QShowEvent *e )
123 {
124 QgsPanelWidget::showEvent( e );
125 const QString lastSelectedPath( QgsSettings().value( QStringLiteral( "datasourceSelectLastSelectedItem" ),
126 QString(), QgsSettings::Section::Gui ).toString() );
127 if ( ! lastSelectedPath.isEmpty() )
128 {
129 const QModelIndexList items = mBrowserProxyModel.match(
130 mBrowserProxyModel.index( 0, 0 ),
131 QgsBrowserGuiModel::PathRole,
132 QVariant::fromValue( lastSelectedPath ),
133 1,
134 Qt::MatchRecursive );
135 if ( items.count( ) > 0 )
136 {
137 const QModelIndex expandIndex = items.at( 0 );
138 if ( expandIndex.isValid() )
139 {
140 mBrowserTreeView->scrollTo( expandIndex, QgsBrowserTreeView::ScrollHint::PositionAtTop );
141 mBrowserTreeView->expand( expandIndex );
142 }
143 }
144 }
145 }
146
showFilterWidget(bool visible)147 void QgsDataSourceSelectWidget::showFilterWidget( bool visible )
148 {
149 QgsSettings().setValue( QStringLiteral( "datasourceSelectFilterVisible" ), visible, QgsSettings::Section::Gui );
150 mWidgetFilter->setVisible( visible );
151 if ( ! visible )
152 {
153 mLeFilter->setText( QString() );
154 setFilter();
155 }
156 else
157 {
158 mLeFilter->setFocus();
159 }
160 }
161
setDescription(const QString & description)162 void QgsDataSourceSelectWidget::setDescription( const QString &description )
163 {
164 if ( !description.isEmpty() )
165 {
166 if ( !mDescriptionLabel )
167 {
168 mDescriptionLabel = new QLabel();
169 mDescriptionLabel->setWordWrap( true );
170 mDescriptionLabel->setMargin( 4 );
171 mDescriptionLabel->setTextInteractionFlags( Qt::TextBrowserInteraction );
172 connect( mDescriptionLabel, &QLabel::linkActivated, this, [ = ]( const QString & link )
173 {
174 const QUrl url( link );
175 const QFileInfo file( url.toLocalFile() );
176 if ( file.exists() && !file.isDir() )
177 QgsGui::instance()->nativePlatformInterface()->openFileExplorerAndSelectFile( url.toLocalFile() );
178 else
179 QDesktopServices::openUrl( url );
180 } );
181 verticalLayout->insertWidget( 1, mDescriptionLabel );
182 }
183 mDescriptionLabel->setText( description );
184 }
185 else
186 {
187 if ( mDescriptionLabel )
188 {
189 verticalLayout->removeWidget( mDescriptionLabel );
190 delete mDescriptionLabel;
191 mDescriptionLabel = nullptr;
192 }
193 }
194 }
195
setFilter()196 void QgsDataSourceSelectWidget::setFilter()
197 {
198 const QString filter = mLeFilter->text();
199 mBrowserProxyModel.setFilterString( filter );
200 }
201
202
refreshModel(const QModelIndex & index)203 void QgsDataSourceSelectWidget::refreshModel( const QModelIndex &index )
204 {
205
206 QgsDataItem *item = mBrowserModel->dataItem( index );
207 if ( item )
208 {
209 QgsDebugMsgLevel( "path = " + item->path(), 2 );
210 }
211 else
212 {
213 QgsDebugMsg( QStringLiteral( "invalid item" ) );
214 }
215
216 if ( item && ( item->capabilities2() & Qgis::BrowserItemCapability::Fertile ) )
217 {
218 mBrowserModel->refresh( index );
219 }
220
221 for ( int i = 0; i < mBrowserModel->rowCount( index ); i++ )
222 {
223 const QModelIndex idx = mBrowserModel->index( i, 0, index );
224 const QModelIndex proxyIdx = mBrowserProxyModel.mapFromSource( idx );
225 QgsDataItem *child = mBrowserModel->dataItem( idx );
226
227 // Check also expanded descendants so that the whole expanded path does not get collapsed if one item is collapsed.
228 // Fast items (usually root items) are refreshed so that when collapsed, it is obvious they are if empty (no expand symbol).
229 if ( mBrowserTreeView->isExpanded( proxyIdx ) || mBrowserTreeView->hasExpandedDescendant( proxyIdx ) || ( child && child->capabilities2() & Qgis::BrowserItemCapability::Fast ) )
230 {
231 refreshModel( idx );
232 }
233 else
234 {
235 if ( child && ( child->capabilities2() & Qgis::BrowserItemCapability::Fertile ) )
236 {
237 child->depopulate();
238 }
239 }
240 }
241 }
242
setValid(bool valid)243 void QgsDataSourceSelectWidget::setValid( bool valid )
244 {
245 const bool prev = mIsValid;
246 mIsValid = valid;
247 if ( prev != mIsValid )
248 emit validationChanged( mIsValid );
249
250 }
251
252
setFilterSyntax(QAction * action)253 void QgsDataSourceSelectWidget::setFilterSyntax( QAction *action )
254 {
255 if ( !action )
256 return;
257 mBrowserProxyModel.setFilterSyntax( static_cast< QgsBrowserProxyModel::FilterSyntax >( action->data().toInt() ) );
258 }
259
setCaseSensitive(bool caseSensitive)260 void QgsDataSourceSelectWidget::setCaseSensitive( bool caseSensitive )
261 {
262 mBrowserProxyModel.setFilterCaseSensitivity( caseSensitive ? Qt::CaseSensitive : Qt::CaseInsensitive );
263 }
264
setLayerTypeFilter(QgsMapLayerType layerType)265 void QgsDataSourceSelectWidget::setLayerTypeFilter( QgsMapLayerType layerType )
266 {
267 mBrowserProxyModel.setFilterByLayerType( true );
268 mBrowserProxyModel.setLayerType( layerType );
269 // reset model and button
270 mBrowserTreeView->setModel( &mBrowserProxyModel );
271 setValid( false );
272 }
273
uri() const274 QgsMimeDataUtils::Uri QgsDataSourceSelectWidget::uri() const
275 {
276 return mUri;
277 }
278
onLayerSelected(const QModelIndex & index)279 void QgsDataSourceSelectWidget::onLayerSelected( const QModelIndex &index )
280 {
281 bool isLayerCompatible = false;
282 mUri = QgsMimeDataUtils::Uri();
283 if ( index.isValid() )
284 {
285 const QgsDataItem *dataItem( mBrowserProxyModel.dataItem( index ) );
286 if ( dataItem )
287 {
288 const QgsLayerItem *layerItem = qobject_cast<const QgsLayerItem *>( dataItem );
289 if ( layerItem && ( ! mBrowserProxyModel.filterByLayerType() ||
290 ( layerItem->mapLayerType() == mBrowserProxyModel.layerType() ) ) )
291 {
292 isLayerCompatible = true;
293 mUri = layerItem->mimeUris().isEmpty() ? QgsMimeDataUtils::Uri() : layerItem->mimeUris().first();
294 // Store last viewed item
295 QgsSettings().setValue( QStringLiteral( "datasourceSelectLastSelectedItem" ), mBrowserProxyModel.data( index, QgsBrowserGuiModel::PathRole ).toString(), QgsSettings::Section::Gui );
296 }
297 }
298 }
299 setValid( isLayerCompatible );
300 emit selectionChanged();
301 }
302
itemDoubleClicked(const QModelIndex & index)303 void QgsDataSourceSelectWidget::itemDoubleClicked( const QModelIndex &index )
304 {
305 onLayerSelected( index );
306 if ( mIsValid )
307 emit itemTriggered( uri() );
308 }
309
310 //
311 // QgsDataSourceSelectDialog
312 //
313
QgsDataSourceSelectDialog(QgsBrowserGuiModel * browserModel,bool setFilterByLayerType,QgsMapLayerType layerType,QWidget * parent)314 QgsDataSourceSelectDialog::QgsDataSourceSelectDialog( QgsBrowserGuiModel *browserModel, bool setFilterByLayerType, QgsMapLayerType layerType, QWidget *parent )
315 : QDialog( parent )
316 {
317 setWindowTitle( tr( "Select a Data Source" ) );
318 setObjectName( QStringLiteral( "QgsDataSourceSelectDialog" ) );
319 QgsGui::enableAutoGeometryRestore( this );
320
321 mWidget = new QgsDataSourceSelectWidget( browserModel, setFilterByLayerType, layerType );
322
323 QVBoxLayout *vl = new QVBoxLayout();
324 vl->addWidget( mWidget, 1 );
325 vl->setContentsMargins( 4, 4, 4, 4 );
326 QDialogButtonBox *buttonBox = new QDialogButtonBox( QDialogButtonBox::Ok | QDialogButtonBox::Cancel );
327 connect( buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept );
328 connect( buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject );
329 buttonBox->button( QDialogButtonBox::Ok )->setEnabled( false );
330 connect( mWidget, &QgsDataSourceSelectWidget::validationChanged, buttonBox->button( QDialogButtonBox::Ok ), &QWidget::setEnabled );
331 connect( mWidget, &QgsDataSourceSelectWidget::itemTriggered, this, &QDialog::accept );
332
333 // pressing escape should reject the dialog
334 connect( mWidget, &QgsPanelWidget::panelAccepted, this, &QDialog::reject );
335
336 vl->addWidget( buttonBox );
337 setLayout( vl );
338 }
339
setLayerTypeFilter(QgsMapLayerType layerType)340 void QgsDataSourceSelectDialog::setLayerTypeFilter( QgsMapLayerType layerType )
341 {
342 mWidget->setLayerTypeFilter( layerType );
343 }
344
setDescription(const QString & description)345 void QgsDataSourceSelectDialog::setDescription( const QString &description )
346 {
347 mWidget->setDescription( description );
348 }
349
uri() const350 QgsMimeDataUtils::Uri QgsDataSourceSelectDialog::uri() const
351 {
352 return mWidget->uri();
353 }
354
showFilterWidget(bool visible)355 void QgsDataSourceSelectDialog::showFilterWidget( bool visible )
356 {
357 mWidget->showFilterWidget( visible );
358 }
359
setFilterSyntax(QAction * syntax)360 void QgsDataSourceSelectDialog::setFilterSyntax( QAction *syntax )
361 {
362 mWidget->setFilterSyntax( syntax );
363 }
364
setCaseSensitive(bool caseSensitive)365 void QgsDataSourceSelectDialog::setCaseSensitive( bool caseSensitive )
366 {
367 mWidget->setCaseSensitive( caseSensitive );
368 }
369
setFilter()370 void QgsDataSourceSelectDialog::setFilter()
371 {
372 mWidget->setFilter();
373
374 }
375