QGIS API Documentation  2.5.0-Master
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros Groups Pages
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 
33 QgsColorSwatchGrid::QgsColorSwatchGrid( QgsColorScheme* scheme, QString context, QWidget *parent )
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 
70 void QgsColorSwatchGrid::setContext( const QString context )
71 {
72  mContext = context;
73  refreshColors();
74 }
75 
76 void QgsColorSwatchGrid::setBaseColor( const QColor baseColor )
77 {
79  refreshColors();
80 }
81 
83 {
84  //get colors from scheme
86 
87  //have to update size of widget in case number of colors has changed
88  updateGeometry();
89  repaint();
90 }
91 
92 void QgsColorSwatchGrid::paintEvent( QPaintEvent *event )
93 {
94  Q_UNUSED( event );
95  QPainter painter( this );
96  draw( painter );
97  painter.end();
98 }
99 
100 void QgsColorSwatchGrid::mouseMoveEvent( QMouseEvent *event )
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 
139 void QgsColorSwatchGrid::mousePressEvent( QMouseEvent *event )
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 
150 void QgsColorSwatchGrid::mouseReleaseEvent( QMouseEvent *event )
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 
172 void QgsColorSwatchGrid::keyPressEvent( QKeyEvent *event )
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
196  focusPreviousChild();
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 
231 void QgsColorSwatchGrid::focusInEvent( QFocusEvent *event )
232 {
233  Q_UNUSED( event );
234  mFocused = true;
235  repaint();
236 }
237 
238 void QgsColorSwatchGrid::focusOutEvent( QFocusEvent *event )
239 {
240  Q_UNUSED( event );
241  mFocused = false;
242  repaint();
243 }
244 
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::iterator colorIt = mColors.begin();
270  int index = 0;
271  for ( ; colorIt != mColors.end(); ++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
312  {
313  painter.setPen( QColor( 197, 197, 197 ) );
314  }
315 
316  painter.setBrush(( *colorIt ).first );
317  painter.drawRect( swatchRect );
318 
319  index++;
320  }
321 }
322 
324 {
325  static QPixmap transpBkgrd;
326 
327  if ( transpBkgrd.isNull() )
328  transpBkgrd = QgsApplication::getThemePixmap( "/transp-background_8x8.png" );
329 
330  return transpBkgrd;
331 }
332 
333 int QgsColorSwatchGrid::swatchForPosition( const QPoint &position ) const
334 {
335  //calculate box for position
336  int box = -1;
337  int column = ( position.x() - LEFT_MARGIN ) / ( SWATCH_SIZE + SWATCH_SPACING );
338  int xRem = ( position.x() - LEFT_MARGIN ) % ( SWATCH_SIZE + SWATCH_SPACING );
339  int row = ( position.y() - TOP_MARGIN - LABEL_SIZE ) / ( SWATCH_SIZE + SWATCH_SPACING );
340  int yRem = ( position.y() - TOP_MARGIN - LABEL_SIZE ) % ( SWATCH_SIZE + SWATCH_SPACING );
341 
342  if ( xRem <= SWATCH_SIZE + 1 && yRem <= SWATCH_SIZE + 1 && column < NUMBER_COLORS_PER_ROW )
343  {
344  //if pos is actually inside a valid box, calculate which box
345  box = column + row * NUMBER_COLORS_PER_ROW;
346  }
347  return box;
348 }
349 
350 
351 //
352 // QgsColorGridAction
353 //
354 
355 
356 QgsColorSwatchGridAction::QgsColorSwatchGridAction( QgsColorScheme* scheme, QMenu *menu, QString context, QWidget *parent )
357  : QWidgetAction( parent )
358  , mMenu( menu )
359  , mSuppressRecurse( false )
360 {
361  mColorSwatchGrid = new QgsColorSwatchGrid( scheme, context, parent );
362 
363  setDefaultWidget( mColorSwatchGrid );
364  connect( mColorSwatchGrid, SIGNAL( colorChanged( QColor ) ), this, SLOT( setColor( QColor ) ) );
365 
366  connect( this, SIGNAL( hovered() ), this, SLOT( onHover() ) );
367  connect( mColorSwatchGrid, SIGNAL( hovered() ), this, SLOT( onHover() ) );
368 
369  //hide the action if no colors to be shown
370  setVisible( mColorSwatchGrid->colors()->count() > 0 );
371 }
372 
374 {
375 
376 }
377 
378 void QgsColorSwatchGridAction::setBaseColor( const QColor baseColor )
379 {
380  mColorSwatchGrid->setBaseColor( baseColor );
381 }
382 
384 {
385  return mColorSwatchGrid->baseColor();
386 }
387 
389 {
390  return mColorSwatchGrid->context();
391 }
392 
393 void QgsColorSwatchGridAction::setContext( const QString context )
394 {
395  mColorSwatchGrid->setContext( context );
396 }
397 
399 {
401  //hide the action if no colors shown
402  setVisible( mColorSwatchGrid->colors()->count() > 0 );
403 }
404 
405 void QgsColorSwatchGridAction::setColor( const QColor &color )
406 {
407  emit colorChanged( color );
408  QAction::trigger();
409  if ( mMenu )
410  {
411  mMenu->hide();
412  }
413 }
414 
416 {
417  //see https://bugreports.qt-project.org/browse/QTBUG-10427?focusedCommentId=185610&page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel#comment-185610
418 
419  if ( mSuppressRecurse )
420  {
421  return;
422  }
423 
424  if ( mMenu )
425  {
426  mSuppressRecurse = true;
427  mMenu->setActiveAction( this );
428  mSuppressRecurse = false;
429  }
430 }
static unsigned index
void paintEvent(QPaintEvent *event)
void setContext(const QString context)
Sets the current context for the grid.
void focusInEvent(QFocusEvent *event)
void setBaseColor(const QColor baseColor)
Sets the base color for the color grid.
void refreshColors()
Reload colors from scheme and redraws the widget.
Abstract base class for color schemes.
void colorChanged(const QColor color)
Emitted when a color has been selected from the widget.
const QPixmap & transparentBackground()
Generates a checkboard pattern for transparent color backgrounds.
QgsColorScheme * mScheme
A grid of color swatches, which allows for user selection.
void keyPressEvent(QKeyEvent *event)
void updateTooltip(const int colorIdx)
Updates the widget's tooltip for a given color index.
#define LEFT_MARGIN
static QPixmap getThemePixmap(const QString &theName)
Helper to get a theme icon as a pixmap.
QgsColorSwatchGrid * mColorSwatchGrid
QgsNamedColorList * colors()
Gets the list of colors shown in the grid.
#define RIGHT_MARGIN
void onHover()
Handles setting the active action for the menu when cursor hovers over color grid.
#define NUMBER_COLORS_PER_ROW
void hovered()
Emitted when mouse hovers over widget.
QString context() const
Get the current context for the grid.
QgsColorSwatchGridAction(QgsColorScheme *scheme, QMenu *menu=0, QString context=QString(), QWidget *parent=0)
Construct a new color swatch grid action.
int calculateHeight() const
Calculate height of widget based on number of colors.
QgsNamedColorList mColors
void mouseMoveEvent(QMouseEvent *event)
#define LABEL_MARGIN
void mousePressEvent(QMouseEvent *event)
void setColor(const QColor &color)
Emits color changed signal and closes parent menu.
QColor baseColor() const
Get the base color for the color grid.
virtual QSize sizeHint() const
#define TOP_MARGIN
void draw(QPainter &painter)
Draws widget.
virtual QString schemeName() const =0
Gets the name for the color scheme.
void focusOutEvent(QFocusEvent *event)
virtual QgsNamedColorList fetchColors(const QString context=QString(), const QColor baseColor=QColor())=0
Gets a list of colors from the scheme.
void mouseReleaseEvent(QMouseEvent *event)
QgsColorSwatchGrid(QgsColorScheme *scheme, QString context=QString(), QWidget *parent=0)
Construct a new color swatch grid.
#define SWATCH_SPACING
void refreshColors()
Reload colors from scheme and redraws the widget.
#define LABEL_SIZE
int swatchForPosition(const QPoint &position) const
Calculate swatch corresponding to a position within the widget.
QColor baseColor() const
Get the base color for the widget.
#define SWATCH_SIZE
void setBaseColor(const QColor baseColor)
Sets the base color for the widget.
void colorChanged(const QColor color)
Emitted when a color has been selected from the widget.
void setContext(const QString context)
Sets the current context for the color grid.
QString context() const
Get the current context for the color grid.
virtual QSize minimumSizeHint() const
#define tr(sourceText)
#define BOTTOM_MARGIN