Skip to content
Permalink
Browse files

[FEATURE] Show Project Colors in color bound data defined buttons

This adds a new "Project Colors" section in data defined buttons
which are linked to a color value. The color menu contains all
colors defined as part of the current project's Project Color
Scheme (which is defined through project properties).

When a project color is selected from the button, the property
becomes linked to that color. It will automatically follow any
future changes to the color when made through project properties.

This allows users to define common colors for a project once,
and then "bind" symbol, label, layout, etc colors to these
preset colors. The link is live, so you change it once, and
the change is reflected EVERYWHERE. Sure beats updating a color
100 times when it's use has been scattered throughout a project's
symbols, labels, etc...

(Basically, this is just adding a shortcut to setting a data
defined expression "project_color(...)" for the property. The
project_color function has been around a LOOONG time, but it's
only really been usable by power users before this change)
  • Loading branch information
nyalldawson committed Jan 11, 2019
1 parent 79d01a9 commit eecfe50dc5242cddc34a71cfb02cada0dba227db
@@ -212,6 +212,12 @@ Updates list of fields.
Set whether the current property override definition is to be used
%End



void aboutToShowMenu();
void menuActionTriggered( QAction *action );


signals:

void changed();
@@ -23,12 +23,15 @@
#include "qgspanelwidget.h"
#include "qgspropertyassistantwidget.h"
#include "qgsauxiliarystorage.h"
#include "qgscolorschemeregistry.h"
#include "qgscolorbutton.h"

#include <QClipboard>
#include <QMenu>
#include <QMouseEvent>
#include <QPointer>
#include <QGroupBox>
#include <QRegularExpression>

QgsPropertyOverrideButton::QgsPropertyOverrideButton( QWidget *parent,
const QgsVectorLayer *layer )
@@ -69,6 +72,10 @@ QgsPropertyOverrideButton::QgsPropertyOverrideButton( QWidget *parent,
mVariablesMenu = new QMenu( this );
mActionVariables->setMenu( mVariablesMenu );

mActionColors = new QAction( tr( "Color" ), this );
mColorsMenu = new QMenu( this );
mActionColors->setMenu( mColorsMenu );

mActionActive = new QAction( this );
QFont f = mActionActive->font();
f.setBold( true );
@@ -309,6 +316,7 @@ void QgsPropertyOverrideButton::setToProperty( const QgsProperty &property )
updateGui();
}

///@cond PRIVATE
void QgsPropertyOverrideButton::aboutToShowMenu()
{
mDefineMenu->clear();
@@ -410,6 +418,52 @@ void QgsPropertyOverrideButton::aboutToShowMenu()
mFieldsMenu->menuAction()->setCheckable( true );
mFieldsMenu->menuAction()->setChecked( fieldActive && mProperty.propertyType() == QgsProperty::FieldBasedProperty && !mProperty.transformer() );

bool colorActive = false;
mColorsMenu->clear();
if ( mDefinition.standardTemplate() == QgsPropertyDefinition::ColorWithAlpha
|| mDefinition.standardTemplate() == QgsPropertyDefinition::ColorNoAlpha )
{
// project colors menu
QAction *colorTitleAct = mDefineMenu->addAction( tr( "Project Color" ) );
colorTitleAct->setFont( titlefont );
colorTitleAct->setEnabled( false );

QList<QgsProjectColorScheme *> projectSchemes;
QgsApplication::colorSchemeRegistry()->schemes( projectSchemes );
if ( projectSchemes.length() > 0 )
{
QgsProjectColorScheme *scheme = projectSchemes.at( 0 );
const QgsNamedColorList colors = scheme->fetchColors();
for ( const auto &color : colors )
{
if ( color.second.isEmpty() )
continue;

QPixmap icon = QgsColorButton::createMenuIcon( color.first, mDefinition.standardTemplate() == QgsPropertyDefinition::ColorWithAlpha );
QAction *act = mColorsMenu->addAction( color.second );
act->setIcon( icon );
if ( mProperty.propertyType() == QgsProperty::ExpressionBasedProperty && hasExp && mExpressionString == QStringLiteral( "project_color('%1')" ).arg( color.second ) )
{
act->setCheckable( true );
act->setChecked( true );
colorActive = true;
}
}
}

if ( mColorsMenu->actions().isEmpty() )
{
QAction *act = mColorsMenu->addAction( tr( "No colors set" ) );
act->setEnabled( false );
}

mDefineMenu->addAction( mActionColors );
mColorsMenu->menuAction()->setCheckable( true );
mColorsMenu->menuAction()->setChecked( colorActive && !mProperty.transformer() );

mDefineMenu->addSeparator();
}

QAction *exprTitleAct = mDefineMenu->addAction( tr( "Expression" ) );
exprTitleAct->setFont( titlefont );
exprTitleAct->setEnabled( false );
@@ -470,7 +524,7 @@ void QgsPropertyOverrideButton::aboutToShowMenu()
mActionExpression->setText( expString );
}
mDefineMenu->addAction( mActionExpression );
mActionExpression->setChecked( mProperty.propertyType() == QgsProperty::ExpressionBasedProperty && !variableActive && !mProperty.transformer() );
mActionExpression->setChecked( mProperty.propertyType() == QgsProperty::ExpressionBasedProperty && !variableActive && !colorActive && !mProperty.transformer() );

