QGIS API Documentation  2.8.2-Wien
 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 {
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
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 
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::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 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( const 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 
361 QgsColorSwatchGridAction::QgsColorSwatchGridAction( QgsColorScheme* scheme, QMenu *menu, QString context, QWidget *parent )
362  : QWidgetAction( parent )
363  , mMenu( menu )
364  , mSuppressRecurse( false )
365 {
366  mColorSwatchGrid = new QgsColorSwatchGrid( scheme, context, parent );
367 
368  setDefaultWidget( mColorSwatchGrid );
369  connect( mColorSwatchGrid, SIGNAL( colorChanged( QColor ) ), this, SLOT( setColor( QColor ) ) );
370 
371  connect( this, SIGNAL( hovered() ), this, SLOT( onHover() ) );
372  connect( mColorSwatchGrid, SIGNAL( hovered() ), this, SLOT( onHover() ) );
373 
374  //hide the action if no colors to be shown
375  setVisible( mColorSwatchGrid->colors()->count() > 0 );
376 }
377 
379 {
380 
381 }
382 
383 void QgsColorSwatchGridAction::setBaseColor( const QColor &baseColor )
384 {
385  mColorSwatchGrid->setBaseColor( baseColor );
386 }
387 
389 {
390  return mColorSwatchGrid->baseColor();
391 }
392 
394 {
395  return mColorSwatchGrid->context();
396 }
397 
398 void QgsColorSwatchGridAction::setContext( const QString &context )
399 {
400  mColorSwatchGrid->setContext( context );
401 }
402 
404 {
405  mColorSwatchGrid->refreshColors();
406  //hide the action if no colors shown
407  setVisible( mColorSwatchGrid->colors()->count() > 0 );
408 }
409 
410 void QgsColorSwatchGridAction::setColor( const QColor &color )
411 {
412  emit colorChanged( color );
413  QAction::trigger();
414  if ( mMenu )
415  {
416  mMenu->hide();
417  }
418 }
419 
420 void QgsColorSwatchGridAction::onHover()
421 {
422  //see https://bugreports.qt-project.org/browse/QTBUG-10427?focusedCommentId=185610&page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel#comment-185610
423 
424  if ( mSuppressRecurse )
425  {
426  return;
427  }
428 
429  if ( mMenu )
430  {
431  mSuppressRecurse = true;
432  mMenu->setActiveAction( this );
433  mSuppressRecurse = false;
434  }
435 }