Skip to content

Commit 22c4740

Browse files
committedJul 24, 2017
[FEATURE] New standard widget for symbol buttons
Button widgets for configuring symbol properties were reimplemented multiple times throughout the codebase. This commit creates a new standard QgsSymbolButton widget which should be used whenever a button for configuring symbol properties is required. Features include: - automatic use of inline panels whenever possible - dropdown menu with shortcuts to color settings, copy/pasting colors - accepts drag and dropped colors to set symbol color
1 parent 9a12249 commit 22c4740

File tree

7 files changed

+904
-0
lines changed

7 files changed

+904
-0
lines changed
 

‎python/gui/gui_auto.sip

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,7 @@
188188
%Include qgsstatusbar.sip
189189
%Include qgssublayersdialog.sip
190190
%Include qgssubstitutionlistwidget.sip
191+
%Include qgssymbolbutton.sip
191192
%Include qgstablewidgetbase.sip
192193
%Include qgstabwidget.sip
193194
%Include qgstaskmanagerwidget.sip

‎python/gui/qgssymbolbutton.sip

Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
/************************************************************************
2+
* This file has been generated automatically from *
3+
* *
4+
* src/gui/qgssymbolbutton.h *
5+
* *
6+
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
7+
************************************************************************/
8+
9+
10+
11+
class QgsSymbolButton : QToolButton
12+
{
13+
%Docstring
14+
A button for creating and modifying QgsSymbol settings.
15+
16+
The button shows a preview icon for the current symbol, and will open a detailed symbol editor dialog (or
17+
panel widget) when clicked.
18+
19+
.. versionadded:: 3.0
20+
%End
21+
22+
%TypeHeaderCode
23+
#include "qgssymbolbutton.h"
24+
%End
25+
public:
26+
27+
QgsSymbolButton( QWidget *parent /TransferThis/ = 0, const QString &dialogTitle = QString() );
28+
%Docstring
29+
Construct a new symbol button.
30+
Use ``dialogTitle`` string to define the title to show in the symbol settings dialog.
31+
%End
32+
33+
virtual QSize minimumSizeHint() const;
34+
35+
void setDialogTitle( const QString &title );
36+
%Docstring
37+
Sets the ``title`` for the symbol settings dialog window.
38+
.. seealso:: dialogTitle()
39+
%End
40+
41+
QString dialogTitle() const;
42+
%Docstring
43+
Returns the title for the symbol settings dialog window.
44+
.. seealso:: setDialogTitle()
45+
:rtype: str
46+
%End
47+
48+
QgsSymbol *symbol();
49+
%Docstring
50+
Returns the current symbol defined by the button.
51+
.. seealso:: setSymbol()
52+
.. seealso:: changed()
53+
:rtype: QgsSymbol
54+
%End
55+
56+
QgsMapCanvas *mapCanvas() const;
57+
%Docstring
58+
Returns the map canvas associated with the widget.
59+
.. seealso:: setMapCanvas()
60+
:rtype: QgsMapCanvas
61+
%End
62+
63+
void setMapCanvas( QgsMapCanvas *canvas );
64+
%Docstring
65+
Sets a map ``canvas`` to associate with the widget. This allows the
66+
widget to fetch current settings from the map canvas, such as current scale.
67+
.. seealso:: mapCanvas()
68+
%End
69+
70+
QgsVectorLayer *layer() const;
71+
%Docstring
72+
Returns the layer associated with the widget.
73+
.. seealso:: setLayer()
74+
:rtype: QgsVectorLayer
75+
%End
76+
77+
void setLayer( QgsVectorLayer *layer );
78+
%Docstring
79+
Sets a ``layer`` to associate with the widget. This allows the
80+
widget to setup layer related settings within the symbol settings dialog,
81+
such as correctly populating data defined override buttons.
82+
.. seealso:: layer()
83+
%End
84+
85+
void registerExpressionContextGenerator( QgsExpressionContextGenerator *generator );
86+
%Docstring
87+
Register an expression context generator class that will be used to retrieve
88+
an expression context for the button when required.
89+
%End
90+
91+
public slots:
92+
93+
void setSymbol( QgsSymbol *symbol /Transfer/ );
94+
%Docstring
95+
Sets the ``symbol`` for the button. Ownership of ``symbol`` is transferred to the
96+
button.
97+
.. seealso:: symbol()
98+
.. seealso:: changed()
99+
%End
100+
101+
void setColor( const QColor &color );
102+
%Docstring
103+
Sets the current ``color`` for the symbol. Will emit a changed() signal if the color is different
104+
to the previous symbol color.
105+
%End
106+
107+
void copyColor();
108+
%Docstring
109+
Copies the current symbol color to the clipboard.
110+
.. seealso:: pasteColor()
111+
%End
112+
113+
void pasteColor();
114+
%Docstring
115+
Pastes a color from the clipboard to the symbol. If clipboard does not contain a valid
116+
color or string representation of a color, then no change is applied.
117+
.. seealso:: copyColor()
118+
%End
119+
120+
signals:
121+
122+
void changed();
123+
%Docstring
124+
Emitted when the symbol's settings are changed.
125+
.. seealso:: symbol()
126+
.. seealso:: setSymbol()
127+
%End
128+
129+
protected:
130+
131+
virtual void changeEvent( QEvent *e );
132+
133+
virtual void showEvent( QShowEvent *e );
134+
135+
virtual void resizeEvent( QResizeEvent *event );
136+
137+
138+
virtual void mousePressEvent( QMouseEvent *e );
139+
140+
virtual void mouseMoveEvent( QMouseEvent *e );
141+
142+
virtual void dragEnterEvent( QDragEnterEvent *e );
143+
144+
145+
virtual void dragLeaveEvent( QDragLeaveEvent *e );
146+
147+
148+
virtual void dropEvent( QDropEvent *e );
149+
150+
151+
};
152+
153+
/************************************************************************
154+
* This file has been generated automatically from *
155+
* *
156+
* src/gui/qgssymbolbutton.h *
157+
* *
158+
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
159+
************************************************************************/

