Skip to content

Commit d7edeac

Browse files
committed
Make style model decoration icons size responsive
We hack the model a bit here, but as much as possible avoid bleeding view properties into the model API. So we use a QObject property ("icon_size") to specify icons sizes for the model to generate. This is set on instances of the model to indicate the required sizes for decorations in all views connected to the model, and allows the model to have size responsive icons. By using a QObject property we avoid having public GUI/view related API within the model, and mostly avoid view related properties contaminating the pure model, yet still have pixel-perfect symbol renders for the required view icon sizes.
1 parent 28836d2 commit d7edeac

File tree

2 files changed

+70
-3
lines changed

2 files changed

+70
-3
lines changed

src/core/symbology/qgsstylemodel.cpp

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
#include "qgssymbollayerutils.h"
1919
#include <QIcon>
2020

21+
const double ICON_PADDING_FACTOR = 0.16;
2122

2223
QgsStyleModel::QgsStyleModel( QgsStyle *style, QObject *parent )
2324
: QAbstractItemModel( parent )
@@ -70,18 +71,41 @@ QVariant QgsStyleModel::data( const QModelIndex &index, int role ) const
7071

7172
case Qt::DecorationRole:
7273
{
74+
// check the model custom property for icon sizes to generate. This is used
75+
// by instances of the model to indicate the required sizes for decorations in all
76+
// views connected to the model, and allows the model to have size responsive icons.
77+
// By using a QObject property we avoid having public GUI/view related API within
78+
// the model, and mostly avoid view related properties contaminating the pure model...
79+
const QVariantList iconSizes = property( "icon_sizes" ).toList();
80+
7381
switch ( index.column() )
7482
{
7583
case Name:
7684
if ( !isColorRamp )
7785
{
7886
std::unique_ptr< QgsSymbol > symbol( mStyle->symbol( name ) );
79-
return QgsSymbolLayerUtils::symbolPreviewIcon( symbol.get(), QSize( 24, 24 ), 2 );
87+
QIcon icon;
88+
icon.addPixmap( QgsSymbolLayerUtils::symbolPreviewPixmap( symbol.get(), QSize( 24, 24 ), 1 ) );
89+
90+
for ( const QVariant &size : iconSizes )
91+
{
92+
QSize s = size.toSize();
93+
icon.addPixmap( QgsSymbolLayerUtils::symbolPreviewPixmap( symbol.get(), s, static_cast< int >( s.width() * ICON_PADDING_FACTOR ) ) );
94+
}
95+
96+
return icon;
8097
}
8198
else
8299
{
83100
std::unique_ptr< QgsColorRamp > ramp( mStyle->colorRamp( name ) );
84-
return QgsSymbolLayerUtils::colorRampPreviewIcon( ramp.get(), QSize( 24, 24 ), 2 );
101+
QIcon icon;
102+
icon.addPixmap( QgsSymbolLayerUtils::colorRampPreviewPixmap( ramp.get(), QSize( 24, 24 ), 1 ) );
103+
for ( const QVariant &size : iconSizes )
104+
{
105+
QSize s = size.toSize();
106+
icon.addPixmap( QgsSymbolLayerUtils::colorRampPreviewPixmap( ramp.get(), s, static_cast< int >( s.width() * ICON_PADDING_FACTOR ) ) );
107+
}
108+
return icon;
85109
}
86110
case Tags:
87111
return QVariant();

tests/src/python/test_qgsstylemodel.py

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
QgsStyle,
2424
QgsStyleProxyModel)
2525
from qgis.testing import start_app, unittest
26-
from qgis.PyQt.QtCore import Qt
26+
from qgis.PyQt.QtCore import Qt, QSize
2727
from qgis.PyQt.QtGui import QColor
2828

2929
start_app()
@@ -715,6 +715,49 @@ def test_filter_proxy(self):
715715
model.setSmartGroupId(-1)
716716
self.assertEqual(model.rowCount(), 8)
717717

718+
def testIconSize(self):
719+
"""
720+
Test that model has responsive icon sizes for decorations
721+
"""
722+
style = QgsStyle()
723+
style.createMemoryDatabase()
724+
725+
symbol_a = createMarkerSymbol()
726+
symbol_a.setColor(QColor(255, 10, 10))
727+
self.assertTrue(style.addSymbol('a', symbol_a, True))
728+
ramp_a = QgsLimitedRandomColorRamp(5)
729+
self.assertTrue(style.addColorRamp('ramp a', ramp_a, True))
730+
731+
model = QgsStyleModel(style)
732+
self.assertEqual(model.rowCount(), 2)
733+
for i in range(2):
734+
icon = model.data(model.index(i, 0), Qt.DecorationRole)
735+
# by default, only 24x24 icon
736+
self.assertEqual(icon.availableSizes(), [QSize(24, 24)])
737+
self.assertEqual(icon.actualSize(QSize(10, 10)), QSize(10, 10))
738+
self.assertEqual(icon.actualSize(QSize(24, 24)), QSize(24, 24))
739+
self.assertEqual(icon.actualSize(QSize(90, 90)), QSize(24, 24))
740+
741+
model.setProperty('icon_sizes', [QSize(24, 24), QSize(100, 90)])
742+
icon = model.data(model.index(i, 0), Qt.DecorationRole)
743+
self.assertEqual(icon.availableSizes(), [QSize(24, 24), QSize(100, 90)])
744+
self.assertEqual(icon.actualSize(QSize(10, 10)), QSize(10, 10))
745+
self.assertEqual(icon.actualSize(QSize(24, 24)), QSize(24, 24))
746+
self.assertEqual(icon.actualSize(QSize(25, 25)), QSize(25, 22))
747+
self.assertEqual(icon.actualSize(QSize(90, 90)), QSize(90, 81))
748+
self.assertEqual(icon.actualSize(QSize(125, 125)), QSize(100, 90))
749+
750+
model.setProperty('icon_sizes', [QSize(100, 90), QSize(200, 180)])
751+
icon = model.data(model.index(i, 0), Qt.DecorationRole)
752+
self.assertEqual(icon.availableSizes(), [QSize(24, 24), QSize(100, 90), QSize(200, 180)])
753+
self.assertEqual(icon.actualSize(QSize(10, 10)), QSize(10, 10))
754+
self.assertEqual(icon.actualSize(QSize(24, 24)), QSize(24, 24))
755+
self.assertEqual(icon.actualSize(QSize(25, 25)), QSize(25, 22))
756+
self.assertEqual(icon.actualSize(QSize(90, 90)), QSize(90, 81))
757+
self.assertEqual(icon.actualSize(QSize(125, 125)), QSize(125, 112))
758+
self.assertEqual(icon.actualSize(QSize(225, 225)), QSize(200, 180))
759+
model.setProperty('icon_sizes', None)
760+
718761

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

0 commit comments

Comments
 (0)