QGIS API Documentation  3.4.15-Madeira (e83d02e274)
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 
34 QgsFileWidget::QgsFileWidget( QWidget *parent )
35  : QWidget( parent )
36 {
37  setBackgroundRole( QPalette::Window );
38  setAutoFillBackground( true );
39 
40  mLayout = new QHBoxLayout();
41  mLayout->setMargin( 0 );
42 
43  // If displaying a hyperlink, use a QLabel
44  mLinkLabel = new QLabel( this );
45  // Make Qt opens the link with the OS defined viewer
46  mLinkLabel->setOpenExternalLinks( true );
47  // Label should always be enabled to be able to open
48  // the link on read only mode.
49  mLinkLabel->setEnabled( true );
50  mLinkLabel->setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Preferred );
51  mLinkLabel->setTextFormat( Qt::RichText );
52  mLinkLabel->hide(); // do not show by default
53 
54  // otherwise, use the traditional QLineEdit subclass
55  mLineEdit = new QgsFileDropEdit( this );
56  mLineEdit->setToolTip( tr( "Full path to the file(s), including name and extension" ) );
57  connect( mLineEdit, &QLineEdit::textChanged, this, &QgsFileWidget::textEdited );
58  mLayout->addWidget( mLineEdit );
59 
60  mFileWidgetButton = new QToolButton( this );
61  mFileWidgetButton->setText( QChar( 0x2026 ) );
62  mFileWidgetButton->setToolTip( tr( "Browse" ) );
63  connect( mFileWidgetButton, &QAbstractButton::clicked, this, &QgsFileWidget::openFileDialog );
64  mLayout->addWidget( mFileWidgetButton );
65 
66  setLayout( mLayout );
67 }
68 
70 {
71  return mFilePath;
72 }
73 
74 QStringList QgsFileWidget::splitFilePaths( const QString &path )
75 {
76  QStringList paths;
77  const QStringList pathParts = path.split( QRegExp( "\"\\s+\"" ), QString::SkipEmptyParts );
78  for ( const auto &pathsPart : pathParts )
79  {
80  QString cleaned = pathsPart;
81  cleaned.remove( QRegExp( "(^\\s*\")|(\"\\s*)" ) );
82  paths.append( cleaned );
83  }
84  return paths;
85 }
86 
87 void QgsFileWidget::setFilePath( QString path )
88 {
89  if ( path == QgsApplication::nullRepresentation() )
90  {
91  path.clear();
92  }
93 
94  //will trigger textEdited slot
95  mLineEdit->setValue( path );
96 
97 }
98 
99 void QgsFileWidget::setReadOnly( bool readOnly )
100 {
101  mFileWidgetButton->setEnabled( !readOnly );
102  mLineEdit->setEnabled( !readOnly );
103 }
104 
105 QString QgsFileWidget::dialogTitle() const
106 {
107  return mDialogTitle;
108 }
109 
110 void QgsFileWidget::setDialogTitle( const QString &title )
111 {
112  mDialogTitle = title;
113 }
114 
115 QString QgsFileWidget::filter() const
116 {
117  return mFilter;
118 }
119 
120 void QgsFileWidget::setFilter( const QString &filters )
121 {
122  mFilter = filters;
123  mLineEdit->setFilters( filters );
124 }
125 
127 {
128  return mButtonVisible;
129 }
130 
132 {
133  mButtonVisible = visible;
134  mFileWidgetButton->setVisible( visible );
135 }
136 
137 void QgsFileWidget::textEdited( const QString &path )
138 {
139  mFilePath = path;
140  mLinkLabel->setText( toUrl( path ) );
141  // Show tooltip if multiple files are selected
142  if ( path.contains( QStringLiteral( "\" \"" ) ) )
143  {
144  mLineEdit->setToolTip( tr( "Selected files:<br><ul><li>%1</li></ul><br>" ).arg( splitFilePaths( path ).join( QStringLiteral( "</li><li>" ) ) ) );
145  }
146  else
147  {
148  mLineEdit->setToolTip( QString() );
149  }
150  emit fileChanged( mFilePath );
151 }
152 
153 bool QgsFileWidget::useLink() const
154 {
155  return mUseLink;
156 }
157 
159 {
160  mUseLink = useLink;
161  mLinkLabel->setVisible( mUseLink );
162  mLineEdit->setVisible( !mUseLink );
163  if ( mUseLink )
164  {
165  mLayout->removeWidget( mLineEdit );
166  mLayout->insertWidget( 0, mLinkLabel );
167  }
168  else
169  {
170  mLayout->removeWidget( mLinkLabel );
171  mLayout->insertWidget( 0, mLineEdit );
172  }
173 }
174 
175 bool QgsFileWidget::fullUrl() const
176 {
177  return mFullUrl;
178 }
179 
181 {
182  mFullUrl = fullUrl;
183 }
184 
185 QString QgsFileWidget::defaultRoot() const
186 {
187  return mDefaultRoot;
188 }
189 
191 {
192  mDefaultRoot = defaultRoot;
193 }
194 
196 {
197  return mStorageMode;
198 }
199 
201 {
202  mStorageMode = storageMode;
203  mLineEdit->setStorageMode( storageMode );
204 }
205 
207 {
208  return mRelativeStorage;
209 }
210 
212 {
213  mRelativeStorage = relativeStorage;
214 }
215 
217 {
218  return mLineEdit;
219 }
220 
221 void QgsFileWidget::openFileDialog()
222 {
223  QgsSettings settings;
224  QString oldPath;
225 
226  // if we use a relative path option, we need to obtain the full path
227  // first choice is the current file path, if one is entered
228  if ( !mFilePath.isEmpty() )
229  {
230  oldPath = relativePath( mFilePath, false );
231  }
232  // If we use fixed default path
233  // second choice is the default root
234  else if ( !mDefaultRoot.isEmpty() )
235  {
236  oldPath = QDir::cleanPath( mDefaultRoot );
237  }
238 
239  // If there is no valid value, find a default path to use
240  QUrl url = QUrl::fromUserInput( oldPath );
241  if ( !url.isValid() )
242  {
243  QString defPath = QDir::cleanPath( QFileInfo( QgsProject::instance()->absoluteFilePath() ).path() );
244  if ( defPath.isEmpty() )
245  {
246  defPath = QDir::homePath();
247  }
248  oldPath = settings.value( QStringLiteral( "UI/lastFileNameWidgetDir" ), defPath ).toString();
249  }
250 
251  // Handle Storage
252  QString fileName;
253  QStringList fileNames;
254  QString title;
255 
256  emit blockEvents( true );
257  switch ( mStorageMode )
258  {
259  case GetFile:
260  title = !mDialogTitle.isEmpty() ? mDialogTitle : tr( "Select a file" );
261  fileName = QFileDialog::getOpenFileName( this, title, QFileInfo( oldPath ).absoluteFilePath(), mFilter, &mSelectedFilter );
262  break;
263  case GetMultipleFiles:
264  title = !mDialogTitle.isEmpty() ? mDialogTitle : tr( "Select one or more files" );
265  fileNames = QFileDialog::getOpenFileNames( this, title, QFileInfo( oldPath ).absoluteFilePath(), mFilter, &mSelectedFilter );
266  break;
267  case GetDirectory:
268  title = !mDialogTitle.isEmpty() ? mDialogTitle : tr( "Select a directory" );
269  fileName = QFileDialog::getExistingDirectory( this, title, QFileInfo( oldPath ).absoluteFilePath(), QFileDialog::ShowDirsOnly );
270  break;
271  case SaveFile:
272  {
273  title = !mDialogTitle.isEmpty() ? mDialogTitle : tr( "Create or select a file" );
274  if ( !confirmOverwrite() )
275  {
276  fileName = QFileDialog::getSaveFileName( this, title, QFileInfo( oldPath ).absoluteFilePath(), mFilter, &mSelectedFilter, QFileDialog::DontConfirmOverwrite );
277  }
278  else
279  {
280  fileName = QFileDialog::getSaveFileName( this, title, QFileInfo( oldPath ).absoluteFilePath(), mFilter, &mSelectedFilter );
281  }
282 
283  // make sure filename ends with filter. This isn't automatically done by
284  // getSaveFileName on some platforms (e.g. gnome)
285  fileName = QgsFileUtils::addExtensionFromFilter( fileName, mSelectedFilter );
286  }
287  break;
288  }
289  emit blockEvents( false );
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 paths;
437  if ( event->mimeData()->hasUrls() )
438  {
439  Q_FOREACH ( const QUrl &url, event->mimeData()->urls() )
440  {
441  QFileInfo file( url.toLocalFile() );
442  if ( ( mStorageMode != QgsFileWidget::GetDirectory && file.isFile() &&
443  ( mAcceptableExtensions.isEmpty() || mAcceptableExtensions.contains( file.suffix(), Qt::CaseInsensitive ) ) )
444  || ( mStorageMode == QgsFileWidget::GetDirectory && file.isDir() ) )
445  paths.append( file.filePath() );
446  }
447  }
448  if ( paths.size() > 1 )
449  {
450  return QStringLiteral( "\"%1\"" ).arg( paths.join( QStringLiteral( "\" \"" ) ) );
451  }
452  else if ( paths.size() == 1 )
453  {
454  return paths.first();
455  }
456  else
457  {
458  return QString();
459  }
460 }
461 
462 void QgsFileDropEdit::dragEnterEvent( QDragEnterEvent *event )
463 {
464  QString filePath = acceptableFilePath( event );
465  if ( !filePath.isEmpty() )
466  {
467  event->acceptProposedAction();
468  mDragActive = true;
469  update();
470  }
471  else
472  {
473  event->ignore();
474  }
475 }
476 
477 void QgsFileDropEdit::dragLeaveEvent( QDragLeaveEvent *event )
478 {
479  QgsFilterLineEdit::dragLeaveEvent( event );
480  event->accept();
481  mDragActive = false;
482  update();
483 }
484 
485 void QgsFileDropEdit::dropEvent( QDropEvent *event )
486 {
487  QString filePath = acceptableFilePath( event );
488  if ( !filePath.isEmpty() )
489  {
490  setText( filePath );
491  selectAll();
492  setFocus( Qt::MouseFocusReason );
493  event->acceptProposedAction();
494  mDragActive = false;
495  update();
496  }
497 }
498 
499 void QgsFileDropEdit::paintEvent( QPaintEvent *e )
500 {
501  QgsFilterLineEdit::paintEvent( e );
502  if ( mDragActive )
503  {
504  QPainter p( this );
505  int width = 2; // width of highlight rectangle inside frame
506  p.setPen( QPen( palette().highlight(), width ) );
507  QRect r = rect().adjusted( width, width, -width, -width );
508  p.drawRect( r );
509  }
510 }
511 
void setDefaultRoot(const QString &defaultRoot)
determines the default root path used as the first shown location when picking a file and used if the...
void setFilter(const QString &filter)
setFilter sets the filter used by the model to filters.
bool confirmOverwrite() const
Returns whether a confirmation will be shown when overwriting an existing file.
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
bool useLink() const
determines if the file path will be shown as a link
#define QgsDebugMsg(str)
Definition: qgslogger.h:38
Select a single file.
Definition: qgsfilewidget.h:66
bool fileWidgetButtonVisible() const
determines if the tool button is shown
void setReadOnly(bool readOnly)
defines if the widget is readonly
QVariant value(const QString &key, const QVariant &defaultValue=QVariant(), Section section=NoSection) const
Returns the value for setting key.
bool fullUrl() const
returns if the links shows the full path or not
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...
QString dialogTitle() const
returns the open file dialog title
QString filePath()
Returns the current file path(s) when multiple files are selected, they are quoted and separated by a...
void setRelativeStorage(QgsFileWidget::RelativeStorage relativeStorage)
determines if the relative path is with respect to the project path or the default path ...
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
QgsFileWidget::StorageMode storageMode() const
returns the storage mode (i.e. file or directory)
StorageMode
The StorageMode enum determines if the file picker should pick files or directories.
Definition: qgsfilewidget.h:63
void setValue(const QString &key, const QVariant &value, QgsSettings::Section section=QgsSettings::NoSection)
Sets the value of setting key to value.
QString filter() const
returns the filters used for QDialog::getOpenFileName
QString defaultRoot() const
returns the default root path
static QgsProject * instance()
Returns the QgsProject singleton instance.
Definition: qgsproject.cpp:411
Select multiple files.
Definition: qgsfilewidget.h:68
QgsFileWidget::RelativeStorage relativeStorage() const
returns if the relative path is with respect to the project path or the default path ...
static QString addExtensionFromFilter(const QString &fileName, const QString &filter)
Ensures that a fileName ends with an extension from the specified filter string.
void blockEvents(bool)
Emitted before and after showing the file dialog.
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