QGIS API Documentation  2.8.2-Wien
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros Groups Pages
qgscolorbutton.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgscolorbutton.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 "qgscolorbutton.h"
17 #include "qgscolordialog.h"
18 #include "qgsapplication.h"
19 #include "qgslogger.h"
20 #include "qgssymbollayerv2utils.h"
21 #include "qgscursors.h"
22 
23 #include <QPainter>
24 #include <QSettings>
25 #include <QTemporaryFile>
26 #include <QMouseEvent>
27 #include <QMenu>
28 #include <QClipboard>
29 #include <QDrag>
30 #include <QDesktopWidget>
31 
32 #ifdef Q_OS_WIN
33 #include <windows.h>
34 QString QgsColorButton::fullPath( const QString &path )
35 {
36  TCHAR buf[MAX_PATH];
37  int len = GetLongPathName( path.toUtf8().constData(), buf, MAX_PATH );
38 
39  if ( len == 0 || len > MAX_PATH )
40  {
41  QgsDebugMsg( QString( "GetLongPathName('%1') failed with %2: %3" )
42  .arg( path ).arg( len ).arg( GetLastError() ) );
43  return path;
44  }
45 
46  QString res = QString::fromUtf8( buf );
47  return res;
48 }
49 #endif
50 
67 QgsColorButton::QgsColorButton( QWidget *parent, QString cdt, QColorDialog::ColorDialogOptions cdo )
68  : QPushButton( parent )
69  , mColorDialogTitle( cdt.isEmpty() ? tr( "Select Color" ) : cdt )
70  , mColor( Qt::black )
71  , mColorDialogOptions( cdo )
72  , mAcceptLiveUpdates( true )
73  , mTempPNG( NULL )
74  , mColorSet( false )
75  , mPickingColor( false )
76 {
77  setAcceptDrops( true );
78  connect( this, SIGNAL( clicked() ), this, SLOT( onButtonClicked() ) );
79 }
80 
82 {
83  if ( mTempPNG.exists() )
84  mTempPNG.remove();
85 }
86 
88 {
89  static QPixmap transpBkgrd;
90 
91  if ( transpBkgrd.isNull() )
92  transpBkgrd = QgsApplication::getThemePixmap( "/transp-background_8x8.png" );
93 
94  return transpBkgrd;
95 }
96 
97 void QgsColorButton::onButtonClicked()
98 {
99  QColor newColor;
100  QSettings settings;
101 
102  //using native color dialogs?
103  bool useNative = settings.value( "/qgis/native_color_dialogs", false ).toBool();
104 
105  if ( useNative )
106  {
107  if ( mAcceptLiveUpdates && settings.value( "/qgis/live_color_dialogs", false ).toBool() )
108  {
109  newColor = QgsColorDialog::getLiveColor(
110  color(), this, SLOT( setValidColor( const QColor& ) ),
111  this->parentWidget(), mColorDialogTitle, mColorDialogOptions );
112  }
113  else
114  {
115  newColor = QColorDialog::getColor( color(), this->parentWidget(), mColorDialogTitle, mColorDialogOptions );
116  }
117  }
118  else
119  {
120  //use QGIS style color dialogs
121  if ( mAcceptLiveUpdates && settings.value( "/qgis/live_color_dialogs", false ).toBool() )
122  {
124  color(), this, SLOT( setValidColor( const QColor& ) ),
125  this->parentWidget(), mColorDialogTitle, mColorDialogOptions & QColorDialog::ShowAlphaChannel );
126  }
127  else
128  {
129  QgsColorDialogV2 dialog( this, 0, color() );
130  dialog.setTitle( mColorDialogTitle );
131  dialog.setAllowAlpha( mColorDialogOptions & QColorDialog::ShowAlphaChannel );
132 
133  if ( dialog.exec() )
134  {
135  newColor = dialog.color();
136  }
137  }
138  }
139 
140  if ( newColor.isValid() )
141  {
142  setValidColor( newColor );
143  }
144 
145  // reactivate button's window
146  activateWindow();
147 }
148 
149 void QgsColorButton::mousePressEvent( QMouseEvent *e )
150 {
151  if ( mPickingColor )
152  {
153  //don't show dialog if in color picker mode
154  e->accept();
155  return;
156  }
157 
158  if ( e->button() == Qt::RightButton )
159  {
160  showContextMenu( e );
161  return;
162  }
163  else if ( e->button() == Qt::LeftButton )
164  {
165  mDragStartPosition = e->pos();
166  }
168 }
169 
170 QMimeData * QgsColorButton::createColorMimeData() const
171 {
172  QMimeData *mimeData = new QMimeData;
173  mimeData->setColorData( QVariant( mColor ) );
174  mimeData->setText( mColor.name() );
175  return mimeData;
176 }
177 
178 bool QgsColorButton::colorFromMimeData( const QMimeData * mimeData, QColor& resultColor )
179 {
180  //attempt to read color data directly from mime
181  QColor mimeColor = mimeData->colorData().value<QColor>();
182  if ( mimeColor.isValid() )
183  {
184  if ( !( mColorDialogOptions & QColorDialog::ShowAlphaChannel ) )
185  {
186  //remove alpha channel
187  mimeColor.setAlpha( 255 );
188  }
189  resultColor = mimeColor;
190  return true;
191  }
192 
193  //attempt to intrepret a color from mime text data
194  bool hasAlpha = false;
195  QColor textColor = QgsSymbolLayerV2Utils::parseColorWithAlpha( mimeData->text(), hasAlpha );
196  if ( textColor.isValid() )
197  {
198  if ( !( mColorDialogOptions & QColorDialog::ShowAlphaChannel ) )
199  {
200  //remove alpha channel
201  textColor.setAlpha( 255 );
202  }
203  else if ( !hasAlpha )
204  {
205  //mime color has no explicit alpha component, so keep existing alpha
206  textColor.setAlpha( mColor.alpha() );
207  }
208  resultColor = textColor;
209  return true;
210  }
211 
212  //could not get color from mime data
213  return false;
214 }
215 
216 void QgsColorButton::mouseMoveEvent( QMouseEvent *e )
217 {
218  if ( mPickingColor )
219  {
220  //currently in color picker mode
221  if ( e->buttons() & Qt::LeftButton )
222  {
223  //if left button depressed, sample color under cursor and temporarily update button color
224  //to give feedback to user
225  QPixmap snappedPixmap = QPixmap::grabWindow( QApplication::desktop()->winId(), e->globalPos().x(), e->globalPos().y(), 1, 1 );
226  QImage snappedImage = snappedPixmap.toImage();
227  QColor hoverColor = snappedImage.pixel( 0, 0 );
228  setButtonBackground( hoverColor );
229  }
230  e->accept();
231  return;
232  }
233 
234  //handle dragging colors from button
235  if ( !( e->buttons() & Qt::LeftButton ) )
236  {
238  return;
239  }
240 
241  if (( e->pos() - mDragStartPosition ).manhattanLength() < QApplication::startDragDistance() )
242  {
244  return;
245  }
246 
247  QDrag *drag = new QDrag( this );
248  drag->setMimeData( createColorMimeData() );
249 
250  //craft a pixmap for the drag icon
251  QImage colorImage( 50, 50, QImage::Format_RGB32 );
252  QPainter imagePainter;
253  imagePainter.begin( &colorImage );
254  //start with a light gray background
255  imagePainter.fillRect( QRect( 0, 0, 50, 50 ), QBrush( QColor( 200, 200, 200 ) ) );
256  //draw rect with white border, filled with current color
257  QColor pixmapColor = mColor;
258  pixmapColor.setAlpha( 255 );
259  imagePainter.setBrush( QBrush( pixmapColor ) );
260  imagePainter.setPen( QPen( Qt::white ) );
261  imagePainter.drawRect( QRect( 1, 1, 47, 47 ) );
262  imagePainter.end();
263  //set as drag pixmap
264  drag->setPixmap( QPixmap::fromImage( colorImage ) );
265 
266  drag->exec( Qt::CopyAction );
267  setDown( false );
268 }
269 
270 void QgsColorButton::mouseReleaseEvent( QMouseEvent *e )
271 {
272  if ( mPickingColor )
273  {
274  //end color picking operation by sampling the color under cursor
275  stopPicking( e->globalPos() );
276  e->accept();
277  return;
278  }
279 
281 }
282 
283 void QgsColorButton::stopPicking( QPointF eventPos, bool sampleColor )
284 {
285  //release mouse and reset cursor
286  releaseMouse();
287  unsetCursor();
288  mPickingColor = false;
289 
290  if ( !sampleColor )
291  {
292  //not sampling color, nothing more to do
293  return;
294  }
295 
296  //grab snapshot of pixel under mouse cursor
297  QPixmap snappedPixmap = QPixmap::grabWindow( QApplication::desktop()->winId(), eventPos.x(), eventPos.y(), 1, 1 );
298  QImage snappedImage = snappedPixmap.toImage();
299  //extract color from pixel and set color
300  setColor( snappedImage.pixel( 0, 0 ) );
301 }
302 
303 void QgsColorButton::keyPressEvent( QKeyEvent *e )
304 {
305  if ( !mPickingColor )
306  {
307  //if not picking a color, use default push button behaviour
309  return;
310  }
311 
312  //cancel picking, sampling the color if space was pressed
313  stopPicking( QCursor::pos(), e->key() == Qt::Key_Space );
314 }
315 
316 void QgsColorButton::dragEnterEvent( QDragEnterEvent *e )
317 {
318  //is dragged data valid color data?
319  QColor mimeColor;
320  if ( colorFromMimeData( e->mimeData(), mimeColor ) )
321  {
322  e->acceptProposedAction();
323  }
324 }
325 
326 void QgsColorButton::dropEvent( QDropEvent *e )
327 {
328  //is dropped data valid color data?
329  QColor mimeColor;
330  if ( colorFromMimeData( e->mimeData(), mimeColor ) )
331  {
332  e->acceptProposedAction();
333  setColor( mimeColor );
334  }
335 }
336 
337 void QgsColorButton::showContextMenu( QMouseEvent *event )
338 {
339  QMenu colorContextMenu;
340 
341  QAction* copyColorAction = new QAction( tr( "Copy color" ), 0 );
342  colorContextMenu.addAction( copyColorAction );
343  QAction* pasteColorAction = new QAction( tr( "Paste color" ), 0 );
344  pasteColorAction->setEnabled( false );
345  colorContextMenu.addAction( pasteColorAction );
346 #ifndef Q_OS_MAC
347  //disabled for OSX, as it is impossible to grab the mouse under OSX
348  //see note for QWidget::grabMouse() re OSX Cocoa
349  //http://qt-project.org/doc/qt-4.8/qwidget.html#grabMouse
350  QAction* pickColorAction = new QAction( tr( "Pick color" ), 0 );
351  colorContextMenu.addSeparator();
352  colorContextMenu.addAction( pickColorAction );
353 #endif
354 
355  QColor clipColor;
356  if ( colorFromMimeData( QApplication::clipboard()->mimeData(), clipColor ) )
357  {
358  pasteColorAction->setEnabled( true );
359  }
360 
361  QAction* selectedAction = colorContextMenu.exec( event->globalPos() );
362  if ( selectedAction == copyColorAction )
363  {
364  //copy color
365  QApplication::clipboard()->setMimeData( createColorMimeData() );
366  }
367  else if ( selectedAction == pasteColorAction )
368  {
369  //paste color
370  setColor( clipColor );
371  }
372 #ifndef Q_OS_MAC
373  else if ( selectedAction == pickColorAction )
374  {
375  //pick color
376  QPixmap samplerPixmap = QPixmap(( const char ** ) sampler_cursor );
377  setCursor( QCursor( samplerPixmap, 0, 0 ) );
378  grabMouse();
379  mPickingColor = true;
380  }
381  delete pickColorAction;
382 #endif
383 
384  delete copyColorAction;
385  delete pasteColorAction;
386 }
387 
388 void QgsColorButton::setValidColor( const QColor& newColor )
389 {
390  if ( newColor.isValid() )
391  {
392  setColor( newColor );
393  }
394 }
395 
397 {
398  if ( e->type() == QEvent::EnabledChange )
399  {
401  }
403 }
404 
405 #if 0 // causes too many cyclical updates, but may be needed on some platforms
406 void QgsColorButton::paintEvent( QPaintEvent* e )
407 {
408  QPushButton::paintEvent( e );
409 
410  if ( !mBackgroundSet )
411  {
413  }
414 }
415 #endif
416 
417 void QgsColorButton::showEvent( QShowEvent* e )
418 {
421 }
422 
423 void QgsColorButton::setColor( const QColor &color )
424 {
425  if ( !color.isValid() )
426  {
427  return;
428  }
429  QColor oldColor = mColor;
430  mColor = color;
431 
432  // handle when initially set color is same as default (Qt::black); consider it a color change
433  if ( oldColor != mColor || ( mColor == QColor( Qt::black ) && !mColorSet ) )
434  {
436  if ( isEnabled() )
437  {
438  // TODO: May be beneficial to have the option to set color without emitting this signal.
439  // Now done by blockSignals( bool ) where button is used
440  emit colorChanged( mColor );
441  }
442  }
443  mColorSet = true;
444 }
445 
447 {
448  if ( !color.isValid() )
449  {
450  color = mColor;
451  }
452  if ( !text().isEmpty() )
453  {
454  // generate icon pixmap for regular pushbutton
455  setFlat( false );
456 
457  QPixmap pixmap;
458  pixmap = QPixmap( iconSize() );
459  pixmap.fill( QColor( 0, 0, 0, 0 ) );
460 
461  int iconW = iconSize().width();
462  int iconH = iconSize().height();
463  QRect rect( 0, 0, iconW, iconH );
464 
465  // QPainterPath::addRoundRect has flaws, draw chamfered corners instead
466  QPainterPath roundRect;
467  int chamfer = 3;
468  int inset = 1;
469  roundRect.moveTo( chamfer, inset );
470  roundRect.lineTo( iconW - chamfer, inset );
471  roundRect.lineTo( iconW - inset, chamfer );
472  roundRect.lineTo( iconW - inset, iconH - chamfer );
473  roundRect.lineTo( iconW - chamfer, iconH - inset );
474  roundRect.lineTo( chamfer, iconH - inset );
475  roundRect.lineTo( inset, iconH - chamfer );
476  roundRect.lineTo( inset, chamfer );
477  roundRect.closeSubpath();
478 
479  QPainter p;
480  p.begin( &pixmap );
481  p.setRenderHint( QPainter::Antialiasing );
482  p.setClipPath( roundRect );
483  p.setPen( Qt::NoPen );
484  if ( color.alpha() < 255 )
485  {
486  p.drawTiledPixmap( rect, transpBkgrd() );
487  }
488  p.setBrush( color );
489  p.drawRect( rect );
490  p.end();
491 
492  // set this pixmap as icon
493  setIcon( QIcon( pixmap ) );
494  }
495  else
496  {
497  // generate temp background image file with checkerboard canvas to be used via stylesheet
498 
499  // set flat, or inline spacing (widget margins) needs to be manually calculated and set
500  setFlat( true );
501 
502  bool useAlpha = ( mColorDialogOptions & QColorDialog::ShowAlphaChannel );
503 
504  // in case margins need to be adjusted
505  QString margin = QString( "%1px %2px %3px %4px" ).arg( 0 ).arg( 0 ).arg( 0 ).arg( 0 );
506 
507  //QgsDebugMsg( QString( "%1 margin: %2" ).arg( objectName() ).arg( margin ) );
508 
509  QString bkgrd = QString( " background-color: rgba(%1,%2,%3,%4);" )
510  .arg( color.red() )
511  .arg( color.green() )
512  .arg( color.blue() )
513  .arg( useAlpha ? color.alpha() : 255 );
514 
515  if ( useAlpha && color.alpha() < 255 )
516  {
517  QPixmap pixmap = transpBkgrd();
518  QRect rect( 0, 0, pixmap.width(), pixmap.height() );
519 
520  QPainter p;
521  p.begin( &pixmap );
522  p.setRenderHint( QPainter::Antialiasing );
523  p.setPen( Qt::NoPen );
524  p.setBrush( mColor );
525  p.drawRect( rect );
526  p.end();
527 
528  if ( mTempPNG.open() )
529  {
530  mTempPNG.setAutoRemove( false );
531  pixmap.save( mTempPNG.fileName(), "PNG" );
532  mTempPNG.close();
533  }
534 
535  QString bgFileName = mTempPNG.fileName();
536 #ifdef Q_OS_WIN
537  //on windows, mTempPNG will use a shortened path for the temporary folder name
538  //this does not work with stylesheets, resulting in the whole button disappearing (#10187)
539  bgFileName = fullPath( bgFileName );
540 #endif
541  bkgrd = QString( " background-image: url(%1);" ).arg( bgFileName );
542  }
543 
544  // TODO: get OS-style focus color and switch border to that color when button in focus
545  setStyleSheet( QString( "QgsColorButton{"
546  " %1"
547  " background-position: top left;"
548  " background-origin: content;"
549  " background-clip: content;"
550  " padding: 2px;"
551  " margin: %2;"
552  " outline: none;"
553  " border-style: %4;"
554  " border-width: 1px;"
555  " border-color: rgb(%3,%3,%3);"
556  " border-radius: 3px;} "
557  "QgsColorButton:pressed{"
558  " %1"
559  " background-position: top left;"
560  " background-origin: content;"
561  " background-clip: content;"
562  " padding: 1px;"
563  " margin: %2;"
564  " outline: none;"
565  " border-style: inset;"
566  " border-width: 2px;"
567  " border-color: rgb(128,128,128);"
568  " border-radius: 4px;} " )
569  .arg( bkgrd )
570  .arg( margin )
571  .arg( isEnabled() ? "128" : "110" )
572  .arg( isEnabled() ? "outset" : "dotted" ) );
573  }
574 }
575 
576 QColor QgsColorButton::color() const
577 {
578  return mColor;
579 }
580 
581 void QgsColorButton::setColorDialogOptions( QColorDialog::ColorDialogOptions cdo )
582 {
583  mColorDialogOptions = cdo;
584 }
585 
586 QColorDialog::ColorDialogOptions QgsColorButton::colorDialogOptions()
587 {
588  return mColorDialogOptions;
589 }
590 
592 {
593  mColorDialogTitle = cdt;
594 }
595 
597 {
598  return mColorDialogTitle;
599 }