QGIS API Documentation  2.14.0-Essen
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 "qgsapplication.h"
18 #include "qgslogger.h"
19 #include <QPainter>
20 #include <QMouseEvent>
21 #include <QMenu>
22 
23 #define NUMBER_COLORS_PER_ROW 10 //number of color swatches per row
24 #define SWATCH_SIZE 14 //width/height of color swatches
25 #define SWATCH_SPACING 4 //horizontal/vertical gap between swatches
26 #define LEFT_MARGIN 6 //margin between left edge and first swatch
27 #define RIGHT_MARGIN 6 //margin between right edge and last swatch
28 #define TOP_MARGIN 6 //margin between label and first swatch
29 #define BOTTOM_MARGIN 6 //margin between last swatch row and end of widget
30 #define LABEL_SIZE 20 //label rect height
31 #define LABEL_MARGIN 4 //spacing between label box and text
32 
34  : QWidget( parent )
35  , mScheme( scheme )
36  , mContext( context )
37  , mDrawBoxDepressed( false )
38  , mCurrentHoverBox( -1 )
39  , mFocused( false )
40  , mCurrentFocusBox( 0 )
41  , mPressedOnWidget( false )
42 {
43  //need to receive all mouse over events
44  setMouseTracking( true );
45 
46  setFocusPolicy( Qt::StrongFocus );
47  setSizePolicy( QSizePolicy::Fixed, QSizePolicy::Fixed );
48 
49  //calculate widget width
51 
52  refreshColors();
53 }
54 
56 {
57 
58 }
59 
61 {
62  return QSize( mWidth, calculateHeight() );
63 }
64 
66 {
67  return QSize( mWidth, calculateHeight() );
68 }
69 
71 {
72  mContext = context;
73  refreshColors();
74 }
75 
77 {
78  mBaseColor = baseColor;
79  refreshColors();
80 }
81 
83 {
84  //get colors from scheme
85  mColors = mScheme->fetchColors( mContext, mBaseColor );
86 
87  //have to update size of widget in case number of colors has changed
89  repaint();
90 }
91 
93 {
94  Q_UNUSED( event );
95  QPainter painter( this );
96  draw( painter );
97  painter.end();
98 }
99 
101 {
102  //calculate box mouse cursor is over
103  int newBox = swatchForPosition( event->pos() );
104 
105  mDrawBoxDepressed = event->buttons() & Qt::LeftButton;
106  if ( newBox != mCurrentHoverBox )
107  {
108  //only repaint if changes are required
109  mCurrentHoverBox = newBox;
110  repaint();
111 
112  updateTooltip( newBox );
113  }
114 
115  emit hovered();
116 }
117 
118 void QgsColorSwatchGrid::updateTooltip( const int colorIdx )
119 {
120  if ( colorIdx >= 0 && colorIdx < mColors.length() )
121  {
122  //if color has an associated name from the color scheme, use that
123  QString colorName = mColors.at( colorIdx ).second;
124  if ( colorName.isEmpty() )
125  {
126  //otherwise, build a default string
127  QColor color = mColors.at( colorIdx ).first;
128  colorName = QString( tr( "rgb(%1, %2, %3)" ) ).arg( color.red() ).arg( color.green() ).arg( color.blue() );
129  }
130  setToolTip( colorName );
131  }
132  else
133  {
134  //clear tooltip
135  setToolTip( QString() );
136  }
137 }
138 
140 {
141  if ( !mDrawBoxDepressed && event->buttons() & Qt::LeftButton )
142  {
143  mCurrentHoverBox = swatchForPosition( event->pos() );
144  mDrawBoxDepressed = true;
145  repaint();
146  }
147  mPressedOnWidget = true;
148 }
149 
151 {
152  if ( ! mPressedOnWidget )
153  {
154  return;
155  }
156 
157  int box = swatchForPosition( event->pos() );
158  if ( mDrawBoxDepressed && event->button() == Qt::LeftButton )
159  {
160  mCurrentHoverBox = box;
161  mDrawBoxDepressed = false;
162  repaint();
163  }
164 
165  if ( box >= 0 && box < mColors.length() && event->button() == Qt::LeftButton )
166  {
167  //color clicked
168  emit colorChanged( mColors.at( box ).first );
169  }
170 }
171 
173 {
174  //handle keyboard navigation
175  if ( event->key() == Qt::Key_Right )
176  {
177  mCurrentFocusBox = qMin( mCurrentFocusBox + 1, mColors.length() - 1 );
178  }
179  else if ( event->key() == Qt::Key_Left )
180  {
181  mCurrentFocusBox = qMax( mCurrentFocusBox - 1, 0 );
182  }
183  else if ( event->key() == Qt::Key_Up )
184  {
185  int currentRow = mCurrentFocusBox / NUMBER_COLORS_PER_ROW;
186  int currentColumn = mCurrentFocusBox % NUMBER_COLORS_PER_ROW;
187  currentRow--;
188 
189  if ( currentRow >= 0 )
190  {
191  mCurrentFocusBox = currentRow * NUMBER_COLORS_PER_ROW + currentColumn;
192  }
193  else
194  {
195  //moved above first row
197  }
198  }
199  else if ( event->key() == Qt::Key_Down )
200  {
201  int currentRow = mCurrentFocusBox / NUMBER_COLORS_PER_ROW;
202  int currentColumn = mCurrentFocusBox % NUMBER_COLORS_PER_ROW;
203  currentRow++;
204  int box = currentRow * NUMBER_COLORS_PER_ROW + currentColumn;
205 
206  if ( box < mColors.length() )
207  {
208  mCurrentFocusBox = box;
209  }
210  else
211  {
212  //moved below first row
213  focusNextChild();
214  }
215  }
216  else if ( event->key() == Qt::Key_Enter || event->key() == Qt::Key_Space )
217  {
218  //color clicked
219  emit colorChanged( mColors.at( mCurrentFocusBox ).first );
220  }
221  else
222  {
223  //some other key, pass it on
224  QWidget::keyPressEvent( event );
225  return;
226  }
227 
228  repaint();
229 }
230 
232 {
233  Q_UNUSED( event );
234  mFocused = true;
235  repaint();
236 }
237 
239 {
240  Q_UNUSED( event );
241  mFocused = false;
242  repaint();
243 }
244 
245 int QgsColorSwatchGrid::calculateHeight() const
246 {
247  int numberRows = ceil(( double )mColors.length() / NUMBER_COLORS_PER_ROW );
248  return numberRows * ( SWATCH_SIZE ) + ( numberRows - 1 ) * SWATCH_SPACING + TOP_MARGIN + LABEL_SIZE + BOTTOM_MARGIN;
249 }
250 
251 void QgsColorSwatchGrid::draw( QPainter &painter )
252 {
253  QPalette pal = QPalette( qApp->palette() );
254  QColor headerBgColor = pal.color( QPalette::Mid );
255  QColor headerTextColor = pal.color( QPalette::BrightText );
256  QColor highlight = pal.color( QPalette::Highlight );
257 
258  //draw header background
259  painter.setBrush( headerBgColor );
260  painter.setPen( Qt::NoPen );
261  painter.drawRect( QRect( 0, 0, width(), LABEL_SIZE ) );
262 
263  //draw header text
264  painter.setPen( headerTextColor );
265  painter.drawText( QRect( LABEL_MARGIN, 0, width() - 2 * LABEL_MARGIN, LABEL_SIZE ),
266  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  int row = index / NUMBER_COLORS_PER_ROW;
274  int column = index % NUMBER_COLORS_PER_ROW;
275 
276  QRect swatchRect = QRect( column * ( SWATCH_SIZE + SWATCH_SPACING ) + LEFT_MARGIN,
279 
280  if ( mCurrentHoverBox == index )
281  {
282  //hovered boxes are slightly larger
283  swatchRect.adjust( -1, -1, 1, 1 );
284  }
285 
286  //start with checkboard pattern for semi-transparent colors
287  if (( *colorIt ).first.alpha() != 255 )
288  {
289  QBrush checkBrush = QBrush( transparentBackground() );
290  painter.setPen( Qt::NoPen );
291  painter.setBrush( checkBrush );
292  painter.drawRect( swatchRect );
293  }
294 
295  if ( mCurrentHoverBox == index )
296  {
297  if ( mDrawBoxDepressed )
298  {
299  painter.setPen( QColor( 100, 100, 100 ) );
300  }
301  else
302  {
303  //hover color
304  painter.setPen( QColor( 220, 220, 220 ) );
305  }
306  }
307  else if ( mFocused && index == mCurrentFocusBox )
308  {
309  painter.setPen( highlight );
310  }
311  else if (( *colorIt ).first.name() == mBaseColor.name() )
312  {
313  //currently active color
314  painter.setPen( QColor( 75, 75, 75 ) );
315  }
316  else
317  {
318  painter.setPen( QColor( 197, 197, 197 ) );
319  }
320 
321  painter.setBrush(( *colorIt ).first );
322  painter.drawRect( swatchRect );
323 
324  index++;
325  }
326 }
327 
328 const QPixmap& QgsColorSwatchGrid::transparentBackground()
329 {
330  static QPixmap transpBkgrd;
331 
332  if ( transpBkgrd.isNull() )
333  transpBkgrd = QgsApplication::getThemePixmap( "/transp-background_8x8.png" );
334 
335  return transpBkgrd;
336 }
337 
338 int QgsColorSwatchGrid::swatchForPosition( QPoint position ) const
339 {
340  //calculate box for position
341  int box = -1;
342  int column = ( position.x() - LEFT_MARGIN ) / ( SWATCH_SIZE + SWATCH_SPACING );
343  int xRem = ( position.x() - LEFT_MARGIN ) % ( SWATCH_SIZE + SWATCH_SPACING );
344  int row = ( position.y() - TOP_MARGIN - LABEL_SIZE ) / ( SWATCH_SIZE + SWATCH_SPACING );
345  int yRem = ( position.y() - TOP_MARGIN - LABEL_SIZE ) % ( SWATCH_SIZE + SWATCH_SPACING );
346 
347  if ( xRem <= SWATCH_SIZE + 1 && yRem <= SWATCH_SIZE + 1 && column < NUMBER_COLORS_PER_ROW )
348  {
349  //if pos is actually inside a valid box, calculate which box
350  box = column + row * NUMBER_COLORS_PER_ROW;
351  }
352  return box;
353 }
354 
355 
356 //
357 // QgsColorGridAction
358 //
359 
360 
362  : QWidgetAction( parent )
363  , mMenu( menu )
364  , mSuppressRecurse( false )
365  , mDismissOnColorSelection( true )
366 {
367  mColorSwatchGrid = new QgsColorSwatchGrid( scheme, context, parent );
368 
369  setDefaultWidget( mColorSwatchGrid );
370  connect( mColorSwatchGrid, SIGNAL( colorChanged( QColor ) ), this, SLOT( setColor( QColor ) ) );
371 
372  connect( this, SIGNAL( hovered() ), this, SLOT( onHover() ) );
373  connect( mColorSwatchGrid, SIGNAL( hovered() ), this, SLOT( onHover() ) );
374 
375  //hide the action if no colors to be shown
376  setVisible( !mColorSwatchGrid->colors()->isEmpty() );
377 }
378 
380 {
381 
382 }
383 
385 {
386  mColorSwatchGrid->setBaseColor( baseColor );
387 }
388 
390 {
391  return mColorSwatchGrid->baseColor();
392 }
393 
395 {
396  return mColorSwatchGrid->context();
397 }
398 
400 {
401  mColorSwatchGrid->setContext( context );
402 }
403 
405 {
406  mColorSwatchGrid->refreshColors();
407  //hide the action if no colors shown
408  setVisible( !mColorSwatchGrid->colors()->isEmpty() );
409 }
410 
411 void QgsColorSwatchGridAction::setColor( const QColor &color )
412 {
413  emit colorChanged( color );
415  if ( mMenu && mDismissOnColorSelection )
416  {
417  mMenu->hide();
418  }
419 }
420 
421 void QgsColorSwatchGridAction::onHover()
422 {
423  //see https://bugreports.qt-project.org/browse/QTBUG-10427?focusedCommentId=185610&page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel#comment-185610
424 
425  if ( mSuppressRecurse )
426  {
427  return;
428  }
429 
430  if ( mMenu )
431  {
432  mSuppressRecurse = true;
433  mMenu->setActiveAction( this );
434  mSuppressRecurse = false;
435  }
436 }
void mouseReleaseEvent(QMouseEvent *event) override
void focusOutEvent(QFocusEvent *event) override
static unsigned index
bool end()
QgsColorSwatchGrid(QgsColorScheme *scheme, const QString &context=QString(), QWidget *parent=nullptr)
Construct a new color swatch grid.
void updateGeometry()
void colorChanged(const QColor &color)
Emitted when a color has been selected from the widget.
void refreshColors()
Reload colors from scheme and redraws the widget.
QString name() const
int length() const
void setFocusPolicy(Qt::FocusPolicy policy)
Abstract base class for color schemes.
void paintEvent(QPaintEvent *event) override
virtual QSize sizeHint() const override
void setActiveAction(QAction *act)
void setVisible(bool)
const QColor & color(ColorGroup group, ColorRole role) const
const T & at(int i) const
bool focusPreviousChild()
virtual QSize minimumSizeHint() const override
void colorChanged(const QColor &color)
Emitted when a color has been selected from the widget.
A grid of color swatches, which allows for user selection.
Qt::MouseButtons buttons() const
#define LEFT_MARGIN
QString tr(const char *sourceText, const char *disambiguation, int n)
static QPixmap getThemePixmap(const QString &theName)
Helper to get a theme icon as a pixmap.
int x() const
int y() const
QgsNamedColorList * colors()
Gets the list of colors shown in the grid.
void trigger()
#define RIGHT_MARGIN
int width() const
void drawRect(const QRectF &rectangle)
#define NUMBER_COLORS_PER_ROW
QgsColorSwatchGridAction(QgsColorScheme *scheme, QMenu *menu=nullptr, const QString &context=QString(), QWidget *parent=nullptr)
Construct a new color swatch grid action.
void hovered()
Emitted when mouse hovers over widget.
QString context() const
Get the current context for the grid.
int red() const
void setPen(const QColor &color)
Qt::MouseButton button() const
bool isEmpty() const
bool isEmpty() const
void setBrush(const QBrush &brush)
void drawText(const QPointF &position, const QString &text)
void hide()
void setSizePolicy(QSizePolicy)
void mousePressEvent(QMouseEvent *event) override
int green() const
int key() const
void mouseMoveEvent(QMouseEvent *event) override
bool isNull() const
void repaint()
#define LABEL_MARGIN
QColor baseColor() const
Get the base color for the color grid.
int blue() const
void keyPressEvent(QKeyEvent *event) override
void setDefaultWidget(QWidget *widget)
#define TOP_MARGIN
void hovered()
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 adjust(int dx1, int dy1, int dx2, int dy2)
virtual void keyPressEvent(QKeyEvent *event)
void setBaseColor(const QColor &baseColor)
Sets the base color for the widget.
void setMouseTracking(bool enable)
#define SWATCH_SPACING
void refreshColors()
Reload colors from scheme and redraws the widget.
const QPoint & pos() const
void setToolTip(const QString &)
const_iterator constEnd() const
const_iterator constBegin() const
#define LABEL_SIZE
void setContext(const QString &context)
Sets the current context for the grid.
QColor baseColor() const
Get the base color for the widget.
bool connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
#define SWATCH_SIZE
bool focusNextChild()
QObject * parent() const
QString arg(qlonglong a, int fieldWidth, int base, const QChar &fillChar) const
virtual bool event(QEvent *event)
void setContext(const QString &context)
Sets the current context for the color grid.
QString context() const
Get the current context for the color grid.
void setBaseColor(const QColor &baseColor)
Sets the base color for the color grid.
#define BOTTOM_MARGIN
void focusInEvent(QFocusEvent *event) override