QGIS API Documentation 3.41.0-Master (cea29feecf2)
Loading...
Searching...
No Matches
qgscolorswatchgrid.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgscolorswatchgrid.cpp
3 ------------------
4 Date : July 2014
5 Copyright : (C) 2014 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
16#include "qgscolorswatchgrid.h"
17#include "moc_qgscolorswatchgrid.cpp"
18#include "qgsapplication.h"
19#include "qgssymbollayerutils.h"
20#include "qgscolortooltip_p.h"
21#include <QPainter>
22#include <QMouseEvent>
23#include <QMenu>
24#include <QBuffer>
25
26#define NUMBER_COLORS_PER_ROW 10 //number of color swatches per row
27
28QgsColorSwatchGrid::QgsColorSwatchGrid( QgsColorScheme *scheme, const QString &context, QWidget *parent )
29 : QWidget( parent )
30 , mScheme( scheme )
31 , mContext( context )
32 , mDrawBoxDepressed( false )
33 , mCurrentHoverBox( -1 )
34 , mFocused( false )
35 , mCurrentFocusBox( 0 )
36 , mPressedOnWidget( false )
37{
38 //need to receive all mouse over events
39 setMouseTracking( true );
40
41 setFocusPolicy( Qt::StrongFocus );
42 setSizePolicy( QSizePolicy::Fixed, QSizePolicy::Fixed );
43
44 mLabelHeight = Qgis::UI_SCALE_FACTOR * fontMetrics().height();
45 mLabelMargin = Qgis::UI_SCALE_FACTOR * fontMetrics().horizontalAdvance( '.' );
46 mSwatchSize = Qgis::UI_SCALE_FACTOR * fontMetrics().horizontalAdvance( 'X' ) * 1.75;
47 mSwatchOutlineSize = std::max( fontMetrics().horizontalAdvance( '.' ) * 0.4, 1.0 );
48
49 mSwatchSpacing = mSwatchSize * 0.3;
50 mSwatchMargin = mLabelMargin;
51
52 //calculate widget width
53 mWidth = NUMBER_COLORS_PER_ROW * mSwatchSize + ( NUMBER_COLORS_PER_ROW - 1 ) * mSwatchSpacing + mSwatchMargin + mSwatchMargin;
54
56}
57
59{
60 return QSize( mWidth, calculateHeight() );
61}
62
64{
65 return QSize( mWidth, calculateHeight() );
66}
67
68void QgsColorSwatchGrid::setContext( const QString &context )
69{
70 mContext = context;
72}
73
74void QgsColorSwatchGrid::setBaseColor( const QColor &baseColor )
75{
76 mBaseColor = baseColor;
78}
79
81{
82 //get colors from scheme
83 mColors = mScheme->fetchColors( mContext, mBaseColor );
84
85 //have to update size of widget in case number of colors has changed
86 updateGeometry();
87 repaint();
88}
89
90void QgsColorSwatchGrid::paintEvent( QPaintEvent *event )
91{
92 Q_UNUSED( event )
93 QPainter painter( this );
94 draw( painter );
95 painter.end();
96}
97
98void QgsColorSwatchGrid::mouseMoveEvent( QMouseEvent *event )
99{
100 //calculate box mouse cursor is over
101 const int newBox = swatchForPosition( event->pos() );
102
103 mDrawBoxDepressed = event->buttons() & Qt::LeftButton;
104 if ( newBox != mCurrentHoverBox )
105 {
106 //only repaint if changes are required
107 mCurrentHoverBox = newBox;
108 repaint();
109
110 updateTooltip( newBox );
111 }
112
113 emit hovered();
114}
115
116void QgsColorSwatchGrid::updateTooltip( const int colorIdx )
117{
118 if ( colorIdx >= 0 && colorIdx < mColors.length() )
119 {
120 const QColor color = mColors.at( colorIdx ).first;
121
122 //if color has an associated name from the color scheme, use that
123 const QString colorName = mColors.at( colorIdx ).second;
124
125 QString info;
126 if ( !colorName.isEmpty() )
127 info += QStringLiteral( "<h3>%1</h3><p>" ).arg( colorName );
128
129 info += QgsColorTooltip::htmlDescription( color, this );
130
131 setToolTip( info );
132 }
133 else
134 {
135 //clear tooltip
136 setToolTip( QString() );
137 }
138}
139
140void QgsColorSwatchGrid::mousePressEvent( QMouseEvent *event )
141{
142 if ( !mDrawBoxDepressed && event->buttons() & Qt::LeftButton )
143 {
144 mCurrentHoverBox = swatchForPosition( event->pos() );
145 mDrawBoxDepressed = true;
146 repaint();
147 }
148 mPressedOnWidget = true;
149}
150
152{
153 if ( !mPressedOnWidget )
154 {
155 return;
156 }
157
158 const int box = swatchForPosition( event->pos() );
159 if ( mDrawBoxDepressed && event->button() == Qt::LeftButton )
160 {
161 mCurrentHoverBox = box;
162 mDrawBoxDepressed = false;
163 repaint();
164 }
165
166 if ( box >= 0 && box < mColors.length() && event->button() == Qt::LeftButton )
167 {
168 //color clicked
169 emit colorChanged( mColors.at( box ).first );
170 }
171}
172
173void QgsColorSwatchGrid::keyPressEvent( QKeyEvent *event )
174{
175 //handle keyboard navigation
176 if ( event->key() == Qt::Key_Right )
177 {
178 mCurrentFocusBox = std::min<int>( mCurrentFocusBox + 1, mColors.length() - 1 );
179 }
180 else if ( event->key() == Qt::Key_Left )
181 {
182 mCurrentFocusBox = std::max<int>( mCurrentFocusBox - 1, 0 );
183 }
184 else if ( event->key() == Qt::Key_Up )
185 {
186 int currentRow = mCurrentFocusBox / NUMBER_COLORS_PER_ROW;
187 const int currentColumn = mCurrentFocusBox % NUMBER_COLORS_PER_ROW;
188 currentRow--;
189
190 if ( currentRow >= 0 )
191 {
192 mCurrentFocusBox = currentRow * NUMBER_COLORS_PER_ROW + currentColumn;
193 }
194 else
195 {
196 //moved above first row
197 focusPreviousChild();
198 }
199 }
200 else if ( event->key() == Qt::Key_Down )
201 {
202 int currentRow = mCurrentFocusBox / NUMBER_COLORS_PER_ROW;
203 const int currentColumn = mCurrentFocusBox % NUMBER_COLORS_PER_ROW;
204 currentRow++;
205 const int box = currentRow * NUMBER_COLORS_PER_ROW + currentColumn;
206
207 if ( box < mColors.length() )
208 {
209 mCurrentFocusBox = box;
210 }
211 else
212 {
213 //moved below first row
214 focusNextChild();
215 }
216 }
217 else if ( event->key() == Qt::Key_Enter || event->key() == Qt::Key_Space )
218 {
219 //color clicked
220 emit colorChanged( mColors.at( mCurrentFocusBox ).first );
221 }
222 else
223 {
224 //some other key, pass it on
225 QWidget::keyPressEvent( event );
226 return;
227 }
228
229 repaint();
230}
231
232void QgsColorSwatchGrid::focusInEvent( QFocusEvent *event )
233{
234 Q_UNUSED( event )
235 mFocused = true;
236 repaint();
237}
238
239void QgsColorSwatchGrid::focusOutEvent( QFocusEvent *event )
240{
241 Q_UNUSED( event )
242 mFocused = false;
243 repaint();
244}
245
246int QgsColorSwatchGrid::calculateHeight() const
247{
248 const int numberRows = std::ceil( static_cast<double>( mColors.length() ) / NUMBER_COLORS_PER_ROW );
249 return numberRows * ( mSwatchSize ) + ( numberRows - 1 ) * mSwatchSpacing + mSwatchMargin + mLabelHeight + 0.5 * mLabelMargin + mSwatchMargin;
250}
251
252void QgsColorSwatchGrid::draw( QPainter &painter )
253{
254 const QPalette pal = QPalette( qApp->palette() );
255 const QColor headerBgColor = pal.color( QPalette::Mid );
256 const QColor headerTextColor = pal.color( QPalette::BrightText );
257 const QColor highlight = pal.color( QPalette::Highlight );
258
259 //draw header background
260 painter.setBrush( headerBgColor );
261 painter.setPen( Qt::NoPen );
262 painter.drawRect( QRect( 0, 0, width(), mLabelHeight + 0.5 * mLabelMargin ) );
263
264 //draw header text
265 painter.setPen( headerTextColor );
266 painter.drawText( QRect( mLabelMargin, 0.25 * mLabelMargin, width() - 2 * mLabelMargin, mLabelHeight ), Qt::AlignLeft | Qt::AlignVCenter, mScheme->schemeName() );
267
268 //draw color swatches
269 QgsNamedColorList::const_iterator colorIt = mColors.constBegin();
270 int index = 0;
271 for ( ; colorIt != mColors.constEnd(); ++colorIt )
272 {
273 const int row = index / NUMBER_COLORS_PER_ROW;
274 const int column = index % NUMBER_COLORS_PER_ROW;
275
276 QRect swatchRect = QRect( column * ( mSwatchSize + mSwatchSpacing ) + mSwatchMargin, row * ( mSwatchSize + mSwatchSpacing ) + mSwatchMargin + mLabelHeight + 0.5 * mLabelMargin, mSwatchSize, mSwatchSize );
277
278 if ( mCurrentHoverBox == index )
279 {
280 //hovered boxes are slightly larger
281 swatchRect.adjust( -1, -1, 1, 1 );
282 }
283
284 //start with checkboard pattern for semi-transparent colors
285 if ( ( *colorIt ).first.alpha() != 255 )
286 {
287 const QBrush checkBrush = QBrush( transparentBackground() );
288 painter.setPen( Qt::NoPen );
289 painter.setBrush( checkBrush );
290 painter.drawRect( swatchRect );
291 }
292
293 if ( mCurrentHoverBox == index )
294 {
295 if ( mDrawBoxDepressed )
296 {
297 painter.setPen( QPen( QColor( 100, 100, 100 ), mSwatchOutlineSize ) );
298 }
299 else
300 {
301 //hover color
302 painter.setPen( QPen( QColor( 220, 220, 220 ), mSwatchOutlineSize ) );
303 }
304 }
305 else if ( mFocused && index == mCurrentFocusBox )
306 {
307 painter.setPen( highlight );
308 }
309 else if ( ( *colorIt ).first.name() == mBaseColor.name() )
310 {
311 //currently active color
312 painter.setPen( QPen( QColor( 75, 75, 75 ), mSwatchOutlineSize ) );
313 }
314 else
315 {
316 painter.setPen( QPen( QColor( 197, 197, 197 ), mSwatchOutlineSize ) );
317 }
318
319 painter.setBrush( ( *colorIt ).first );
320 painter.drawRect( swatchRect );
321
322 index++;
323 }
324}
325
326QPixmap QgsColorSwatchGrid::transparentBackground()
327{
328 static QPixmap sTranspBkgrd;
329
330 if ( sTranspBkgrd.isNull() )
331 sTranspBkgrd = QgsApplication::getThemePixmap( QStringLiteral( "/transp-background_8x8.png" ) );
332
333 return sTranspBkgrd;
334}
335
336int QgsColorSwatchGrid::swatchForPosition( QPoint position ) const
337{
338 //calculate box for position
339 int box = -1;
340 const int column = ( position.x() - mSwatchMargin ) / ( mSwatchSize + mSwatchSpacing );
341 const int xRem = ( position.x() - mSwatchMargin ) % ( mSwatchSize + mSwatchSpacing );
342 const int row = ( position.y() - mSwatchMargin - mLabelHeight ) / ( mSwatchSize + mSwatchSpacing );
343 const int yRem = ( position.y() - mSwatchMargin - mLabelHeight ) % ( mSwatchSize + mSwatchSpacing );
344
345 if ( xRem <= mSwatchSize + 1 && yRem <= mSwatchSize + 1 && column < NUMBER_COLORS_PER_ROW )
346 {
347 //if pos is actually inside a valid box, calculate which box
348 box = column + row * NUMBER_COLORS_PER_ROW;
349 }
350 return box;
351}
352
353
354//
355// QgsColorGridAction
356//
357
358
359QgsColorSwatchGridAction::QgsColorSwatchGridAction( QgsColorScheme *scheme, QMenu *menu, const QString &context, QWidget *parent )
360 : QWidgetAction( parent )
361 , mMenu( menu )
362 , mSuppressRecurse( false )
363 , mDismissOnColorSelection( true )
364{
365 mColorSwatchGrid = new QgsColorSwatchGrid( scheme, context, parent );
366
367 setDefaultWidget( mColorSwatchGrid );
368 connect( mColorSwatchGrid, &QgsColorSwatchGrid::colorChanged, this, &QgsColorSwatchGridAction::setColor );
369
370 connect( this, &QAction::hovered, this, &QgsColorSwatchGridAction::onHover );
371 connect( mColorSwatchGrid, &QgsColorSwatchGrid::hovered, this, &QgsColorSwatchGridAction::onHover );
372
373 //hide the action if no colors to be shown
374 setVisible( !mColorSwatchGrid->colors()->isEmpty() );
375}
376
377void QgsColorSwatchGridAction::setBaseColor( const QColor &baseColor )
378{
379 mColorSwatchGrid->setBaseColor( baseColor );
380}
381
383{
384 return mColorSwatchGrid->baseColor();
385}
386
388{
389 return mColorSwatchGrid->context();
390}
391
392void QgsColorSwatchGridAction::setContext( const QString &context )
393{
394 mColorSwatchGrid->setContext( context );
395}
396
398{
399 mColorSwatchGrid->refreshColors();
400 //hide the action if no colors shown
401 setVisible( !mColorSwatchGrid->colors()->isEmpty() );
402}
403
404void QgsColorSwatchGridAction::setColor( const QColor &color )
405{
406 emit colorChanged( color );
407 QAction::trigger();
408 if ( mMenu && mDismissOnColorSelection )
409 {
410 mMenu->hide();
411 }
412}
413
414void QgsColorSwatchGridAction::onHover()
415{
416 //see https://bugreports.qt.io/browse/QTBUG-10427?focusedCommentId=185610&page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel#comment-185610
417
418 if ( mSuppressRecurse )
419 {
420 return;
421 }
422
423 if ( mMenu )
424 {
425 mSuppressRecurse = true;
426 mMenu->setActiveAction( this );
427 mSuppressRecurse = false;
428 }
429}
static const double UI_SCALE_FACTOR
UI scaling factor.
Definition qgis.h:5733
static QPixmap getThemePixmap(const QString &name, const QColor &foreColor=QColor(), const QColor &backColor=QColor(), int size=16)
Helper to get a theme icon as a pixmap.
Abstract base class for color schemes.
virtual QString schemeName() const =0
Gets the name for the color scheme.
virtual QgsNamedColorList fetchColors(const QString &context=QString(), const QColor &baseColor=QColor())=0
Gets a list of colors from the scheme.
void setBaseColor(const QColor &baseColor)
Sets the base color for the color grid.
QString context() const
Gets the current context for the color grid.
void colorChanged(const QColor &color)
Emitted when a color has been selected from the widget.
QgsColorSwatchGridAction(QgsColorScheme *scheme, QMenu *menu=nullptr, const QString &context=QString(), QWidget *parent=nullptr)
Construct a new color swatch grid action.
void refreshColors()
Reload colors from scheme and redraws the widget.
QColor baseColor() const
Gets the base color for the color grid.
void setContext(const QString &context)
Sets the current context for the color grid.
A grid of color swatches, which allows for user selection.
void mouseMoveEvent(QMouseEvent *event) override
void mousePressEvent(QMouseEvent *event) override
QColor baseColor() const
Gets the base color for the widget.
QgsColorSwatchGrid(QgsColorScheme *scheme, const QString &context=QString(), QWidget *parent=nullptr)
Construct a new color swatch grid.
void paintEvent(QPaintEvent *event) override
QgsNamedColorList * colors()
Gets the list of colors shown in the grid.
void colorChanged(const QColor &color)
Emitted when a color has been selected from the widget.
void setBaseColor(const QColor &baseColor)
Sets the base color for the widget.
QSize minimumSizeHint() const override
void mouseReleaseEvent(QMouseEvent *event) override
QSize sizeHint() const override
void refreshColors()
Reload colors from scheme and redraws the widget.
void focusInEvent(QFocusEvent *event) override
void keyPressEvent(QKeyEvent *event) override
void focusOutEvent(QFocusEvent *event) override
void hovered()
Emitted when mouse hovers over widget.
void setContext(const QString &context)
Sets the current context for the grid.
QString context() const
Gets the current context for the grid.
static QString htmlDescription(QColor color, T *widget)
Returns an HTML desciption given a color with a preview image of the color.
#define NUMBER_COLORS_PER_ROW