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