/*************************************************************************** qgsfilewidget.cpp --------------------- begin : 17.12.2015 copyright : (C) 2015 by Denis Rouzaud email : denis.rouzaud@gmail.com *************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * ***************************************************************************/ #include "qgsfilewidget.h" #include #include #include #include #include #include #include #include "qgssettings.h" #include "qgsfilterlineedit.h" #include "qgslogger.h" #include "qgsproject.h" #include "qgsapplication.h" #include "qgsfileutils.h" QgsFileWidget::QgsFileWidget( QWidget *parent ) : QWidget( parent ) { setBackgroundRole( QPalette::Window ); setAutoFillBackground( true ); mLayout = new QHBoxLayout(); mLayout->setMargin( 0 ); // If displaying a hyperlink, use a QLabel mLinkLabel = new QLabel( this ); // Make Qt opens the link with the OS defined viewer mLinkLabel->setOpenExternalLinks( true ); // Label should always be enabled to be able to open // the link on read only mode. mLinkLabel->setEnabled( true ); mLinkLabel->setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Preferred ); mLinkLabel->setTextFormat( Qt::RichText ); mLinkLabel->hide(); // do not show by default // otherwise, use the traditional QLineEdit subclass mLineEdit = new QgsFileDropEdit( this ); connect( mLineEdit, &QLineEdit::textChanged, this, &QgsFileWidget::textEdited ); mLayout->addWidget( mLineEdit ); mFileWidgetButton = new QToolButton( this ); mFileWidgetButton->setText( QChar( 0x2026 ) ); connect( mFileWidgetButton, &QAbstractButton::clicked, this, &QgsFileWidget::openFileDialog ); mLayout->addWidget( mFileWidgetButton ); setLayout( mLayout ); } QString QgsFileWidget::filePath() { return mFilePath; } QStringList QgsFileWidget::splitFilePaths( const QString &path ) { QStringList paths; const QStringList pathParts = path.split( QRegExp( "\"\\s+\"" ), QString::SkipEmptyParts ); for ( const auto &pathsPart : pathParts ) { QString cleaned = pathsPart; cleaned.remove( QRegExp( "(^\\s*\")|(\"\\s*)" ) ); paths.append( cleaned ); } return paths; } void QgsFileWidget::setFilePath( QString path ) { if ( path == QgsApplication::nullRepresentation() ) { path.clear(); } //will trigger textEdited slot mLineEdit->setValue( path ); } void QgsFileWidget::setReadOnly( bool readOnly ) { mFileWidgetButton->setEnabled( !readOnly ); mLineEdit->setEnabled( !readOnly ); } QString QgsFileWidget::dialogTitle() const { return mDialogTitle; } void QgsFileWidget::setDialogTitle( const QString &title ) { mDialogTitle = title; } QString QgsFileWidget::filter() const { return mFilter; } void QgsFileWidget::setFilter( const QString &filters ) { mFilter = filters; mLineEdit->setFilters( filters ); } bool QgsFileWidget::fileWidgetButtonVisible() const { return mButtonVisible; } void QgsFileWidget::setFileWidgetButtonVisible( bool visible ) { mButtonVisible = visible; mFileWidgetButton->setVisible( visible ); } void QgsFileWidget::textEdited( const QString &path ) { mFilePath = path; mLinkLabel->setText( toUrl( path ) ); // Show tooltip if multiple files are selected if ( path.contains( QStringLiteral( "\" \"" ) ) ) { mLineEdit->setToolTip( tr( "Selected files:
  • %1