mDefineMenu->addAction( mActionExpDialog );
mDefineMenu->addAction( mActionCopyExpr );
@@ -582,7 +636,21 @@ void QgsPropertyOverrideButton::menuActionTriggered( QAction *action )
updateGui();
emit changed();
}
else if ( mColorsMenu->actions().contains( action ) ) // a color name clicked
{
if ( mExpressionString != QStringLiteral( "project_color('%1')" ).arg( action->text() ) )
{
mExpressionString = QStringLiteral( "project_color('%1')" ).arg( action->text() );
}
mProperty.setExpressionString( mExpressionString );
mProperty.setTransformer( nullptr );
setActivePrivate( true );
updateSiblingWidgets( isActive() );
updateGui();
emit changed();
}
}
///@endcond

void QgsPropertyOverrideButton::showDescriptionDialog()
{
@@ -220,6 +220,15 @@ class GUI_EXPORT QgsPropertyOverrideButton: public QToolButton
*/
void setActive( bool active );


///@cond PRIVATE

// exposed to Python for testing only
void aboutToShowMenu();
void menuActionTriggered( QAction *action );

///@endcond

signals:

//! Emitted when property definition changes
@@ -263,6 +272,8 @@ class GUI_EXPORT QgsPropertyOverrideButton: public QToolButton
QMenu *mFieldsMenu = nullptr;
QMenu *mVariablesMenu = nullptr;
QAction *mActionVariables = nullptr;
QMenu *mColorsMenu = nullptr;
QAction *mActionColors = nullptr;

QAction *mActionActive = nullptr;
QAction *mActionDescription = nullptr;
@@ -312,8 +323,7 @@ class GUI_EXPORT QgsPropertyOverrideButton: public QToolButton
std::shared_ptr< QgsSymbol > mSymbol;

private slots:
void aboutToShowMenu();
void menuActionTriggered( QAction *action );

void showHelp();
void updateSiblingWidgets( bool state );
};
@@ -156,6 +156,7 @@ ADD_PYTHON_TEST(PyQgsProcessingInPlace test_qgsprocessinginplace.py)
ADD_PYTHON_TEST(PyQgsProcessingAlgDecorator test_processing_alg_decorator.py)
ADD_PYTHON_TEST(PyQgsProjectionSelectionWidgets test_qgsprojectionselectionwidgets.py)
ADD_PYTHON_TEST(PyQgsProjectMetadata test_qgsprojectmetadata.py)
ADD_PYTHON_TEST(PyQgsPropertyOverrideButton test_qgspropertyoverridebutton.py)
ADD_PYTHON_TEST(PyQgsRange test_qgsrange.py)
ADD_PYTHON_TEST(PyQgsRangeWidgets test_qgsrangewidgets.py)
ADD_PYTHON_TEST(PyQgsRasterBandComboBox test_qgsrasterbandcombobox.py)
@@ -0,0 +1,90 @@
# -*- coding: utf-8 -*-
"""QGIS Unit tests for QgsPropertyOverrideButton.
.. 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__ = '11/01/2019'
__copyright__ = 'Copyright 2019, 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 (QgsPropertyDefinition,
QgsProperty,
QgsApplication,
QgsProjectColorScheme)

from qgis.gui import (QgsColorButton,
QgsPropertyOverrideButton)

from qgis.testing import start_app, unittest
from qgis.PyQt.QtGui import QColor


start_app()


class TestQgsPropertyOverrideButton(unittest.TestCase):

def testProjectColor(self):
definition = QgsPropertyDefinition('test', 'test', QgsPropertyDefinition.ColorWithAlpha)
button = QgsPropertyOverrideButton()
button.init(0, QgsProperty(), definition)

button.aboutToShowMenu()

self.assertIn('Project Color', [a.text() for a in button.menu().actions()])
self.assertIn('Color', [a.text() for a in button.menu().actions()])
color_action = [a for a in button.menu().actions() if a.text() == 'Color'][0]
self.assertEqual([a.text() for a in color_action.menu().actions()][0], 'No colors set')

# add some project colors
scheme = [s for s in QgsApplication.colorSchemeRegistry().schemes() if isinstance(s, QgsProjectColorScheme)][0]
scheme.setColors([[QColor(255, 0, 0), 'color 1'], [QColor(255, 255, 0), 'burnt marigold']])

button.aboutToShowMenu()
self.assertIn('Project Color', [a.text() for a in button.menu().actions()])
self.assertIn('Color', [a.text() for a in button.menu().actions()])
color_action = [a for a in button.menu().actions() if a.text() == 'Color'][0]
self.assertEqual([a.text() for a in color_action.menu().actions()], ['color 1', 'burnt marigold'])

button.menuActionTriggered(color_action.menu().actions()[1])
self.assertTrue(button.toProperty().isActive())
self.assertEqual(button.toProperty().asExpression(), 'project_color(\'burnt marigold\')')

button.menuActionTriggered(color_action.menu().actions()[0])
self.assertTrue(button.toProperty().isActive())
self.assertEqual(button.toProperty().asExpression(), 'project_color(\'color 1\')')

button.setToProperty(QgsProperty.fromExpression('project_color(\'burnt marigold\')'))
button.aboutToShowMenu()
color_action = [a for a in button.menu().actions() if a.text() == 'Color'][0]
self.assertTrue(color_action.isChecked())
self.assertEqual([a.isChecked() for a in color_action.menu().actions()], [False, True])

# should also see color menu for ColorNoAlpha properties
definition = QgsPropertyDefinition('test', 'test', QgsPropertyDefinition.ColorNoAlpha)
button = QgsPropertyOverrideButton()
button.init(0, QgsProperty(), definition)

button.aboutToShowMenu()
self.assertIn('Project Color', [a.text() for a in button.menu().actions()])
self.assertIn('Color', [a.text() for a in button.menu().actions()])

# but no color menu for other types
definition = QgsPropertyDefinition('test', 'test', QgsPropertyDefinition.Double)
button = QgsPropertyOverrideButton()
button.init(0, QgsProperty(), definition)

button.aboutToShowMenu()
self.assertNotIn('Project Color', [a.text() for a in button.menu().actions()])
self.assertNotIn('Color', [a.text() for a in button.menu().actions()])


if __name__ == '__main__':
unittest.main()

0 comments on commit eecfe50

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