32#include <QRegularExpression>
37#include "moc_qgsfilewidget.cpp"
39using namespace Qt::StringLiterals;
45 mLayout->setContentsMargins( 0, 0, 0, 0 );
54 mLinkLabel->setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Preferred );
62 mLineEdit->setToolTip( tr(
"Full path to the file(s), including name and extension" ) );
63 connect(
mLineEdit, &QLineEdit::textChanged,
this, &QgsFileWidget::textEdited );
64 connect(
mLineEdit, &QgsFileDropEdit::fileDropped,
this, &QgsFileWidget::fileDropped );
70 connect(
mLinkEditButton, &QToolButton::clicked,
this, &QgsFileWidget::editLink );
76 connect(
mFileWidgetButton, &QAbstractButton::clicked,
this, &QgsFileWidget::openFileDialog );
89 QStringList pathParts;
94 const thread_local QRegularExpression partSeparatorsRegex = QRegularExpression( u
"(?:\")(\\s+)(?:\")"_s );
95 QRegularExpressionMatchIterator partSeparatorMatches = partSeparatorsRegex.globalMatch( path );
96 int substringStart = 0;
97 while ( partSeparatorMatches.hasNext() )
99 QRegularExpressionMatch match = partSeparatorMatches.next();
100 int substringEnd = match.capturedStart() + 1;
101 int substringLength = substringEnd - substringStart;
102 pathParts.append( path.mid( substringStart, substringLength ) );
103 substringStart = match.capturedEnd() - 1;
104 if ( !partSeparatorMatches.hasNext() )
106 pathParts.append( path.mid( substringStart ) );
109 if ( pathParts.length() == 0 )
111 pathParts.append( path );
115 const thread_local QRegularExpression doubleQuoteWrappedRegex( u
"(?:^\\s*\")(.+)(?:\"\\s*$)"_s );
116 for (
const QString &pathsPart : pathParts )
118 QRegularExpressionMatch match = doubleQuoteWrappedRegex.match( pathsPart );
120 if ( match.hasMatch() )
122 finalPath = match.captured( 1 );
126 finalPath = pathsPart;
128 paths.append( finalPath );
193 return path.contains( u
"\" \""_s );
196void QgsFileWidget::textEdited(
const QString &path )
203 mLineEdit->setToolTip( tr(
"Selected files:<br><ul><li>%1</li></ul><br>" ).arg(
splitFilePaths( path ).join(
"</li><li>"_L1 ) ) );
212void QgsFileWidget::editLink()
221void QgsFileWidget::fileDropped(
const QString &filePath )
225 mLineEdit->setFocus( Qt::MouseFocusReason );
302void QgsFileWidget::openFileDialog()
321 QUrl url = QUrl::fromUserInput( oldPath );
322 if ( !url.isValid() )
324 QString defPath = QDir::cleanPath( QFileInfo(
QgsProject::instance()->absoluteFilePath() ).path() );
325 if ( defPath.isEmpty() )
327 defPath = QDir::homePath();
329 oldPath = settings.
value( u
"UI/lastFileNameWidgetDir"_s, defPath ).toString();
334 QStringList fileNames;
338 QgsFocusKeeper focusKeeper;
351 fileName = QFileDialog::getExistingDirectory(
this, title, QFileInfo( oldPath ).absoluteFilePath(),
mOptions );
358 fileName = QFileDialog::getSaveFileName(
this, title, QFileInfo( oldPath ).absoluteFilePath(),
mFilter, &
mSelectedFilter,
mOptions | QFileDialog::DontConfirmOverwrite );
374 if (
mFilter.contains(
"(*.gdb *.GDB gdb)"_L1 ) && ( fileName.endsWith(
"/gdb.gdb"_L1 ) || fileName.endsWith(
"\\gdb.gdb"_L1 ) ) )
376 fileName.chop(
static_cast<int>( strlen(
"/gdb.gdb" ) ) );
387 if ( fileName.isEmpty() && fileNames.isEmpty() )
391 fileNames << fileName;
393 for (
int i = 0; i < fileNames.length(); i++ )
395 fileNames.replace( i, QDir::toNativeSeparators( QDir::cleanPath( QFileInfo( fileNames.at( i ) ).absoluteFilePath() ) ) );
404 settings.
setValue( u
"UI/lastFileNameWidgetDir"_s, QFileInfo( fileNames.first() ).absolutePath() );
407 settings.
setValue( u
"UI/lastFileNameWidgetDir"_s, fileNames.first() );
416 Q_ASSERT( fileNames.count() );
419 for (
int i = 0; i < fileNames.length(); i++ )
421 fileNames.replace( i,
relativePath( fileNames.at( i ),
true ) );
435 if ( filePaths.length() > 1 )
437 setFilePath( u
"\"%1\""_s.arg( filePaths.join(
"\" \""_L1 ) ) );
448 QString RelativePath;
451 RelativePath = QDir::toNativeSeparators( QDir::cleanPath( QFileInfo(
QgsProject::instance()->absoluteFilePath() ).path() ) );
455 RelativePath = QDir::toNativeSeparators( QDir::cleanPath(
mDefaultRoot ) );
458 if ( !RelativePath.isEmpty() )
460 if ( removeRelative )
462 return QDir::cleanPath( QDir( RelativePath ).relativeFilePath(
filePath ) );
475 QSize size {
mLineEdit->minimumSizeHint() };
477 size.setWidth( size.width() + btnSize.width() );
478 size.setHeight( std::max( size.height(), btnSize.height() ) );
493 return u
"<a>%1</a>"_s.arg( path );
497 QUrl url = QUrl::fromUserInput( urlStr );
498 if ( !url.isValid() || !url.isLocalFile() )
500 QgsDebugMsgLevel( u
"URL: %1 is not valid or not a local file!"_s.arg( path ), 2 );
504 QString pathStr = url.toString();
507 rep = u
"<a href=\"%1\">%2</a>"_s.arg( pathStr, path );
511 QString fileName = QFileInfo( urlStr ).fileName();
512 rep = u
"<a href=\"%1\">%2</a>"_s.arg( pathStr, fileName );
522QgsFileDropEdit::QgsFileDropEdit( QWidget *parent )
525 setAcceptDrops(
true );
528void QgsFileDropEdit::setFilters(
const QString &filters )
530 mAcceptableExtensions.clear();
532 if ( filters.contains( u
"*.*"_s ) )
535 const thread_local QRegularExpression rx( u
"\\*\\.(\\w+)"_s );
536 QRegularExpressionMatchIterator i = rx.globalMatch( filters );
537 while ( i.hasNext() )
539 QRegularExpressionMatch match = i.next();
540 if ( match.hasMatch() )
542 mAcceptableExtensions << match.captured( 1 ).toLower();
547QStringList QgsFileDropEdit::acceptableFilePaths( QDropEvent *event )
const
549 QStringList rawPaths;
551 if ( event->mimeData()->hasUrls() )
553 const QList<QUrl> urls =
event->mimeData()->urls();
554 rawPaths.reserve( urls.count() );
555 for (
const QUrl &url : urls )
557 const QString local = url.toLocalFile();
558 if ( !rawPaths.contains( local ) )
559 rawPaths.append( local );
566 if ( !rawPaths.contains( u.uri ) )
567 rawPaths.append( u.uri );
570 if ( !event->mimeData()->text().isEmpty() && !rawPaths.contains( event->mimeData()->text() ) )
571 rawPaths.append( event->mimeData()->text() );
573 paths.reserve( rawPaths.count() );
574 for (
const QString &path : std::as_const( rawPaths ) )
576 QFileInfo file( path );
577 switch ( mStorageMode )
583 if ( file.isFile() && ( mAcceptableExtensions.isEmpty() || mAcceptableExtensions.contains( file.suffix(), Qt::CaseInsensitive ) ) )
584 paths.append( file.filePath() );
592 paths.append( file.filePath() );
593 else if ( file.isFile() )
596 paths.append( file.absolutePath() );
607QString QgsFileDropEdit::acceptableFilePath( QDropEvent *event )
const
609 const QStringList paths = acceptableFilePaths( event );
610 if ( paths.size() > 1 )
612 return u
"\"%1\""_s.arg( paths.join(
"\" \""_L1 ) );
614 else if ( paths.size() == 1 )
616 return paths.first();
624void QgsFileDropEdit::dragEnterEvent( QDragEnterEvent *event )
626 QString filePath = acceptableFilePath( event );
627 if ( !filePath.isEmpty() )
629 event->acceptProposedAction();
630 setHighlighted(
true );
638void QgsFileDropEdit::dragLeaveEvent( QDragLeaveEvent *event )
640 QgsFilterLineEdit::dragLeaveEvent( event );
642 setHighlighted(
false );
645void QgsFileDropEdit::dropEvent( QDropEvent *event )
647 QString filePath = acceptableFilePath( event );
648 if ( !filePath.isEmpty() )
650 event->acceptProposedAction();
651 emit fileDropped( filePath );
654 setHighlighted(
false );
static QString nullRepresentation()
Returns the string used to represent the value NULL throughout QGIS.
static QIcon getThemeIcon(const QString &name, const QColor &fillColor=QColor(), const QColor &strokeColor=QColor())
Helper to get a theme icon.
static QString addExtensionFromFilter(const QString &fileName, const QString &filter)
Ensures that a fileName ends with an extension from the specified filter string.
QLineEdit subclass with built in support for clearing the widget's value and handling custom null val...
A QgsFilterLineEdit subclass with the ability to "highlight" the edges of the widget.
QList< QgsMimeDataUtils::Uri > UriList
static UriList decodeUriList(const QMimeData *data)
static QgsProject * instance()
Returns the QgsProject singleton instance.
Stores settings for use within QGIS.
QVariant value(const QString &key, const QVariant &defaultValue=QVariant(), Section section=NoSection) const
Returns the value for setting key.
void setValue(const QString &key, const QVariant &value, QgsSettings::Section section=QgsSettings::NoSection)
Sets the value of setting key to value.
#define QgsDebugMsgLevel(str, level)