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