QGIS API Documentation  2.8.2-Wien
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros Groups Pages
qgscolorbuttonv2.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgscolorbuttonv2.cpp - Button which displays a color
3  --------------------------------------
4  Date : 12-Dec-2006
5  Copyright : (C) 2006 by Tom Elwertowski
6  Email : telwertowski at users dot sourceforge dot net
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 "qgscolorbuttonv2.h"
17 #include "qgscolordialog.h"
18 #include "qgsapplication.h"
19 #include "qgslogger.h"
20 #include "qgssymbollayerv2utils.h"
21 #include "qgscursors.h"
22 #include "qgscolorswatchgrid.h"
23 #include "qgscolorschemeregistry.h"
24 #include "qgscolorwidgets.h"
25 
26 #include <QPainter>
27 #include <QSettings>
28 #include <QTemporaryFile>
29 #include <QMouseEvent>
30 #include <QMenu>
31 #include <QClipboard>
32 #include <QDrag>
33 #include <QDesktopWidget>
34 #include <QStyle>
35 #include <QStyleOptionToolButton>
36 #include <QWidgetAction>
37 #include <QLabel>
38 #include <QGridLayout>
39 #include <QPushButton>
40 
41 QgsColorButtonV2::QgsColorButtonV2( QWidget *parent, QString cdt, QgsColorSchemeRegistry* registry )
42  : QToolButton( parent )
43  , mBehaviour( QgsColorButtonV2::ShowDialog )
44  , mColorDialogTitle( cdt.isEmpty() ? tr( "Select Color" ) : cdt )
45  , mColor( QColor() )
46  , mDefaultColor( QColor() ) //default to invalid color
47  , mAllowAlpha( false )
48  , mAcceptLiveUpdates( true )
49  , mColorSet( false )
50  , mShowNoColorOption( false )
51  , mNoColorString( tr( "No color" ) )
52  , mPickingColor( false )
53  , mMenu( 0 )
54 
55 {
56  //if a color scheme registry was specified, use it, otherwise use the global instance
57  mColorSchemeRegistry = registry ? registry : QgsColorSchemeRegistry::instance();
58 
59  setAcceptDrops( true );
60  setMinimumSize( QSize( 24, 16 ) );
61  connect( this, SIGNAL( clicked() ), this, SLOT( buttonClicked() ) );
62 
63  //setup dropdown menu
64  mMenu = new QMenu( this );
65  connect( mMenu, SIGNAL( aboutToShow() ), this, SLOT( prepareMenu() ) );
66  setMenu( mMenu );
67  setPopupMode( QToolButton::MenuButtonPopup );
68 }
69 
71 {
72 }
73 
75 {
76  //make sure height of button looks good under different platforms
77 #ifdef Q_OS_WIN
78  return QSize( 120, 22 );
79 #else
80  return QSize( 120, 28 );
81 #endif
82 }
83 
85 {
86  static QPixmap transpBkgrd;
87 
88  if ( transpBkgrd.isNull() )
89  transpBkgrd = QgsApplication::getThemePixmap( "/transp-background_8x8.png" );
90 
91  return transpBkgrd;
92 }
93 
94 void QgsColorButtonV2::showColorDialog()
95 {
96  QColor newColor;
97  QSettings settings;
98 
99  //using native color dialogs?
100  bool useNative = settings.value( "/qgis/native_color_dialogs", false ).toBool();
101 
102  if ( useNative )
103  {
104  // use native o/s dialogs
105  if ( mAcceptLiveUpdates && settings.value( "/qgis/live_color_dialogs", false ).toBool() )
106  {
107  newColor = QgsColorDialog::getLiveColor(
108  color(), this, SLOT( setValidColor( const QColor& ) ),
109  this->parentWidget(), mColorDialogTitle, mAllowAlpha ? QColorDialog::ShowAlphaChannel : ( QColorDialog::ColorDialogOption )0 );
110  }
111  else
112  {
113  newColor = QColorDialog::getColor( color(), this->parentWidget(), mColorDialogTitle, mAllowAlpha ? QColorDialog::ShowAlphaChannel : ( QColorDialog::ColorDialogOption )0 );
114  }
115  }
116  else
117  {
118  //use QGIS style color dialogs
119  if ( mAcceptLiveUpdates && settings.value( "/qgis/live_color_dialogs", false ).toBool() )
120  {
122  color(), this, SLOT( setValidColor( const QColor& ) ),
123  this->parentWidget(), mColorDialogTitle, mAllowAlpha );
124  }
125  else
126  {
127  QgsColorDialogV2 dialog( this, 0, color() );
128  dialog.setTitle( mColorDialogTitle );
129  dialog.setAllowAlpha( mAllowAlpha );
130 
131  if ( dialog.exec() )
132  {
133  newColor = dialog.color();
134  }
135  }
136  }
137 
138  if ( newColor.isValid() )
139  {
140  setValidColor( newColor );
141  addRecentColor( newColor );
142  }
143 
144  // reactivate button's window
145  activateWindow();
146 }
147 
149 {
150  if ( !mDefaultColor.isValid() )
151  {
152  return;
153  }
154 
155  setColor( mDefaultColor );
156 }
157 
159 {
160  if ( mAllowAlpha )
161  {
162  setColor( QColor( 0, 0, 0, 0 ) );
163  }
164 }
165 
166 void QgsColorButtonV2::mousePressEvent( QMouseEvent *e )
167 {
168  if ( mPickingColor )
169  {
170  //don't show dialog if in color picker mode
171  e->accept();
172  return;
173  }
174 
175  if ( e->button() == Qt::RightButton )
176  {
178  return;
179  }
180  else if ( e->button() == Qt::LeftButton )
181  {
182  mDragStartPosition = e->pos();
183  }
185 }
186 
187 bool QgsColorButtonV2::colorFromMimeData( const QMimeData * mimeData, QColor& resultColor )
188 {
189  bool hasAlpha = false;
190  QColor mimeColor = QgsSymbolLayerV2Utils::colorFromMimeData( mimeData, hasAlpha );
191 
192  if ( mimeColor.isValid() )
193  {
194  if ( !mAllowAlpha )
195  {
196  //remove alpha channel
197  mimeColor.setAlpha( 255 );
198  }
199  else if ( !hasAlpha )
200  {
201  //mime color has no explicit alpha component, so keep existing alpha
202  mimeColor.setAlpha( mColor.alpha() );
203  }
204  resultColor = mimeColor;
205  return true;
206  }
207 
208  //could not get color from mime data
209  return false;
210 }
211 
212 void QgsColorButtonV2::mouseMoveEvent( QMouseEvent *e )
213 {
214  if ( mPickingColor )
215  {
216  //currently in color picker mode
217  if ( e->buttons() & Qt::LeftButton )
218  {
219  //if left button depressed, sample color under cursor and temporarily update button color
220  //to give feedback to user
221  QPixmap snappedPixmap = QPixmap::grabWindow( QApplication::desktop()->winId(), e->globalPos().x(), e->globalPos().y(), 1, 1 );
222  QImage snappedImage = snappedPixmap.toImage();
223  QColor hoverColor = snappedImage.pixel( 0, 0 );
224  setButtonBackground( hoverColor );
225  }
226  e->accept();
227  return;
228  }
229 
230  //handle dragging colors from button
231 
232  if ( !( e->buttons() & Qt::LeftButton ) || !mColor.isValid() )
233  {
234  //left button not depressed or no color set, so not a drag
236  return;
237  }
238 
239  if (( e->pos() - mDragStartPosition ).manhattanLength() < QApplication::startDragDistance() )
240  {
241  //mouse not moved, so not a drag
243  return;
244  }
245 
246  //user is dragging color
247  QDrag *drag = new QDrag( this );
248  drag->setMimeData( QgsSymbolLayerV2Utils::colorToMimeData( mColor ) );
249  drag->setPixmap( QgsColorWidget::createDragIcon( mColor ) );
250  drag->exec( Qt::CopyAction );
251  setDown( false );
252 }
253 
255 {
256  if ( mPickingColor )
257  {
258  //end color picking operation by sampling the color under cursor
259  stopPicking( e->globalPos() );
260  e->accept();
261  return;
262  }
263 
265 }
266 
267 void QgsColorButtonV2::stopPicking( QPointF eventPos, bool sampleColor )
268 {
269  //release mouse and keyboard, and reset cursor
270  releaseMouse();
271  releaseKeyboard();
272  unsetCursor();
273  mPickingColor = false;
274 
275  if ( !sampleColor )
276  {
277  //not sampling color, nothing more to do
278  return;
279  }
280 
281  //grab snapshot of pixel under mouse cursor
282  QPixmap snappedPixmap = QPixmap::grabWindow( QApplication::desktop()->winId(), eventPos.x(), eventPos.y(), 1, 1 );
283  QImage snappedImage = snappedPixmap.toImage();
284  //extract color from pixel and set color
285  setColor( snappedImage.pixel( 0, 0 ) );
286  addRecentColor( mColor );
287 }
288 
290 {
291  if ( !mPickingColor )
292  {
293  //if not picking a color, use default tool button behaviour
295  return;
296  }
297 
298  //cancel picking, sampling the color if space was pressed
299  stopPicking( QCursor::pos(), e->key() == Qt::Key_Space );
300 }
301 
302 void QgsColorButtonV2::dragEnterEvent( QDragEnterEvent *e )
303 {
304  //is dragged data valid color data?
305  QColor mimeColor;
306  if ( colorFromMimeData( e->mimeData(), mimeColor ) )
307  {
308  //if so, we accept the drag, and temporarily change the button's color
309  //to match the dragged color. This gives immediate feedback to the user
310  //that colors can be dropped here
311  e->acceptProposedAction();
312  setButtonBackground( mimeColor );
313  }
314 }
315 
316 void QgsColorButtonV2::dragLeaveEvent( QDragLeaveEvent *e )
317 {
318  Q_UNUSED( e );
319  //reset button color
320  setButtonBackground( mColor );
321 }
322 
323 void QgsColorButtonV2::dropEvent( QDropEvent *e )
324 {
325  //is dropped data valid color data?
326  QColor mimeColor;
327  if ( colorFromMimeData( e->mimeData(), mimeColor ) )
328  {
329  //accept drop and set new color
330  e->acceptProposedAction();
331  setColor( mimeColor );
332  addRecentColor( mimeColor );
333  }
334 }
335 
336 void QgsColorButtonV2::setValidColor( const QColor& newColor )
337 {
338  if ( newColor.isValid() )
339  {
340  setColor( newColor );
341  addRecentColor( newColor );
342  }
343 }
344 
345 QPixmap QgsColorButtonV2::createMenuIcon( const QColor &color, const bool showChecks )
346 {
347  //create an icon pixmap
348  QPixmap pixmap( 16, 16 );
349  pixmap.fill( Qt::transparent );
350 
351  QPainter p;
352  p.begin( &pixmap );
353 
354  //start with checkboard pattern
355  if ( showChecks )
356  {
357  QBrush checkBrush = QBrush( transparentBackground() );
358  p.setPen( Qt::NoPen );
359  p.setBrush( checkBrush );
360  p.drawRect( 0, 0, 15, 15 );
361  }
362 
363  //draw color over pattern
364  p.setBrush( QBrush( color ) );
365 
366  //draw border
367  p.setPen( QColor( 197, 197, 197 ) );
368  p.drawRect( 0, 0, 15, 15 );
369  p.end();
370  return pixmap;
371 }
372 
373 void QgsColorButtonV2::buttonClicked()
374 {
375  switch ( mBehaviour )
376  {
377  case ShowDialog:
378  showColorDialog();
379  return;
380  case SignalOnly:
381  emit colorClicked( mColor );
382  return;
383  }
384 }
385 
386 void QgsColorButtonV2::prepareMenu()
387 {
388  //we need to tear down and rebuild this menu every time it is shown. Otherwise the space allocated to any
389  //QgsColorSwatchGridAction is not recalculated by Qt and the swatch grid may not be the correct size
390  //for the number of colors shown in the grid. Note that we MUST refresh color swatch grids every time this
391  //menu is opened, otherwise color schemes like the recent color scheme grid are meaningless
392  mMenu->clear();
393 
394  //show default color option if set
395  if ( mDefaultColor.isValid() )
396  {
397  QAction* defaultColorAction = new QAction( tr( "Default color" ), this );
398  defaultColorAction->setIcon( createMenuIcon( mDefaultColor ) );
399  mMenu->addAction( defaultColorAction );
400  connect( defaultColorAction, SIGNAL( triggered() ), this, SLOT( setToDefaultColor() ) );
401  }
402 
403  if ( mShowNoColorOption && mAllowAlpha )
404  {
405  QAction* noColorAction = new QAction( mNoColorString, this );
406  noColorAction->setIcon( createMenuIcon( Qt::transparent, false ) );
407  mMenu->addAction( noColorAction );
408  connect( noColorAction, SIGNAL( triggered() ), this, SLOT( setToNoColor() ) );
409  }
410 
411  if ( mColorSchemeRegistry )
412  {
413  //get schemes with ShowInColorButtonMenu flag set
414  QList< QgsColorScheme* > schemeList = mColorSchemeRegistry->schemes( QgsColorScheme::ShowInColorButtonMenu );
415  QList< QgsColorScheme* >::iterator it = schemeList.begin();
416  for ( ; it != schemeList.end(); ++it )
417  {
418  QgsColorSwatchGridAction* colorAction = new QgsColorSwatchGridAction( *it, mMenu, mContext, this );
419  colorAction->setBaseColor( mColor );
420  mMenu->addAction( colorAction );
421  connect( colorAction, SIGNAL( colorChanged( const QColor& ) ), this, SLOT( setValidColor( const QColor& ) ) );
422  connect( colorAction, SIGNAL( colorChanged( const QColor& ) ), this, SLOT( addRecentColor( const QColor& ) ) );
423  }
424  }
425 
426  mMenu->addSeparator();
427 
428  QAction* copyColorAction = new QAction( tr( "Copy color" ), this );
429  mMenu->addAction( copyColorAction );
430  connect( copyColorAction, SIGNAL( triggered() ), this, SLOT( copyColor() ) );
431 
432  QAction* pasteColorAction = new QAction( tr( "Paste color" ), this );
433  //enable or disable paste action based on current clipboard contents. We always show the paste
434  //action, even if it's disabled, to give hint to the user that pasting colors is possible
435  QColor clipColor;
436  if ( colorFromMimeData( QApplication::clipboard()->mimeData(), clipColor ) )
437  {
438  pasteColorAction->setIcon( createMenuIcon( clipColor ) );
439  }
440  else
441  {
442  pasteColorAction->setEnabled( false );
443  }
444  mMenu->addAction( pasteColorAction );
445  connect( pasteColorAction, SIGNAL( triggered() ), this, SLOT( pasteColor() ) );
446 
447 #ifndef Q_OS_MAC
448  //disabled for OSX, as it is impossible to grab the mouse under OSX
449  //see note for QWidget::grabMouse() re OSX Cocoa
450  //http://qt-project.org/doc/qt-4.8/qwidget.html#grabMouse
451  QAction* pickColorAction = new QAction( tr( "Pick color" ), this );
452  mMenu->addAction( pickColorAction );
453  connect( pickColorAction, SIGNAL( triggered() ), this, SLOT( activatePicker() ) );
454 #endif
455  QAction* chooseColorAction = new QAction( tr( "Choose color..." ), this );
456  mMenu->addAction( chooseColorAction );
457  connect( chooseColorAction, SIGNAL( triggered() ), this, SLOT( showColorDialog() ) );
458 }
459 
461 {
462  if ( e->type() == QEvent::EnabledChange )
463  {
465  }
467 }
468 
469 #if 0 // causes too many cyclical updates, but may be needed on some platforms
470 void QgsColorButtonV2::paintEvent( QPaintEvent* e )
471 {
472  QToolButton::paintEvent( e );
473 
474  if ( !mBackgroundSet )
475  {
477  }
478 }
479 #endif
480 
481 void QgsColorButtonV2::showEvent( QShowEvent* e )
482 {
485 }
486 
487 void QgsColorButtonV2::resizeEvent( QResizeEvent *event )
488 {
489  QToolButton::resizeEvent( event );
490  //recalculate icon size and redraw icon
491  mIconSize = QSize();
492  setButtonBackground( mColor );
493 }
494 
495 void QgsColorButtonV2::setColor( const QColor &color )
496 {
497  QColor oldColor = mColor;
498  mColor = color;
499 
500  // handle when initially set color is same as default (Qt::black); consider it a color change
501  if ( oldColor != mColor || ( mColor == QColor( Qt::black ) && !mColorSet ) )
502  {
504  if ( isEnabled() )
505  {
506  // TODO: May be beneficial to have the option to set color without emitting this signal.
507  // Now done by blockSignals( bool ) where button is used
508  emit colorChanged( mColor );
509  }
510  }
511  mColorSet = true;
512 }
513 
514 void QgsColorButtonV2::addRecentColor( const QColor& color )
515 {
516  if ( !color.isValid() )
517  {
518  return;
519  }
520 
521  //strip alpha from color
522  QColor opaqueColor = color;
523  opaqueColor.setAlpha( 255 );
524 
525  QSettings settings;
526  QList< QVariant > recentColorVariants = settings.value( QString( "/colors/recent" ) ).toList();
527 
528  //remove colors by name
529  for ( int colorIdx = recentColorVariants.length() - 1; colorIdx >= 0; --colorIdx )
530  {
531  if (( recentColorVariants.at( colorIdx ).value<QColor>() ).name() == opaqueColor.name() )
532  {
533  recentColorVariants.removeAt( colorIdx );
534  }
535  }
536 
537  //add color
538  QVariant colorVariant = QVariant( opaqueColor );
539  recentColorVariants.prepend( colorVariant );
540 
541  //trim to 20 colors
542  while ( recentColorVariants.count() > 20 )
543  {
544  recentColorVariants.pop_back();
545  }
546 
547  settings.setValue( QString( "/colors/recent" ), recentColorVariants );
548 }
549 
550 void QgsColorButtonV2::setButtonBackground( const QColor &color )
551 {
552  QColor backgroundColor = color;
553 
554  if ( !color.isValid() )
555  {
556  backgroundColor = mColor;
557  }
558 
559  QSize currentIconSize;
560  //icon size is button size with a small margin
561  if ( menu() )
562  {
563  if ( !mIconSize.isValid() )
564  {
565  //calculate size of push button part of widget (ie, without the menu dropdown button part)
566  QStyleOptionToolButton opt;
567  initStyleOption( &opt );
568  QRect buttonSize = QApplication::style()->subControlRect( QStyle::CC_ToolButton, &opt, QStyle::SC_ToolButton,
569  this );
570  //make sure height of icon looks good under different platforms
571 #ifdef Q_OS_WIN
572  mIconSize = QSize( buttonSize.width() - 10, height() - 6 );
573 #else
574  mIconSize = QSize( buttonSize.width() - 10, height() - 12 );
575 #endif
576  }
577  currentIconSize = mIconSize;
578  }
579  else
580  {
581  //no menu
582 #ifdef Q_OS_WIN
583  currentIconSize = QSize( width() - 10, height() - 6 );
584 #else
585  currentIconSize = QSize( width() - 10, height() - 12 );
586 #endif
587  }
588 
589  if ( !currentIconSize.isValid() || currentIconSize.width() <= 0 || currentIconSize.height() <= 0 )
590  {
591  return;
592  }
593 
594  //create an icon pixmap
595  QPixmap pixmap( currentIconSize );
596  pixmap.fill( Qt::transparent );
597 
598  if ( backgroundColor.isValid() )
599  {
600  QRect rect( 0, 0, currentIconSize.width(), currentIconSize.height() );
601  QPainter p;
602  p.begin( &pixmap );
603  p.setRenderHint( QPainter::Antialiasing );
604  p.setPen( Qt::NoPen );
605  if ( mAllowAlpha && backgroundColor.alpha() < 255 )
606  {
607  //start with checkboard pattern
608  QBrush checkBrush = QBrush( transparentBackground() );
609  p.setBrush( checkBrush );
610  p.drawRoundedRect( rect, 3, 3 );
611  }
612 
613  //draw semi-transparent color on top
614  p.setBrush( backgroundColor );
615  p.drawRoundedRect( rect, 3, 3 );
616  p.end();
617  }
618 
619  setIconSize( currentIconSize );
620  setIcon( pixmap );
621 }
622 
624 {
625  //copy color
626  QApplication::clipboard()->setMimeData( QgsSymbolLayerV2Utils::colorToMimeData( mColor ) );
627 }
628 
630 {
631  QColor clipColor;
632  if ( colorFromMimeData( QApplication::clipboard()->mimeData(), clipColor ) )
633  {
634  //paste color
635  setColor( clipColor );
636  addRecentColor( clipColor );
637  }
638 }
639 
641 {
642  //pick color
643  QPixmap samplerPixmap = QPixmap(( const char ** ) sampler_cursor );
644  setCursor( QCursor( samplerPixmap, 0, 0 ) );
645  grabMouse();
646  grabKeyboard();
647  mPickingColor = true;
648 }
649 
650 QColor QgsColorButtonV2::color() const
651 {
652  return mColor;
653 }
654 
655 void QgsColorButtonV2::setAllowAlpha( const bool allowAlpha )
656 {
657  mAllowAlpha = allowAlpha;
658 }
659 
660 void QgsColorButtonV2::setColorDialogTitle( const QString title )
661 {
662  mColorDialogTitle = title;
663 }
664 
666 {
667  return mColorDialogTitle;
668 }
669 
670 void QgsColorButtonV2::setShowMenu( const bool showMenu )
671 {
672  setMenu( showMenu ? mMenu : 0 );
673  setPopupMode( showMenu ? QToolButton::MenuButtonPopup : QToolButton::DelayedPopup );
674  //force recalculation of icon size
675  mIconSize = QSize();
676  setButtonBackground( mColor );
677 }
678 
680 {
681  mBehaviour = behaviour;
682 }
683 
684 void QgsColorButtonV2::setDefaultColor( const QColor color )
685 {
686  mDefaultColor = color;
687 }
688