QGIS API Documentation  2.4.0-Chugiak
 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 
22 #include <QPainter>
23 #include <QSettings>
24 #include <QTemporaryFile>
25 #include <QMouseEvent>
26 #include <QMenu>
27 #include <QClipboard>
28 #include <QDrag>
29 
30 #ifdef Q_OS_WIN
31 #include <windows.h>
32 QString QgsColorButton::fullPath( const QString &path )
33 {
34  TCHAR buf[MAX_PATH];
35  int len = GetLongPathName( path.toUtf8().constData(), buf, MAX_PATH );
36 
37  if ( len == 0 || len > MAX_PATH )
38  {
39  QgsDebugMsg( QString( "GetLongPathName('%1') failed with %2: %3" )
40  .arg( path ).arg( len ).arg( GetLastError() ) );
41  return path;
42  }
43 
44  QString res = QString::fromUtf8( buf );
45  return res;
46 }
47 #endif
48 
65 QgsColorButton::QgsColorButton( QWidget *parent, QString cdt, QColorDialog::ColorDialogOptions cdo )
66  : QPushButton( parent )
67  , mColorDialogTitle( cdt.isEmpty() ? tr( "Select Color" ) : cdt )
68  , mColor( Qt::black )
69  , mColorDialogOptions( cdo )
70  , mAcceptLiveUpdates( true )
71  , mTempPNG( NULL )
72  , mColorSet( false )
73 {
74  setAcceptDrops( true );
75  connect( this, SIGNAL( clicked() ), this, SLOT( onButtonClicked() ) );
76 }
77 
79 {
80  if ( mTempPNG.exists() )
81  mTempPNG.remove();
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 
95 {
96  //QgsDebugMsg( "entered" );
97  QColor newColor;
98  QSettings settings;
99  if ( mAcceptLiveUpdates && settings.value( "/qgis/live_color_dialogs", false ).toBool() )
100  {
101  newColor = QgsColorDialog::getLiveColor(
102  color(), this, SLOT( setValidColor( const QColor& ) ),
103  this->parentWidget(), mColorDialogTitle, mColorDialogOptions );
104  }
105  else
106  {
107  newColor = QColorDialog::getColor( color(), this->parentWidget(), mColorDialogTitle, mColorDialogOptions );
108  }
109  setValidColor( newColor );
110 
111  // reactivate button's window
112  activateWindow();
113 }
114 
115 void QgsColorButton::mousePressEvent( QMouseEvent *e )
116 {
117  if ( e->button() == Qt::RightButton )
118  {
119  showContextMenu( e );
120  return;
121  }
122  else if ( e->button() == Qt::LeftButton )
123  {
124  mDragStartPosition = e->pos();
125  }
126  QPushButton::mousePressEvent( e );
127 }
128 
130 {
131  QMimeData *mimeData = new QMimeData;
132  mimeData->setColorData( QVariant( mColor ) );
133  mimeData->setText( mColor.name() );
134  return mimeData;
135 }
136 
137 bool QgsColorButton::colorFromMimeData( const QMimeData * mimeData, QColor& resultColor )
138 {
139  //attempt to read color data directly from mime
140  QColor mimeColor = mimeData->colorData().value<QColor>();
141  if ( mimeColor.isValid() )
142  {
143  if ( !( mColorDialogOptions & QColorDialog::ShowAlphaChannel ) )
144  {
145  //remove alpha channel
146  mimeColor.setAlpha( 255 );
147  }
148  resultColor = mimeColor;
149  return true;
150  }
151 
152  //attempt to intrepret a color from mime text data
153  bool hasAlpha = false;
154  QColor textColor = QgsSymbolLayerV2Utils::parseColorWithAlpha( mimeData->text(), hasAlpha );
155  if ( textColor.isValid() )
156  {
157  if ( !( mColorDialogOptions & QColorDialog::ShowAlphaChannel ) )
158  {
159  //remove alpha channel
160  textColor.setAlpha( 255 );
161  }
162  else if ( !hasAlpha )
163  {
164  //mime color has no explicit alpha component, so keep existing alpha
165  textColor.setAlpha( mColor.alpha() );
166  }
167  resultColor = textColor;
168  return true;
169  }
170 
171  //could not get color from mime data
172  return false;
173 }
174 
175 void QgsColorButton::mouseMoveEvent( QMouseEvent *e )
176 {
177  if ( !( e->buttons() & Qt::LeftButton ) )
178  {
179  QPushButton::mouseMoveEvent( e );
180  return;
181  }
182 
183  if (( e->pos() - mDragStartPosition ).manhattanLength() < QApplication::startDragDistance() )
184  {
185  QPushButton::mouseMoveEvent( e );
186  return;
187  }
188 
189  QDrag *drag = new QDrag( this );
190  drag->setMimeData( createColorMimeData() );
191 
192  //craft a pixmap for the drag icon
193  QImage colorImage( 50, 50, QImage::Format_RGB32 );
194  QPainter imagePainter;
195  imagePainter.begin( &colorImage );
196  //start with a light gray background
197  imagePainter.fillRect( QRect( 0, 0, 50, 50 ), QBrush( QColor( 200, 200, 200 ) ) );
198  //draw rect with white border, filled with current color
199  QColor pixmapColor = mColor;
200  pixmapColor.setAlpha( 255 );
201  imagePainter.setBrush( QBrush( pixmapColor ) );
202  imagePainter.setPen( QPen( Qt::white ) );
203  imagePainter.drawRect( QRect( 1, 1, 47, 47 ) );
204  imagePainter.end();
205  //set as drag pixmap
206  drag->setPixmap( QPixmap::fromImage( colorImage ) );
207 
208  drag->exec( Qt::CopyAction );
209  setDown( false );
210 }
211 
212 void QgsColorButton::dragEnterEvent( QDragEnterEvent *e )
213 {
214  //is dragged data valid color data?
215  QColor mimeColor;
216  if ( colorFromMimeData( e->mimeData(), mimeColor ) )
217  {
218  e->acceptProposedAction();
219  }
220 }
221 
222 void QgsColorButton::dropEvent( QDropEvent *e )
223 {
224  //is dropped data valid color data?
225  QColor mimeColor;
226  if ( colorFromMimeData( e->mimeData(), mimeColor ) )
227  {
228  e->acceptProposedAction();
229  setColor( mimeColor );
230  }
231 }
232 
233 void QgsColorButton::showContextMenu( QMouseEvent *event )
234 {
235  QMenu colorContextMenu;
236 
237  QAction* copyColorAction = new QAction( tr( "Copy color" ), 0 );
238  colorContextMenu.addAction( copyColorAction );
239  QAction* pasteColorAction = new QAction( tr( "Paste color" ), 0 );
240  pasteColorAction->setEnabled( false );
241  colorContextMenu.addSeparator();
242  colorContextMenu.addAction( pasteColorAction );
243 
244  QColor clipColor;
245  if ( colorFromMimeData( QApplication::clipboard()->mimeData(), clipColor ) )
246  {
247  pasteColorAction->setEnabled( true );
248  }
249 
250  QAction* selectedAction = colorContextMenu.exec( event->globalPos( ) );
251  if ( selectedAction == copyColorAction )
252  {
253  //copy color
254  QApplication::clipboard()->setMimeData( createColorMimeData() );
255  }
256  else if ( selectedAction == pasteColorAction )
257  {
258  //paste color
259  setColor( clipColor );
260  }
261 
262  delete copyColorAction;
263  delete pasteColorAction;
264 }
265 
266 void QgsColorButton::setValidColor( const QColor& newColor )
267 {
268  if ( newColor.isValid() )
269  {
270  setColor( newColor );
271  }
272 }
273 
275 {
276  if ( e->type() == QEvent::EnabledChange )
277  {
279  }
280  QPushButton::changeEvent( e );
281 }
282 
283 #if 0 // causes too many cyclical updates, but may be needed on some platforms
284 void QgsColorButton::paintEvent( QPaintEvent* e )
285 {
286  QPushButton::paintEvent( e );
287 
288  if ( !mBackgroundSet )
289  {
291  }
292 }
293 #endif
294 
295 void QgsColorButton::showEvent( QShowEvent* e )
296 {
298  QPushButton::showEvent( e );
299 }
300 
301 void QgsColorButton::setColor( const QColor &color )
302 {
303  if ( !color.isValid() )
304  {
305  return;
306  }
307  QColor oldColor = mColor;
308  mColor = color;
309 
310  // handle when initially set color is same as default (Qt::black); consider it a color change
311  if ( oldColor != mColor || ( mColor == QColor( Qt::black ) && !mColorSet ) )
312  {
314  if ( isEnabled() )
315  {
316  // TODO: May be beneficial to have the option to set color without emitting this signal.
317  // Now done by blockSignals( bool ) where button is used
318  emit colorChanged( mColor );
319  }
320  }
321  mColorSet = true;
322 }
323 
325 {
326  if ( !text().isEmpty() )
327  {
328  // generate icon pixmap for regular pushbutton
329  setFlat( false );
330 
331  QPixmap pixmap;
332  pixmap = QPixmap( iconSize() );
333  pixmap.fill( QColor( 0, 0, 0, 0 ) );
334 
335  int iconW = iconSize().width();
336  int iconH = iconSize().height();
337  QRect rect( 0, 0, iconW, iconH );
338 
339  // QPainterPath::addRoundRect has flaws, draw chamfered corners instead
340  QPainterPath roundRect;
341  int chamfer = 3;
342  int inset = 1;
343  roundRect.moveTo( chamfer, inset );
344  roundRect.lineTo( iconW - chamfer, inset );
345  roundRect.lineTo( iconW - inset, chamfer );
346  roundRect.lineTo( iconW - inset, iconH - chamfer );
347  roundRect.lineTo( iconW - chamfer, iconH - inset );
348  roundRect.lineTo( chamfer, iconH - inset );
349  roundRect.lineTo( inset, iconH - chamfer );
350  roundRect.lineTo( inset, chamfer );
351  roundRect.closeSubpath();
352 
353  QPainter p;
354  p.begin( &pixmap );
355  p.setRenderHint( QPainter::Antialiasing );
356  p.setClipPath( roundRect );
357  p.setPen( Qt::NoPen );
358  if ( mColor.alpha() < 255 )
359  {
360  p.drawTiledPixmap( rect, transpBkgrd() );
361  }
362  p.setBrush( mColor );
363  p.drawRect( rect );
364  p.end();
365 
366  // set this pixmap as icon
367  setIcon( QIcon( pixmap ) );
368  }
369  else
370  {
371  // generate temp background image file with checkerboard canvas to be used via stylesheet
372 
373  // set flat, or inline spacing (widget margins) needs to be manually calculated and set
374  setFlat( true );
375 
376  bool useAlpha = ( mColorDialogOptions & QColorDialog::ShowAlphaChannel );
377 
378  // in case margins need to be adjusted
379  QString margin = QString( "%1px %2px %3px %4px" ).arg( 0 ).arg( 0 ).arg( 0 ).arg( 0 );
380 
381  //QgsDebugMsg( QString( "%1 margin: %2" ).arg( objectName() ).arg( margin ) );
382 
383  QString bkgrd = QString( " background-color: rgba(%1,%2,%3,%4);" )
384  .arg( mColor.red() )
385  .arg( mColor.green() )
386  .arg( mColor.blue() )
387  .arg( useAlpha ? mColor.alpha() : 255 );
388 
389  if ( useAlpha && mColor.alpha() < 255 )
390  {
391  QPixmap pixmap = transpBkgrd();
392  QRect rect( 0, 0, pixmap.width(), pixmap.height() );
393 
394  QPainter p;
395  p.begin( &pixmap );
396  p.setRenderHint( QPainter::Antialiasing );
397  p.setPen( Qt::NoPen );
398  p.setBrush( mColor );
399  p.drawRect( rect );
400  p.end();
401 
402  if ( mTempPNG.open() )
403  {
404  mTempPNG.setAutoRemove( false );
405  pixmap.save( mTempPNG.fileName(), "PNG" );
406  mTempPNG.close();
407  }
408 
409  QString bgFileName = mTempPNG.fileName();
410 #ifdef Q_OS_WIN
411  //on windows, mTempPNG will use a shortened path for the temporary folder name
412  //this does not work with stylesheets, resulting in the whole button disappearing (#10187)
413  bgFileName = fullPath( bgFileName );
414 #endif
415  bkgrd = QString( " background-image: url(%1);" ).arg( bgFileName );
416  }
417 
418  // TODO: get OS-style focus color and switch border to that color when button in focus
419  setStyleSheet( QString( "QgsColorButton{"
420  " %1"
421  " background-position: top left;"
422  " background-origin: content;"
423  " background-clip: content;"
424  " padding: 2px;"
425  " margin: %2;"
426  " outline: none;"
427  " border-style: %4;"
428  " border-width: 1px;"
429  " border-color: rgb(%3,%3,%3);"
430  " border-radius: 3px;} "
431  "QgsColorButton:pressed{"
432  " %1"
433  " background-position: top left;"
434  " background-origin: content;"
435  " background-clip: content;"
436  " padding: 1px;"
437  " margin: %2;"
438  " outline: none;"
439  " border-style: inset;"
440  " border-width: 2px;"
441  " border-color: rgb(128,128,128);"
442  " border-radius: 4px;} " )
443  .arg( bkgrd )
444  .arg( margin )
445  .arg( isEnabled() ? "128" : "110" )
446  .arg( isEnabled() ? "outset" : "dotted" ) );
447  }
448 }
449 
450 QColor QgsColorButton::color() const
451 {
452  return mColor;
453 }
454 
455 void QgsColorButton::setColorDialogOptions( QColorDialog::ColorDialogOptions cdo )
456 {
457  mColorDialogOptions = cdo;
458 }
459 
460 QColorDialog::ColorDialogOptions QgsColorButton::colorDialogOptions()
461 {
462  return mColorDialogOptions;
463 }
464 
466 {
467  mColorDialogTitle = cdt;
468 }
469 
471 {
472  return mColorDialogTitle;
473 }
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 changeEvent(QEvent *e)
QString colorDialogTitle()
Returns the title, which the color chooser dialog shows.
#define QgsDebugMsg(str)
Definition: qgslogger.h:36
void setButtonBackground()
Sets the background pixmap for the button based upon set color and transparency.
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.
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.
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.
#define tr(sourceText)