QGIS API Documentation  3.13.0-Master (5a3b1ced84)
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  emit blockEvents( true );
259  switch ( mStorageMode )
260  {
261  case GetFile:
262  title = !mDialogTitle.isEmpty() ? mDialogTitle : tr( "Select a file" );
263  fileName = QFileDialog::getOpenFileName( this, title, QFileInfo( oldPath ).absoluteFilePath(), mFilter, &mSelectedFilter );
264  break;
265  case GetMultipleFiles:
266  title = !mDialogTitle.isEmpty() ? mDialogTitle : tr( "Select one or more files" );
267  fileNames = QFileDialog::getOpenFileNames( this, title, QFileInfo( oldPath ).absoluteFilePath(), mFilter, &mSelectedFilter );
268  break;
269  case GetDirectory:
270  title = !mDialogTitle.isEmpty() ? mDialogTitle : tr( "Select a directory" );
271  fileName = QFileDialog::getExistingDirectory( this, title, QFileInfo( oldPath ).absoluteFilePath(), QFileDialog::ShowDirsOnly );
272  break;
273  case SaveFile:
274  {
275  title = !mDialogTitle.isEmpty() ? mDialogTitle : tr( "Create or select a file" );
276  if ( !confirmOverwrite() )
277  {
278  fileName = QFileDialog::getSaveFileName( this, title, QFileInfo( oldPath ).absoluteFilePath(), mFilter, &mSelectedFilter, QFileDialog::DontConfirmOverwrite );
279  }
280  else
281  {
282  fileName = QFileDialog::getSaveFileName( this, title, QFileInfo( oldPath ).absoluteFilePath(), mFilter, &mSelectedFilter );
283  }
284 
285  // make sure filename ends with filter. This isn't automatically done by
286  // getSaveFileName on some platforms (e.g. gnome)
287  fileName = QgsFileUtils::addExtensionFromFilter( fileName, mSelectedFilter );
288  }
289  break;
290  }
291  emit blockEvents( false );
292 
293  if ( fileName.isEmpty() && fileNames.isEmpty( ) )
294  return;
295 
296  if ( mStorageMode != GetMultipleFiles )
297  {
298  fileName = QDir::toNativeSeparators( QDir::cleanPath( QFileInfo( fileName ).absoluteFilePath() ) );
299  }
300  else
301  {
302  for ( int i = 0; i < fileNames.length(); i++ )
303  {
304  fileNames.replace( i, QDir::toNativeSeparators( QDir::cleanPath( QFileInfo( fileNames.at( i ) ).absoluteFilePath() ) ) );
305  }
306  }
307 
308  // Store the last used path:
309  switch ( mStorageMode )
310  {
311  case GetFile:
312  case SaveFile:
313  settings.setValue( QStringLiteral( "UI/lastFileNameWidgetDir" ), QFileInfo( fileName ).absolutePath() );
314  break;
315  case GetDirectory:
316  settings.setValue( QStringLiteral( "UI/lastFileNameWidgetDir" ), fileName );
317  break;
318  case GetMultipleFiles:
319  settings.setValue( QStringLiteral( "UI/lastFileNameWidgetDir" ), QFileInfo( fileNames.first( ) ).absolutePath() );
320  break;
321  }
322 
323  // Handle relative Path storage
324  if ( mStorageMode != GetMultipleFiles )
325  {
326  fileName = relativePath( fileName, true );
327  setFilePath( fileName );
328  }
329  else
330  {
331  for ( int i = 0; i < fileNames.length(); i++ )
332  {
333  fileNames.replace( i, relativePath( fileNames.at( i ), true ) );
334  }
335  if ( fileNames.length() > 1 )
336  {
337  setFilePath( QStringLiteral( "\"%1\"" ).arg( fileNames.join( QStringLiteral( "\" \"" ) ) ) );
338  }
339  else
340  {
341  setFilePath( fileNames.first( ) );
342  }
343  }
344 }
345 
346 
347 QString QgsFileWidget::relativePath( const QString &filePath, bool removeRelative ) const
348 {
349  QString RelativePath;
350  if ( mRelativeStorage == RelativeProject )
351  {
352  RelativePath = QDir::toNativeSeparators( QDir::cleanPath( QFileInfo( QgsProject::instance()->absoluteFilePath() ).path() ) );
353  }
354  else if ( mRelativeStorage == RelativeDefaultPath && !mDefaultRoot.isEmpty() )
355  {
356  RelativePath = QDir::toNativeSeparators( QDir::cleanPath( mDefaultRoot ) );
357  }
358 
359  if ( !RelativePath.isEmpty() )
360  {
361  if ( removeRelative )
362  {
363  return QDir::cleanPath( QDir( RelativePath ).relativeFilePath( filePath ) );
364  }
365  else
366  {
367  return QDir::cleanPath( QDir( RelativePath ).filePath( filePath ) );
368  }
369  }
370 
371  return filePath;
372 }
373 
374 
375 QString QgsFileWidget::toUrl( const QString &path ) const
376 {
377  QString rep;
378  if ( path.isEmpty() )
379  {
381  }
382 
383  QString urlStr = relativePath( path, false );
384  QUrl url = QUrl::fromUserInput( urlStr );
385  if ( !url.isValid() || !url.isLocalFile() )
386  {
387  QgsDebugMsg( QStringLiteral( "URL: %1 is not valid or not a local file!" ).arg( path ) );
388  rep = path;
389  }
390 
391  QString pathStr = url.toString();
392  if ( mFullUrl )
393  {
394  rep = QStringLiteral( "<a href=\"%1\">%2</a>" ).arg( pathStr, path );
395  }
396  else
397  {
398  QString fileName = QFileInfo( urlStr ).fileName();
399  rep = QStringLiteral( "<a href=\"%1\">%2</a>" ).arg( pathStr, fileName );
400  }
401 
402  return rep;
403 }
404 
405 
406 
408 
409 
410 QgsFileDropEdit::QgsFileDropEdit( QWidget *parent )
411  : QgsFilterLineEdit( parent )
412 {
413  mDragActive = false;
414  setAcceptDrops( true );
415 }
416 
417 void QgsFileDropEdit::setFilters( const QString &filters )
418 {
419  mAcceptableExtensions.clear();
420 
421  if ( filters.contains( QStringLiteral( "*.*" ) ) )
422  return; // everything is allowed!
423 
424  QRegularExpression rx( QStringLiteral( "\\*\\.(\\w+)" ) );
425  QRegularExpressionMatchIterator i = rx.globalMatch( filters );
426  while ( i.hasNext() )
427  {
428  QRegularExpressionMatch match = i.next();
429  if ( match.hasMatch() )
430  {
431  mAcceptableExtensions << match.captured( 1 ).toLower();
432  }
433  }
434 }
435 
436 QString QgsFileDropEdit::acceptableFilePath( QDropEvent *event ) const
437 {
438  QStringList rawPaths;
439  QStringList paths;
440  if ( event->mimeData()->hasUrls() )
441  {
442  const QList< QUrl > urls = event->mimeData()->urls();
443  rawPaths.reserve( urls.count() );
444  for ( const QUrl &url : urls )
445  {
446  const QString local = url.toLocalFile();
447  if ( !rawPaths.contains( local ) )
448  rawPaths.append( local );
449  }
450  }
451 
453  for ( const QgsMimeDataUtils::Uri &u : lst )
454  {
455  if ( !rawPaths.contains( u.uri ) )
456  rawPaths.append( u.uri );
457  }
458 
459  if ( !event->mimeData()->text().isEmpty() && !rawPaths.contains( event->mimeData()->text() ) )
460  rawPaths.append( event->mimeData()->text() );
461 
462  paths.reserve( rawPaths.count() );
463  for ( const QString &path : qgis::as_const( rawPaths ) )
464  {
465  QFileInfo file( path );
466  switch ( mStorageMode )
467  {
471  {
472  if ( file.isFile() && ( mAcceptableExtensions.isEmpty() || mAcceptableExtensions.contains( file.suffix(), Qt::CaseInsensitive ) ) )
473  paths.append( file.filePath() );
474 
475  break;
476  }
477 
479  {
480  if ( file.isDir() )
481  paths.append( file.filePath() );
482  else if ( file.isFile() )
483  {
484  // folder mode, but a file dropped. So get folder name from file
485  paths.append( file.absolutePath() );
486  }
487 
488  break;
489  }
490  }
491  }
492 
493  if ( paths.size() > 1 )
494  {
495  return QStringLiteral( "\"%1\"" ).arg( paths.join( QStringLiteral( "\" \"" ) ) );
496  }
497  else if ( paths.size() == 1 )
498  {
499  return paths.first();
500  }
501  else
502  {
503  return QString();
504  }
505 }
506 
507 void QgsFileDropEdit::dragEnterEvent( QDragEnterEvent *event )
508 {
509  QString filePath = acceptableFilePath( event );
510  if ( !filePath.isEmpty() )
511  {
512  event->acceptProposedAction();
513  mDragActive = true;
514  update();
515  }
516  else
517  {
518  event->ignore();
519  }
520 }
521 
522 void QgsFileDropEdit::dragLeaveEvent( QDragLeaveEvent *event )
523 {
524  QgsFilterLineEdit::dragLeaveEvent( event );
525  event->accept();
526  mDragActive = false;
527  update();
528 }
529 
530 void QgsFileDropEdit::dropEvent( QDropEvent *event )
531 {
532  QString filePath = acceptableFilePath( event );
533  if ( !filePath.isEmpty() )
534  {
535  setText( filePath );
536  selectAll();
537  setFocus( Qt::MouseFocusReason );
538  event->acceptProposedAction();
539  mDragActive = false;
540  update();
541  }
542 }
543 
544 void QgsFileDropEdit::paintEvent( QPaintEvent *e )
545 {
546  QgsFilterLineEdit::paintEvent( e );
547  if ( mDragActive )
548  {
549  QPainter p( this );
550  int width = 2; // width of highlight rectangle inside frame
551  p.setPen( QPen( palette().highlight(), width ) );
552  QRect r = rect().adjusted( width, width, -width, -width );
553  p.drawRect( r );
554  }
555 }
556 
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 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:61
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
void fileChanged(const QString &path)
Emitted whenever the current file or directory path is changed.
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:457
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
void blockEvents(bool)
Emitted before and after showing the file dialog.
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:75