" ).arg( splitFilePaths( path ).join( QStringLiteral( "
  • " ) ) ) ); } else { mLineEdit->setToolTip( QString() ); } emit fileChanged( mFilePath ); } bool QgsFileWidget::useLink() const { return mUseLink; } void QgsFileWidget::setUseLink( bool useLink ) { mUseLink = useLink; mLinkLabel->setVisible( mUseLink ); mLineEdit->setVisible( !mUseLink ); if ( mUseLink ) { mLayout->removeWidget( mLineEdit ); mLayout->insertWidget( 0, mLinkLabel ); } else { mLayout->removeWidget( mLinkLabel ); mLayout->insertWidget( 0, mLineEdit ); } } bool QgsFileWidget::fullUrl() const { return mFullUrl; } void QgsFileWidget::setFullUrl( bool fullUrl ) { mFullUrl = fullUrl; } QString QgsFileWidget::defaultRoot() const { return mDefaultRoot; } void QgsFileWidget::setDefaultRoot( const QString &defaultRoot ) { mDefaultRoot = defaultRoot; } QgsFileWidget::StorageMode QgsFileWidget::storageMode() const { return mStorageMode; } void QgsFileWidget::setStorageMode( QgsFileWidget::StorageMode storageMode ) { mStorageMode = storageMode; mLineEdit->setStorageMode( storageMode ); } QgsFileWidget::RelativeStorage QgsFileWidget::relativeStorage() const { return mRelativeStorage; } void QgsFileWidget::setRelativeStorage( QgsFileWidget::RelativeStorage relativeStorage ) { mRelativeStorage = relativeStorage; } QgsFilterLineEdit *QgsFileWidget::lineEdit() { return mLineEdit; } void QgsFileWidget::openFileDialog() { QgsSettings settings; QString oldPath; // If we use fixed default path if ( !mDefaultRoot.isEmpty() ) { oldPath = QDir::cleanPath( mDefaultRoot ); } // if we use a relative path option, we need to obtain the full path else if ( !mFilePath.isEmpty() ) { oldPath = relativePath( mFilePath, false ); } // If there is no valid value, find a default path to use QUrl url = QUrl::fromUserInput( oldPath ); if ( !url.isValid() ) { QString defPath = QDir::cleanPath( QgsProject::instance()->fileInfo().absolutePath() ); if ( defPath.isEmpty() ) { defPath = QDir::homePath(); } oldPath = settings.value( QStringLiteral( "UI/lastFileNameWidgetDir" ), defPath ).toString(); } // Handle Storage QString fileName; QStringList fileNames; QString title; switch ( mStorageMode ) { case GetFile: title = !mDialogTitle.isEmpty() ? mDialogTitle : tr( "Select a file" ); fileName = QFileDialog::getOpenFileName( this, title, QFileInfo( oldPath ).absoluteFilePath(), mFilter, &mSelectedFilter ); break; case GetMultipleFiles: title = !mDialogTitle.isEmpty() ? mDialogTitle : tr( "Select one or more files" ); fileNames = QFileDialog::getOpenFileNames( this, title, QFileInfo( oldPath ).absoluteFilePath(), mFilter, &mSelectedFilter ); break; case GetDirectory: title = !mDialogTitle.isEmpty() ? mDialogTitle : tr( "Select a directory" ); fileName = QFileDialog::getExistingDirectory( this, title, QFileInfo( oldPath ).absoluteFilePath(), QFileDialog::ShowDirsOnly ); break; case SaveFile: { title = !mDialogTitle.isEmpty() ? mDialogTitle : tr( "Create or select a file" ); if ( !confirmOverwrite() ) { fileName = QFileDialog::getSaveFileName( this, title, QFileInfo( oldPath ).absoluteFilePath(), mFilter, &mSelectedFilter, QFileDialog::DontConfirmOverwrite ); } else { fileName = QFileDialog::getSaveFileName( this, title, QFileInfo( oldPath ).absoluteFilePath(), mFilter, &mSelectedFilter ); } // make sure filename ends with filter. This isn't automatically done by // getSaveFileName on some platforms (e.g. gnome) fileName = QgsFileUtils::addExtensionFromFilter( fileName, mSelectedFilter ); } break; } if ( fileName.isEmpty() && fileNames.isEmpty( ) ) return; if ( mStorageMode != GetMultipleFiles ) { fileName = QDir::toNativeSeparators( QDir::cleanPath( QFileInfo( fileName ).absoluteFilePath() ) ); } else { for ( int i = 0; i < fileNames.length(); i++ ) { fileNames.replace( i, QDir::toNativeSeparators( QDir::cleanPath( QFileInfo( fileNames.at( i ) ).absoluteFilePath() ) ) ); } } // Store the last used path: switch ( mStorageMode ) { case GetFile: case SaveFile: settings.setValue( QStringLiteral( "UI/lastFileNameWidgetDir" ), QFileInfo( fileName ).absolutePath() ); break; case GetDirectory: settings.setValue( QStringLiteral( "UI/lastFileNameWidgetDir" ), fileName ); break; case GetMultipleFiles: settings.setValue( QStringLiteral( "UI/lastFileNameWidgetDir" ), QFileInfo( fileNames.first( ) ).absolutePath() ); break; } // Handle relative Path storage if ( mStorageMode != GetMultipleFiles ) { fileName = relativePath( fileName, true ); setFilePath( fileName ); } else { for ( int i = 0; i < fileNames.length(); i++ ) { fileNames.replace( i, relativePath( fileNames.at( i ), true ) ); } if ( fileNames.length() > 1 ) { setFilePath( QStringLiteral( "\"%1\"" ).arg( fileNames.join( QStringLiteral( "\" \"" ) ) ) ); } else { setFilePath( fileNames.first( ) ); } } } QString QgsFileWidget::relativePath( const QString &filePath, bool removeRelative ) const { QString RelativePath; if ( mRelativeStorage == RelativeProject ) { RelativePath = QDir::toNativeSeparators( QDir::cleanPath( QgsProject::instance()->fileInfo().absolutePath() ) ); } else if ( mRelativeStorage == RelativeDefaultPath && !mDefaultRoot.isEmpty() ) { RelativePath = QDir::toNativeSeparators( QDir::cleanPath( mDefaultRoot ) ); } if ( !RelativePath.isEmpty() ) { if ( removeRelative ) { return QDir::cleanPath( QDir( RelativePath ).relativeFilePath( filePath ) ); } else { return QDir::cleanPath( QDir( RelativePath ).filePath( filePath ) ); } } return filePath; } QString QgsFileWidget::toUrl( const QString &path ) const { QString rep; if ( path.isEmpty() ) { return QgsApplication::nullRepresentation(); } QString urlStr = relativePath( path, false ); QUrl url = QUrl::fromUserInput( urlStr ); if ( !url.isValid() || !url.isLocalFile() ) { QgsDebugMsg( QString( "URL: %1 is not valid or not a local file!" ).arg( path ) ); rep = path; } QString pathStr = url.toString(); if ( mFullUrl ) { rep = QStringLiteral( "%2" ).arg( pathStr, path ); } else { QString fileName = QFileInfo( urlStr ).fileName(); rep = QStringLiteral( "%2" ).arg( pathStr, fileName ); } return rep; } ///@cond PRIVATE QgsFileDropEdit::QgsFileDropEdit( QWidget *parent ) : QgsFilterLineEdit( parent ) { mDragActive = false; setAcceptDrops( true ); } void QgsFileDropEdit::setFilters( const QString &filters ) { mAcceptableExtensions.clear(); if ( filters.contains( QStringLiteral( "*.*" ) ) ) return; // everything is allowed! QRegularExpression rx( QStringLiteral( "\\*\\.(\\w+)" ) ); QRegularExpressionMatchIterator i = rx.globalMatch( filters ); while ( i.hasNext() ) { QRegularExpressionMatch match = i.next(); if ( match.hasMatch() ) { mAcceptableExtensions << match.captured( 1 ).toLower(); } } } QString QgsFileDropEdit::acceptableFilePath( QDropEvent *event ) const { QStringList paths; if ( event->mimeData()->hasUrls() ) { Q_FOREACH ( const QUrl &url, event->mimeData()->urls() ) { QFileInfo file( url.toLocalFile() ); if ( ( mStorageMode != QgsFileWidget::GetDirectory && file.isFile() && ( mAcceptableExtensions.isEmpty() || mAcceptableExtensions.contains( file.suffix(), Qt::CaseInsensitive ) ) ) || ( mStorageMode == QgsFileWidget::GetDirectory && file.isDir() ) ) paths.append( file.filePath() ); } } if ( paths.size() > 1 ) { return QStringLiteral( "\"%1\"" ).arg( paths.join( QStringLiteral( "\" \"" ) ) ); } else if ( paths.size() == 1 ) { return paths.first(); } else { return QString(); } } void QgsFileDropEdit::dragEnterEvent( QDragEnterEvent *event ) { QString filePath = acceptableFilePath( event ); if ( !filePath.isEmpty() ) { event->acceptProposedAction(); mDragActive = true; update(); } else { event->ignore(); } } void QgsFileDropEdit::dragLeaveEvent( QDragLeaveEvent *event ) { QgsFilterLineEdit::dragLeaveEvent( event ); event->accept(); mDragActive = false; update(); } void QgsFileDropEdit::dropEvent( QDropEvent *event ) { QString filePath = acceptableFilePath( event ); if ( !filePath.isEmpty() ) { setText( filePath ); selectAll(); setFocus( Qt::MouseFocusReason ); event->acceptProposedAction(); mDragActive = false; update(); } } void QgsFileDropEdit::paintEvent( QPaintEvent *e ) { QgsFilterLineEdit::paintEvent( e ); if ( mDragActive ) { QPainter p( this ); int width = 2; // width of highlight rectangle inside frame p.setPen( QPen( palette().highlight(), width ) ); QRect r = rect().adjusted( width, width, -width, -width ); p.drawRect( r ); } } ///@endcond