mirror of
https://github.com/qgis/QGIS.git
synced 2025-04-16 00:03:12 -04:00
[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
This commit is contained in:
parent
9a12249b02
commit
22c4740f63
@ -188,6 +188,7 @@
|
|||||||
%Include qgsstatusbar.sip
|
%Include qgsstatusbar.sip
|
||||||
%Include qgssublayersdialog.sip
|
%Include qgssublayersdialog.sip
|
||||||
%Include qgssubstitutionlistwidget.sip
|
%Include qgssubstitutionlistwidget.sip
|
||||||
|
%Include qgssymbolbutton.sip
|
||||||
%Include qgstablewidgetbase.sip
|
%Include qgstablewidgetbase.sip
|
||||||
%Include qgstabwidget.sip
|
%Include qgstabwidget.sip
|
||||||
%Include qgstaskmanagerwidget.sip
|
%Include qgstaskmanagerwidget.sip
|
||||||
|
159
python/gui/qgssymbolbutton.sip
Normal file
159
python/gui/qgssymbolbutton.sip
Normal file
@ -0,0 +1,159 @@
|
|||||||
|
/************************************************************************
|
||||||
|
* This file has been generated automatically from *
|
||||||
|
* *
|
||||||
|
* src/gui/qgssymbolbutton.h *
|
||||||
|
* *
|
||||||
|
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
|
||||||
|
************************************************************************/
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class QgsSymbolButton : QToolButton
|
||||||
|
{
|
||||||
|
%Docstring
|
||||||
|
A button for creating and modifying QgsSymbol settings.
|
||||||
|
|
||||||
|
The button shows a preview icon for the current symbol, and will open a detailed symbol editor dialog (or
|
||||||
|
panel widget) when clicked.
|
||||||
|
|
||||||
|
.. versionadded:: 3.0
|
||||||
|
%End
|
||||||
|
|
||||||
|
%TypeHeaderCode
|
||||||
|
#include "qgssymbolbutton.h"
|
||||||
|
%End
|
||||||
|
public:
|
||||||
|
|
||||||
|
QgsSymbolButton( QWidget *parent /TransferThis/ = 0, const QString &dialogTitle = QString() );
|
||||||
|
%Docstring
|
||||||
|
Construct a new symbol button.
|
||||||
|
Use ``dialogTitle`` string to define the title to show in the symbol settings dialog.
|
||||||
|
%End
|
||||||
|
|
||||||
|
virtual QSize minimumSizeHint() const;
|
||||||
|
|
||||||
|
void setDialogTitle( const QString &title );
|
||||||
|
%Docstring
|
||||||
|
Sets the ``title`` for the symbol settings dialog window.
|
||||||
|
.. seealso:: dialogTitle()
|
||||||
|
%End
|
||||||
|
|
||||||
|
QString dialogTitle() const;
|
||||||
|
%Docstring
|
||||||
|
Returns the title for the symbol settings dialog window.
|
||||||
|
.. seealso:: setDialogTitle()
|
||||||
|
:rtype: str
|
||||||
|
%End
|
||||||
|
|
||||||
|
QgsSymbol *symbol();
|
||||||
|
%Docstring
|
||||||
|
Returns the current symbol defined by the button.
|
||||||
|
.. seealso:: setSymbol()
|
||||||
|
.. seealso:: changed()
|
||||||
|
:rtype: QgsSymbol
|
||||||
|
%End
|
||||||
|
|
||||||
|
QgsMapCanvas *mapCanvas() const;
|
||||||
|
%Docstring
|
||||||
|
Returns the map canvas associated with the widget.
|
||||||
|
.. seealso:: setMapCanvas()
|
||||||
|
:rtype: QgsMapCanvas
|
||||||
|
%End
|
||||||
|
|
||||||
|
void setMapCanvas( QgsMapCanvas *canvas );
|
||||||
|
%Docstring
|
||||||
|
Sets a map ``canvas`` to associate with the widget. This allows the
|
||||||
|
widget to fetch current settings from the map canvas, such as current scale.
|
||||||
|
.. seealso:: mapCanvas()
|
||||||
|
%End
|
||||||
|
|
||||||
|
QgsVectorLayer *layer() const;
|
||||||
|
%Docstring
|
||||||
|
Returns the layer associated with the widget.
|
||||||
|
.. seealso:: setLayer()
|
||||||
|
:rtype: QgsVectorLayer
|
||||||
|
%End
|
||||||
|
|
||||||
|
void setLayer( QgsVectorLayer *layer );
|
||||||
|
%Docstring
|
||||||
|
Sets a ``layer`` to associate with the widget. This allows the
|
||||||
|
widget to setup layer related settings within the symbol settings dialog,
|
||||||
|
such as correctly populating data defined override buttons.
|
||||||
|
.. seealso:: layer()
|
||||||
|
%End
|
||||||
|
|
||||||
|
void registerExpressionContextGenerator( QgsExpressionContextGenerator *generator );
|
||||||
|
%Docstring
|
||||||
|
Register an expression context generator class that will be used to retrieve
|
||||||
|
an expression context for the button when required.
|
||||||
|
%End
|
||||||
|
|
||||||
|
public slots:
|
||||||
|
|
||||||
|
void setSymbol( QgsSymbol *symbol /Transfer/ );
|
||||||
|
%Docstring
|
||||||
|
Sets the ``symbol`` for the button. Ownership of ``symbol`` is transferred to the
|
||||||
|
button.
|
||||||
|
.. seealso:: symbol()
|
||||||
|
.. seealso:: changed()
|
||||||
|
%End
|
||||||
|
|
||||||
|
void setColor( const QColor &color );
|
||||||
|
%Docstring
|
||||||
|
Sets the current ``color`` for the symbol. Will emit a changed() signal if the color is different
|
||||||
|
to the previous symbol color.
|
||||||
|
%End
|
||||||
|
|
||||||
|
void copyColor();
|
||||||
|
%Docstring
|
||||||
|
Copies the current symbol color to the clipboard.
|
||||||
|
.. seealso:: pasteColor()
|
||||||
|
%End
|
||||||
|
|
||||||
|
void pasteColor();
|
||||||
|
%Docstring
|
||||||
|
Pastes a color from the clipboard to the symbol. If clipboard does not contain a valid
|
||||||
|
color or string representation of a color, then no change is applied.
|
||||||
|
.. seealso:: copyColor()
|
||||||
|
%End
|
||||||
|
|
||||||
|
signals:
|
||||||
|
|
||||||
|
void changed();
|
||||||
|
%Docstring
|
||||||
|
Emitted when the symbol's settings are changed.
|
||||||
|
.. seealso:: symbol()
|
||||||
|
.. seealso:: setSymbol()
|
||||||
|
%End
|
||||||
|
|
||||||
|
protected:
|
||||||
|
|
||||||
|
virtual void changeEvent( QEvent *e );
|
||||||
|
|
||||||
|
virtual void showEvent( QShowEvent *e );
|
||||||
|
|
||||||
|
virtual void resizeEvent( QResizeEvent *event );
|
||||||
|
|
||||||
|
|
||||||
|
virtual void mousePressEvent( QMouseEvent *e );
|
||||||
|
|
||||||
|
virtual void mouseMoveEvent( QMouseEvent *e );
|
||||||
|
|
||||||
|
virtual void dragEnterEvent( QDragEnterEvent *e );
|
||||||
|
|
||||||
|
|
||||||
|
virtual void dragLeaveEvent( QDragLeaveEvent *e );
|
||||||
|
|
||||||
|
|
||||||
|
virtual void dropEvent( QDropEvent *e );
|
||||||
|
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
/************************************************************************
|
||||||
|
* This file has been generated automatically from *
|
||||||
|
* *
|
||||||
|
* src/gui/qgssymbolbutton.h *
|
||||||
|
* *
|
||||||
|
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
|
||||||
|
************************************************************************/
|
@ -336,6 +336,7 @@ SET(QGIS_GUI_SRCS
|
|||||||
qgssubstitutionlistwidget.cpp
|
qgssubstitutionlistwidget.cpp
|
||||||
qgssqlcomposerdialog.cpp
|
qgssqlcomposerdialog.cpp
|
||||||
qgsstatusbar.cpp
|
qgsstatusbar.cpp
|
||||||
|
qgssymbolbutton.cpp
|
||||||
qgstablewidgetbase.cpp
|
qgstablewidgetbase.cpp
|
||||||
qgstabwidget.cpp
|
qgstabwidget.cpp
|
||||||
qgstablewidgetitem.cpp
|
qgstablewidgetitem.cpp
|
||||||
@ -492,6 +493,7 @@ SET(QGIS_GUI_MOC_HDRS
|
|||||||
qgsstatusbar.h
|
qgsstatusbar.h
|
||||||
qgssublayersdialog.h
|
qgssublayersdialog.h
|
||||||
qgssubstitutionlistwidget.h
|
qgssubstitutionlistwidget.h
|
||||||
|
qgssymbolbutton.h
|
||||||
qgstablewidgetbase.h
|
qgstablewidgetbase.h
|
||||||
qgstabwidget.h
|
qgstabwidget.h
|
||||||
qgstaskmanagerwidget.h
|
qgstaskmanagerwidget.h
|
||||||
|
446
src/gui/qgssymbolbutton.cpp
Normal file
446
src/gui/qgssymbolbutton.cpp
Normal file
@ -0,0 +1,446 @@
|
|||||||
|
/***************************************************************************
|
||||||
|
qgssymbolbutton.h
|
||||||
|
-----------------
|
||||||
|
Date : July 2017
|
||||||
|
Copyright : (C) 2017 by Nyall Dawson
|
||||||
|
Email : nyall dot dawson at gmail dot 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 "qgssymbolbutton.h"
|
||||||
|
#include "qgspanelwidget.h"
|
||||||
|
#include "qgsexpressioncontext.h"
|
||||||
|
#include "qgsexpressioncontextgenerator.h"
|
||||||
|
#include "qgsvectorlayer.h"
|
||||||
|
#include "qgssymbolselectordialog.h"
|
||||||
|
#include "qgsstyle.h"
|
||||||
|
#include "qgscolorwidgets.h"
|
||||||
|
#include "qgscolorschemeregistry.h"
|
||||||
|
#include "qgscolorswatchgrid.h"
|
||||||
|
#include "qgssymbollayerutils.h"
|
||||||
|
#include <QMenu>
|
||||||
|
#include <QClipboard>
|
||||||
|
#include <QDrag>
|
||||||
|
|
||||||
|
QgsSymbolButton::QgsSymbolButton( QWidget *parent, const QString &dialogTitle )
|
||||||
|
: QToolButton( parent )
|
||||||
|
, mDialogTitle( dialogTitle.isEmpty() ? tr( "Symbol Settings" ) : dialogTitle )
|
||||||
|
{
|
||||||
|
mSymbol.reset( QgsFillSymbol::createSimple( QgsStringMap() ) );
|
||||||
|
|
||||||
|
setAcceptDrops( true );
|
||||||
|
connect( this, &QAbstractButton::clicked, this, &QgsSymbolButton::showSettingsDialog );
|
||||||
|
|
||||||
|
//setup dropdown menu
|
||||||
|
mMenu = new QMenu( this );
|
||||||
|
connect( mMenu, &QMenu::aboutToShow, this, &QgsSymbolButton::prepareMenu );
|
||||||
|
setMenu( mMenu );
|
||||||
|
setPopupMode( QToolButton::MenuButtonPopup );
|
||||||
|
}
|
||||||
|
|
||||||
|
QSize QgsSymbolButton::minimumSizeHint() const
|
||||||
|
{
|
||||||
|
//make sure height of button looks good under different platforms
|
||||||
|
QSize size = QToolButton::minimumSizeHint();
|
||||||
|
int fontHeight = fontMetrics().height() * 1.4;
|
||||||
|
return QSize( size.width(), qMax( size.height(), fontHeight ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
void QgsSymbolButton::showSettingsDialog()
|
||||||
|
{
|
||||||
|
QgsExpressionContext context;
|
||||||
|
if ( mExpressionContextGenerator )
|
||||||
|
context = mExpressionContextGenerator->createExpressionContext();
|
||||||
|
else
|
||||||
|
{
|
||||||
|
context.appendScopes( QgsExpressionContextUtils::globalProjectLayerScopes( mLayer.data() ) );
|
||||||
|
}
|
||||||
|
QgsSymbol *newSymbol = mSymbol->clone();
|
||||||
|
|
||||||
|
QgsSymbolWidgetContext symbolContext;
|
||||||
|
symbolContext.setExpressionContext( &context );
|
||||||
|
symbolContext.setMapCanvas( mMapCanvas );
|
||||||
|
|
||||||
|
QgsPanelWidget *panel = QgsPanelWidget::findParentPanel( this );
|
||||||
|
if ( panel && panel->dockMode() )
|
||||||
|
{
|
||||||
|
QgsSymbolSelectorWidget *d = new QgsSymbolSelectorWidget( newSymbol, QgsStyle::defaultStyle(), mLayer, nullptr );
|
||||||
|
d->setContext( symbolContext );
|
||||||
|
connect( d, &QgsPanelWidget::widgetChanged, this, &QgsSymbolButton::updateSymbolFromWidget );
|
||||||
|
connect( d, &QgsPanelWidget::panelAccepted, this, &QgsSymbolButton::cleanUpSymbolSelector );
|
||||||
|
panel->openPanel( d );
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
QgsSymbolSelectorDialog dialog( newSymbol, QgsStyle::defaultStyle(), mLayer, nullptr );
|
||||||
|
dialog.setWindowTitle( mDialogTitle );
|
||||||
|
dialog.setContext( symbolContext );
|
||||||
|
if ( dialog.exec() )
|
||||||
|
{
|
||||||
|
setSymbol( newSymbol );
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
delete newSymbol;
|
||||||
|
}
|
||||||
|
|
||||||
|
// reactivate button's window
|
||||||
|
activateWindow();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void QgsSymbolButton::updateSymbolFromWidget()
|
||||||
|
{
|
||||||
|
if ( QgsSymbolSelectorWidget *w = qobject_cast<QgsSymbolSelectorWidget *>( sender() ) )
|
||||||
|
setSymbol( w->symbol()->clone() );
|
||||||
|
}
|
||||||
|
|
||||||
|
void QgsSymbolButton::cleanUpSymbolSelector( QgsPanelWidget *container )
|
||||||
|
{
|
||||||
|
QgsSymbolSelectorWidget *w = qobject_cast<QgsSymbolSelectorWidget *>( container );
|
||||||
|
if ( !w )
|
||||||
|
return;
|
||||||
|
|
||||||
|
delete w->symbol();
|
||||||
|
}
|
||||||
|
|
||||||
|
QgsMapCanvas *QgsSymbolButton::mapCanvas() const
|
||||||
|
{
|
||||||
|
return mMapCanvas;
|
||||||
|
}
|
||||||
|
|
||||||
|
void QgsSymbolButton::setMapCanvas( QgsMapCanvas *mapCanvas )
|
||||||
|
{
|
||||||
|
mMapCanvas = mapCanvas;
|
||||||
|
}
|
||||||
|
|
||||||
|
QgsVectorLayer *QgsSymbolButton::layer() const
|
||||||
|
{
|
||||||
|
return mLayer;
|
||||||
|
}
|
||||||
|
|
||||||
|
void QgsSymbolButton::setLayer( QgsVectorLayer *layer )
|
||||||
|
{
|
||||||
|
mLayer = layer;
|
||||||
|
}
|
||||||
|
|
||||||
|
void QgsSymbolButton::registerExpressionContextGenerator( QgsExpressionContextGenerator *generator )
|
||||||
|
{
|
||||||
|
mExpressionContextGenerator = generator;
|
||||||
|
}
|
||||||
|
|
||||||
|
void QgsSymbolButton::setSymbol( QgsSymbol *symbol )
|
||||||
|
{
|
||||||
|
mSymbol.reset( symbol );
|
||||||
|
updatePreview();
|
||||||
|
emit changed();
|
||||||
|
}
|
||||||
|
|
||||||
|
void QgsSymbolButton::setColor( const QColor &color )
|
||||||
|
{
|
||||||
|
QColor opaque = color;
|
||||||
|
opaque.setAlphaF( 1.0 );
|
||||||
|
|
||||||
|
if ( opaque == mSymbol->color() )
|
||||||
|
return;
|
||||||
|
|
||||||
|
mSymbol->setColor( opaque );
|
||||||
|
updatePreview();
|
||||||
|
emit changed();
|
||||||
|
}
|
||||||
|
|
||||||
|
void QgsSymbolButton::copyColor()
|
||||||
|
{
|
||||||
|
QApplication::clipboard()->setMimeData( QgsSymbolLayerUtils::colorToMimeData( mSymbol->color() ) );
|
||||||
|
}
|
||||||
|
|
||||||
|
void QgsSymbolButton::pasteColor()
|
||||||
|
{
|
||||||
|
QColor clipColor;
|
||||||
|
bool hasAlpha = false;
|
||||||
|
if ( colorFromMimeData( QApplication::clipboard()->mimeData(), clipColor, hasAlpha ) )
|
||||||
|
{
|
||||||
|
//paste color
|
||||||
|
setColor( clipColor );
|
||||||
|
QgsRecentColorScheme::addRecentColor( clipColor );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void QgsSymbolButton::mousePressEvent( QMouseEvent *e )
|
||||||
|
{
|
||||||
|
if ( e->button() == Qt::RightButton )
|
||||||
|
{
|
||||||
|
QToolButton::showMenu();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else if ( e->button() == Qt::LeftButton )
|
||||||
|
{
|
||||||
|
mDragStartPosition = e->pos();
|
||||||
|
}
|
||||||
|
QToolButton::mousePressEvent( e );
|
||||||
|
}
|
||||||
|
|
||||||
|
void QgsSymbolButton::mouseMoveEvent( QMouseEvent *e )
|
||||||
|
{
|
||||||
|
//handle dragging colors/symbols from button
|
||||||
|
|
||||||
|
if ( !( e->buttons() & Qt::LeftButton ) )
|
||||||
|
{
|
||||||
|
//left button not depressed, so not a drag
|
||||||
|
QToolButton::mouseMoveEvent( e );
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( ( e->pos() - mDragStartPosition ).manhattanLength() < QApplication::startDragDistance() )
|
||||||
|
{
|
||||||
|
//mouse not moved, so not a drag
|
||||||
|
QToolButton::mouseMoveEvent( e );
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
//user is dragging
|
||||||
|
QDrag *drag = new QDrag( this );
|
||||||
|
drag->setMimeData( QgsSymbolLayerUtils::colorToMimeData( mSymbol->color() ) );
|
||||||
|
drag->setPixmap( QgsColorWidget::createDragIcon( mSymbol->color() ) );
|
||||||
|
drag->exec( Qt::CopyAction );
|
||||||
|
setDown( false );
|
||||||
|
}
|
||||||
|
|
||||||
|
void QgsSymbolButton::dragEnterEvent( QDragEnterEvent *e )
|
||||||
|
{
|
||||||
|
//is dragged data valid color data?
|
||||||
|
QColor mimeColor;
|
||||||
|
bool hasAlpha = false;
|
||||||
|
|
||||||
|
if ( colorFromMimeData( e->mimeData(), mimeColor, hasAlpha ) )
|
||||||
|
{
|
||||||
|
//if so, we accept the drag, and temporarily change the button's color
|
||||||
|
//to match the dragged color. This gives immediate feedback to the user
|
||||||
|
//that colors can be dropped here
|
||||||
|
e->acceptProposedAction();
|
||||||
|
updatePreview( mimeColor );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void QgsSymbolButton::dragLeaveEvent( QDragLeaveEvent *e )
|
||||||
|
{
|
||||||
|
Q_UNUSED( e );
|
||||||
|
//reset button color
|
||||||
|
updatePreview();
|
||||||
|
}
|
||||||
|
|
||||||
|
void QgsSymbolButton::dropEvent( QDropEvent *e )
|
||||||
|
{
|
||||||
|
//is dropped data valid format data?
|
||||||
|
QColor mimeColor;
|
||||||
|
bool hasAlpha = false;
|
||||||
|
if ( colorFromMimeData( e->mimeData(), mimeColor, hasAlpha ) )
|
||||||
|
{
|
||||||
|
//accept drop and set new color
|
||||||
|
e->acceptProposedAction();
|
||||||
|
|
||||||
|
if ( hasAlpha )
|
||||||
|
{
|
||||||
|
mSymbol->setOpacity( mimeColor.alphaF() );
|
||||||
|
}
|
||||||
|
mimeColor.setAlphaF( 1.0 );
|
||||||
|
mSymbol->setColor( mimeColor );
|
||||||
|
QgsRecentColorScheme::addRecentColor( mimeColor );
|
||||||
|
updatePreview();
|
||||||
|
emit changed();
|
||||||
|
}
|
||||||
|
updatePreview();
|
||||||
|
}
|
||||||
|
|
||||||
|
void QgsSymbolButton::prepareMenu()
|
||||||
|
{
|
||||||
|
//we need to tear down and rebuild this menu every time it is shown. Otherwise the space allocated to any
|
||||||
|
//QgsColorSwatchGridAction is not recalculated by Qt and the swatch grid may not be the correct size
|
||||||
|
//for the number of colors shown in the grid. Note that we MUST refresh color swatch grids every time this
|
||||||
|
//menu is opened, otherwise color schemes like the recent color scheme grid are meaningless
|
||||||
|
mMenu->clear();
|
||||||
|
|
||||||
|
QAction *configureAction = new QAction( tr( "Configure symbol..." ), this );
|
||||||
|
mMenu->addAction( configureAction );
|
||||||
|
connect( configureAction, &QAction::triggered, this, &QgsSymbolButton::showSettingsDialog );
|
||||||
|
|
||||||
|
mMenu->addSeparator();
|
||||||
|
|
||||||
|
QgsColorWheel *colorWheel = new QgsColorWheel( mMenu );
|
||||||
|
colorWheel->setColor( mSymbol->color() );
|
||||||
|
QgsColorWidgetAction *colorAction = new QgsColorWidgetAction( colorWheel, mMenu, mMenu );
|
||||||
|
colorAction->setDismissOnColorSelection( false );
|
||||||
|
connect( colorAction, &QgsColorWidgetAction::colorChanged, this, &QgsSymbolButton::setColor );
|
||||||
|
mMenu->addAction( colorAction );
|
||||||
|
|
||||||
|
//get schemes with ShowInColorButtonMenu flag set
|
||||||
|
QList< QgsColorScheme * > schemeList = QgsApplication::colorSchemeRegistry()->schemes( QgsColorScheme::ShowInColorButtonMenu );
|
||||||
|
QList< QgsColorScheme * >::iterator it = schemeList.begin();
|
||||||
|
for ( ; it != schemeList.end(); ++it )
|
||||||
|
{
|
||||||
|
QgsColorSwatchGridAction *colorAction = new QgsColorSwatchGridAction( *it, mMenu, QStringLiteral( "symbology" ), this );
|
||||||
|
colorAction->setBaseColor( mSymbol->color() );
|
||||||
|
mMenu->addAction( colorAction );
|
||||||
|
connect( colorAction, &QgsColorSwatchGridAction::colorChanged, this, &QgsSymbolButton::setColor );
|
||||||
|
connect( colorAction, &QgsColorSwatchGridAction::colorChanged, this, &QgsSymbolButton::addRecentColor );
|
||||||
|
}
|
||||||
|
|
||||||
|
mMenu->addSeparator();
|
||||||
|
|
||||||
|
QAction *copyColorAction = new QAction( tr( "Copy color" ), this );
|
||||||
|
mMenu->addAction( copyColorAction );
|
||||||
|
connect( copyColorAction, &QAction::triggered, this, &QgsSymbolButton::copyColor );
|
||||||
|
|
||||||
|
QAction *pasteColorAction = new QAction( tr( "Paste color" ), this );
|
||||||
|
//enable or disable paste action based on current clipboard contents. We always show the paste
|
||||||
|
//action, even if it's disabled, to give hint to the user that pasting colors is possible
|
||||||
|
QColor clipColor;
|
||||||
|
bool hasAlpha = false;
|
||||||
|
if ( colorFromMimeData( QApplication::clipboard()->mimeData(), clipColor, hasAlpha ) )
|
||||||
|
{
|
||||||
|
pasteColorAction->setIcon( createColorIcon( clipColor ) );
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
pasteColorAction->setEnabled( false );
|
||||||
|
}
|
||||||
|
mMenu->addAction( pasteColorAction );
|
||||||
|
connect( pasteColorAction, &QAction::triggered, this, &QgsSymbolButton::pasteColor );
|
||||||
|
}
|
||||||
|
|
||||||
|
void QgsSymbolButton::addRecentColor( const QColor &color )
|
||||||
|
{
|
||||||
|
QgsRecentColorScheme::addRecentColor( color );
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void QgsSymbolButton::changeEvent( QEvent *e )
|
||||||
|
{
|
||||||
|
if ( e->type() == QEvent::EnabledChange )
|
||||||
|
{
|
||||||
|
updatePreview();
|
||||||
|
}
|
||||||
|
QToolButton::changeEvent( e );
|
||||||
|
}
|
||||||
|
|
||||||
|
void QgsSymbolButton::showEvent( QShowEvent *e )
|
||||||
|
{
|
||||||
|
updatePreview();
|
||||||
|
QToolButton::showEvent( e );
|
||||||
|
}
|
||||||
|
|
||||||
|
void QgsSymbolButton::resizeEvent( QResizeEvent *event )
|
||||||
|
{
|
||||||
|
QToolButton::resizeEvent( event );
|
||||||
|
//recalculate icon size and redraw icon
|
||||||
|
mIconSize = QSize();
|
||||||
|
updatePreview();
|
||||||
|
}
|
||||||
|
|
||||||
|
void QgsSymbolButton::updatePreview( const QColor &color, QgsSymbol *tempSymbol )
|
||||||
|
{
|
||||||
|
std::unique_ptr< QgsSymbol > previewSymbol;
|
||||||
|
|
||||||
|
if ( tempSymbol )
|
||||||
|
previewSymbol.reset( tempSymbol->clone() );
|
||||||
|
else
|
||||||
|
previewSymbol.reset( mSymbol->clone() );
|
||||||
|
|
||||||
|
if ( color.isValid() )
|
||||||
|
previewSymbol->setColor( color );
|
||||||
|
|
||||||
|
QSize currentIconSize;
|
||||||
|
//icon size is button size with a small margin
|
||||||
|
if ( menu() )
|
||||||
|
{
|
||||||
|
if ( !mIconSize.isValid() )
|
||||||
|
{
|
||||||
|
//calculate size of push button part of widget (ie, without the menu dropdown button part)
|
||||||
|
QStyleOptionToolButton opt;
|
||||||
|
initStyleOption( &opt );
|
||||||
|
QRect buttonSize = QApplication::style()->subControlRect( QStyle::CC_ToolButton, &opt, QStyle::SC_ToolButton,
|
||||||
|
this );
|
||||||
|
//make sure height of icon looks good under different platforms
|
||||||
|
#ifdef Q_OS_WIN
|
||||||
|
mIconSize = QSize( buttonSize.width() - 10, height() - 6 );
|
||||||
|
#else
|
||||||
|
mIconSize = QSize( buttonSize.width() - 10, height() - 12 );
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
currentIconSize = mIconSize;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
//no menu
|
||||||
|
#ifdef Q_OS_WIN
|
||||||
|
currentIconSize = QSize( width() - 10, height() - 6 );
|
||||||
|
#else
|
||||||
|
currentIconSize = QSize( width() - 10, height() - 12 );
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( !currentIconSize.isValid() || currentIconSize.width() <= 0 || currentIconSize.height() <= 0 )
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
//create an icon pixmap
|
||||||
|
QIcon icon = QgsSymbolLayerUtils::symbolPreviewIcon( previewSymbol.get(), currentIconSize );
|
||||||
|
setIconSize( currentIconSize );
|
||||||
|
setIcon( icon );
|
||||||
|
}
|
||||||
|
|
||||||
|
bool QgsSymbolButton::colorFromMimeData( const QMimeData *mimeData, QColor &resultColor, bool &hasAlpha )
|
||||||
|
{
|
||||||
|
hasAlpha = false;
|
||||||
|
QColor mimeColor = QgsSymbolLayerUtils::colorFromMimeData( mimeData, hasAlpha );
|
||||||
|
|
||||||
|
if ( mimeColor.isValid() )
|
||||||
|
{
|
||||||
|
resultColor = mimeColor;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
//could not get color from mime data
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
QPixmap QgsSymbolButton::createColorIcon( const QColor &color ) const
|
||||||
|
{
|
||||||
|
//create an icon pixmap
|
||||||
|
QPixmap pixmap( 16, 16 );
|
||||||
|
pixmap.fill( Qt::transparent );
|
||||||
|
|
||||||
|
QPainter p;
|
||||||
|
p.begin( &pixmap );
|
||||||
|
|
||||||
|
//draw color over pattern
|
||||||
|
p.setBrush( QBrush( color ) );
|
||||||
|
|
||||||
|
//draw border
|
||||||
|
p.setPen( QColor( 197, 197, 197 ) );
|
||||||
|
p.drawRect( 0, 0, 15, 15 );
|
||||||
|
p.end();
|
||||||
|
return pixmap;
|
||||||
|
}
|
||||||
|
|
||||||
|
void QgsSymbolButton::setDialogTitle( const QString &title )
|
||||||
|
{
|
||||||
|
mDialogTitle = title;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString QgsSymbolButton::dialogTitle() const
|
||||||
|
{
|
||||||
|
return mDialogTitle;
|
||||||
|
}
|
||||||
|
|
||||||
|
QgsSymbol *QgsSymbolButton::symbol()
|
||||||
|
{
|
||||||
|
return mSymbol.get();
|
||||||
|
}
|
218
src/gui/qgssymbolbutton.h
Normal file
218
src/gui/qgssymbolbutton.h
Normal file
@ -0,0 +1,218 @@
|
|||||||
|
/***************************************************************************
|
||||||
|
qgssymbolbutton.h
|
||||||
|
-----------------
|
||||||
|
Date : July 2017
|
||||||
|
Copyright : (C) 2017 by Nyall Dawson
|
||||||
|
Email : nyall dot dawson at gmail dot 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 QGSSYMBOLBUTTON_H
|
||||||
|
#define QGSSYMBOLBUTTON_H
|
||||||
|
|
||||||
|
#include "qgis_gui.h"
|
||||||
|
#include "qgis.h"
|
||||||
|
#include "qgssymbol.h"
|
||||||
|
#include <QToolButton>
|
||||||
|
#include <QPointer>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
class QgsMapCanvas;
|
||||||
|
class QgsVectorLayer;
|
||||||
|
class QgsExpressionContextGenerator;
|
||||||
|
class QgsPanelWidget;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \ingroup gui
|
||||||
|
* \class QgsSymbolButton
|
||||||
|
* A button for creating and modifying QgsSymbol settings.
|
||||||
|
*
|
||||||
|
* The button shows a preview icon for the current symbol, and will open a detailed symbol editor dialog (or
|
||||||
|
* panel widget) when clicked.
|
||||||
|
*
|
||||||
|
* \since QGIS 3.0
|
||||||
|
*/
|
||||||
|
class GUI_EXPORT QgsSymbolButton : public QToolButton
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
Q_PROPERTY( QString dialogTitle READ dialogTitle WRITE setDialogTitle )
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct a new symbol button.
|
||||||
|
* Use \a dialogTitle string to define the title to show in the symbol settings dialog.
|
||||||
|
*/
|
||||||
|
QgsSymbolButton( QWidget *parent SIP_TRANSFERTHIS = nullptr, const QString &dialogTitle = QString() );
|
||||||
|
|
||||||
|
virtual QSize minimumSizeHint() const override;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the \a title for the symbol settings dialog window.
|
||||||
|
* \see dialogTitle()
|
||||||
|
*/
|
||||||
|
void setDialogTitle( const QString &title );
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the title for the symbol settings dialog window.
|
||||||
|
* \see setDialogTitle()
|
||||||
|
*/
|
||||||
|
QString dialogTitle() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the current symbol defined by the button.
|
||||||
|
* \see setSymbol()
|
||||||
|
* \see changed()
|
||||||
|
*/
|
||||||
|
QgsSymbol *symbol();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the map canvas associated with the widget.
|
||||||
|
* \see setMapCanvas()
|
||||||
|
*/
|
||||||
|
QgsMapCanvas *mapCanvas() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets a map \a canvas to associate with the widget. This allows the
|
||||||
|
* widget to fetch current settings from the map canvas, such as current scale.
|
||||||
|
* \see mapCanvas()
|
||||||
|
*/
|
||||||
|
void setMapCanvas( QgsMapCanvas *canvas );
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the layer associated with the widget.
|
||||||
|
* \see setLayer()
|
||||||
|
*/
|
||||||
|
QgsVectorLayer *layer() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets a \a layer to associate with the widget. This allows the
|
||||||
|
* widget to setup layer related settings within the symbol settings dialog,
|
||||||
|
* such as correctly populating data defined override buttons.
|
||||||
|
* \see layer()
|
||||||
|
*/
|
||||||
|
void setLayer( QgsVectorLayer *layer );
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register an expression context generator class that will be used to retrieve
|
||||||
|
* an expression context for the button when required.
|
||||||
|
*/
|
||||||
|
void registerExpressionContextGenerator( QgsExpressionContextGenerator *generator );
|
||||||
|
|
||||||
|
public slots:
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the \a symbol for the button. Ownership of \a symbol is transferred to the
|
||||||
|
* button.
|
||||||
|
* \see symbol()
|
||||||
|
* \see changed()
|
||||||
|
*/
|
||||||
|
void setSymbol( QgsSymbol *symbol SIP_TRANSFER );
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the current \a color for the symbol. Will emit a changed() signal if the color is different
|
||||||
|
* to the previous symbol color.
|
||||||
|
*/
|
||||||
|
void setColor( const QColor &color );
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Copies the current symbol color to the clipboard.
|
||||||
|
* \see pasteColor()
|
||||||
|
*/
|
||||||
|
void copyColor();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pastes a color from the clipboard to the symbol. If clipboard does not contain a valid
|
||||||
|
* color or string representation of a color, then no change is applied.
|
||||||
|
* \see copyColor()
|
||||||
|
*/
|
||||||
|
void pasteColor();
|
||||||
|
|
||||||
|
signals:
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Emitted when the symbol's settings are changed.
|
||||||
|
* \see symbol()
|
||||||
|
* \see setSymbol()
|
||||||
|
*/
|
||||||
|
void changed();
|
||||||
|
|
||||||
|
protected:
|
||||||
|
|
||||||
|
void changeEvent( QEvent *e ) override;
|
||||||
|
void showEvent( QShowEvent *e ) override;
|
||||||
|
void resizeEvent( QResizeEvent *event ) override;
|
||||||
|
|
||||||
|
// Reimplemented to detect right mouse button clicks on the color button and allow dragging colors
|
||||||
|
void mousePressEvent( QMouseEvent *e ) override;
|
||||||
|
// Reimplemented to allow dragging colors/symbols from button
|
||||||
|
void mouseMoveEvent( QMouseEvent *e ) override;
|
||||||
|
// Reimplemented to accept dragged colors
|
||||||
|
void dragEnterEvent( QDragEnterEvent *e ) override;
|
||||||
|
|
||||||
|
// Reimplemented to reset button appearance after drag leave
|
||||||
|
void dragLeaveEvent( QDragLeaveEvent *e ) override;
|
||||||
|
|
||||||
|
// Reimplemented to accept dropped colors
|
||||||
|
void dropEvent( QDropEvent *e ) override;
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
|
||||||
|
void showSettingsDialog();
|
||||||
|
void updateSymbolFromWidget();
|
||||||
|
void cleanUpSymbolSelector( QgsPanelWidget *container );
|
||||||
|
|
||||||
|
/** Creates the drop-down menu entries
|
||||||
|
*/
|
||||||
|
void prepareMenu();
|
||||||
|
|
||||||
|
void addRecentColor( const QColor &color );
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
QString mDialogTitle;
|
||||||
|
|
||||||
|
QgsMapCanvas *mMapCanvas = nullptr;
|
||||||
|
|
||||||
|
QPoint mDragStartPosition;
|
||||||
|
|
||||||
|
QMenu *mMenu = nullptr;
|
||||||
|
|
||||||
|
QPointer< QgsVectorLayer > mLayer;
|
||||||
|
|
||||||
|
QSize mIconSize;
|
||||||
|
|
||||||
|
std::unique_ptr< QgsSymbol > mSymbol;
|
||||||
|
|
||||||
|
QgsExpressionContextGenerator *mExpressionContextGenerator = nullptr;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Regenerates the text preview. If \a color is specified, a temporary color preview
|
||||||
|
* is shown instead.
|
||||||
|
*/
|
||||||
|
void updatePreview( const QColor &color = QColor(), QgsSymbol *tempSymbol = nullptr );
|
||||||
|
|
||||||
|
/** Attempts to parse mimeData as a color, either via the mime data's color data or by
|
||||||
|
* parsing a textual representation of a color.
|
||||||
|
* \returns true if mime data could be intrepreted as a color
|
||||||
|
* \param mimeData mime data
|
||||||
|
* \param resultColor QColor to store evaluated color
|
||||||
|
* \param hasAlpha will be set to true if mime data also included an alpha component
|
||||||
|
* \see formatFromMimeData
|
||||||
|
*/
|
||||||
|
bool colorFromMimeData( const QMimeData *mimeData, QColor &resultColor, bool &hasAlpha );
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a \a color icon for display in the drop-down menu.
|
||||||
|
*/
|
||||||
|
QPixmap createColorIcon( const QColor &color ) const;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // QGSSYMBOLBUTTON_H
|
@ -126,6 +126,7 @@ ADD_PYTHON_TEST(PyQgsRenderer test_qgsrenderer.py)
|
|||||||
ADD_PYTHON_TEST(PyQgsRulebasedRenderer test_qgsrulebasedrenderer.py)
|
ADD_PYTHON_TEST(PyQgsRulebasedRenderer test_qgsrulebasedrenderer.py)
|
||||||
ADD_PYTHON_TEST(PyQgsSingleSymbolRenderer test_qgssinglesymbolrenderer.py)
|
ADD_PYTHON_TEST(PyQgsSingleSymbolRenderer test_qgssinglesymbolrenderer.py)
|
||||||
ADD_PYTHON_TEST(PyQgsShapefileProvider test_provider_shapefile.py)
|
ADD_PYTHON_TEST(PyQgsShapefileProvider test_provider_shapefile.py)
|
||||||
|
ADD_PYTHON_TEST(PyQgsSymbolButton test_qgssymbolbutton.py)
|
||||||
ADD_PYTHON_TEST(PyQgsTabfileProvider test_provider_tabfile.py)
|
ADD_PYTHON_TEST(PyQgsTabfileProvider test_provider_tabfile.py)
|
||||||
ADD_PYTHON_TEST(PyQgsTabWidget test_qgstabwidget.py)
|
ADD_PYTHON_TEST(PyQgsTabWidget test_qgstabwidget.py)
|
||||||
ADD_PYTHON_TEST(PyQgsTextRenderer test_qgstextrenderer.py)
|
ADD_PYTHON_TEST(PyQgsTextRenderer test_qgstextrenderer.py)
|
||||||
|
77
tests/src/python/test_qgssymbolbutton.py
Normal file
77
tests/src/python/test_qgssymbolbutton.py
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""QGIS Unit tests for QgsSymbolButton.
|
||||||
|
|
||||||
|
.. 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__ = '23/07/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.core import QgsFillSymbol, QgsMarkerSymbol
|
||||||
|
from qgis.gui import QgsSymbolButton, QgsMapCanvas
|
||||||
|
from qgis.testing import start_app, unittest
|
||||||
|
from qgis.PyQt.QtGui import QColor, QFont
|
||||||
|
from qgis.PyQt.QtTest import QSignalSpy
|
||||||
|
from utilities import getTestFont
|
||||||
|
|
||||||
|
start_app()
|
||||||
|
|
||||||
|
|
||||||
|
class TestQgsSymbolButton(unittest.TestCase):
|
||||||
|
|
||||||
|
def testGettersSetters(self):
|
||||||
|
button = QgsSymbolButton()
|
||||||
|
canvas = QgsMapCanvas()
|
||||||
|
|
||||||
|
button.setDialogTitle('test title')
|
||||||
|
self.assertEqual(button.dialogTitle(), 'test title')
|
||||||
|
|
||||||
|
button.setMapCanvas(canvas)
|
||||||
|
self.assertEqual(button.mapCanvas(), canvas)
|
||||||
|
|
||||||
|
def testSetGetSymbol(self):
|
||||||
|
button = QgsSymbolButton()
|
||||||
|
symbol = QgsMarkerSymbol.createSimple({})
|
||||||
|
symbol.setColor(QColor(255, 0, 0))
|
||||||
|
|
||||||
|
signal_spy = QSignalSpy(button.changed)
|
||||||
|
button.setSymbol(symbol)
|
||||||
|
self.assertEqual(len(signal_spy), 1)
|
||||||
|
|
||||||
|
r = button.symbol()
|
||||||
|
self.assertEqual(r.color(), QColor(255, 0, 0))
|
||||||
|
|
||||||
|
def testSetColor(self):
|
||||||
|
button = QgsSymbolButton()
|
||||||
|
|
||||||
|
symbol = QgsMarkerSymbol.createSimple({})
|
||||||
|
symbol.setColor(QColor(255, 255, 0))
|
||||||
|
|
||||||
|
button.setSymbol(symbol)
|
||||||
|
|
||||||
|
signal_spy = QSignalSpy(button.changed)
|
||||||
|
button.setColor(QColor(0, 255, 0))
|
||||||
|
self.assertEqual(len(signal_spy), 1)
|
||||||
|
|
||||||
|
r = button.symbol()
|
||||||
|
self.assertEqual(r.color().name(), QColor(0, 255, 0).name())
|
||||||
|
|
||||||
|
# set same color, should not emit signal
|
||||||
|
button.setColor(QColor(0, 255, 0))
|
||||||
|
self.assertEqual(len(signal_spy), 1)
|
||||||
|
|
||||||
|
# color with transparency - should be stripped
|
||||||
|
button.setColor(QColor(0, 255, 0, 100))
|
||||||
|
r = button.symbol()
|
||||||
|
self.assertEqual(r.color(), QColor(0, 255, 0))
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
unittest.main()
|
Loading…
x
Reference in New Issue
Block a user