From e58f25dbb9a7351e3b8113b97f9caff8581da3ba Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Tue, 30 May 2017 11:36:24 +1000 Subject: [PATCH] Create new QgsOpacityWidget widget Allows consistent behavior and appearance across all opacity controls --- python/gui/gui.sip | 1 + python/gui/qgsopacitywidget.sip | 66 +++++++++++++ src/customwidgets/CMakeLists.txt | 3 + src/customwidgets/qgiscustomwidgets.cpp | 2 + src/customwidgets/qgsopacitywidgetplugin.cpp | 97 ++++++++++++++++++++ src/customwidgets/qgsopacitywidgetplugin.h | 51 ++++++++++ src/gui/CMakeLists.txt | 2 + src/gui/qgsopacitywidget.cpp | 69 ++++++++++++++ src/gui/qgsopacitywidget.h | 83 +++++++++++++++++ tests/src/python/CMakeLists.txt | 1 + tests/src/python/test_qgsopacitywidget.py | 58 ++++++++++++ 11 files changed, 433 insertions(+) create mode 100644 python/gui/qgsopacitywidget.sip create mode 100644 src/customwidgets/qgsopacitywidgetplugin.cpp create mode 100644 src/customwidgets/qgsopacitywidgetplugin.h create mode 100644 src/gui/qgsopacitywidget.cpp create mode 100644 src/gui/qgsopacitywidget.h create mode 100644 tests/src/python/test_qgsopacitywidget.py diff --git a/python/gui/gui.sip b/python/gui/gui.sip index 30b08667709..d2e6caba04c 100644 --- a/python/gui/gui.sip +++ b/python/gui/gui.sip @@ -128,6 +128,7 @@ %Include qgsnewnamedialog.sip %Include qgsnewvectorlayerdialog.sip %Include qgsnewgeopackagelayerdialog.sip +%Include qgsopacitywidget.sip %Include qgsoptionsdialogbase.sip %Include qgsoptionswidgetfactory.sip %Include qgsorderbydialog.sip diff --git a/python/gui/qgsopacitywidget.sip b/python/gui/qgsopacitywidget.sip new file mode 100644 index 00000000000..86c8f1122df --- /dev/null +++ b/python/gui/qgsopacitywidget.sip @@ -0,0 +1,66 @@ +/************************************************************************ + * This file has been generated automatically from * + * * + * src/gui/qgsopacitywidget.h * + * * + * Do not edit manually ! Edit header and run scripts/sipify.pl again * + ************************************************************************/ + + + + +class QgsOpacityWidget : QWidget +{ +%Docstring + A widget for setting an opacity value. +.. versionadded:: 3.0 +%End + +%TypeHeaderCode +#include "qgsopacitywidget.h" +%End + public: + + explicit QgsOpacityWidget( QWidget *parent /TransferThis/ = 0 ); +%Docstring + Constructor for QgsOpacityWidget. +%End + + double opacity() const; +%Docstring + Returns the current opacity selected in the widget, where opacity ranges from 0.0 (transparent) + to 1.0 (opaque). +.. seealso:: setOpacity() +.. seealso:: opacityChanged() + :rtype: float +%End + + public slots: + + void setOpacity( double opacity ); +%Docstring + Sets the current ``opacity`` to show in the widget, where ``opacity`` ranges from 0.0 (transparent) + to 1.0 (opaque). +.. seealso:: opacity() +.. seealso:: opacityChanged() +%End + + signals: + + void opacityChanged( double opacity ); +%Docstring + Emitted when the ``opacity`` is changed in the widget, where ``opacity`` ranges from 0.0 (transparent) + to 1.0 (opaque). +.. seealso:: setOpacity() +.. seealso:: opacity() +%End + +}; + +/************************************************************************ + * This file has been generated automatically from * + * * + * src/gui/qgsopacitywidget.h * + * * + * Do not edit manually ! Edit header and run scripts/sipify.pl again * + ************************************************************************/ diff --git a/src/customwidgets/CMakeLists.txt b/src/customwidgets/CMakeLists.txt index 1669fdd656e..a31f6e92d5d 100644 --- a/src/customwidgets/CMakeLists.txt +++ b/src/customwidgets/CMakeLists.txt @@ -26,6 +26,7 @@ SET (QGIS_CUSTOMWIDGETS_SRCS qgsfilewidgetplugin.cpp qgsfilterlineeditplugin.cpp qgsmaplayercomboboxplugin.cpp + qgsopacitywidgetplugin.cpp qgspasswordlineeditplugin.cpp qgsprojectionselectionwidgetplugin.cpp qgspropertyoverridebuttonplugin.cpp @@ -54,6 +55,7 @@ SET (QGIS_CUSTOMWIDGETS_MOC_HDRS qgsfilewidgetplugin.h qgsfilterlineeditplugin.h qgsmaplayercomboboxplugin.h + qgsopacitywidgetplugin.h qgspasswordlineeditplugin.h qgsprojectionselectionwidgetplugin.h qgspropertyoverridebuttonplugin.h @@ -87,6 +89,7 @@ SET(QGIS_CUSTOMWIDGETS_HDRS qgsfilewidgetplugin.h qgsfilterlineeditplugin.h qgsmaplayercomboboxplugin.h + qgsopacitywidgetplugin.h qgsprojectionselectionwidgetplugin.h qgspropertyoverridebuttonplugin.h qgsrasterbandcomboboxplugin.h diff --git a/src/customwidgets/qgiscustomwidgets.cpp b/src/customwidgets/qgiscustomwidgets.cpp index ea61e4a61c7..926f2a9869b 100644 --- a/src/customwidgets/qgiscustomwidgets.cpp +++ b/src/customwidgets/qgiscustomwidgets.cpp @@ -28,6 +28,7 @@ #include "qgsfilewidgetplugin.h" #include "qgsfilterlineeditplugin.h" #include "qgsmaplayercomboboxplugin.h" +#include "qgsopacitywidgetplugin.h" #include "qgsprojectionselectionwidgetplugin.h" #include "qgspropertyoverridebuttonplugin.h" #include "qgsrasterbandcomboboxplugin.h" @@ -53,6 +54,7 @@ QgisCustomWidgets::QgisCustomWidgets( QObject *parent ) mWidgets.append( new QgsFileWidgetPlugin( this ) ); mWidgets.append( new QgsFilterLineEditPlugin( this ) ); mWidgets.append( new QgsMapLayerComboBoxPlugin( this ) ); + mWidgets.append( new QgsOpacityWidgetPlugin( this ) ); mWidgets.append( new QgsProjectionSelectionWidgetPlugin( this ) ); mWidgets.append( new QgsPropertyOverrideButtonPlugin( this ) ); mWidgets.append( new QgsRasterBandComboBoxPlugin( this ) ); diff --git a/src/customwidgets/qgsopacitywidgetplugin.cpp b/src/customwidgets/qgsopacitywidgetplugin.cpp new file mode 100644 index 00000000000..70786347d83 --- /dev/null +++ b/src/customwidgets/qgsopacitywidgetplugin.cpp @@ -0,0 +1,97 @@ +/*************************************************************************** + qgsopacitywidgetplugin.cpp + ------------------------- + Date : 30.05.2017 + Copyright : (C) 2017 Nyall Dawson + Email : nyall.dawson@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 "qgiscustomwidgets.h" +#include "qgsopacitywidget.h" +#include "qgsopacitywidgetplugin.h" + + +QgsOpacityWidgetPlugin::QgsOpacityWidgetPlugin( QObject *parent ) + : QObject( parent ) + , mInitialized( false ) +{ +} + + +QString QgsOpacityWidgetPlugin::name() const +{ + return "QgsOpacityWidget"; +} + +QString QgsOpacityWidgetPlugin::group() const +{ + return QgisCustomWidgets::groupName(); +} + +QString QgsOpacityWidgetPlugin::includeFile() const +{ + return "qgsopacitywidget.h"; +} + +QIcon QgsOpacityWidgetPlugin::icon() const +{ + return QIcon( ":/images/icons/qgis-icon-60x60.png" ); +} + +bool QgsOpacityWidgetPlugin::isContainer() const +{ + return false; +} + +QWidget *QgsOpacityWidgetPlugin::createWidget( QWidget *parent ) +{ + return new QgsOpacityWidget( parent ); +} + +bool QgsOpacityWidgetPlugin::isInitialized() const +{ + return mInitialized; +} + +void QgsOpacityWidgetPlugin::initialize( QDesignerFormEditorInterface *core ) +{ + Q_UNUSED( core ); + if ( mInitialized ) + return; + mInitialized = true; +} + + +QString QgsOpacityWidgetPlugin::toolTip() const +{ + return tr( "A widget for specifying an opacity value." ); +} + +QString QgsOpacityWidgetPlugin::whatsThis() const +{ + return tr( "A widget for specifying an opacity value." ); +} + +QString QgsOpacityWidgetPlugin::domXml() const +{ + return QString( "\n" + " \n" + " \n" + " \n" + " 0\n" + " 0\n" + " 160\n" + " 27\n" + " \n" + " \n" + " \n" + "\n" ) + .arg( name() ); +} diff --git a/src/customwidgets/qgsopacitywidgetplugin.h b/src/customwidgets/qgsopacitywidgetplugin.h new file mode 100644 index 00000000000..dc1dfbab8f3 --- /dev/null +++ b/src/customwidgets/qgsopacitywidgetplugin.h @@ -0,0 +1,51 @@ +/*************************************************************************** + qgsopacitywidgetplugin.h + ----------------------- + Date : 30.05.2017 + Copyright : (C) 2017 Nyall Dawson + Email : nyall.dawson@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. * +* * +***************************************************************************/ + +#ifndef QGSOPACITYWIDGETPLUGIN_H +#define QGSOPACITYWIDGETPLUGIN_H + + +#include +#include +#include +#include "qgis_customwidgets.h" + + +class CUSTOMWIDGETS_EXPORT QgsOpacityWidgetPlugin : public QObject, public QDesignerCustomWidgetInterface +{ + Q_OBJECT + Q_INTERFACES( QDesignerCustomWidgetInterface ) + + public: + explicit QgsOpacityWidgetPlugin( QObject *parent = 0 ); + + private: + bool mInitialized; + + // QDesignerCustomWidgetInterface interface + public: + QString name() const override; + QString group() const override; + QString includeFile() const override; + QIcon icon() const override; + bool isContainer() const override; + QWidget *createWidget( QWidget *parent ) override; + bool isInitialized() const override; + void initialize( QDesignerFormEditorInterface *core ) override; + QString toolTip() const override; + QString whatsThis() const override; + QString domXml() const override; +}; +#endif // QGSOPACITYWIDGETPLUGIN_H diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt index 75585dc8e55..d4bff41d46a 100755 --- a/src/gui/CMakeLists.txt +++ b/src/gui/CMakeLists.txt @@ -273,6 +273,7 @@ SET(QGIS_GUI_SRCS qgsnewnamedialog.cpp qgsnewvectorlayerdialog.cpp qgsnewgeopackagelayerdialog.cpp + qgsopacitywidget.cpp qgsoptionsdialogbase.cpp qgsorderbydialog.cpp qgsowssourceselect.cpp @@ -421,6 +422,7 @@ SET(QGIS_GUI_MOC_HDRS qgsnewnamedialog.h qgsnewvectorlayerdialog.h qgsnewgeopackagelayerdialog.h + qgsopacitywidget.h qgsoptionsdialogbase.h qgsoptionswidgetfactory.h qgsorderbydialog.h diff --git a/src/gui/qgsopacitywidget.cpp b/src/gui/qgsopacitywidget.cpp new file mode 100644 index 00000000000..bd38fae40b9 --- /dev/null +++ b/src/gui/qgsopacitywidget.cpp @@ -0,0 +1,69 @@ +/*************************************************************************** + qgsopacitywidget.cpp + ------------------- + Date : May 2017 + Copyright : (C) 2017 Nyall Dawson + Email : nyall.dawson@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 "qgsopacitywidget.h" +#include "qgsdoublespinbox.h" +#include +#include + +QgsOpacityWidget::QgsOpacityWidget( QWidget *parent ) + : QWidget( parent ) +{ + QHBoxLayout *layout = new QHBoxLayout(); + layout->setContentsMargins( 0, 0, 0, 0 ); + layout->setSpacing( 0 ); + setLayout( layout ); + + mSlider = new QSlider(); + mSlider->setMinimum( 0 ); + mSlider->setMaximum( 1000 ); + mSlider->setSingleStep( 10 ); + mSlider->setPageStep( 100 ); + mSlider->setValue( 1000 ); + mSlider->setOrientation( Qt::Horizontal ); + layout->addWidget( mSlider, 1 ); + + mSpinBox = new QgsDoubleSpinBox(); + mSpinBox->setMinimum( 0.0 ); + mSpinBox->setMaximum( 100.0 ); + mSpinBox->setValue( 100.0 ); + mSpinBox->setClearValue( 100.0 ); + mSpinBox->setMinimumSize( QSize( 100, 0 ) ); + mSpinBox->setDecimals( 1 ); + mSpinBox->setSuffix( tr( " %" ) ); + layout->addWidget( mSpinBox, 0 ); + + setFocusProxy( mSpinBox ); + + connect( mSlider, &QSlider::valueChanged, this, [ = ]( int value ) { mSpinBox->setValue( value / 10.0 ); } ); + connect( mSpinBox, static_cast < void ( QgsDoubleSpinBox::* )( double ) > ( &QgsDoubleSpinBox::valueChanged ), this, [ = ]( double value ) { whileBlocking( mSlider )->setValue( value * 10 ); } ); + connect( mSpinBox, static_cast < void ( QgsDoubleSpinBox::* )( double ) > ( &QgsDoubleSpinBox::valueChanged ), this, &QgsOpacityWidget::spinChanged ); +} + +double QgsOpacityWidget::opacity() const +{ + return mSpinBox->value() / 100.0; +} + +void QgsOpacityWidget::setOpacity( double opacity ) +{ + mSpinBox->setValue( opacity * 100.0 ); +} + +void QgsOpacityWidget::spinChanged( double value ) +{ + emit opacityChanged( value / 100.0 ); +} + diff --git a/src/gui/qgsopacitywidget.h b/src/gui/qgsopacitywidget.h new file mode 100644 index 00000000000..10739177fd4 --- /dev/null +++ b/src/gui/qgsopacitywidget.h @@ -0,0 +1,83 @@ +/*************************************************************************** + qgsopacitywidget.h + ----------------- + Date : May 2017 + Copyright : (C) 2017 Nyall Dawson + Email : nyall.dawson@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. * + * * + ***************************************************************************/ + +#ifndef QGSOPACITYWIDGET_H +#define QGSOPACITYWIDGET_H + +#include +#include "qgis.h" +#include "qgis_gui.h" + +class QgsDoubleSpinBox; +class QSlider; + +/** + * \class QgsOpacityWidget + * \ingroup gui + * \brief A widget for setting an opacity value. + * \since QGIS 3.0 + */ +class GUI_EXPORT QgsOpacityWidget : public QWidget +{ + Q_OBJECT + Q_PROPERTY( double opacity READ opacity WRITE setOpacity NOTIFY opacityChanged ) + + public: + + /** + * Constructor for QgsOpacityWidget. + */ + explicit QgsOpacityWidget( QWidget *parent SIP_TRANSFERTHIS = nullptr ); + + /** + * Returns the current opacity selected in the widget, where opacity ranges from 0.0 (transparent) + * to 1.0 (opaque). + * \see setOpacity() + * \see opacityChanged() + */ + double opacity() const; + + public slots: + + /** + * Sets the current \a opacity to show in the widget, where \a opacity ranges from 0.0 (transparent) + * to 1.0 (opaque). + * \see opacity() + * \see opacityChanged() + */ + void setOpacity( double opacity ); + + signals: + + /** + * Emitted when the \a opacity is changed in the widget, where \a opacity ranges from 0.0 (transparent) + * to 1.0 (opaque). + * \see setOpacity() + * \see opacity() + */ + void opacityChanged( double opacity ); + + private slots: + + void spinChanged( double value ); + + private: + + QgsDoubleSpinBox *mSpinBox = nullptr; + QSlider *mSlider = nullptr; + +}; + +#endif // QGSOPACITYWIDGET_H diff --git a/tests/src/python/CMakeLists.txt b/tests/src/python/CMakeLists.txt index 58f427b7dd8..17d2afe9fc2 100755 --- a/tests/src/python/CMakeLists.txt +++ b/tests/src/python/CMakeLists.txt @@ -91,6 +91,7 @@ ADD_PYTHON_TEST(PyQgsNewGeoPackageLayerDialog test_qgsnewgeopackagelayerdialog.p ADD_PYTHON_TEST(PyQgsNoApplication test_qgsnoapplication.py) ADD_PYTHON_TEST(PyQgsOGRProviderGpkg test_provider_ogr_gpkg.py) ADD_PYTHON_TEST(PyQgsOGRProviderSqlite test_provider_ogr_sqlite.py) +ADD_PYTHON_TEST(PyQgsOpacityWidget test_qgsopacitywidget.py) ADD_PYTHON_TEST(PyQgsOptional test_qgsoptional.py) ADD_PYTHON_TEST(PyQgsPalLabelingBase test_qgspallabeling_base.py) ADD_PYTHON_TEST(PyQgsPalLabelingCanvas test_qgspallabeling_canvas.py) diff --git a/tests/src/python/test_qgsopacitywidget.py b/tests/src/python/test_qgsopacitywidget.py new file mode 100644 index 00000000000..9f97a9a8879 --- /dev/null +++ b/tests/src/python/test_qgsopacitywidget.py @@ -0,0 +1,58 @@ +# -*- coding: utf-8 -*- +"""QGIS Unit tests for QgsOpacityWidget + +.. note:: 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. +""" +__author__ = 'Nyall Dawson' +__date__ = '30/05/2017' +__copyright__ = 'Copyright 2017, The QGIS Project' +# This will get replaced with a git SHA1 when you do a git archive +__revision__ = '$Format:%H$' + +import qgis # NOQA + +from qgis.gui import QgsOpacityWidget + +from qgis.PyQt.QtTest import QSignalSpy +from qgis.testing import start_app, unittest + +start_app() + + +class TestQgsOpacityWidget(unittest.TestCase): + + def testGettersSetters(self): + """ test widget getters/setters """ + w = qgis.gui.QgsOpacityWidget() + + w.setOpacity(0.2) + self.assertEqual(w.opacity(), 0.2) + + # bad values + w.setOpacity(-0.2) + self.assertEqual(w.opacity(), 0.0) + w.setOpacity(100) + self.assertEqual(w.opacity(), 1.0) + + def test_ChangedSignals(self): + """ test that signals are correctly emitted when setting opacity""" + + w = qgis.gui.QgsOpacityWidget() + + spy = QSignalSpy(w.opacityChanged) + w.setOpacity(0.2) + + self.assertEqual(len(spy), 1) + self.assertEqual(spy[0][0], 0.2) + + # bad value + w.setOpacity(100) + self.assertEqual(len(spy), 2) + self.assertEqual(spy[1][0], 1.0) + + +if __name__ == '__main__': + unittest.main()