Skip to content

Commit

Permalink
Improve FileName editor widget
Browse files Browse the repository at this point in the history
* Use an hyperlink for file name: when checked the widget displays an hyperlink (in a QLabel) instead of a QLineEdit. The link is always clickable, even in non edit mode. In this mode, user can only change the content of the field by selecting a file with the file selector (which is opened with the "..." button). If you want to revert to editable QLineEdit file name widget, just unchek the option.
* Display the full path: when checked, the hyperlink will display the absolute path of the file. When unchecked, the hyperlink will display only the name of the file (useful if the file path is always long).
Default Path: this is the path that will be used by the file selector when opening. The file selector will start at this path (if the option is not empty).
* Store Relative paths to the default path: when checked, file names are stored relatively to the default path described above. It is useful to store long paths into text shapefiles attributes limited to 254 characters.
* Store relative paths to the project path: when checked, file names are stored relatively to the project (.qgs file) path. This option will override the "save paths" setting of the project because it is sometimes useful to store code/svg/layer path relatively but not for the file name attributes.
* File storage options are exclusive options to make the file selector select files (default mode) or only directories.

File Name editor widget:

* add a isFieldSupported method to the factory to allow File Name widget only on fields that are QStrings.
* add a new QSetting to save the last path used in the widget. Before the PR, users were always defaulted to QGIS home directory when using the file selector button. This QSetting could also be used for the photo widget.
* when you want to change the value of an already set attribute, file selector will point to this path.
* when the value of the attribute is null or non valid (not a file), the file selector will use the default path (if set), or the last used path or the home directory.
  • Loading branch information
