QGIS API Documentation  3.2.0-Bonn (bc43194)
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  connect( mLineEdit, &QLineEdit::textChanged, this, &QgsFileWidget::textEdited );
57  mLayout->addWidget( mLineEdit );
58 
59  mFileWidgetButton = new QToolButton( this );
60  mFileWidgetButton->setText( QChar( 0x2026 ) );
61  connect( mFileWidgetButton, &QAbstractButton::clicked, this, &QgsFileWidget::openFileDialog );
62  mLayout->addWidget( mFileWidgetButton );
63 
64  setLayout( mLayout );
65 }
66 
68 {
69  return mFilePath;
70 }
71 
72 QStringList QgsFileWidget::splitFilePaths( const QString &path )
73 {
74  QStringList paths;
75  const QStringList pathParts = path.split( QRegExp( "\"\\s+\"" ), QString::SkipEmptyParts );
76  for ( const auto &pathsPart : pathParts )
77  {
78  QString cleaned = pathsPart;
79  cleaned.remove( QRegExp( "(^\\s*\")|(\"\\s*)" ) );
80  paths.append( cleaned );
81  }
82  return paths;
83 }
84 
85 void QgsFileWidget::setFilePath( QString path )
86 {
87  if ( path == QgsApplication::nullRepresentation() )
88  {
89  path.clear();
90  }
91 
92  //will trigger textEdited slot
93  mLineEdit->setValue( path );
94 
95 }
96 
97 void QgsFileWidget::setReadOnly( bool readOnly )
98 {
99  mFileWidgetButton->setEnabled( !readOnly );
100  mLineEdit->setEnabled( !readOnly );
101 }
102 
103 QString QgsFileWidget::dialogTitle() const
104 {
105  return mDialogTitle;
106 }
107 
108 void QgsFileWidget::setDialogTitle( const QString &title )
109 {
110  mDialogTitle = title;
111 }
112 
113 QString QgsFileWidget::filter() const
114 {
115  return mFilter;
116 }
117 
118 void QgsFileWidget::setFilter( const QString &filters )
119 {
120  mFilter = filters;
121  mLineEdit->setFilters( filters );
122 }
123 
125 {
126  return mButtonVisible;
127 }
128 
130 {
131  mButtonVisible = visible;
132  mFileWidgetButton->setVisible( visible );
133 }
134 
135 void QgsFileWidget::textEdited( const QString &path )
136 {
137  mFilePath = path;
138  mLinkLabel->setText( toUrl( path ) );
139  // Show tooltip if multiple files are selected
140  if ( path.contains( QStringLiteral( "\" \"" ) ) )
141  {
142  mLineEdit->setToolTip( tr( "Selected files:<br><ul><li>%1</li></ul><br>" ).arg( splitFilePaths( path ).join( QStringLiteral( "</li><li>" ) ) ) );
143  }
144  else
145  {
146  mLineEdit->setToolTip( QString() );
147  }
148  emit fileChanged( mFilePath );
149 }
150 
151 bool QgsFileWidget::useLink() const
152 {
153  return mUseLink;
154 }
155 
157 {
158  mUseLink = useLink;
159  mLinkLabel->setVisible( mUseLink );
160  mLineEdit->setVisible( !mUseLink );
161  if ( mUseLink )
162  {
163  mLayout->removeWidget( mLineEdit );
164  mLayout->insertWidget( 0, mLinkLabel );
165  }
166  else
167  {
168  mLayout->removeWidget( mLinkLabel );
169  mLayout->insertWidget( 0, mLineEdit );
170  }
171 }
172 
173 bool QgsFileWidget::fullUrl() const
174 {
175  return mFullUrl;
176 }
177 
179 {
180  mFullUrl = fullUrl;
181 }
182 
183 QString QgsFileWidget::defaultRoot() const
184 {
185  return mDefaultRoot;
186 }
187 
189 {
190  mDefaultRoot = defaultRoot;
191 }
192 
194 {
195  return mStorageMode;
196 }
197 
199 {
200  mStorageMode = storageMode;
201  mLineEdit->setStorageMode( storageMode );
202 }
203 
205 {
206  return mRelativeStorage;
207 }
208 
210 {
211  mRelativeStorage = relativeStorage;
212 }
213 
215 {
216  return mLineEdit;
217 }
218 
219 void QgsFileWidget::openFileDialog()
220 {
221  QgsSettings settings;
222  QString oldPath;
223 
224  // If we use fixed default path
225  if ( !mDefaultRoot.isEmpty() )
226  {
227  oldPath = QDir::cleanPath( mDefaultRoot );
228  }
229  // if we use a relative path option, we need to obtain the full path
230  else if ( !mFilePath.isEmpty() )
231  {
232  oldPath = relativePath( mFilePath, false );
233  }
234 
235  // If there is no valid value, find a default path to use
236  QUrl url = QUrl::fromUserInput( oldPath );
237  if ( !url.isValid() )
238  {
239  QString defPath = QDir::cleanPath( QFileInfo( QgsProject::instance()->absoluteFilePath() ).path() );
240  if ( defPath.isEmpty() )
241  {
242  defPath = QDir::homePath();
243  }
244  oldPath = settings.value( QStringLiteral( "UI/lastFileNameWidgetDir" ), defPath ).toString();
245  }
246 
247  // Handle Storage
248  QString fileName;
249  QStringList fileNames;
250  QString title;
251 
252  switch ( mStorageMode )
253  {
254  case GetFile:
255  title = !mDialogTitle.isEmpty() ? mDialogTitle : tr( "Select a file" );
256  fileName = QFileDialog::getOpenFileName( this, title, QFileInfo( oldPath ).absoluteFilePath(), mFilter, &mSelectedFilter );
257  break;
258  case GetMultipleFiles:
259  title = !mDialogTitle.isEmpty() ? mDialogTitle : tr( "Select one or more files" );
260  fileNames = QFileDialog::getOpenFileNames( this, title, QFileInfo( oldPath ).absoluteFilePath(), mFilter, &mSelectedFilter );
261  break;
262  case GetDirectory:
263  title = !mDialogTitle.isEmpty() ? mDialogTitle : tr( "Select a directory" );
264  fileName = QFileDialog::getExistingDirectory( this, title, QFileInfo( oldPath ).absoluteFilePath(), QFileDialog::ShowDirsOnly );
265  break;
266  case SaveFile:
267  {
268  title = !mDialogTitle.isEmpty() ? mDialogTitle : tr( "Create or select a file" );
269  if ( !confirmOverwrite() )
270  {
271  fileName = QFileDialog::getSaveFileName( this, title, QFileInfo( oldPath ).absoluteFilePath(), mFilter, &mSelectedFilter, QFileDialog::DontConfirmOverwrite );
272  }
273  else
274  {
275  fileName = QFileDialog::getSaveFileName( this, title, QFileInfo( oldPath ).absoluteFilePath(), mFilter, &mSelectedFilter );
276  }
277 
278  // make sure filename ends with filter. This isn't automatically done by
279  // getSaveFileName on some platforms (e.g. gnome)
280  fileName = QgsFileUtils::addExtensionFromFilter( fileName, mSelectedFilter );
281  }
282  break;
283  }
284 
285  if ( fileName.isEmpty() && fileNames.isEmpty( ) )
286  return;
287 
288  if ( mStorageMode != GetMultipleFiles )
289  {
290  fileName = QDir::toNativeSeparators( QDir::cleanPath( QFileInfo( fileName ).absoluteFilePath() ) );
291  }
292  else
293  {
294  for ( int i = 0; i < fileNames.length(); i++ )
295  {
296  fileNames.replace( i, QDir::toNativeSeparators( QDir::cleanPath( QFileInfo( fileNames.at( i ) ).absoluteFilePath() ) ) );
297  }
298  }
299 
300  // Store the last used path:
301  switch ( mStorageMode )
302  {
303  case GetFile:
304  case SaveFile:
305  settings.setValue( QStringLiteral( "UI/lastFileNameWidgetDir" ), QFileInfo( fileName ).absolutePath() );
306  break;
307  case GetDirectory:
308  settings.setValue( QStringLiteral( "UI/lastFileNameWidgetDir" ), fileName );
309  break;
310  case GetMultipleFiles:
311  settings.setValue( QStringLiteral( "UI/lastFileNameWidgetDir" ), QFileInfo( fileNames.first( ) ).absolutePath() );
312  break;
313  }
314 
315  // Handle relative Path storage
316  if ( mStorageMode != GetMultipleFiles )
317  {
318  fileName = relativePath( fileName, true );
319  setFilePath( fileName );
320  }
321  else
322  {
323  for ( int i = 0; i < fileNames.length(); i++ )
324  {
325  fileNames.replace( i, relativePath( fileNames.at( i ), true ) );
326  }
327  if ( fileNames.length() > 1 )
328  {
329  setFilePath( QStringLiteral( "\"%1\"" ).arg( fileNames.join( QStringLiteral( "\" \"" ) ) ) );
330  }
331  else
332  {
333  setFilePath( fileNames.first( ) );
334  }
335  }
336 }
337 
338 
339 QString QgsFileWidget::relativePath( const QString &filePath, bool removeRelative ) const
340 {
341  QString RelativePath;
342  if ( mRelativeStorage == RelativeProject )
343  {
344  RelativePath = QDir::toNativeSeparators( QDir::cleanPath( QFileInfo( QgsProject::instance()->absoluteFilePath() ).path() ) );
345  }
346  else if ( mRelativeStorage == RelativeDefaultPath && !mDefaultRoot.isEmpty() )
347  {
348  RelativePath = QDir::toNativeSeparators( QDir::cleanPath( mDefaultRoot ) );
349  }
350 
351  if ( !RelativePath.isEmpty() )
352  {
353  if ( removeRelative )
354  {
355  return QDir::cleanPath( QDir( RelativePath ).relativeFilePath( filePath ) );
356  }
357  else
358  {
359  return QDir::cleanPath( QDir( RelativePath ).filePath( filePath ) );
360  }
361  }
362 
363  return filePath;
364 }
365 
366 
367 QString QgsFileWidget::toUrl( const QString &path ) const
368 {
369  QString rep;
370  if ( path.isEmpty() )
371  {
373  }
374 
375  QString urlStr = relativePath( path, false );
376  QUrl url = QUrl::fromUserInput( urlStr );
377  if ( !url.isValid() || !url.isLocalFile() )
378  {
379  QgsDebugMsg( QString( "URL: %1 is not valid or not a local file!" ).arg( path ) );
380  rep = path;
381  }
382 
383  QString pathStr = url.toString();
384  if ( mFullUrl )
385  {
386  rep = QStringLiteral( "<a href=\"%1\">%2</a>" ).arg( pathStr, path );
387  }
388  else
389  {
390  QString fileName = QFileInfo( urlStr ).fileName();
391  rep = QStringLiteral( "<a href=\"%1\">%2</a>" ).arg( pathStr, fileName );
392  }
393 
394  return rep;
395 }
396 
397 
398 
400 
401 
402 QgsFileDropEdit::QgsFileDropEdit( QWidget *parent )
403  : QgsFilterLineEdit( parent )
404 {
405  mDragActive = false;
406  setAcceptDrops( true );
407 }
408 
409 void QgsFileDropEdit::setFilters( const QString &filters )
410 {
411  mAcceptableExtensions.clear();
412 
413  if ( filters.contains( QStringLiteral( "*.*" ) ) )
414  return; // everything is allowed!
415 
416  QRegularExpression rx( QStringLiteral( "\\*\\.(\\w+)" ) );
417  QRegularExpressionMatchIterator i = rx.globalMatch( filters );
418  while ( i.hasNext() )
419  {
420  QRegularExpressionMatch match = i.next();
421  if ( match.hasMatch() )
422  {
423  mAcceptableExtensions << match.captured( 1 ).toLower();
424  }
425  }
426 }
427 
428 QString QgsFileDropEdit::acceptableFilePath( QDropEvent *event ) const
429 {
430  QStringList paths;
431  if ( event->mimeData()->hasUrls() )
432  {
433  Q_FOREACH ( const QUrl &url, event->mimeData()->urls() )
434  {
435  QFileInfo file( url.toLocalFile() );
436  if ( ( mStorageMode != QgsFileWidget::GetDirectory && file.isFile() &&
437  ( mAcceptableExtensions.isEmpty() || mAcceptableExtensions.contains( file.suffix(), Qt::CaseInsensitive ) ) )
438  || ( mStorageMode == QgsFileWidget::GetDirectory && file.isDir() ) )
439  paths.append( file.filePath() );
440  }
441  }
442  if ( paths.size() > 1 )
443  {
444  return QStringLiteral( "\"%1\"" ).arg( paths.join( QStringLiteral( "\" \"" ) ) );
445  }
446  else if ( paths.size() == 1 )
447  {
448  return paths.first();
449  }
450  else
451  {
452  return QString();
453  }
454 }
455 
456 void QgsFileDropEdit::dragEnterEvent( QDragEnterEvent *event )
457 {
458  QString filePath = acceptableFilePath( event );
459  if ( !filePath.isEmpty() )
460  {
461  event->acceptProposedAction();
462  mDragActive = true;
463  update();
464  }
465  else
466  {
467  event->ignore();
468  }
469 }
470 
471 void QgsFileDropEdit::dragLeaveEvent( QDragLeaveEvent *event )
472 {
473  QgsFilterLineEdit::dragLeaveEvent( event );
474  event->accept();
475  mDragActive = false;
476  update();
477 }
478 
479 void QgsFileDropEdit::dropEvent( QDropEvent *event )
480 {
481  QString filePath = acceptableFilePath( event );
482  if ( !filePath.isEmpty() )
483  {
484  setText( filePath );
485  selectAll();
486  setFocus( Qt::MouseFocusReason );
487  event->acceptProposedAction();
488  mDragActive = false;
489  update();
490  }
491 }
492 
493 void QgsFileDropEdit::paintEvent( QPaintEvent *e )
494 {
495  QgsFilterLineEdit::paintEvent( e );
496  if ( mDragActive )
497  {
498  QPainter p( this );
499  int width = 2; // width of highlight rectangle inside frame
500  p.setPen( QPen( palette().highlight(), width ) );
501  QRect r = rect().adjusted( width, width, -width, -width );
502  p.drawRect( r );
503  }
504 }
505 
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 single file.
Definition: qgsfilewidget.h:66
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 ...
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
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:391
Select multiple files.
Definition: qgsfilewidget.h:68
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