QGIS API Documentation  3.8.0-Zanzibar (11aff65)
qgsfilewidget.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgsfilewidget.cpp
3 
4  ---------------------
5  begin : 17.12.2015
6  copyright : (C) 2015 by Denis Rouzaud
7  email : [email protected]
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 "qgsfilewidget.h"
18 
19 #include <QLineEdit>
20 #include <QToolButton>
21 #include <QLabel>
22 #include <QFileDialog>
23 #include <QGridLayout>
24 #include <QUrl>
25 #include <QDropEvent>
26 
27 #include "qgssettings.h"
28 #include "qgsfilterlineedit.h"
29 #include "qgslogger.h"
30 #include "qgsproject.h"
31 #include "qgsapplication.h"
32 #include "qgsfileutils.h"
33 #include "qgsmimedatautils.h"
34 
35 QgsFileWidget::QgsFileWidget( QWidget *parent )
36  : QWidget( parent )
37 {
38  setBackgroundRole( QPalette::Window );
39  setAutoFillBackground( true );
40 
41  mLayout = new QHBoxLayout();
42  mLayout->setMargin( 0 );
43 
44  // If displaying a hyperlink, use a QLabel
45  mLinkLabel = new QLabel( this );
46  // Make Qt opens the link with the OS defined viewer
47  mLinkLabel->setOpenExternalLinks( true );
48  // Label should always be enabled to be able to open
49  // the link on read only mode.
50  mLinkLabel->setEnabled( true );
51  mLinkLabel->setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Preferred );
52  mLinkLabel->setTextFormat( Qt::RichText );
53  mLinkLabel->hide(); // do not show by default
54 
55  // otherwise, use the traditional QLineEdit subclass
56  mLineEdit = new QgsFileDropEdit( this );
57  mLineEdit->setDragEnabled( true );
58  mLineEdit->setToolTip( tr( "Full path to the file(s), including name and extension" ) );
59  connect( mLineEdit, &QLineEdit::textChanged, this, &QgsFileWidget::textEdited );
60  mLayout->addWidget( mLineEdit );
61 
62  mFileWidgetButton = new QToolButton( this );
63  mFileWidgetButton->setText( QChar( 0x2026 ) );
64  mFileWidgetButton->setToolTip( tr( "Browse" ) );
65  connect( mFileWidgetButton, &QAbstractButton::clicked, this, &QgsFileWidget::openFileDialog );
66  mLayout->addWidget( mFileWidgetButton );
67 
68  setLayout( mLayout );
69 }
70 
72 {
73  return mFilePath;
74 }
75 
76 QStringList QgsFileWidget::splitFilePaths( const QString &path )
77 {
78  QStringList paths;
79  const QStringList pathParts = path.split( QRegExp( "\"\\s+\"" ), QString::SkipEmptyParts );
80  for ( const auto &pathsPart : pathParts )
81  {
82  QString cleaned = pathsPart;
83  cleaned.remove( QRegExp( "(^\\s*\")|(\"\\s*)" ) );
84  paths.append( cleaned );
85  }
86  return paths;
87 }
88 
89 void QgsFileWidget::setFilePath( QString path )
90 {
91  if ( path == QgsApplication::nullRepresentation() )
92  {
93  path.clear();
94  }
95 
96  //will trigger textEdited slot
97  mLineEdit->setValue( path );
98 
99 }
100 
101 void QgsFileWidget::setReadOnly( bool readOnly )
102 {
103  mFileWidgetButton->setEnabled( !readOnly );
104  mLineEdit->setEnabled( !readOnly );
105 }
106 
107 QString QgsFileWidget::dialogTitle() const
108 {
109  return mDialogTitle;
110 }
111 
112 void QgsFileWidget::setDialogTitle( const QString &title )
113 {
114  mDialogTitle = title;
115 }
116 
117 QString QgsFileWidget::filter() const
118 {
119  return mFilter;
120 }
121 
122 void QgsFileWidget::setFilter( const QString &filters )
123 {
124  mFilter = filters;
125  mLineEdit->setFilters( filters );
126 }
127 
129 {
130  return mButtonVisible;
131 }
132 
134 {
135  mButtonVisible = visible;
136  mFileWidgetButton->setVisible( visible );
137 }
138 
139 void QgsFileWidget::textEdited( const QString &path )
140 {
141  mFilePath = path;
142  mLinkLabel->setText( toUrl( path ) );
143  // Show tooltip if multiple files are selected
144  if ( path.contains( QStringLiteral( "\" \"" ) ) )
145  {
146  mLineEdit->setToolTip( tr( "Selected files:<br><ul><li>%1</li></ul><br>" ).arg( splitFilePaths( path ).join( QStringLiteral( "</li><li>" ) ) ) );
147  }
148  else
149  {
150  mLineEdit->setToolTip( QString() );
151  }
152  emit fileChanged( mFilePath );
153 }
154 
155 bool QgsFileWidget::useLink() const
156 {
157  return mUseLink;
158 }
159 
161 {
162  mUseLink = useLink;
163  mLinkLabel->setVisible( mUseLink );
164  mLineEdit->setVisible( !mUseLink );
165  if ( mUseLink )
166  {
167  mLayout->removeWidget( mLineEdit );
168  mLayout->insertWidget( 0, mLinkLabel );
169  }
170  else
171  {
172  mLayout->removeWidget( mLinkLabel );
173  mLayout->insertWidget( 0, mLineEdit );
174  }
175 }
176 
177 bool QgsFileWidget::fullUrl() const
178 {
179  return mFullUrl;
180 }
181 
183 {
184  mFullUrl = fullUrl;
185 }
186 
187 QString QgsFileWidget::defaultRoot() const
188 {
189  return mDefaultRoot;
190 }
191 
193 {
194  mDefaultRoot = defaultRoot;
195 }
196 
198 {
199  return mStorageMode;
200 }
201 
203 {
204  mStorageMode = storageMode;
205  mLineEdit->setStorageMode( storageMode );
206 }
207 
209 {
210  return mRelativeStorage;
211 }
212 
214 {
215  mRelativeStorage = relativeStorage;
216 }
217 
219 {
220  return mLineEdit;
221 }
222 
223 void QgsFileWidget::openFileDialog()
224 {
225  QgsSettings settings;
226  QString oldPath;
227 
228  // if we use a relative path option, we need to obtain the full path
229  // first choice is the current file path, if one is entered
230  if ( !mFilePath.isEmpty() )
231  {
232  oldPath = relativePath( mFilePath, false );
233  }
234  // If we use fixed default path
235  // second choice is the default root
236  else if ( !mDefaultRoot.isEmpty() )
237  {
238  oldPath = QDir::cleanPath( mDefaultRoot );
239  }
240 
241  // If there is no valid value, find a default path to use
242  QUrl url = QUrl::fromUserInput( oldPath );
243  if ( !url.isValid() )
244  {
245  QString defPath = QDir::cleanPath( QFileInfo( QgsProject::instance()->absoluteFilePath() ).path() );
246  if ( defPath.isEmpty() )
247  {
248  defPath = QDir::homePath();
249  }
250  oldPath = settings.value( QStringLiteral( "UI/lastFileNameWidgetDir" ), defPath ).toString();
251  }
252 
253  // Handle Storage
254  QString fileName;
255  QStringList fileNames;
256  QString title;
257 
258  switch ( mStorageMode )
259  {
260  case GetFile:
261  title = !mDialogTitle.isEmpty() ? mDialogTitle : tr( "Select a file" );
262  fileName = QFileDialog::getOpenFileName( this, title, QFileInfo( oldPath ).absoluteFilePath(), mFilter, &mSelectedFilter );
263  break;
264  case GetMultipleFiles:
265  title = !mDialogTitle.isEmpty() ? mDialogTitle : tr( "Select one or more files" );
266  fileNames = QFileDialog::getOpenFileNames( this, title, QFileInfo( oldPath ).absoluteFilePath(), mFilter, &mSelectedFilter );
267  break;
268  case GetDirectory:
269  title = !mDialogTitle.isEmpty() ? mDialogTitle : tr( "Select a directory" );
270  fileName = QFileDialog::getExistingDirectory( this, title, QFileInfo( oldPath ).absoluteFilePath(), QFileDialog::ShowDirsOnly );
271  break;
272  case SaveFile:
273  {
274  title = !mDialogTitle.isEmpty() ? mDialogTitle : tr( "Create or select a file" );
275  if ( !confirmOverwrite() )
276  {
277  fileName = QFileDialog::getSaveFileName( this, title, QFileInfo( oldPath ).absoluteFilePath(), mFilter, &mSelectedFilter, QFileDialog::DontConfirmOverwrite );
278  }
279  else
280  {
281  fileName = QFileDialog::getSaveFileName( this, title, QFileInfo( oldPath ).absoluteFilePath(), mFilter, &mSelectedFilter );
282  }
283 
284  // make sure filename ends with filter. This isn't automatically done by
285  // getSaveFileName on some platforms (e.g. gnome)
286  fileName = QgsFileUtils::addExtensionFromFilter( fileName, mSelectedFilter );
287  }
288  break;
289  }
290 
291  if ( fileName.isEmpty() && fileNames.isEmpty( ) )
292  return;
293 
294  if ( mStorageMode != GetMultipleFiles )
295  {
296  fileName = QDir::toNativeSeparators( QDir::cleanPath( QFileInfo( fileName ).absoluteFilePath() ) );
297  }
298  else
299  {
300  for ( int i = 0; i < fileNames.length(); i++ )
301  {
302  fileNames.replace( i, QDir::toNativeSeparators( QDir::cleanPath( QFileInfo( fileNames.at( i ) ).absoluteFilePath() ) ) );
303  }
304  }
305 
306  // Store the last used path:
307  switch ( mStorageMode )
308  {
309  case GetFile:
310  case SaveFile:
311  settings.setValue( QStringLiteral( "UI/lastFileNameWidgetDir" ), QFileInfo( fileName ).absolutePath() );
312  break;
313  case GetDirectory:
314  settings.setValue( QStringLiteral( "UI/lastFileNameWidgetDir" ), fileName );
315  break;
316  case GetMultipleFiles:
317  settings.setValue( QStringLiteral( "UI/lastFileNameWidgetDir" ), QFileInfo( fileNames.first( ) ).absolutePath() );
318  break;
319  }
320 
321  // Handle relative Path storage
322  if ( mStorageMode != GetMultipleFiles )
323  {
324  fileName = relativePath( fileName, true );
325  setFilePath( fileName );
326  }
327  else
328  {
329  for ( int i = 0; i < fileNames.length(); i++ )
330  {
331  fileNames.replace( i, relativePath( fileNames.at( i ), true ) );
332  }
333  if ( fileNames.length() > 1 )
334  {
335  setFilePath( QStringLiteral( "\"%1\"" ).arg( fileNames.join( QStringLiteral( "\" \"" ) ) ) );
336  }
337  else
338  {
339  setFilePath( fileNames.first( ) );
340  }
341  }
342 }
343 
344 
345 QString QgsFileWidget::relativePath( const QString &filePath, bool removeRelative ) const
346 {
347  QString RelativePath;
348  if ( mRelativeStorage == RelativeProject )
349  {
350  RelativePath = QDir::toNativeSeparators( QDir::cleanPath( QFileInfo( QgsProject::instance()->absoluteFilePath() ).path() ) );
351  }
352  else if ( mRelativeStorage == RelativeDefaultPath && !mDefaultRoot.isEmpty() )
353  {
354  RelativePath = QDir::toNativeSeparators( QDir::cleanPath( mDefaultRoot ) );
355  }
356 
357  if ( !RelativePath.isEmpty() )
358  {
359  if ( removeRelative )
360  {
361  return QDir::cleanPath( QDir( RelativePath ).relativeFilePath( filePath ) );
362  }
363  else
364  {
365  return QDir::cleanPath( QDir( RelativePath ).filePath( filePath ) );
366  }
367  }
368 
369  return filePath;
370 }
371 
372 
373 QString QgsFileWidget::toUrl( const QString &path ) const
374 {
375  QString rep;
376  if ( path.isEmpty() )
377  {
379  }
380 
381  QString urlStr = relativePath( path, false );
382  QUrl url = QUrl::fromUserInput( urlStr );
383  if ( !url.isValid() || !url.isLocalFile() )
384  {
385  QgsDebugMsg( QStringLiteral( "URL: %1 is not valid or not a local file!" ).arg( path ) );
386  rep = path;
387  }
388 
389  QString pathStr = url.toString();
390  if ( mFullUrl )
391  {
392  rep = QStringLiteral( "<a href=\"%1\">%2</a>" ).arg( pathStr, path );
393  }
394  else
395  {
396  QString fileName = QFileInfo( urlStr ).fileName();
397  rep = QStringLiteral( "<a href=\"%1\">%2</a>" ).arg( pathStr, fileName );
398  }
399 
400  return rep;
401 }
402 
403 
404 
406 
407 
408 QgsFileDropEdit::QgsFileDropEdit( QWidget *parent )
409  : QgsFilterLineEdit( parent )
410 {
411  mDragActive = false;
412  setAcceptDrops( true );
413 }
414 
415 void QgsFileDropEdit::setFilters( const QString &filters )
416 {
417  mAcceptableExtensions.clear();
418 
419  if ( filters.contains( QStringLiteral( "*.*" ) ) )
420  return; // everything is allowed!
421 
422  QRegularExpression rx( QStringLiteral( "\\*\\.(\\w+)" ) );
423  QRegularExpressionMatchIterator i = rx.globalMatch( filters );
424  while ( i.hasNext() )
425  {
426  QRegularExpressionMatch match = i.next();
427  if ( match.hasMatch() )
428  {
429  mAcceptableExtensions << match.captured( 1 ).toLower();
430  }
431  }
432 }
433 
434 QString QgsFileDropEdit::acceptableFilePath( QDropEvent *event ) const
435 {
436  QStringList rawPaths;
437  QStringList paths;
438  if ( event->mimeData()->hasUrls() )
439  {
440  const QList< QUrl > urls = event->mimeData()->urls();
441  rawPaths.reserve( urls.count() );
442  for ( const QUrl &url : urls )
443  {
444  const QString local = url.toLocalFile();
445  if ( !rawPaths.contains( local ) )
446  rawPaths.append( local );
447  }
448  }
449 
451  for ( const QgsMimeDataUtils::Uri &u : lst )
452  {
453  if ( !rawPaths.contains( u.uri ) )
454  rawPaths.append( u.uri );
455  }
456 
457  if ( !event->mimeData()->text().isEmpty() && !rawPaths.contains( event->mimeData()->text() ) )
458  rawPaths.append( event->mimeData()->text() );
459 
460  paths.reserve( rawPaths.count() );
461  for ( const QString &path : qgis::as_const( rawPaths ) )
462  {
463  QFileInfo file( path );
464  switch ( mStorageMode )
465  {
469  {
470  if ( file.isFile() && ( mAcceptableExtensions.isEmpty() || mAcceptableExtensions.contains( file.suffix(), Qt::CaseInsensitive ) ) )
471  paths.append( file.filePath() );
472 
473  break;
474  }
475 
477  {
478  if ( file.isDir() )
479  paths.append( file.filePath() );
480  else if ( file.isFile() )
481  {
482  // folder mode, but a file dropped. So get folder name from file
483  paths.append( file.absolutePath() );
484  }
485 
486  break;
487  }
488  }
489  }
490 
491  if ( paths.size() > 1 )
492  {
493  return QStringLiteral( "\"%1\"" ).arg( paths.join( QStringLiteral( "\" \"" ) ) );
494  }
495  else if ( paths.size() == 1 )
496  {
497  return paths.first();
498  }
499  else
500  {
501  return QString();
502  }
503 }
504 
505 void QgsFileDropEdit::dragEnterEvent( QDragEnterEvent *event )
506 {
507  QString filePath = acceptableFilePath( event );
508  if ( !filePath.isEmpty() )
509  {
510  event->acceptProposedAction();
511  mDragActive = true;
512  update();
513  }
514  else
515  {
516  event->ignore();
517  }
518 }
519 
520 void QgsFileDropEdit::dragLeaveEvent( QDragLeaveEvent *event )
521 {
522  QgsFilterLineEdit::dragLeaveEvent( event );
523  event->accept();
524  mDragActive = false;
525  update();
526 }
527 
528 void QgsFileDropEdit::dropEvent( QDropEvent *event )
529 {
530  QString filePath = acceptableFilePath( event );
531  if ( !filePath.isEmpty() )
532  {
533  setText( filePath );
534  selectAll();
535  setFocus( Qt::MouseFocusReason );
536  event->acceptProposedAction();
537  mDragActive = false;
538  update();
539  }
540 }
541 
542 void QgsFileDropEdit::paintEvent( QPaintEvent *e )
543 {
544  QgsFilterLineEdit::paintEvent( e );
545  if ( mDragActive )
546  {
547  QPainter p( this );
548  int width = 2; // width of highlight rectangle inside frame
549  p.setPen( QPen( palette().highlight(), width ) );
550  QRect r = rect().adjusted( width, width, -width, -width );
551  p.drawRect( r );
552  }
553 }
554 
void setDefaultRoot(const QString &defaultRoot)
determines the default root path used as the first shown location when picking a file and used if the...
bool useLink() const
determines if the file path will be shown as a link
QgsFileWidget::StorageMode storageMode() const
returns the storage mode (i.e. file or directory)
void setFilter(const QString &filter)
setFilter sets the filter used by the model to filters.
void fileChanged(const QString &)
emitted as soon as the current file or directory is changed
void setUseLink(bool useLink)
determines if the file path will be shown as a link
This class is a composition of two QSettings instances:
Definition: qgssettings.h:58
QVariant value(const QString &key, const QVariant &defaultValue=QVariant(), Section section=NoSection) const
Returns the value for setting key.
#define QgsDebugMsg(str)
Definition: qgslogger.h:38
Select a directory.
Definition: qgsfilewidget.h:66
static UriList decodeUriList(const QMimeData *data)
void setReadOnly(bool readOnly)
defines if the widget is readonly
void setStorageMode(QgsFileWidget::StorageMode storageMode)
determines the storage mode (i.e. file or directory)
QgsFilterLineEdit * lineEdit()
Returns a pointer to the widget&#39;s line edit, which can be used to customize the appearance and behavi...
bool confirmOverwrite() const
Returns whether a confirmation will be shown when overwriting an existing file.
QString filePath()
Returns the current file path(s) when multiple files are selected, they are quoted and separated by a...
QString defaultRoot() const
returns the default root path
QString dialogTitle() const
returns the open file dialog title
void setRelativeStorage(QgsFileWidget::RelativeStorage relativeStorage)
determines if the relative path is with respect to the project path or the default path ...
Select a single file.
Definition: qgsfilewidget.h:65
QgsFileWidget(QWidget *parent=nullptr)
QgsFileWidget creates a widget for selecting a file or a folder.
void setDialogTitle(const QString &title)
setDialogTitle defines the open file dialog title
QLineEdit subclass with built in support for clearing the widget&#39;s value and handling custom null val...
static QString nullRepresentation()
This string is used to represent the value NULL throughout QGIS.
void setFileWidgetButtonVisible(bool visible)
determines if the tool button is shown
StorageMode
The StorageMode enum determines if the file picker should pick files or directories.
Definition: qgsfilewidget.h:63
Select multiple files.
Definition: qgsfilewidget.h:67
void setValue(const QString &key, const QVariant &value, QgsSettings::Section section=QgsSettings::NoSection)
Sets the value of setting key to value.
QgsFileWidget::RelativeStorage relativeStorage() const
returns if the relative path is with respect to the project path or the default path ...
static QgsProject * instance()
Returns the QgsProject singleton instance.
Definition: qgsproject.cpp:438
Select a single new or pre-existing file.
Definition: qgsfilewidget.h:68
QList< QgsMimeDataUtils::Uri > UriList
static QString addExtensionFromFilter(const QString &fileName, const QString &filter)
Ensures that a fileName ends with an extension from the specified filter string.
bool fileWidgetButtonVisible() const
determines if the tool button is shown
bool fullUrl() const
returns if the links shows the full path or not
QString filter() const
returns the filters used for QDialog::getOpenFileName
static QStringList splitFilePaths(const QString &path)
Split the the quoted and space separated path and returns a QString list.
void setFullUrl(bool fullUrl)
determines if the links shows the full path or not
void setFilePath(QString path)
Sets the file path.
RelativeStorage
The RelativeStorage enum determines if path is absolute, relative to the current project path or rela...
Definition: qgsfilewidget.h:74