‎src/gui/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -336,6 +336,7 @@ SET(QGIS_GUI_SRCS
336336
qgssubstitutionlistwidget.cpp
337337
qgssqlcomposerdialog.cpp
338338
qgsstatusbar.cpp
339+
qgssymbolbutton.cpp
339340
qgstablewidgetbase.cpp
340341
qgstabwidget.cpp
341342
qgstablewidgetitem.cpp
@@ -492,6 +493,7 @@ SET(QGIS_GUI_MOC_HDRS
492493
qgsstatusbar.h
493494
qgssublayersdialog.h
494495
qgssubstitutionlistwidget.h
496+
qgssymbolbutton.h
495497
qgstablewidgetbase.h
496498
qgstabwidget.h
497499
qgstaskmanagerwidget.h

‎src/gui/qgssymbolbutton.cpp

Lines changed: 446 additions & 0 deletions
Large diffs are not rendered by default.

‎src/gui/qgssymbolbutton.h

Lines changed: 218 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,218 @@
1+
/***************************************************************************
2+
qgssymbolbutton.h
3+
-----------------
4+
Date : July 2017
5+
Copyright : (C) 2017 by Nyall Dawson
6+
Email : nyall dot dawson at gmail dot com
7+
***************************************************************************
8+
* *
9+
* This program is free software; you can redistribute it and/or modify *
10+
* it under the terms of the GNU General Public License as published by *
11+
* the Free Software Foundation; either version 2 of the License, or *
12+
* (at your option) any later version. *
13+
* *
14+
***************************************************************************/
15+
#ifndef QGSSYMBOLBUTTON_H
16+
#define QGSSYMBOLBUTTON_H
17+
18+
#include "qgis_gui.h"
19+
#include "qgis.h"
20+
#include "qgssymbol.h"
21+
#include <QToolButton>
22+
#include <QPointer>
23+
#include <memory>
24+
25+
class QgsMapCanvas;
26+
class QgsVectorLayer;
27+
class QgsExpressionContextGenerator;
28+
class QgsPanelWidget;
29+
30+
/**
31+
* \ingroup gui
32+
* \class QgsSymbolButton
33+
* A button for creating and modifying QgsSymbol settings.
34+
*
35+
* The button shows a preview icon for the current symbol, and will open a detailed symbol editor dialog (or
36+
* panel widget) when clicked.
37+
*
38+
* \since QGIS 3.0
39+
*/
40+
class GUI_EXPORT QgsSymbolButton : public QToolButton
41+
{
42+
Q_OBJECT
43+
44+
Q_PROPERTY( QString dialogTitle READ dialogTitle WRITE setDialogTitle )
45+
46+
public:
47+
48+
/**
49+
* Construct a new symbol button.
50+
* Use \a dialogTitle string to define the title to show in the symbol settings dialog.
51+
*/
52+
QgsSymbolButton( QWidget *parent SIP_TRANSFERTHIS = nullptr, const QString &dialogTitle = QString() );
53+
54+
virtual QSize minimumSizeHint() const override;
55+
56+
/**
57+
* Sets the \a title for the symbol settings dialog window.
58+
* \see dialogTitle()
59+
*/
60+
void setDialogTitle( const QString &title );
61+
62+
/**
63+
* Returns the title for the symbol settings dialog window.
64+
* \see setDialogTitle()
65+
*/
66+
QString dialogTitle() const;
67+
68+
/**
69+
* Returns the current symbol defined by the button.
70+
* \see setSymbol()
71+
* \see changed()
72+
*/
73+
QgsSymbol *symbol();
74+
75+
/**
76+
* Returns the map canvas associated with the widget.
77+
* \see setMapCanvas()
78+
*/
79+
QgsMapCanvas *mapCanvas() const;
80+
81+
/**
82+
* Sets a map \a canvas to associate with the widget. This allows the
83+
* widget to fetch current settings from the map canvas, such as current scale.
84+
* \see mapCanvas()
85+
*/
86+
void setMapCanvas( QgsMapCanvas *canvas );
87+
88+
/**
89+
* Returns the layer associated with the widget.
90+
* \see setLayer()
91+
*/
92+
QgsVectorLayer *layer() const;
93+
94+
/**
95+
* Sets a \a layer to associate with the widget. This allows the
96+
* widget to setup layer related settings within the symbol settings dialog,
97+
* such as correctly populating data defined override buttons.
98+
* \see layer()
99+
*/
100+
void setLayer( QgsVectorLayer *layer );
101+
102+
/**
103+
* Register an expression context generator class that will be used to retrieve
104+
* an expression context for the button when required.
105+
*/
106+
void registerExpressionContextGenerator( QgsExpressionContextGenerator *generator );
107+
108+
public slots:
109+
110+
/**
111+
* Sets the \a symbol for the button. Ownership of \a symbol is transferred to the
112+
* button.
113+
* \see symbol()
114+
* \see changed()
115+
*/
116+
void setSymbol( QgsSymbol *symbol SIP_TRANSFER );
117+
118+
/**
119+
* Sets the current \a color for the symbol. Will emit a changed() signal if the color is different
120+
* to the previous symbol color.
121+
*/
122+
void setColor( const QColor &color );
123+
124+
/**
125+
* Copies the current symbol color to the clipboard.
126+
* \see pasteColor()
127+
*/
128+
void copyColor();
129+
130+
/**
131+
* Pastes a color from the clipboard to the symbol. If clipboard does not contain a valid
132+
* color or string representation of a color, then no change is applied.
133+
* \see copyColor()
134+
*/
135+
void pasteColor();
136+
137+
signals:
138+
139+
/**
140+
* Emitted when the symbol's settings are changed.
141+
* \see symbol()
142+
* \see setSymbol()
143+
*/
144+
void changed();
145+
146+
protected:
147+
148+
void changeEvent( QEvent *e ) override;
149+
void showEvent( QShowEvent *e ) override;
150+
void resizeEvent( QResizeEvent *event ) override;
151+
152+
// Reimplemented to detect right mouse button clicks on the color button and allow dragging colors
153+
void mousePressEvent( QMouseEvent *e ) override;
154+
// Reimplemented to allow dragging colors/symbols from button
155+
void mouseMoveEvent( QMouseEvent *e ) override;
156+
// Reimplemented to accept dragged colors
157+
void dragEnterEvent( QDragEnterEvent *e ) override;
158+
159+
// Reimplemented to reset button appearance after drag leave
160+
void dragLeaveEvent( QDragLeaveEvent *e ) override;
161+
162+
// Reimplemented to accept dropped colors
163+
void dropEvent( QDropEvent *e ) override;
164+
165+
private slots:
166+
167+
void showSettingsDialog();
168+
void updateSymbolFromWidget();
169+
void cleanUpSymbolSelector( QgsPanelWidget *container );
170+
171+
/** Creates the drop-down menu entries
172+
*/
173+
void prepareMenu();
174+
175+
void addRecentColor( const QColor &color );
176+
177+
private:
178+
179+
QString mDialogTitle;
180+
181+
QgsMapCanvas *mMapCanvas = nullptr;
182+
183+
QPoint mDragStartPosition;
184+
185+
QMenu *mMenu = nullptr;
186+
187+
QPointer< QgsVectorLayer > mLayer;
188+
189+
QSize mIconSize;
190+
191+
std::unique_ptr< QgsSymbol > mSymbol;
192+
193+
QgsExpressionContextGenerator *mExpressionContextGenerator = nullptr;
194+
195+
/**
196+
* Regenerates the text preview. If \a color is specified, a temporary color preview
197+
* is shown instead.
198+
*/
199+
void updatePreview( const QColor &color = QColor(), QgsSymbol *tempSymbol = nullptr );
200+
201+
/** Attempts to parse mimeData as a color, either via the mime data's color data or by
202+
* parsing a textual representation of a color.
203+
* \returns true if mime data could be intrepreted as a color
204+
* \param mimeData mime data
205+
* \param resultColor QColor to store evaluated color
206+
* \param hasAlpha will be set to true if mime data also included an alpha component
207+
* \see formatFromMimeData
208+
*/
209+
bool colorFromMimeData( const QMimeData *mimeData, QColor &resultColor, bool &hasAlpha );
210+
211+
/**
212+
* Create a \a color icon for display in the drop-down menu.
213+
*/
214+
QPixmap createColorIcon( const QColor &color ) const;
215+
216+
};
217+
218+
#endif // QGSSYMBOLBUTTON_H

‎tests/src/python/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,7 @@ ADD_PYTHON_TEST(PyQgsRenderer test_qgsrenderer.py)
126126
ADD_PYTHON_TEST(PyQgsRulebasedRenderer test_qgsrulebasedrenderer.py)
127127
ADD_PYTHON_TEST(PyQgsSingleSymbolRenderer test_qgssinglesymbolrenderer.py)
128128
ADD_PYTHON_TEST(PyQgsShapefileProvider test_provider_shapefile.py)
129+
ADD_PYTHON_TEST(PyQgsSymbolButton test_qgssymbolbutton.py)
129130
ADD_PYTHON_TEST(PyQgsTabfileProvider test_provider_tabfile.py)
130131
ADD_PYTHON_TEST(PyQgsTabWidget test_qgstabwidget.py)
131132
ADD_PYTHON_TEST(PyQgsTextRenderer test_qgstextrenderer.py)
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
# -*- coding: utf-8 -*-
2+
"""QGIS Unit tests for QgsSymbolButton.
3+
4+
.. note:: This program is free software; you can redistribute it and/or modify
5+
it under the terms of the GNU General Public License as published by
6+
the Free Software Foundation; either version 2 of the License, or
7+
(at your option) any later version.
8+
"""
9+
__author__ = 'Nyall Dawson'
10+
__date__ = '23/07/2017'
11+
__copyright__ = 'Copyright 2017, The QGIS Project'
12+
# This will get replaced with a git SHA1 when you do a git archive
13+
__revision__ = '$Format:%H$'
14+
15+
import qgis # NOQA
16+
17+
from qgis.core import QgsFillSymbol, QgsMarkerSymbol
18+
from qgis.gui import QgsSymbolButton, QgsMapCanvas
19+
from qgis.testing import start_app, unittest
20+
from qgis.PyQt.QtGui import QColor, QFont
21+
from qgis.PyQt.QtTest import QSignalSpy
22+
from utilities import getTestFont
23+
24+
start_app()
25+
26+
27+
class TestQgsSymbolButton(unittest.TestCase):
28+
29+
def testGettersSetters(self):
30+
button = QgsSymbolButton()
31+
canvas = QgsMapCanvas()
32+
33+
button.setDialogTitle('test title')
34+
self.assertEqual(button.dialogTitle(), 'test title')
35+
36+
button.setMapCanvas(canvas)
37+
self.assertEqual(button.mapCanvas(), canvas)
38+
39+
def testSetGetSymbol(self):
40+
button = QgsSymbolButton()
41+
symbol = QgsMarkerSymbol.createSimple({})
42+
symbol.setColor(QColor(255, 0, 0))
43+
44+
signal_spy = QSignalSpy(button.changed)
45+
button.setSymbol(symbol)
46+
self.assertEqual(len(signal_spy), 1)
47+
48+
r = button.symbol()
49+
self.assertEqual(r.color(), QColor(255, 0, 0))
50+
51+
def testSetColor(self):
52+
button = QgsSymbolButton()
53+
54+
symbol = QgsMarkerSymbol.createSimple({})
55+
symbol.setColor(QColor(255, 255, 0))
56+
57+
button.setSymbol(symbol)
58+
59+
signal_spy = QSignalSpy(button.changed)
60+
button.setColor(QColor(0, 255, 0))
61+
self.assertEqual(len(signal_spy), 1)
62+
63+
r = button.symbol()
64+
self.assertEqual(r.color().name(), QColor(0, 255, 0).name())
65+
66+
# set same color, should not emit signal
67+
button.setColor(QColor(0, 255, 0))
68+
self.assertEqual(len(signal_spy), 1)
69+
70+
# color with transparency - should be stripped
71+
button.setColor(QColor(0, 255, 0, 100))
72+
r = button.symbol()
73+
self.assertEqual(r.color(), QColor(0, 255, 0))
74+
75+
76+
if __name__ == '__main__':
77+
unittest.main()

0 commit comments

Comments
 (0)
Please sign in to comment.