QGIS API Documentation  2.5.0-Master
 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 
98 {
99  //QgsDebugMsg( "entered" );
100  QColor newColor;
101  QSettings settings;
102  if ( mAcceptLiveUpdates && settings.value( "/qgis/live_color_dialogs", false ).toBool() )
103  {
104  newColor = QgsColorDialog::getLiveColor(
105  color(), this, SLOT( setValidColor( const QColor& ) ),
106  this->parentWidget(), mColorDialogTitle, mColorDialogOptions );
107  }
108  else
109  {
110  newColor = QColorDialog::getColor( color(), this->parentWidget(), mColorDialogTitle, mColorDialogOptions );
111  }
112  setValidColor( newColor );
113 
114  // reactivate button's window
115  activateWindow();
116 }
117 
118 void QgsColorButton::mousePressEvent( QMouseEvent *e )
119 {
120  if ( mPickingColor )
121  {
122  //don't show dialog if in color picker mode
123  e->accept();
124  return;
125  }
126 
127  if ( e->button() == Qt::RightButton )
128  {
129  showContextMenu( e );
130  return;
131  }
132  else if ( e->button() == Qt::LeftButton )
133  {
134  mDragStartPosition = e->pos();
135  }
136  QPushButton::mousePressEvent( e );
137 }
138 
140 {
141  QMimeData *mimeData = new QMimeData;
142  mimeData->setColorData( QVariant( mColor ) );
143  mimeData->setText( mColor.name() );
144  return mimeData;
145 }
146 
147 bool QgsColorButton::colorFromMimeData( const QMimeData * mimeData, QColor& resultColor )
148 {
149  //attempt to read color data directly from mime
150  QColor mimeColor = mimeData->colorData().value<QColor>();
151  if ( mimeColor.isValid() )
152  {
153  if ( !( mColorDialogOptions & QColorDialog::ShowAlphaChannel ) )
154  {
155  //remove alpha channel
156  mimeColor.setAlpha( 255 );
157  }
158  resultColor = mimeColor;
159  return true;
160  }
161 
162  //attempt to intrepret a color from mime text data
163  bool hasAlpha = false;
164  QColor textColor = QgsSymbolLayerV2Utils::parseColorWithAlpha( mimeData->text(), hasAlpha );
165  if ( textColor.isValid() )
166  {
167  if ( !( mColorDialogOptions & QColorDialog::ShowAlphaChannel ) )
168  {
169  //remove alpha channel
170  textColor.setAlpha( 255 );
171  }
172  else if ( !hasAlpha )
173  {
174  //mime color has no explicit alpha component, so keep existing alpha
175  textColor.setAlpha( mColor.alpha() );
176  }
177  resultColor = textColor;
178  return true;
179  }
180 
181  //could not get color from mime data
182  return false;
183 }
184 
185 void QgsColorButton::mouseMoveEvent( QMouseEvent *e )
186 {
187  if ( mPickingColor )
188  {
189  //currently in color picker mode
190  if ( e->buttons() & Qt::LeftButton )
191  {
192  //if left button depressed, sample color under cursor and temporarily update button color
193  //to give feedback to user
194  QPixmap snappedPixmap = QPixmap::grabWindow( QApplication::desktop()->winId(), e->globalPos().x(), e->globalPos().y(), 1, 1 );
195  QImage snappedImage = snappedPixmap.toImage();
196  QColor hoverColor = snappedImage.pixel( 0, 0 );
197  setButtonBackground( hoverColor );
198  }
199  e->accept();
200  return;
201  }
202 
203  //handle dragging colors from button
204  if ( !( e->buttons() & Qt::LeftButton ) )
205  {
206  QPushButton::mouseMoveEvent( e );
207  return;
208  }
209 
210  if (( e->pos() - mDragStartPosition ).manhattanLength() < QApplication::startDragDistance() )
211  {
212  QPushButton::mouseMoveEvent( e );
213  return;
214  }
215 
216  QDrag *drag = new QDrag( this );
217  drag->setMimeData( createColorMimeData() );
218 
219  //craft a pixmap for the drag icon
220  QImage colorImage( 50, 50, QImage::Format_RGB32 );
221  QPainter imagePainter;
222  imagePainter.begin( &colorImage );
223  //start with a light gray background
224  imagePainter.fillRect( QRect( 0, 0, 50, 50 ), QBrush( QColor( 200, 200, 200 ) ) );
225  //draw rect with white border, filled with current color
226  QColor pixmapColor = mColor;
227  pixmapColor.setAlpha( 255 );
228  imagePainter.setBrush( QBrush( pixmapColor ) );
229  imagePainter.setPen( QPen( Qt::white ) );
230  imagePainter.drawRect( QRect( 1, 1, 47, 47 ) );
231  imagePainter.end();
232  //set as drag pixmap
233  drag->setPixmap( QPixmap::fromImage( colorImage ) );
234 
235  drag->exec( Qt::CopyAction );
236  setDown( false );
237 }
238 
239 void QgsColorButton::mouseReleaseEvent( QMouseEvent *e )
240 {
241  if ( mPickingColor )
242  {
243  //end color picking operation by sampling the color under cursor
244  stopPicking( e->globalPos() );
245  e->accept();
246  return;
247  }
248 
249  QPushButton::mouseReleaseEvent( e );
250 }
251 
252 void QgsColorButton::stopPicking( QPointF eventPos, bool sampleColor )
253 {
254  //release mouse and reset cursor
255  releaseMouse();
256  unsetCursor();
257  mPickingColor = false;
258 
259  if ( !sampleColor )
260  {
261  //not sampling color, nothing more to do
262  return;
263  }
264 
265  //grab snapshot of pixel under mouse cursor
266  QPixmap snappedPixmap = QPixmap::grabWindow( QApplication::desktop()->winId(), eventPos.x(), eventPos.y(), 1, 1 );
267  QImage snappedImage = snappedPixmap.toImage();
268  //extract color from pixel and set color
269  setColor( snappedImage.pixel( 0, 0 ) );
270 }
271 
272 void QgsColorButton::keyPressEvent( QKeyEvent *e )
273 {
274  if ( !mPickingColor )
275  {
276  //if not picking a color, use default push button behaviour
277  QPushButton::keyPressEvent( e );
278  return;
279  }
280 
281  //cancel picking, sampling the color if space was pressed
282  stopPicking( QCursor::pos(), e->key() == Qt::Key_Space );
283 }
284 
285 void QgsColorButton::dragEnterEvent( QDragEnterEvent *e )
286 {
287  //is dragged data valid color data?
288  QColor mimeColor;
289  if ( colorFromMimeData( e->mimeData(), mimeColor ) )
290  {
291  e->acceptProposedAction();
292  }
293 }
294 
295 void QgsColorButton::dropEvent( QDropEvent *e )
296 {
297  //is dropped data valid color data?
298  QColor mimeColor;
299  if ( colorFromMimeData( e->mimeData(), mimeColor ) )
300  {
301  e->acceptProposedAction();
302  setColor( mimeColor );
303  }
304 }
305 
306 void QgsColorButton::showContextMenu( QMouseEvent *event )
307 {
308  QMenu colorContextMenu;
309 
310  QAction* copyColorAction = new QAction( tr( "Copy color" ), 0 );
311  colorContextMenu.addAction( copyColorAction );
312  QAction* pasteColorAction = new QAction( tr( "Paste color" ), 0 );
313  pasteColorAction->setEnabled( false );
314  colorContextMenu.addAction( pasteColorAction );
315 #ifndef Q_WS_MAC
316  //disabled for OSX, as it is impossible to grab the mouse under OSX
317  //see note for QWidget::grabMouse() re OSX Cocoa
318  //http://qt-project.org/doc/qt-4.8/qwidget.html#grabMouse
319  QAction* pickColorAction = new QAction( tr( "Pick color" ), 0 );
320  colorContextMenu.addSeparator();
321  colorContextMenu.addAction( pickColorAction );
322 #endif
323 
324  QColor clipColor;
325  if ( colorFromMimeData( QApplication::clipboard()->mimeData(), clipColor ) )
326  {
327  pasteColorAction->setEnabled( true );
328  }
329 
330  QAction* selectedAction = colorContextMenu.exec( event->globalPos( ) );
331  if ( selectedAction == copyColorAction )
332  {
333  //copy color
334  QApplication::clipboard()->setMimeData( createColorMimeData() );
335  }
336  else if ( selectedAction == pasteColorAction )
337  {
338  //paste color
339  setColor( clipColor );
340  }
341 #ifndef Q_WS_MAC
342  else if ( selectedAction == pickColorAction )
343  {
344  //pick color
345  QPixmap samplerPixmap = QPixmap(( const char ** ) sampler_cursor );
346  setCursor( QCursor( samplerPixmap, 0, 0 ) );
347  grabMouse();
348  mPickingColor = true;
349  }
350  delete pickColorAction;
351 #endif
352 
353  delete copyColorAction;
354  delete pasteColorAction;
355 }
356 
357 void QgsColorButton::setValidColor( const QColor& newColor )
358 {
359  if ( newColor.isValid() )
360  {
361  setColor( newColor );
362  }
363 }
364 
366 {
367  if ( e->type() == QEvent::EnabledChange )
368  {
370  }
371  QPushButton::changeEvent( e );
372 }
373 
374 #if 0 // causes too many cyclical updates, but may be needed on some platforms
375 void QgsColorButton::paintEvent( QPaintEvent* e )
376 {
377  QPushButton::paintEvent( e );
378 
379  if ( !mBackgroundSet )
380  {
382  }
383 }
384 #endif
385 
386 void QgsColorButton::showEvent( QShowEvent* e )
387 {
389  QPushButton::showEvent( e );
390 }
391 
392 void QgsColorButton::setColor( const QColor &color )
393 {
394  if ( !color.isValid() )
395  {
396  return;
397  }
398  QColor oldColor = mColor;
399  mColor = color;
400 
401  // handle when initially set color is same as default (Qt::black); consider it a color change
402  if ( oldColor != mColor || ( mColor == QColor( Qt::black ) && !mColorSet ) )
403  {
405  if ( isEnabled() )
406  {
407  // TODO: May be beneficial to have the option to set color without emitting this signal.
408  // Now done by blockSignals( bool ) where button is used
409  emit colorChanged( mColor );
410  }
411  }
412  mColorSet = true;
413 }
414 
416 {
417  if ( !color.isValid() )
418  {
419  color = mColor;
420  }
421  if ( !text().isEmpty() )
422  {
423  // generate icon pixmap for regular pushbutton
424  setFlat( false );
425 
426  QPixmap pixmap;
427  pixmap = QPixmap( iconSize() );
428  pixmap.fill( QColor( 0, 0, 0, 0 ) );
429 
430  int iconW = iconSize().width();
431  int iconH = iconSize().height();
432  QRect rect( 0, 0, iconW, iconH );
433 
434  // QPainterPath::addRoundRect has flaws, draw chamfered corners instead
435  QPainterPath roundRect;
436  int chamfer = 3;
437  int inset = 1;
438  roundRect.moveTo( chamfer, inset );
439  roundRect.lineTo( iconW - chamfer, inset );
440  roundRect.lineTo( iconW - inset, chamfer );
441  roundRect.lineTo( iconW - inset, iconH - chamfer );
442  roundRect.lineTo( iconW - chamfer, iconH - inset );
443  roundRect.lineTo( chamfer, iconH - inset );
444  roundRect.lineTo( inset, iconH - chamfer );
445  roundRect.lineTo( inset, chamfer );
446  roundRect.closeSubpath();
447 
448  QPainter p;
449  p.begin( &pixmap );
450  p.setRenderHint( QPainter::Antialiasing );
451  p.setClipPath( roundRect );
452  p.setPen( Qt::NoPen );
453  if ( color.alpha() < 255 )
454  {
455  p.drawTiledPixmap( rect, transpBkgrd() );
456  }
457  p.setBrush( color );
458  p.drawRect( rect );
459  p.end();
460 
461  // set this pixmap as icon
462  setIcon( QIcon( pixmap ) );
463  }
464  else
465  {
466  // generate temp background image file with checkerboard canvas to be used via stylesheet
467 
468  // set flat, or inline spacing (widget margins) needs to be manually calculated and set
469  setFlat( true );
470 
471  bool useAlpha = ( mColorDialogOptions & QColorDialog::ShowAlphaChannel );
472 
473  // in case margins need to be adjusted
474  QString margin = QString( "%1px %2px %3px %4px" ).arg( 0 ).arg( 0 ).arg( 0 ).arg( 0 );
475 
476  //QgsDebugMsg( QString( "%1 margin: %2" ).arg( objectName() ).arg( margin ) );
477 
478  QString bkgrd = QString( " background-color: rgba(%1,%2,%3,%4);" )
479  .arg( color.red() )
480  .arg( color.green() )
481  .arg( color.blue() )
482  .arg( useAlpha ? color.alpha() : 255 );
483 
484  if ( useAlpha && color.alpha() < 255 )
485  {
486  QPixmap pixmap = transpBkgrd();
487  QRect rect( 0, 0, pixmap.width(), pixmap.height() );
488 
489  QPainter p;
490  p.begin( &pixmap );
491  p.setRenderHint( QPainter::Antialiasing );
492  p.setPen( Qt::NoPen );
493  p.setBrush( mColor );
494  p.drawRect( rect );
495  p.end();
496 
497  if ( mTempPNG.open() )
498  {
499  mTempPNG.setAutoRemove( false );
500  pixmap.save( mTempPNG.fileName(), "PNG" );
501  mTempPNG.close();
502  }
503 
504  QString bgFileName = mTempPNG.fileName();
505 #ifdef Q_OS_WIN
506  //on windows, mTempPNG will use a shortened path for the temporary folder name
507  //this does not work with stylesheets, resulting in the whole button disappearing (#10187)
508  bgFileName = fullPath( bgFileName );
509 #endif
510  bkgrd = QString( " background-image: url(%1);" ).arg( bgFileName );
511  }
512 
513  // TODO: get OS-style focus color and switch border to that color when button in focus
514  setStyleSheet( QString( "QgsColorButton{"
515  " %1"
516  " background-position: top left;"
517  " background-origin: content;"
518  " background-clip: content;"
519  " padding: 2px;"
520  " margin: %2;"
521  " outline: none;"
522  " border-style: %4;"
523  " border-width: 1px;"
524  " border-color: rgb(%3,%3,%3);"
525  " border-radius: 3px;} "
526  "QgsColorButton:pressed{"
527  " %1"
528  " background-position: top left;"
529  " background-origin: content;"
530  " background-clip: content;"
531  " padding: 1px;"
532  " margin: %2;"
533  " outline: none;"
534  " border-style: inset;"
535  " border-width: 2px;"
536  " border-color: rgb(128,128,128);"
537  " border-radius: 4px;} " )
538  .arg( bkgrd )
539  .arg( margin )
540  .arg( isEnabled() ? "128" : "110" )
541  .arg( isEnabled() ? "outset" : "dotted" ) );
542  }
543 }
544 
545 QColor QgsColorButton::color() const
546 {
547  return mColor;
548 }
549 
550 void QgsColorButton::setColorDialogOptions( QColorDialog::ColorDialogOptions cdo )
551 {
552  mColorDialogOptions = cdo;
553 }
554 
555 QColorDialog::ColorDialogOptions QgsColorButton::colorDialogOptions()
556 {
557  return mColorDialogOptions;
558 }
559 
561 {
562  mColorDialogTitle = cdt;
563 }
564 
566 {
567  return mColorDialogTitle;
568 }
void keyPressEvent(QKeyEvent *e)
Reimplemented to allow cancelling color pick via keypress, and sample via space bar press...
bool colorFromMimeData(const QMimeData *mimeData, QColor &resultColor)
Attempts to parse mimeData as a color, either via the mime data's color data or by parsing a textual ...
void stopPicking(QPointF eventPos, bool sampleColor=true)
Ends a color picking operation.
void changeEvent(QEvent *e)
QString colorDialogTitle()
Returns the title, which the color chooser dialog shows.
#define QgsDebugMsg(str)
Definition: qgslogger.h:36
QColorDialog::ColorDialogOptions mColorDialogOptions
QColor color() const
Return the currently selected color.
static QPixmap getThemePixmap(const QString &theName)
Helper to get a theme icon as a pixmap.
void dropEvent(QDropEvent *e)
Reimplemented to accept dropped colors.
void setValidColor(const QColor &newColor)
Sets color for button, if valid.
void showEvent(QShowEvent *e)
void colorChanged(const QColor &color)
Is emitted, whenever a new color is accepted.
const char * sampler_cursor[]
Definition: qgscursors.cpp:183
void setColorDialogTitle(QString cdt)
Set the title, which the color chooser dialog will show.
void mousePressEvent(QMouseEvent *e)
Reimplemented to detect right mouse button clicks on the color button and allow dragging colors...
QString mColorDialogTitle
void setColorDialogOptions(QColorDialog::ColorDialogOptions cdo)
Specify the options for the color chooser dialog (e.g.
void setColor(const QColor &color)
Specify the current color.
QgsColorButton(QWidget *parent=0, QString cdt="", QColorDialog::ColorDialogOptions cdo=0)
Construct a new color button.
void mouseMoveEvent(QMouseEvent *e)
Reimplemented to allow dragging colors from button.
void setButtonBackground(QColor color=QColor())
Sets the background pixmap for the button based upon color and transparency.
static QColor parseColorWithAlpha(const QString colorStr, bool &containsAlpha)
Attempts to parse a string as a color using a variety of common formats, including hex codes...
QMimeData * createColorMimeData() const
Creates mime data from the current color.
QPoint mDragStartPosition
static const QPixmap & transpBkgrd()
QTemporaryFile mTempPNG
static QColor getLiveColor(const QColor &initialColor, QObject *updateObject, const char *updateSlot, QWidget *parent=0, const QString &title="", QColorDialog::ColorDialogOptions options=0)
Return a color selection from a QColorDialog, with live updating of interim selections.
QColorDialog::ColorDialogOptions colorDialogOptions()
Returns the options for the color chooser dialog.
void showContextMenu(QMouseEvent *event)
Shows the color button context menu and handles copying and pasting color values. ...
void dragEnterEvent(QDragEnterEvent *e)
Reimplemented to accept dragged colors.
void mouseReleaseEvent(QMouseEvent *e)
Reimplemented to allow color picking.
#define tr(sourceText)