Médéric RIBREUX authored and 3nids committed Jan 13, 2016
1 parent b7170b8 commit 0034108
Show file tree
Hide file tree
Showing 8 changed files with 680 additions and 41 deletions.
2 changes: 2 additions & 0 deletions src/gui/CMakeLists.txt
Expand Up @@ -103,6 +103,7 @@ SET(QGIS_GUI_SRCS
editorwidgets/qgsdummyconfigdlg.cpp
editorwidgets/qgsenumerationwidgetwrapper.cpp
editorwidgets/qgsenumerationwidgetfactory.cpp
editorwidgets/qgsfilenameconfigdlg.cpp
editorwidgets/qgsfilenamewidgetwrapper.cpp
editorwidgets/qgsfilenamewidgetfactory.cpp
editorwidgets/qgshiddenwidgetwrapper.cpp
Expand Down Expand Up @@ -495,6 +496,7 @@ SET(QGIS_GUI_MOC_HDRS
editorwidgets/qgsdoublespinbox.h
editorwidgets/qgsdummyconfigdlg.h
editorwidgets/qgsenumerationwidgetwrapper.h
editorwidgets/qgsfilenameconfigdlg.h
editorwidgets/qgsfilenamewidgetwrapper.h
editorwidgets/qgshiddenwidgetwrapper.h
editorwidgets/qgsphotoconfigdlg.h
Expand Down
176 changes: 176 additions & 0 deletions src/gui/editorwidgets/qgsfilenameconfigdlg.cpp
@@ -0,0 +1,176 @@
/***************************************************************************
qgsfilenameconfigdlg.cpp
--------------------------------------
Date : 2015-11-26
Copyright : (C) 2015 Médéric Ribreux
Email : mederic.ribreux at medspx dot fr
***************************************************************************
* *
* 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 "qgsfilenameconfigdlg.h"
#include "qgsproject.h"

#include <QFileDialog>
#include <QSettings>

class QgsFileNameWidgetWrapper;

QgsFileNameConfigDlg::QgsFileNameConfigDlg( QgsVectorLayer* vl, int fieldIdx, QWidget* parent )
: QgsEditorConfigWidget( vl, fieldIdx, parent )
{
setupUi( this );

// By default, uncheck some options
mUseLink->setChecked( false );
mFullUrl->setChecked( false );
mRootPath->setPlaceholderText( QSettings().value( "/UI/lastFileNameWidgetDir", QDir::toNativeSeparators( QDir::cleanPath( QgsProject::instance()->fileInfo().absolutePath() ) ) ).toString() );

// Add connection to button for choosing default path
connect( mRootPathButton, SIGNAL( clicked() ), this, SLOT( chooseDefaultPath() ) );

// Activate Relative Default Path option only if Default Path is set
connect( mRootPath, SIGNAL( textChanged( const QString & ) ), this, SLOT( enableRelativeDefault() ) );

// Dynamic GroupBox for relative paths option
connect( mRelativeGroupBox, SIGNAL( toggled( bool ) ), this, SLOT( enableRelative( bool ) ) );

// set ids for StorageTypeButtons
mStorageButtonGroup->setId( mStoreFilesButton, 0 );
mStorageButtonGroup->setId( mStoreDirsButton, 1 );

// First button is toggled by default
mStorageButtonGroup->button( 0 )->toggle();

// set ids for RelativeButtons
mRelativeButtonGroup->setId( mRelativeProj, 0 );
mRelativeButtonGroup->setId( mRelativeDefault, 1 );

mRelativeButtonGroup->button( 0 )->toggle();
}

// Choose a base directory for rootPath
void QgsFileNameConfigDlg::chooseDefaultPath()
{
QString dir;
if ( !mRootPath->text().isEmpty() )
dir = mRootPath->text();
else
dir = QSettings().value( "/UI/lastFileNameWidgetDir", QDir::toNativeSeparators( QDir::cleanPath( QgsProject::instance()->fileInfo().absolutePath() ) ) ).toString();

QString rootName = QFileDialog::getExistingDirectory( this, tr( "Select a directory" ), dir, QFileDialog::ShowDirsOnly );

if ( rootName.isNull() )
return;

mRootPath->setText( rootName );
}

// Modify RelativeDefault according to mRootPath content
void QgsFileNameConfigDlg::enableRelativeDefault()
{
// Activate (or not) the RelativeDefault button if default path
if ( mRelativeGroupBox->isChecked() )
mRelativeDefault->setEnabled( !mRootPath->text().isEmpty() );

// If no default path, RelativeProj button enabled by default
if ( mRootPath->text().isEmpty() )
mRelativeProj->toggle();
}

// Dynamic activation of RelativeGroupBox
void QgsFileNameConfigDlg::enableRelative( bool state )
{
if ( state )
{
mRelativeProj->setEnabled( true );
if ( mRootPath->text().isEmpty() )
mRelativeDefault->setEnabled( false );
else
mRelativeDefault->setEnabled( true );
}
else
{
mRelativeProj->setEnabled( false );
mRelativeDefault->setEnabled( false );
}
}

// All non mandatory options are not stored to reduce
// the file size of the project file !
QgsEditorWidgetConfig QgsFileNameConfigDlg::config()
{
QgsEditorWidgetConfig cfg;

if ( mUseLink->isChecked() )
{
cfg.insert( "UseLink", mUseLink->isChecked() );
if ( mFullUrl->isChecked() )
cfg.insert( "FullUrl", mFullUrl->isChecked() );
}

if ( !mRootPath->text().isEmpty() )
cfg.insert( "DefaultRoot", mRootPath->text() );

// Save Storage Mode
int but = mStorageButtonGroup->checkedId();
if ( but != -1 )
{
if ( but == 0 )
cfg.insert( "StorageMode", "Files" );
else if ( but == 1 )
cfg.insert( "StorageMode", "Dirs" );
}

// Save Relative Paths option
if ( mRelativeGroupBox->isChecked() )
{
but = mRelativeButtonGroup->checkedId();
if ( but != -1 )
{
if ( but == 0 )
cfg.insert( "RelativeStorage", "Project" );
else if ( but == 1 )
cfg.insert( "RelativeStorage", "Default" );
}
}

return cfg;
}

// Set the configuration on the widget
void QgsFileNameConfigDlg::setConfig( const QgsEditorWidgetConfig& config )
{
if ( config.contains( "UseLink" ) )
{
mUseLink->setChecked( true );
if ( config.contains( "FullUrl" ) )
mFullUrl->setChecked( true );
}

if ( config.contains( "DefaultRoot" ) )
mRootPath->setText( config.value( "DefaultRoot" ).toString() );

if ( config.contains( "RelativeStorage" ) )
{
mRelativeGroupBox->setChecked( true );
if ( config.value( "RelativeStorage" ) == "Default" )
mRelativeDefault->toggle();
else if ( config.value( "RelativeStorage" ) == "Project" )
mRelativeProj->toggle();
}

// set storage mode
if ( config.contains( "StorageMode" ) )
{
if ( config.value( "StorageMode" ) == "Files" )
mStorageButtonGroup->button( 0 )->toggle();
else if ( config.value( "StorageMode" ) == "Dirs" )
mStorageButtonGroup->button( 1 )->toggle();
}
}
46 changes: 46 additions & 0 deletions src/gui/editorwidgets/qgsfilenameconfigdlg.h
@@ -0,0 +1,46 @@
/***************************************************************************
qgsfilenameconfigdlg.h
--------------------------------------
Date : 2015-11-26
Copyright : (C) 2015 Médéric Ribreux
Email : mederic dot ribreux at medspx dot fr
***************************************************************************
* *
* 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. *
* *
***************************************************************************/

#ifndef QGSFILENAMECONFIGDLG_H
#define QGSFILENAMECONFIGDLG_H

#include "ui_qgsfilenameconfigdlg.h"

#include "qgseditorconfigwidget.h"
//#include "qgsfilenamewidgetwrapper.h"

/** \class QgsFileNameConfigDlg
* \note not available in Python bindings
*/

class GUI_EXPORT QgsFileNameConfigDlg : public QgsEditorConfigWidget, private Ui::QgsFileNameConfigDlg
{
Q_OBJECT

public:
explicit QgsFileNameConfigDlg( QgsVectorLayer* vl, int fieldIdx, QWidget *parent = 0 );

// QgsEditorConfigWidget interface
public:
QgsEditorWidgetConfig config() override;
void setConfig( const QgsEditorWidgetConfig& config ) override;

private slots:
void chooseDefaultPath();
void enableRelativeDefault();
void enableRelative( bool state );
};

#endif // QGSFILENAMECONFIGDLG_H
67 changes: 61 additions & 6 deletions src/gui/editorwidgets/qgsfilenamewidgetfactory.cpp
Expand Up @@ -15,11 +15,8 @@

#include "qgsfilenamewidgetfactory.h"

#include "qgsfilenamewidgetwrapper.h"
#include "qgsdummyconfigdlg.h"

QgsFileNameWidgetFactory::QgsFileNameWidgetFactory( const QString& name ) :
QgsEditorWidgetFactory( name )
QgsFileNameWidgetFactory::QgsFileNameWidgetFactory( const QString& name )
: QgsEditorWidgetFactory( name )
{
}

Expand All @@ -30,5 +27,63 @@ QgsEditorWidgetWrapper* QgsFileNameWidgetFactory::create( QgsVectorLayer* vl, in

QgsEditorConfigWidget* QgsFileNameWidgetFactory::configWidget( QgsVectorLayer* vl, int fieldIdx, QWidget* parent ) const
{
return new QgsDummyConfigDlg( vl, fieldIdx, parent, QObject::tr( "Simplifies file selection by adding a file chooser dialog." ) );
return new QgsFileNameConfigDlg( vl, fieldIdx, parent );
}

void QgsFileNameWidgetFactory::writeConfig( const QgsEditorWidgetConfig& config, QDomElement& configElement, QDomDocument& doc, const QgsVectorLayer* layer, int fieldIdx )
{
Q_UNUSED( doc )
Q_UNUSED( layer )
Q_UNUSED( fieldIdx )

// Non mandatory options are not saved into project file (to save some space).
if ( config.contains( "UseLink" ) )
configElement.setAttribute( "UseLink", config.value( "UseLink" ).toBool() );

if ( config.contains( "FullUrl" ) )
configElement.setAttribute( "FullUrl", config.value( "FullUrl" ).toBool() );

if ( config.contains( "DefaultRoot" ) )
configElement.setAttribute( "DefaultRoot", config.value( "DefaultRoot" ).toString() );

if ( config.contains( "RelativeStorage" ) )
configElement.setAttribute( "RelativeStorage" , config.value( "RelativeStorage" ).toString() );

configElement.setAttribute( "StorageMode", config.value( "StorageMode" ).toString() );
}

QgsEditorWidgetConfig QgsFileNameWidgetFactory::readConfig( const QDomElement& configElement, QgsVectorLayer* layer, int fieldIdx )
{
Q_UNUSED( layer )
Q_UNUSED( fieldIdx )

QgsEditorWidgetConfig cfg;

if ( configElement.hasAttribute( "UseLink" ) )
cfg.insert( "UseLink", configElement.attribute( "UseLink" ) == "1" );

if ( configElement.hasAttribute( "FullUrl" ) )
cfg.insert( "FullUrl", configElement.attribute( "FullUrl" ) == "1" );

if ( configElement.hasAttribute( "DefaultRoot" ) )
cfg.insert( "DefaultRoot", configElement.attribute( "DefaultRoot" ) );

if ( configElement.hasAttribute( "RelativeStorage" ) )
{
if ( ( configElement.attribute( "RelativeStorage" ) == "Default" && configElement.hasAttribute( "DefaultRoot" ) ) ||
configElement.attribute( "RelativeStorage" ) == "Project" )
cfg.insert( "RelativeStorage" , configElement.attribute( "RelativeStorage" ) );
}

cfg.insert( "StorageMode", configElement.attribute( "StorageMode", "Files" ) );

return cfg;
}

bool QgsFileNameWidgetFactory::isFieldSupported( QgsVectorLayer* vl, int fieldIdx )
{
if ( vl->fields().at( fieldIdx ).type() == QVariant::String )
return true;

return false;
}
12 changes: 11 additions & 1 deletion src/gui/editorwidgets/qgsfilenamewidgetfactory.h
Expand Up @@ -17,6 +17,8 @@
#define QGSFILENAMEWIDGETFACTORY_H

#include "qgseditorwidgetfactory.h"
#include "qgsfilenameconfigdlg.h"
#include "qgsfilenamewidgetwrapper.h"

/** \class QgsFileNameWidgetFactory
* \note not available in Python bindings
Expand All @@ -25,12 +27,20 @@
class GUI_EXPORT QgsFileNameWidgetFactory : public QgsEditorWidgetFactory
{
public:
explicit QgsFileNameWidgetFactory( const QString& name );
QgsFileNameWidgetFactory( const QString& name );

// QgsEditorWidgetFactory interface
public:
QgsEditorWidgetWrapper* create( QgsVectorLayer* vl, int fieldIdx, QWidget* editor, QWidget* parent ) const override;
QgsEditorConfigWidget* configWidget( QgsVectorLayer* vl, int fieldIdx, QWidget* parent ) const override;

// QgsEditorWidgetFactory interface
public:
void writeConfig( const QgsEditorWidgetConfig& config, QDomElement& configElement, QDomDocument& doc, const QgsVectorLayer* layer, int fieldIdx ) override;

private:
QgsEditorWidgetConfig readConfig( const QDomElement& configElement, QgsVectorLayer* layer, int fieldIdx ) override;
bool isFieldSupported( QgsVectorLayer* vl, int fieldIdx ) override;
};

#endif // QGSFILENAMEWIDGETFACTORY_H

0 comments on commit 0034108

Please sign in to comment.