mirror of
https://github.com/qgis/QGIS.git
synced 2025-03-31 00:03:42 -04:00
507 lines
13 KiB
C++
507 lines
13 KiB
C++
/***************************************************************************
|
|
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 <QLineEdit>
|
|
#include <QToolButton>
|
|
#include <QLabel>
|
|
#include <QFileDialog>
|
|
#include <QGridLayout>
|
|
#include <QUrl>
|
|
#include <QDropEvent>
|
|
|
|
#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:<br><ul><li>%1</li></ul><br>" ).arg( splitFilePaths( path ).join( QStringLiteral( "</li><li>" ) ) ) );
|
|
}
|
|
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( "<a href=\"%1\">%2</a>" ).arg( pathStr, path );
|
|
}
|
|
else
|
|
{
|
|
QString fileName = QFileInfo( urlStr ).fileName();
|
|
rep = QStringLiteral( "<a href=\"%1\">%2</a>" ).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
|