Skip to content
Permalink
Browse files

[QgsQuick] - externalResource widget handler (#9232)

* [QgsQuick] - extended externalResource widget

Added removeFile function and modified fileName function - former has been missing and photoPanel is using it. The latter needed modification due to a new option to choose image from a gallery.
Added externalResource handler for externalResource widget which enables following features:
* option to choose an image from a gallery - selected image is copied to projects folder, if it doesnt exists there. Added "ic_gallery" icon.
* ability to remove value for externalResource field. Optionally removes referenced image as well ("Ok" option in dialog)
* ability to interact with image preview onClick - the main idea is to have ability to enlarge preview image. Currently its possible only in edit state of the form since the whole field is disabled otherwise.

Fixed resizing of icon/previewImage and component itself as well.

* [QgsQuick] - extended externalResource widget
Commit contains following fixes/changes/additions after review:
* Added QgsQuickUtils::getRelativePath which replaced QgsQuickUtils::getFileName + related changes in photoPanel
* Added test for new QgsQuickUtils functionality
* fixed weird or redundant size definitions in externalResource widget
* Some changes in docs.

* [QgsQuick] Changed "default" case result for QgsQuickUtils::getRelativePath

* [QgsQuick] Fixed test after changed functionality in QgsQuickUtils
  • Loading branch information
sklencar authored and wonder-sk committed Feb 25, 2019
1 parent d49dc89 commit 77f500b12e03c37a238f9b95f986fe117356f9cf
@@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="24" height="24" viewBox="0 0 24 24"><path d="M7,15L11.5,9L15,13.5L17.5,10.5L21,15M22,4H14L12,2H6C4.9,2 4,2.9 4,4V16C4,17.1 4.9,18 6,18H22C23.1,18 24,17.1 24,16V6C24,4.9 23.1,4 22,4M2,6H0V11H0V20C0,21.1 0.9,22 2,22H20V20H2V6Z" /></svg>
@@ -10,5 +10,6 @@
<file>ic_photo_notavailable_white.svg</file>
<file>ic_save_white.svg</file>
<file>ic_camera.svg</file>
<file>ic_gallery.svg</file>
</qresource>
</RCC>
@@ -31,13 +31,13 @@ Item {
property var notavailableImageSource: QgsQuick.Utils.getThemeIcon("ic_photo_notavailable_white")

id: fieldItem
height: image.hasValidSource? customStyle.height * 3 : customStyle.height
anchors {
left: parent.left
right: parent.right
rightMargin: 10 * QgsQuick.Utils.dp
}

height: Math.max(image.height, button.height)
QgsQuick.PhotoCapture {
id: photoCapturePanel
visible: false
@@ -52,11 +52,21 @@ Item {
property bool hasValidSource: false

id: image
height: hasValidSource? customStyle.height * 3 : customStyle.height
height: fieldItem.height
sourceSize.height: height
autoTransform: true
fillMode: Image.PreserveAspectFit
visible: hasValidSource

MouseArea {
anchors.fill: parent
onClicked: externalResourceHandler.previewImage(homePath + "/" + image.currentValue)
}

onCurrentValueChanged: {
image.source = image.getSource()
}

onSourceChanged: {
hasValidSource = (image.source === fieldItem.brokenImageSource ||
image.source === fieldItem.notavailableImageSource) ? false : true
@@ -81,6 +91,66 @@ Item {
visible: !image.hasValidSource
}

Button {
id: deleteButton
visible: fieldItem.enabled && image.hasValidSource
width: customStyle.height
height: width
padding: 0

anchors.right: imageBrowserButton.left
anchors.bottom: parent.bottom
anchors.verticalCenter: parent.verticalCenter

onClicked: externalResourceHandler.removeImage(fieldItem, homePath + "/" + image.currentValue)

background: Image {
id: deleteIcon
source: QgsQuick.Utils.getThemeIcon("ic_delete_forever_white")
width: deleteButton.width
height: deleteButton.height
sourceSize.width: width
sourceSize.height: height
fillMode: Image.PreserveAspectFit
}

ColorOverlay {
anchors.fill: deleteIcon
source: deleteIcon
color: customStyle.fontColor
}
}

Button {
id: imageBrowserButton
visible: fieldItem.enabled
width: customStyle.height
height: width
padding: 0

anchors.right: button.left
anchors.bottom: parent.bottom
anchors.verticalCenter: parent.verticalCenter

onClicked:externalResourceHandler.chooseImage(fieldItem)

background: Image {
id: browseIcon
source: QgsQuick.Utils.getThemeIcon("ic_gallery")
width: imageBrowserButton.width
height: imageBrowserButton.height
sourceSize.width: width
sourceSize.height: height
fillMode: Image.PreserveAspectFit
}

ColorOverlay {
anchors.fill: browseIcon
source: browseIcon
color: customStyle.fontColor
}
}

Button {
id: button
visible: fieldItem.enabled
@@ -36,6 +36,34 @@ Item {
*/
signal canceled

/**
* A handler for extra events in externalSourceWidget.
*/
property var externalResourceHandler: QtObject {

/**
* Called when clicked on the gallery icon to choose a file in a gallery.
* \param itemWidget editorWidget for modified field to send valueChanged signal.
*/
property var chooseImage: function chooseImage(itemWidget) {
}

/**
* Called when clicked on the photo image. Suppose to be used to bring a bigger preview.
* \param imagePath Absolute path to the image.
*/
property var previewImage: function previewImage(imagePath) {
}

/**
* Called when clicked on the trash icon. Suppose to delete the value and optionally also the image.
* \param itemWidget editorWidget for modified field to send valueChanged signal.
* \param imagePath Absolute path to the image.
*/
property var removeImage: function removeImage(itemWidget, imagePath) {
}
}

/**
* AttributeFormModel binded on a feature supporting auto-generated editor layouts and "tab" layout.
*/
@@ -320,6 +348,7 @@ Item {
property var constraintValid: ConstraintValid
property var homePath: form.project ? form.project.homePath : ""
property var customStyle: form.style.fields
property var externalResourceHandler: form.externalResourceHandler

active: widget !== 'Hidden'

@@ -73,7 +73,7 @@ Drawer {
Component.onDestruction: {
if (!captureItem && camera.imageCapture.capturedImagePath != ""){
captureItem.saveImage = false
QgsQuick.Utils.remove(camera.imageCapture.capturedImagePath)
QgsQuick.Utils.removeFile(camera.imageCapture.capturedImagePath)
}
captureItem.saveImage = false
}
@@ -164,7 +164,7 @@ Drawer {
captureItem.saveImage = false
photoPreview.visible = false
if (camera.imageCapture.capturedImagePath != "") {
QgsQuick.Utils.remove(camera.imageCapture.capturedImagePath)
QgsQuick.Utils.removeFile(camera.imageCapture.capturedImagePath)
}
}
}
@@ -200,7 +200,7 @@ Drawer {
onClicked: {
captureItem.saveImage = true
photoPanel.visible = false
photoPanel.lastPhotoName = QgsQuick.Utils.getFileName(camera.imageCapture.capturedImagePath)
photoPanel.lastPhotoName = QgsQuick.Utils.getRelativePath(camera.imageCapture.capturedImagePath, photoPanel.targetDir)
if (photoPanel.lastPhotoName !== "") {
fieldItem.image.source = photoPanel.targetDir + "/" + photoPanel.lastPhotoName
fieldItem.valueChanged(photoPanel.lastPhotoName, photoPanel.lastPhotoName === "" || photoPanel.lastPhotoName === null)
@@ -95,11 +95,22 @@ bool QgsQuickUtils::fileExists( const QString &path )
return ( check_file.exists() && check_file.isFile() );
}

QString QgsQuickUtils::getFileName( const QString &path )
QString QgsQuickUtils::getRelativePath( const QString &path, const QString &prefixPath )
{
QFileInfo fileInfo( path );
QString filename( fileInfo.fileName() );
return filename;
QString resultPath = path;
QString prefixPathWithSlash;
if ( !prefixPath.endsWith( "/" ) )
prefixPathWithSlash = QStringLiteral( "%1/" ).arg( prefixPath );
else
prefixPathWithSlash = prefixPath;

if ( resultPath.startsWith( prefixPathWithSlash ) )
return resultPath.replace( prefixPathWithSlash, QString() );
QString filePrefixPath = QStringLiteral( "file://%1" ).arg( prefixPathWithSlash );
if ( resultPath.startsWith( filePrefixPath ) )
return resultPath.replace( filePrefixPath, QString() );

return QString();
}

void QgsQuickUtils::logMessage( const QString &message, const QString &tag, Qgis::MessageLevel level )
@@ -162,6 +173,12 @@ QString QgsQuickUtils::formatDistance( double distance,
.arg( QgsUnitTypes::toAbbreviatedString( destUnits ) );
}

bool QgsQuickUtils::removeFile( const QString &filePath )
{
QFile file( filePath );
return file.remove( filePath );
}


void QgsQuickUtils::humanReadableDistance( double srcDistance, QgsUnitTypes::DistanceUnit srcUnits,
QgsUnitTypes::SystemOfMeasurement destSystem,
@@ -124,10 +124,13 @@ class QUICK_EXPORT QgsQuickUtils: public QObject
Q_INVOKABLE static bool fileExists( const QString &path );

/**
* Extracts filename from path
* \since QGIS 3.4
* Returns relative path of the file to given prefixPath. If prefixPath does not match a path parameter,
* returns an empty string. If a path starts with "file://", this prefix is ignored.
* \param path Absolute path to file
* \param prefixPath
* \since QGIS 3.8
*/
Q_INVOKABLE static QString getFileName( const QString &path );
Q_INVOKABLE static QString getRelativePath( const QString &path, const QString &prefixPath );

/**
* Log message in QgsMessageLog
@@ -194,6 +197,16 @@ class QUICK_EXPORT QgsQuickUtils: public QObject
int decimals,
QgsUnitTypes::SystemOfMeasurement destSystem = QgsUnitTypes::MetricSystem );

/**
* Deletes file from a given path.
*
* \param filePath Absolute path to file
* \returns bool True, if removal was successful, otherwise false.
*
* \since QGIS 3.8
*/
Q_INVOKABLE static bool removeFile( const QString &filePath );

/**
* Converts distance to human readable distance in destination system of measurement
*
@@ -43,6 +43,7 @@ class TestQgsQuickUtils: public QObject
void loadIcon();
void fileExists();
void loadQmlComponent();
void getRelativePath();

private:
QgsQuickUtils utils;
@@ -144,7 +145,8 @@ void TestQgsQuickUtils::loadIcon()
QUrl url = utils.getThemeIcon( "ic_save_white" );
Q_ASSERT( url.toString() == QStringLiteral( "qrc:/ic_save_white.svg" ) );

QString fileName = utils.getFileName( url.toString() );
QFileInfo fileInfo( url.toString() );
QString fileName( fileInfo.fileName() );
Q_ASSERT( fileName == QStringLiteral( "ic_save_white.svg" ) );
}

@@ -164,5 +166,27 @@ void TestQgsQuickUtils::loadQmlComponent()
Q_ASSERT( valuemap.path() == QString( "qgsquickvaluemap.qml" ) );
}

void TestQgsQuickUtils::getRelativePath()
{
QString prefixPath = QStringLiteral( "%1/" ).arg( TEST_DATA_DIR );
QString fileName = QStringLiteral( "quickapp_project.qgs" );
QString path = prefixPath + fileName;
QString relativePath = utils.getRelativePath( path, prefixPath );
QCOMPARE( fileName, relativePath );

QString fileName2 = QStringLiteral( "zip/test.zip" );
QString path2 = prefixPath + fileName2;
QString relativePath2 = utils.getRelativePath( path2, prefixPath );
QCOMPARE( fileName2, relativePath2 );

QString path3 = QStringLiteral( "file://" ) + path2;
QString relativePath3 = utils.getRelativePath( path3, prefixPath );
QCOMPARE( fileName2, relativePath3 );

QString relativePath4 = utils.getRelativePath( path2, QStringLiteral( "/dummy/path/" ) );
QCOMPARE( QString(), relativePath4 );
}


QGSTEST_MAIN( TestQgsQuickUtils )
#include "testqgsquickutils.moc"

0 comments on commit 77f500b

Please sign in to comment.
You can’t perform that action at this time.