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  QAction* pickColorAction = new QAction( tr( "Pick color" ), 0 );
316  colorContextMenu.addSeparator();
317  colorContextMenu.addAction( pickColorAction );
318 
319  QColor clipColor;
320  if ( colorFromMimeData( QApplication::clipboard()->mimeData(), clipColor ) )
321  {
322  pasteColorAction->setEnabled( true );
323  }
324 
325  QAction* selectedAction = colorContextMenu.exec( event->globalPos( ) );
326  if ( selectedAction == copyColorAction )
327  {
328  //copy color
329  QApplication::clipboard()->setMimeData( createColorMimeData() );
330  }
331  else if ( selectedAction == pasteColorAction )
332  {
333  //paste color
334  setColor( clipColor );
335  }
336  else if ( selectedAction == pickColorAction )
337  {
338  //pick color
339  QPixmap samplerPixmap = QPixmap(( const char ** ) sampler_cursor );
340  setCursor( QCursor( samplerPixmap, 0, 0 ) );
341  grabMouse();
342  mPickingColor = true;
343  }
344 
345  delete copyColorAction;
346  delete pasteColorAction;
347  delete pickColorAction;
348 }
349 
350 void QgsColorButton::setValidColor( const QColor& newColor )
351 {
352  if ( newColor.isValid() )
353  {
354  setColor( newColor );
355  }
356 }
357 
359 {
360  if ( e->type() == QEvent::EnabledChange )
361  {
363  }
364  QPushButton::changeEvent( e );
365 }
366 
367 #if 0 // causes too many cyclical updates, but may be needed on some platforms
368 void QgsColorButton::paintEvent( QPaintEvent* e )
369 {
370  QPushButton::paintEvent( e );
371 
372  if ( !mBackgroundSet )
373  {
375  }
376 }
377 #endif
378 
379 void QgsColorButton::showEvent( QShowEvent* e )
380 {
382  QPushButton::showEvent( e );
383 }
384 
385 void QgsColorButton::setColor( const QColor &color )
386 {
387  if ( !color.isValid() )
388  {
389  return;
390  }
391  QColor oldColor = mColor;
392  mColor = color;
393 
394  // handle when initially set color is same as default (Qt::black); consider it a color change
395  if ( oldColor != mColor || ( mColor == QColor( Qt::black ) && !mColorSet ) )
396  {
398  if ( isEnabled() )
399  {
400  // TODO: May be beneficial to have the option to set color without emitting this signal.
401  // Now done by blockSignals( bool ) where button is used
402  emit colorChanged( mColor );
403  }
404  }
405  mColorSet = true;
406 }
407 
409 {
410  if ( !color.isValid() )
411  {
412  color = mColor;
413  }
414  if ( !text().isEmpty() )
415  {
416  // generate icon pixmap for regular pushbutton
417  setFlat( false );
418 
419  QPixmap pixmap;
420  pixmap = QPixmap( iconSize() );
421  pixmap.fill( QColor( 0, 0, 0, 0 ) );
422 
423  int iconW = iconSize().width();
424  int iconH = iconSize().height();
425  QRect rect( 0, 0, iconW, iconH );
426 
427  // QPainterPath::addRoundRect has flaws, draw chamfered corners instead
428  QPainterPath roundRect;
429  int chamfer = 3;
430  int inset = 1;
431  roundRect.moveTo( chamfer, inset );
432  roundRect.lineTo( iconW - chamfer, inset );
433  roundRect.lineTo( iconW - inset, chamfer );
434  roundRect.lineTo( iconW - inset, iconH - chamfer );
435  roundRect.lineTo( iconW - chamfer, iconH - inset );
436  roundRect.lineTo( chamfer, iconH - inset );
437  roundRect.lineTo( inset, iconH - chamfer );
438  roundRect.lineTo( inset, chamfer );
439  roundRect.closeSubpath();
440 
441  QPainter p;
442  p.begin( &pixmap );
443  p.setRenderHint( QPainter::Antialiasing );
444  p.setClipPath( roundRect );
445  p.setPen( Qt::NoPen );
446  if ( color.alpha() < 255 )
447  {
448  p.drawTiledPixmap( rect, transpBkgrd() );
449  }
450  p.setBrush( color );
451  p.drawRect( rect );
452  p.end();
453 
454  // set this pixmap as icon
455  setIcon( QIcon( pixmap ) );
456  }
457  else
458  {
459  // generate temp background image file with checkerboard canvas to be used via stylesheet
460 
461  // set flat, or inline spacing (widget margins) needs to be manually calculated and set
462  setFlat( true );
463 
464  bool useAlpha = ( mColorDialogOptions & QColorDialog::ShowAlphaChannel );
465 
466  // in case margins need to be adjusted
467  QString margin = QString( "%1px %2px %3px %4px" ).arg( 0 ).arg( 0 ).arg( 0 ).arg( 0 );
468 
469  //QgsDebugMsg( QString( "%1 margin: %2" ).arg( objectName() ).arg( margin ) );
470 
471  QString bkgrd = QString( " background-color: rgba(%1,%2,%3,%4);" )
472  .arg( color.red() )
473  .arg( color.green() )
474  .arg( color.blue() )
475  .arg( useAlpha ? color.alpha() : 255 );
476 
477  if ( useAlpha && color.alpha() < 255 )
478  {
479  QPixmap pixmap = transpBkgrd();
480  QRect rect( 0, 0, pixmap.width(), pixmap.height() );
481 
482  QPainter p;
483  p.begin( &pixmap );
484  p.setRenderHint( QPainter::Antialiasing );
485  p.setPen( Qt::NoPen );
486  p.setBrush( mColor );
487  p.drawRect( rect );
488  p.end();
489 
490  if ( mTempPNG.open() )
491  {
492  mTempPNG.setAutoRemove( false );
493  pixmap.save( mTempPNG.fileName(), "PNG" );
494  mTempPNG.close();
495  }
496 
497  QString bgFileName = mTempPNG.fileName();
498 #ifdef Q_OS_WIN
499  //on windows, mTempPNG will use a shortened path for the temporary folder name
500  //this does not work with stylesheets, resulting in the whole button disappearing (#10187)
501  bgFileName = fullPath( bgFileName );
502 #endif
503  bkgrd = QString( " background-image: url(%1);" ).arg( bgFileName );
504  }
505 
506  // TODO: get OS-style focus color and switch border to that color when button in focus
507  setStyleSheet( QString( "QgsColorButton{"
508  " %1"
509  " background-position: top left;"
510  " background-origin: content;"
511  " background-clip: content;"
512  " padding: 2px;"
513  " margin: %2;"
514  " outline: none;"
515  " border-style: %4;"
516  " border-width: 1px;"
517  " border-color: rgb(%3,%3,%3);"
518  " border-radius: 3px;} "
519  "QgsColorButton:pressed{"
520  " %1"
521  " background-position: top left;"
522  " background-origin: content;"
523  " background-clip: content;"
524  " padding: 1px;"
525  " margin: %2;"
526  " outline: none;"
527  " border-style: inset;"
528  " border-width: 2px;"
529  " border-color: rgb(128,128,128);"
530  " border-radius: 4px;} " )
531  .arg( bkgrd )
532  .arg( margin )
533  .arg( isEnabled() ? "128" : "110" )
534  .arg( isEnabled() ? "outset" : "dotted" ) );
535  }
536 }
537 
538 QColor QgsColorButton::color() const
539 {
540  return mColor;
541 }
542 
543 void QgsColorButton::setColorDialogOptions( QColorDialog::ColorDialogOptions cdo )
544 {
545  mColorDialogOptions = cdo;
546 }
547 
548 QColorDialog::ColorDialogOptions QgsColorButton::colorDialogOptions()
549 {
550  return mColorDialogOptions;
551 }
552 
554 {
555  mColorDialogTitle = cdt;
556 }
557 
559 {
560  return mColorDialogTitle;
561 }
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)