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