QGIS API Documentation  3.0.2-Girona (307d082)
qgscolorwidgets.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgscolorwidgets.cpp - color selection widgets
3  ---------------------
4  begin : September 2014
5  copyright : (C) 2014 by Nyall Dawson
6  email : nyall dot dawson at gmail dot com
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 "qgscolorwidgets.h"
17 #include "qgsapplication.h"
18 #include "qgssymbollayerutils.h"
19 #include "qgssettings.h"
20 #include "qgslogger.h"
21 
22 #include <QResizeEvent>
23 #include <QStyleOptionFrameV3>
24 #include <QPainter>
25 #include <QHBoxLayout>
26 #include <QSpinBox>
27 #include <QLineEdit>
28 #include <QFontMetrics>
29 #include <QToolButton>
30 #include <QMenu>
31 #include <QDrag>
32 
33 #include <cmath>
34 
35 
36 //
37 // QgsColorWidget
38 //
39 
40 QgsColorWidget::QgsColorWidget( QWidget *parent, const ColorComponent component )
41  : QWidget( parent )
42  , mCurrentColor( Qt::red )
43  , mComponent( component )
44 {
45  setAcceptDrops( true );
46 }
47 
49 {
50  return componentValue( mComponent );
51 }
52 
53 QPixmap QgsColorWidget::createDragIcon( const QColor &color )
54 {
55  //craft a pixmap for the drag icon
56  QPixmap pixmap( 50, 50 );
57  pixmap.fill( Qt::transparent );
58  QPainter painter;
59  painter.begin( &pixmap );
60  //start with a light gray background
61  painter.fillRect( QRect( 0, 0, 50, 50 ), QBrush( QColor( 200, 200, 200 ) ) );
62  //draw rect with white border, filled with current color
63  QColor pixmapColor = color;
64  pixmapColor.setAlpha( 255 );
65  painter.setBrush( QBrush( pixmapColor ) );
66  painter.setPen( QPen( Qt::white ) );
67  painter.drawRect( QRect( 1, 1, 47, 47 ) );
68  painter.end();
69  return pixmap;
70 }
71 
73 {
74  if ( !mCurrentColor.isValid() )
75  {
76  return -1;
77  }
78 
79  switch ( component )
80  {
82  return mCurrentColor.red();
84  return mCurrentColor.green();
86  return mCurrentColor.blue();
88  //hue is treated specially, to avoid -1 hues values from QColor for ambiguous hues
89  return hue();
91  return mCurrentColor.hsvSaturation();
93  return mCurrentColor.value();
95  return mCurrentColor.alpha();
96  default:
97  return -1;
98  }
99 }
100 
102 {
103  return componentRange( mComponent );
104 }
105 
107 {
108  if ( component == QgsColorWidget::Multiple )
109  {
110  //no component
111  return -1;
112  }
113 
114  if ( component == QgsColorWidget::Hue )
115  {
116  //hue ranges to 359
117  return 359;
118  }
119  else
120  {
121  //all other components range to 255
122  return 255;
123  }
124 }
125 
127 {
128  if ( mCurrentColor.hue() >= 0 )
129  {
130  return mCurrentColor.hue();
131  }
132  else
133  {
134  return mExplicitHue;
135  }
136 }
137 
138 void QgsColorWidget::alterColor( QColor &color, const QgsColorWidget::ColorComponent component, const int newValue ) const
139 {
140  int h, s, v, a;
141  color.getHsv( &h, &s, &v, &a );
142 
143  //clip value to sensible range
144  int clippedValue = std::min( std::max( 0, newValue ), componentRange( component ) );
145 
146  switch ( component )
147  {
148  case QgsColorWidget::Red:
149  color.setRed( clippedValue );
150  return;
152  color.setGreen( clippedValue );
153  return;
155  color.setBlue( clippedValue );
156  return;
157  case QgsColorWidget::Hue:
158  color.setHsv( clippedValue, s, v, a );
159  return;
161  color.setHsv( h, clippedValue, v, a );
162  return;
164  color.setHsv( h, s, clippedValue, a );
165  return;
167  color.setAlpha( clippedValue );
168  return;
169  default:
170  return;
171  }
172 }
173 
175 {
176  static QPixmap sTranspBkgrd;
177 
178  if ( sTranspBkgrd.isNull() )
179  sTranspBkgrd = QgsApplication::getThemePixmap( QStringLiteral( "/transp-background_8x8.png" ) );
180 
181  return sTranspBkgrd;
182 }
183 
184 void QgsColorWidget::dragEnterEvent( QDragEnterEvent *e )
185 {
186  //is dragged data valid color data?
187  bool hasAlpha;
188  QColor mimeColor = QgsSymbolLayerUtils::colorFromMimeData( e->mimeData(), hasAlpha );
189 
190  if ( mimeColor.isValid() )
191  {
192  //if so, we accept the drag
193  e->acceptProposedAction();
194  }
195 }
196 
197 void QgsColorWidget::dropEvent( QDropEvent *e )
198 {
199  //is dropped data valid color data?
200  bool hasAlpha = false;
201  QColor mimeColor = QgsSymbolLayerUtils::colorFromMimeData( e->mimeData(), hasAlpha );
202 
203  if ( mimeColor.isValid() )
204  {
205  //accept drop and set new color
206  e->acceptProposedAction();
207 
208  if ( !hasAlpha )
209  {
210  //mime color has no explicit alpha component, so keep existing alpha
211  mimeColor.setAlpha( mCurrentColor.alpha() );
212  }
213 
214  setColor( mimeColor );
215  emit colorChanged( mCurrentColor );
216  }
217 
218  //could not get color from mime data
219 }
220 
221 void QgsColorWidget::mouseMoveEvent( QMouseEvent *e )
222 {
223  emit hovered();
224  e->accept();
225  //don't pass to QWidget::mouseMoveEvent, causes issues with widget used in QWidgetAction
226 }
227 
228 void QgsColorWidget::mousePressEvent( QMouseEvent *e )
229 {
230  e->accept();
231  //don't pass to QWidget::mousePressEvent, causes issues with widget used in QWidgetAction
232 }
233 
234 void QgsColorWidget::mouseReleaseEvent( QMouseEvent *e )
235 {
236  e->accept();
237  //don't pass to QWidget::mouseReleaseEvent, causes issues with widget used in QWidgetAction
238 }
239 
240 QColor QgsColorWidget::color() const
241 {
242  return mCurrentColor;
243 }
244 
246 {
247  if ( component == mComponent )
248  {
249  return;
250  }
251 
253  update();
254 }
255 
256 void QgsColorWidget::setComponentValue( const int value )
257 {
259  {
260  return;
261  }
262 
263  //clip value to valid range
264  int valueClipped = std::min( value, componentRange() );
265  valueClipped = std::max( valueClipped, 0 );
266 
267  int r, g, b, a;
268  mCurrentColor.getRgb( &r, &g, &b, &a );
269  int h, s, v;
270  mCurrentColor.getHsv( &h, &s, &v );
271  //overwrite hue with explicit hue if required
272  h = hue();
273 
274  switch ( mComponent )
275  {
276  case QgsColorWidget::Red:
277  if ( r == valueClipped )
278  {
279  return;
280  }
281  mCurrentColor.setRed( valueClipped );
282  break;
284  if ( g == valueClipped )
285  {
286  return;
287  }
288  mCurrentColor.setGreen( valueClipped );
289  break;
291  if ( b == valueClipped )
292  {
293  return;
294  }
295  mCurrentColor.setBlue( valueClipped );
296  break;
297  case QgsColorWidget::Hue:
298  if ( h == valueClipped )
299  {
300  return;
301  }
302  mCurrentColor.setHsv( valueClipped, s, v, a );
303  break;
305  if ( s == valueClipped )
306  {
307  return;
308  }
309  mCurrentColor.setHsv( h, valueClipped, v, a );
310  break;
312  if ( v == valueClipped )
313  {
314  return;
315  }
316  mCurrentColor.setHsv( h, s, valueClipped, a );
317  break;
319  if ( a == valueClipped )
320  {
321  return;
322  }
323  mCurrentColor.setAlpha( valueClipped );
324  break;
325  default:
326  return;
327  }
328 
329  //update recorded hue
330  if ( mCurrentColor.hue() >= 0 )
331  {
332  mExplicitHue = mCurrentColor.hue();
333  }
334 
335  update();
336 }
337 
338 void QgsColorWidget::setColor( const QColor &color, const bool emitSignals )
339 {
340  if ( color == mCurrentColor )
341  {
342  return;
343  }
344 
346 
347  //update recorded hue
348  if ( color.hue() >= 0 )
349  {
350  mExplicitHue = color.hue();
351  }
352 
353  if ( emitSignals )
354  {
355  emit colorChanged( mCurrentColor );
356  }
357 
358  update();
359 }
360 
361 
362 //
363 // QgsColorWheel
364 //
365 
367  : QgsColorWidget( parent )
368 {
369  //create wheel hue brush - only do this once
370  QConicalGradient wheelGradient = QConicalGradient( 0, 0, 0 );
371  int wheelStops = 20;
372  QColor gradColor = QColor::fromHsvF( 1.0, 1.0, 1.0 );
373  for ( int pos = 0; pos <= wheelStops; ++pos )
374  {
375  double relativePos = ( double )pos / wheelStops;
376  gradColor.setHsvF( relativePos, 1, 1 );
377  wheelGradient.setColorAt( relativePos, gradColor );
378  }
379  mWheelBrush = QBrush( wheelGradient );
380 }
381 
383 {
384  delete mWheelImage;
385  delete mTriangleImage;
386  delete mWidgetImage;
387 }
388 
390 {
391  int size = Qgis::UI_SCALE_FACTOR * fontMetrics().width( QStringLiteral( "XXXXXXXXXXXXXXXXXXXXXX" ) );
392  return QSize( size, size );
393 }
394 
395 void QgsColorWheel::paintEvent( QPaintEvent *event )
396 {
397  Q_UNUSED( event );
398  QPainter painter( this );
399 
400  if ( !mWidgetImage || !mWheelImage || !mTriangleImage )
401  {
402  createImages( size() );
403  }
404 
405  //draw everything in an image
406  mWidgetImage->fill( Qt::transparent );
407  QPainter imagePainter( mWidgetImage );
408  imagePainter.setRenderHint( QPainter::Antialiasing );
409 
410  if ( mWheelDirty )
411  {
412  //need to redraw the wheel image
413  createWheel();
414  }
415 
416  //draw wheel centered on widget
417  QPointF center = QPointF( width() / 2.0, height() / 2.0 );
418  imagePainter.drawImage( QPointF( center.x() - ( mWheelImage->width() / 2.0 ), center.y() - ( mWheelImage->height() / 2.0 ) ), *mWheelImage );
419 
420  //draw hue marker
421  int h = hue();
422  double length = mWheelImage->width() / 2.0;
423  QLineF hueMarkerLine = QLineF( center.x(), center.y(), center.x() + length, center.y() );
424  hueMarkerLine.setAngle( h );
425  imagePainter.save();
426  //use sourceIn mode for nicer antialiasing
427  imagePainter.setCompositionMode( QPainter::CompositionMode_SourceIn );
428  QPen pen;
429  pen.setWidth( 2 );
430  //adapt pen color for hue
431  pen.setColor( h > 20 && h < 200 ? Qt::black : Qt::white );
432  imagePainter.setPen( pen );
433  imagePainter.drawLine( hueMarkerLine );
434  imagePainter.restore();
435 
436  //draw triangle
437  if ( mTriangleDirty )
438  {
439  createTriangle();
440  }
441  imagePainter.drawImage( QPointF( center.x() - ( mWheelImage->width() / 2.0 ), center.y() - ( mWheelImage->height() / 2.0 ) ), *mTriangleImage );
442 
443  //draw current color marker
444  double triangleRadius = length - mWheelThickness - 1;
445 
446  //adapted from equations at https://github.com/timjb/colortriangle/blob/master/colortriangle.js by Tim Baumann
447  double lightness = mCurrentColor.lightnessF();
448  double hueRadians = ( h * M_PI / 180.0 );
449  double hx = std::cos( hueRadians ) * triangleRadius;
450  double hy = -std::sin( hueRadians ) * triangleRadius;
451  double sx = -std::cos( -hueRadians + ( M_PI / 3.0 ) ) * triangleRadius;
452  double sy = -std::sin( -hueRadians + ( M_PI / 3.0 ) ) * triangleRadius;
453  double vx = -std::cos( hueRadians + ( M_PI / 3.0 ) ) * triangleRadius;
454  double vy = std::sin( hueRadians + ( M_PI / 3.0 ) ) * triangleRadius;
455  double mx = ( sx + vx ) / 2.0;
456  double my = ( sy + vy ) / 2.0;
457 
458  double a = ( 1 - 2.0 * std::fabs( lightness - 0.5 ) ) * mCurrentColor.hslSaturationF();
459  double x = sx + ( vx - sx ) * lightness + ( hx - mx ) * a;
460  double y = sy + ( vy - sy ) * lightness + ( hy - my ) * a;
461 
462  //adapt pen color for lightness
463  pen.setColor( lightness > 0.7 ? Qt::black : Qt::white );
464  imagePainter.setPen( pen );
465  imagePainter.setBrush( Qt::NoBrush );
466  imagePainter.drawEllipse( QPointF( x + center.x(), y + center.y() ), 4.0, 4.0 );
467  imagePainter.end();
468 
469  //draw image onto widget
470  painter.drawImage( QPoint( 0, 0 ), *mWidgetImage );
471  painter.end();
472 }
473 
474 void QgsColorWheel::setColor( const QColor &color, const bool emitSignals )
475 {
476  if ( color.hue() >= 0 && color.hue() != hue() )
477  {
478  //hue has changed, need to redraw the triangle
479  mTriangleDirty = true;
480  }
481 
482  QgsColorWidget::setColor( color, emitSignals );
483 }
484 
485 void QgsColorWheel::createImages( const QSizeF size )
486 {
487  double wheelSize = std::min( size.width(), size.height() ) - mMargin * 2.0;
488  mWheelThickness = wheelSize / 15.0;
489 
490  //recreate cache images at correct size
491  delete mWheelImage;
492  mWheelImage = new QImage( wheelSize, wheelSize, QImage::Format_ARGB32 );
493  delete mTriangleImage;
494  mTriangleImage = new QImage( wheelSize, wheelSize, QImage::Format_ARGB32 );
495  delete mWidgetImage;
496  mWidgetImage = new QImage( size.width(), size.height(), QImage::Format_ARGB32 );
497 
498  //trigger a redraw for the images
499  mWheelDirty = true;
500  mTriangleDirty = true;
501 }
502 
503 void QgsColorWheel::resizeEvent( QResizeEvent *event )
504 {
505  //recreate images for new size
506  createImages( event->size() );
507  QgsColorWidget::resizeEvent( event );
508 }
509 
510 void QgsColorWheel::setColorFromPos( const QPointF pos )
511 {
512  QPointF center = QPointF( width() / 2.0, height() / 2.0 );
513  //line from center to mouse position
514  QLineF line = QLineF( center.x(), center.y(), pos.x(), pos.y() );
515 
516  QColor newColor = QColor();
517 
518  int h, s, l, alpha;
519  mCurrentColor.getHsl( &h, &s, &l, &alpha );
520  //override hue with explicit hue, so we don't get -1 values from QColor for hue
521  h = hue();
522 
523  if ( mClickedPart == QgsColorWheel::Triangle )
524  {
525  //adapted from equations at https://github.com/timjb/colortriangle/blob/master/colortriangle.js by Tim Baumann
526 
527  //position of event relative to triangle center
528  double x = pos.x() - center.x();
529  double y = pos.y() - center.y();
530 
531  double eventAngleRadians = line.angle() * M_PI / 180.0;
532  double hueRadians = h * M_PI / 180.0;
533  double rad0 = std::fmod( eventAngleRadians + 2.0 * M_PI - hueRadians, 2.0 * M_PI );
534  double rad1 = std::fmod( rad0, ( ( 2.0 / 3.0 ) * M_PI ) ) - ( M_PI / 3.0 );
535  double length = mWheelImage->width() / 2.0;
536  double triangleLength = length - mWheelThickness - 1;
537 
538  double a = 0.5 * triangleLength;
539  double b = std::tan( rad1 ) * a;
540  double r = std::sqrt( x * x + y * y );
541  double maxR = std::sqrt( a * a + b * b );
542 
543  if ( r > maxR )
544  {
545  double dx = std::tan( rad1 ) * r;
546  double rad2 = std::atan( dx / maxR );
547  rad2 = std::min( rad2, M_PI / 3.0 );
548  rad2 = std::max( rad2, -M_PI / 3.0 );
549  eventAngleRadians += rad2 - rad1;
550  rad0 = std::fmod( eventAngleRadians + 2.0 * M_PI - hueRadians, 2.0 * M_PI );
551  rad1 = std::fmod( rad0, ( ( 2.0 / 3.0 ) * M_PI ) ) - ( M_PI / 3.0 );
552  b = std::tan( rad1 ) * a;
553  r = std::sqrt( a * a + b * b );
554  }
555 
556  double triangleSideLength = std::sqrt( 3.0 ) * triangleLength;
557  double newL = ( ( -std::sin( rad0 ) * r ) / triangleSideLength ) + 0.5;
558  double widthShare = 1.0 - ( std::fabs( newL - 0.5 ) * 2.0 );
559  double newS = ( ( ( std::cos( rad0 ) * r ) + ( triangleLength / 2.0 ) ) / ( 1.5 * triangleLength ) ) / widthShare;
560  s = std::min( static_cast< int >( std::round( std::max( 0.0, newS ) * 255.0 ) ), 255 );
561  l = std::min( static_cast< int >( std::round( std::max( 0.0, newL ) * 255.0 ) ), 255 );
562  newColor = QColor::fromHsl( h, s, l );
563  //explicitly set the hue again, so that it's exact
564  newColor.setHsv( h, newColor.hsvSaturation(), newColor.value(), alpha );
565  }
566  else if ( mClickedPart == QgsColorWheel::Wheel )
567  {
568  //use hue angle
569  s = mCurrentColor.hsvSaturation();
570  int v = mCurrentColor.value();
571  int newHue = line.angle();
572  newColor = QColor::fromHsv( newHue, s, v, alpha );
573  //hue has changed, need to redraw triangle
574  mTriangleDirty = true;
575  }
576 
577  if ( newColor.isValid() && newColor != mCurrentColor )
578  {
579  //color has changed
580  mCurrentColor = QColor( newColor );
581 
582  if ( mCurrentColor.hue() >= 0 )
583  {
584  //color has a valid hue, so update the QgsColorWidget's explicit hue
585  mExplicitHue = mCurrentColor.hue();
586  }
587 
588  update();
589  emit colorChanged( mCurrentColor );
590  }
591 }
592 
593 void QgsColorWheel::mouseMoveEvent( QMouseEvent *event )
594 {
595  setColorFromPos( event->pos() );
597 }
598 
599 void QgsColorWheel::mousePressEvent( QMouseEvent *event )
600 {
601  //calculate where the event occurred -- on the wheel or inside the triangle?
602 
603  //create a line from the widget's center to the event
604  QLineF line = QLineF( width() / 2.0, height() / 2.0, event->pos().x(), event->pos().y() );
605 
606  double innerLength = mWheelImage->width() / 2.0 - mWheelThickness;
607  if ( line.length() < innerLength )
608  {
609  mClickedPart = QgsColorWheel::Triangle;
610  }
611  else
612  {
613  mClickedPart = QgsColorWheel::Wheel;
614  }
615  setColorFromPos( event->pos() );
616 }
617 
618 void QgsColorWheel::mouseReleaseEvent( QMouseEvent *event )
619 {
620  Q_UNUSED( event );
621  mClickedPart = QgsColorWheel::None;
622 }
623 
624 void QgsColorWheel::createWheel()
625 {
626  if ( !mWheelImage )
627  {
628  return;
629  }
630 
631  int maxSize = std::min( mWheelImage->width(), mWheelImage->height() );
632  double wheelRadius = maxSize / 2.0;
633 
634  mWheelImage->fill( Qt::transparent );
635  QPainter p( mWheelImage );
636  p.setRenderHint( QPainter::Antialiasing );
637  p.setBrush( mWheelBrush );
638  p.setPen( Qt::NoPen );
639 
640  //draw hue wheel as a circle
641  p.translate( wheelRadius, wheelRadius );
642  p.drawEllipse( QPointF( 0.0, 0.0 ), wheelRadius, wheelRadius );
643 
644  //cut hole in center of circle to make a ring
645  p.setCompositionMode( QPainter::CompositionMode_DestinationOut );
646  p.setBrush( QBrush( Qt::black ) );
647  p.drawEllipse( QPointF( 0.0, 0.0 ), wheelRadius - mWheelThickness, wheelRadius - mWheelThickness );
648  p.end();
649 
650  mWheelDirty = false;
651 }
652 
653 void QgsColorWheel::createTriangle()
654 {
655  if ( !mWheelImage || !mTriangleImage )
656  {
657  return;
658  }
659 
660  QPointF center = QPointF( mWheelImage->width() / 2.0, mWheelImage->height() / 2.0 );
661  mTriangleImage->fill( Qt::transparent );
662 
663  QPainter imagePainter( mTriangleImage );
664  imagePainter.setRenderHint( QPainter::Antialiasing );
665 
666  int angle = hue();
667  double wheelRadius = mWheelImage->width() / 2.0;
668  double triangleRadius = wheelRadius - mWheelThickness - 1;
669 
670  //pure version of hue (at full saturation and value)
671  QColor pureColor = QColor::fromHsv( angle, 255, 255 );
672  //create copy of color but with 0 alpha
673  QColor alphaColor = QColor( pureColor );
674  alphaColor.setAlpha( 0 );
675 
676  //some rather ugly shortcuts to obtain corners and midpoints of triangle
677  QLineF line1 = QLineF( center.x(), center.y(), center.x() - triangleRadius * std::cos( M_PI / 3.0 ), center.y() - triangleRadius * std::sin( M_PI / 3.0 ) );
678  QLineF line2 = QLineF( center.x(), center.y(), center.x() + triangleRadius, center.y() );
679  QLineF line3 = QLineF( center.x(), center.y(), center.x() - triangleRadius * std::cos( M_PI / 3.0 ), center.y() + triangleRadius * std::sin( M_PI / 3.0 ) );
680  QLineF line4 = QLineF( center.x(), center.y(), center.x() - triangleRadius * std::cos( M_PI / 3.0 ), center.y() );
681  QLineF line5 = QLineF( center.x(), center.y(), ( line2.p2().x() + line1.p2().x() ) / 2.0, ( line2.p2().y() + line1.p2().y() ) / 2.0 );
682  line1.setAngle( line1.angle() + angle );
683  line2.setAngle( line2.angle() + angle );
684  line3.setAngle( line3.angle() + angle );
685  line4.setAngle( line4.angle() + angle );
686  line5.setAngle( line5.angle() + angle );
687  QPointF p1 = line1.p2();
688  QPointF p2 = line2.p2();
689  QPointF p3 = line3.p2();
690  QPointF p4 = line4.p2();
691  QPointF p5 = line5.p2();
692 
693  //inspired by Tim Baumann's work at https://github.com/timjb/colortriangle/blob/master/colortriangle.js
694  QLinearGradient colorGrad = QLinearGradient( p4.x(), p4.y(), p2.x(), p2.y() );
695  colorGrad.setColorAt( 0, alphaColor );
696  colorGrad.setColorAt( 1, pureColor );
697  QLinearGradient whiteGrad = QLinearGradient( p3.x(), p3.y(), p5.x(), p5.y() );
698  whiteGrad.setColorAt( 0, QColor( 255, 255, 255, 255 ) );
699  whiteGrad.setColorAt( 1, QColor( 255, 255, 255, 0 ) );
700 
701  QPolygonF triangle;
702  triangle << p2 << p1 << p3 << p2;
703  imagePainter.setPen( Qt::NoPen );
704  //start with a black triangle
705  imagePainter.setBrush( QBrush( Qt::black ) );
706  imagePainter.drawPolygon( triangle );
707  //draw a gradient from transparent to the pure color at the triangle's tip
708  imagePainter.setBrush( QBrush( colorGrad ) );
709  imagePainter.drawPolygon( triangle );
710  //draw a white gradient using additive composition mode
711  imagePainter.setCompositionMode( QPainter::CompositionMode_Plus );
712  imagePainter.setBrush( QBrush( whiteGrad ) );
713  imagePainter.drawPolygon( triangle );
714 
715  //above process results in some small artifacts on the edge of the triangle. Let's clear these up
716  //use source composition mode and draw an outline using a transparent pen
717  //this clears the edge pixels and leaves a nice smooth image
718  imagePainter.setCompositionMode( QPainter::CompositionMode_Source );
719  imagePainter.setBrush( Qt::NoBrush );
720  imagePainter.setPen( QPen( Qt::transparent ) );
721  imagePainter.drawPolygon( triangle );
722 
723  imagePainter.end();
724  mTriangleDirty = false;
725 }
726 
727 
728 
729 //
730 // QgsColorBox
731 //
732 
734  : QgsColorWidget( parent, component )
735 {
736  setFocusPolicy( Qt::StrongFocus );
737  setSizePolicy( QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding );
738 
739  mBoxImage = new QImage( width() - mMargin * 2, height() - mMargin * 2, QImage::Format_RGB32 );
740 }
741 
743 {
744  delete mBoxImage;
745 }
746 
748 {
749  int size = Qgis::UI_SCALE_FACTOR * fontMetrics().width( QStringLiteral( "XXXXXXXXXXXXXXXXXXXXXX" ) );
750  return QSize( size, size );
751 }
752 
753 void QgsColorBox::paintEvent( QPaintEvent *event )
754 {
755  Q_UNUSED( event );
756  QPainter painter( this );
757 
758  QStyleOptionFrame option;
759  option.initFrom( this );
760  option.state = hasFocus() ? QStyle::State_Active : QStyle::State_None;
761  style()->drawPrimitive( QStyle::PE_Frame, &option, &painter );
762 
763  if ( mDirty )
764  {
765  createBox();
766  }
767 
768  //draw background image
769  painter.drawImage( QPoint( mMargin, mMargin ), *mBoxImage );
770 
771  //draw cross lines
772  double xPos = mMargin + ( width() - 2 * mMargin - 1 ) * ( double )xComponentValue() / ( double )valueRangeX();
773  double yPos = mMargin + ( height() - 2 * mMargin - 1 ) - ( height() - 2 * mMargin - 1 ) * ( double )yComponentValue() / ( double )valueRangeY();
774 
775  painter.setBrush( Qt::white );
776  painter.setPen( Qt::NoPen );
777 
778  painter.drawRect( xPos - 1, mMargin, 3, height() - 2 * mMargin - 1 );
779  painter.drawRect( mMargin, yPos - 1, width() - 2 * mMargin - 1, 3 );
780  painter.setPen( Qt::black );
781  painter.drawLine( xPos, mMargin, xPos, height() - mMargin - 1 );
782  painter.drawLine( mMargin, yPos, width() - mMargin - 1, yPos );
783 
784  painter.end();
785 }
786 
788 {
789  if ( component != mComponent )
790  {
791  //need to redraw
792  mDirty = true;
793  }
794  QgsColorWidget::setComponent( component );
795 }
796 
797 void QgsColorBox::setColor( const QColor &color, const bool emitSignals )
798 {
799  //check if we need to redraw the box image
800  if ( mComponent == QgsColorWidget::Red && mCurrentColor.red() != color.red() )
801  {
802  mDirty = true;
803  }
804  else if ( mComponent == QgsColorWidget::Green && mCurrentColor.green() != color.green() )
805  {
806  mDirty = true;
807  }
808  else if ( mComponent == QgsColorWidget::Blue && mCurrentColor.blue() != color.blue() )
809  {
810  mDirty = true;
811  }
812  else if ( mComponent == QgsColorWidget::Hue && color.hsvHue() >= 0 && hue() != color.hsvHue() )
813  {
814  mDirty = true;
815  }
816  else if ( mComponent == QgsColorWidget::Saturation && mCurrentColor.hsvSaturation() != color.hsvSaturation() )
817  {
818  mDirty = true;
819  }
820  else if ( mComponent == QgsColorWidget::Value && mCurrentColor.value() != color.value() )
821  {
822  mDirty = true;
823  }
824  QgsColorWidget::setColor( color, emitSignals );
825 }
826 
827 void QgsColorBox::resizeEvent( QResizeEvent *event )
828 {
829  mDirty = true;
830  delete mBoxImage;
831  mBoxImage = new QImage( event->size().width() - mMargin * 2, event->size().height() - mMargin * 2, QImage::Format_RGB32 );
832  QgsColorWidget::resizeEvent( event );
833 }
834 
835 void QgsColorBox::mouseMoveEvent( QMouseEvent *event )
836 {
837  setColorFromPoint( event->pos() );
839 }
840 
841 void QgsColorBox::mousePressEvent( QMouseEvent *event )
842 {
843  setColorFromPoint( event->pos() );
844 }
845 
846 void QgsColorBox::createBox()
847 {
848  int maxValueX = mBoxImage->width();
849  int maxValueY = mBoxImage->height();
850 
851  //create a temporary color object
852  QColor currentColor = QColor( mCurrentColor );
853  int colorComponentValue;
854 
855  for ( int y = 0; y < maxValueY; ++y )
856  {
857  QRgb *scanLine = ( QRgb * )mBoxImage->scanLine( y );
858 
859  colorComponentValue = int( valueRangeY() - valueRangeY() * ( double( y ) / maxValueY ) );
860  alterColor( currentColor, yComponent(), colorComponentValue );
861  for ( int x = 0; x < maxValueX; ++x )
862  {
863  colorComponentValue = int( valueRangeX() * ( double( x ) / maxValueX ) );
864  alterColor( currentColor, xComponent(), colorComponentValue );
865  scanLine[x] = currentColor.rgb();
866  }
867  }
868  mDirty = false;
869 }
870 
871 int QgsColorBox::valueRangeX() const
872 {
873  return componentRange( xComponent() );
874 }
875 
876 int QgsColorBox::valueRangeY() const
877 {
878  return componentRange( yComponent() );
879 }
880 
881 QgsColorWidget::ColorComponent QgsColorBox::yComponent() const
882 {
883  switch ( mComponent )
884  {
885  case QgsColorWidget::Red:
886  return QgsColorWidget::Green;
889  return QgsColorWidget::Red;
890  case QgsColorWidget::Hue:
894  return QgsColorWidget::Hue;
895  default:
896  //should not occur
897  return QgsColorWidget::Red;
898  }
899 }
900 
901 int QgsColorBox::yComponentValue() const
902 {
903  return componentValue( yComponent() );
904 }
905 
906 QgsColorWidget::ColorComponent QgsColorBox::xComponent() const
907 {
908  switch ( mComponent )
909  {
910  case QgsColorWidget::Red:
912  return QgsColorWidget::Blue;
914  return QgsColorWidget::Green;
915  case QgsColorWidget::Hue:
917  return QgsColorWidget:: Value;
920  default:
921  //should not occur
922  return QgsColorWidget::Red;
923  }
924 }
925 
926 int QgsColorBox::xComponentValue() const
927 {
928  return componentValue( xComponent() );
929 }
930 
931 void QgsColorBox::setColorFromPoint( QPoint point )
932 {
933  int valX = valueRangeX() * ( point.x() - mMargin ) / ( width() - 2 * mMargin - 1 );
934  valX = std::min( std::max( valX, 0 ), valueRangeX() );
935 
936  int valY = valueRangeY() - valueRangeY() * ( point.y() - mMargin ) / ( height() - 2 * mMargin - 1 );
937  valY = std::min( std::max( valY, 0 ), valueRangeY() );
938 
939  QColor color = QColor( mCurrentColor );
940  alterColor( color, xComponent(), valX );
941  alterColor( color, yComponent(), valY );
942 
943  if ( color == mCurrentColor )
944  {
945  return;
946  }
947 
948  if ( color.hue() >= 0 )
949  {
950  mExplicitHue = color.hue();
951  }
952 
954  update();
955  emit colorChanged( color );
956 }
957 
958 
959 //
960 // QgsColorRampWidget
961 //
962 
965  const Orientation orientation )
966  : QgsColorWidget( parent, component )
967 {
968  setFocusPolicy( Qt::StrongFocus );
969  setOrientation( orientation );
970 
971  //create triangle polygons
972  setMarkerSize( 5 );
973 }
974 
976 {
977  if ( mOrientation == QgsColorRampWidget::Horizontal )
978  {
979  //horizontal
980  return QSize( Qgis::UI_SCALE_FACTOR * fontMetrics().width( QStringLiteral( "XXXXXXXXXXXXXXXXXXXXXX" ) ), Qgis::UI_SCALE_FACTOR * fontMetrics().height() * 1.3 );
981  }
982  else
983  {
984  //vertical
985  return QSize( Qgis::UI_SCALE_FACTOR * fontMetrics().height() * 1.3, Qgis::UI_SCALE_FACTOR * fontMetrics().width( QStringLiteral( "XXXXXXXXXXXXXXXXXXXXXX" ) ) );
986  }
987 }
988 
989 void QgsColorRampWidget::paintEvent( QPaintEvent *event )
990 {
991  Q_UNUSED( event );
992  QPainter painter( this );
993 
994  if ( mShowFrame )
995  {
996  //draw frame
997  QStyleOptionFrame option;
998  option.initFrom( this );
999  option.state = hasFocus() ? QStyle::State_KeyboardFocusChange : QStyle::State_None;
1000  style()->drawPrimitive( QStyle::PE_Frame, &option, &painter );
1001  }
1002 
1003  if ( hasFocus() )
1004  {
1005  //draw focus rect
1006  QStyleOptionFocusRect option;
1007  option.initFrom( this );
1008  option.state = QStyle::State_KeyboardFocusChange;
1009  style()->drawPrimitive( QStyle::PE_FrameFocusRect, &option, &painter );
1010  }
1011 
1013  {
1014  int maxValue = ( mOrientation == QgsColorRampWidget::Horizontal ? width() : height() ) - 1 - 2 * mMargin;
1015  QColor color = QColor( mCurrentColor );
1016  color.setAlpha( 255 );
1017  QPen pen;
1018  pen.setWidth( 0 );
1019  painter.setPen( pen );
1020  painter.setBrush( Qt::NoBrush );
1021 
1022  //draw background ramp
1023  for ( int c = 0; c <= maxValue; ++c )
1024  {
1025  int colorVal = componentRange() * ( double )c / maxValue;
1026  //vertical sliders are reversed
1027  if ( mOrientation == QgsColorRampWidget::Vertical )
1028  {
1029  colorVal = componentRange() - colorVal;
1030  }
1031  alterColor( color, mComponent, colorVal );
1032  if ( color.hue() < 0 )
1033  {
1034  color.setHsv( hue(), color.saturation(), color.value() );
1035  }
1036  pen.setColor( color );
1037  painter.setPen( pen );
1038  if ( mOrientation == QgsColorRampWidget::Horizontal )
1039  {
1040  //horizontal
1041  painter.drawLine( c + mMargin, mMargin, c + mMargin, height() - mMargin - 1 );
1042  }
1043  else
1044  {
1045  //vertical
1046  painter.drawLine( mMargin, c + mMargin, width() - mMargin - 1, c + mMargin );
1047  }
1048  }
1049  }
1050  else
1051  {
1052  //alpha ramps are drawn differently
1053  //start with the checkboard pattern
1054  QBrush checkBrush = QBrush( transparentBackground() );
1055  painter.setBrush( checkBrush );
1056  painter.setPen( Qt::NoPen );
1057  painter.drawRect( mMargin, mMargin, width() - 2 * mMargin - 1, height() - 2 * mMargin - 1 );
1058  QLinearGradient colorGrad;
1059  if ( mOrientation == QgsColorRampWidget::Horizontal )
1060  {
1061  //horizontal
1062  colorGrad = QLinearGradient( mMargin, 0, width() - mMargin - 1, 0 );
1063  }
1064  else
1065  {
1066  //vertical
1067  colorGrad = QLinearGradient( 0, mMargin, 0, height() - mMargin - 1 );
1068  }
1069  QColor transparent = QColor( mCurrentColor );
1070  transparent.setAlpha( 0 );
1071  colorGrad.setColorAt( 0, transparent );
1072  QColor opaque = QColor( mCurrentColor );
1073  opaque.setAlpha( 255 );
1074  colorGrad.setColorAt( 1, opaque );
1075  QBrush colorBrush = QBrush( colorGrad );
1076  painter.setBrush( colorBrush );
1077  painter.drawRect( mMargin, mMargin, width() - 2 * mMargin - 1, height() - 2 * mMargin - 1 );
1078  }
1079 
1080  if ( mOrientation == QgsColorRampWidget::Horizontal )
1081  {
1082  //draw marker triangles for horizontal ramps
1083  painter.setRenderHint( QPainter::Antialiasing );
1084  painter.setBrush( QBrush( Qt::black ) );
1085  painter.setPen( Qt::NoPen );
1086  painter.translate( mMargin + ( width() - 2 * mMargin ) * ( double )componentValue() / componentRange(), mMargin - 1 );
1087  painter.drawPolygon( mTopTriangle );
1088  painter.translate( 0, height() - mMargin - 2 );
1089  painter.setBrush( QBrush( Qt::white ) );
1090  painter.drawPolygon( mBottomTriangle );
1091  painter.end();
1092  }
1093  else
1094  {
1095  //draw cross lines for vertical ramps
1096  int ypos = mMargin + ( height() - 2 * mMargin - 1 ) - ( height() - 2 * mMargin - 1 ) * ( double )componentValue() / componentRange();
1097  painter.setBrush( Qt::white );
1098  painter.setPen( Qt::NoPen );
1099  painter.drawRect( mMargin, ypos - 1, width() - 2 * mMargin - 1, 3 );
1100  painter.setPen( Qt::black );
1101  painter.drawLine( mMargin, ypos, width() - mMargin - 1, ypos );
1102  }
1103 }
1104 
1106 {
1107  mOrientation = orientation;
1108  if ( orientation == QgsColorRampWidget::Horizontal )
1109  {
1110  //horizontal
1111  setSizePolicy( QSizePolicy::MinimumExpanding, QSizePolicy::Fixed );
1112  }
1113  else
1114  {
1115  //vertical
1116  setSizePolicy( QSizePolicy::Fixed, QSizePolicy::MinimumExpanding );
1117  }
1118  updateGeometry();
1119 }
1120 
1122 {
1123  if ( margin == mMargin )
1124  {
1125  return;
1126  }
1127  mMargin = margin;
1128  update();
1129 }
1130 
1132 {
1133  if ( showFrame == mShowFrame )
1134  {
1135  return;
1136  }
1137  mShowFrame = showFrame;
1138  update();
1139 }
1140 
1141 void QgsColorRampWidget::setMarkerSize( const int markerSize )
1142 {
1143  //create triangle polygons
1144  mTopTriangle << QPoint( -markerSize, 0 ) << QPoint( markerSize, 0 ) << QPoint( 0, markerSize );
1145  mBottomTriangle << QPoint( -markerSize, 0 ) << QPoint( markerSize, 0 ) << QPoint( 0, -markerSize );
1146  update();
1147 }
1148 
1149 void QgsColorRampWidget::mouseMoveEvent( QMouseEvent *event )
1150 {
1151  setColorFromPoint( event->pos() );
1153 }
1154 
1155 void QgsColorRampWidget::wheelEvent( QWheelEvent *event )
1156 {
1157  int oldValue = componentValue();
1158 
1159  if ( event->delta() > 0 )
1160  {
1162  }
1163  else
1164  {
1166  }
1167 
1168  if ( componentValue() != oldValue )
1169  {
1170  //value has changed
1171  emit colorChanged( mCurrentColor );
1172  emit valueChanged( componentValue() );
1173  }
1174 
1175  event->accept();
1176 }
1177 
1178 void QgsColorRampWidget::mousePressEvent( QMouseEvent *event )
1179 {
1180  setColorFromPoint( event->pos() );
1181 }
1182 
1183 void QgsColorRampWidget::keyPressEvent( QKeyEvent *event )
1184 {
1185  int oldValue = componentValue();
1186  if ( ( mOrientation == QgsColorRampWidget::Horizontal && ( event->key() == Qt::Key_Right || event->key() == Qt::Key_Up ) )
1187  || ( mOrientation == QgsColorRampWidget::Vertical && ( event->key() == Qt::Key_Left || event->key() == Qt::Key_Up ) ) )
1188  {
1190  }
1191  else if ( ( mOrientation == QgsColorRampWidget::Horizontal && ( event->key() == Qt::Key_Left || event->key() == Qt::Key_Down ) )
1192  || ( mOrientation == QgsColorRampWidget::Vertical && ( event->key() == Qt::Key_Right || event->key() == Qt::Key_Down ) ) )
1193  {
1195  }
1196  else if ( ( mOrientation == QgsColorRampWidget::Horizontal && event->key() == Qt::Key_PageDown )
1197  || ( mOrientation == QgsColorRampWidget::Vertical && event->key() == Qt::Key_PageUp ) )
1198  {
1200  }
1201  else if ( ( mOrientation == QgsColorRampWidget::Horizontal && event->key() == Qt::Key_PageUp )
1202  || ( mOrientation == QgsColorRampWidget::Vertical && event->key() == Qt::Key_PageDown ) )
1203  {
1205  }
1206  else if ( ( mOrientation == QgsColorRampWidget::Horizontal && event->key() == Qt::Key_Home )
1207  || ( mOrientation == QgsColorRampWidget::Vertical && event->key() == Qt::Key_End ) )
1208  {
1209  setComponentValue( 0 );
1210  }
1211  else if ( ( mOrientation == QgsColorRampWidget::Horizontal && event->key() == Qt::Key_End )
1212  || ( mOrientation == QgsColorRampWidget::Vertical && event->key() == Qt::Key_Home ) )
1213  {
1214  //set to maximum value
1216  }
1217  else
1218  {
1219  QgsColorWidget::keyPressEvent( event );
1220  return;
1221  }
1222 
1223  if ( componentValue() != oldValue )
1224  {
1225  //value has changed
1226  emit colorChanged( mCurrentColor );
1227  emit valueChanged( componentValue() );
1228  }
1229 }
1230 
1231 void QgsColorRampWidget::setColorFromPoint( QPointF point )
1232 {
1233  int oldValue = componentValue();
1234  int val;
1235  if ( mOrientation == QgsColorRampWidget::Horizontal )
1236  {
1237  val = componentRange() * ( point.x() - mMargin ) / ( width() - 2 * mMargin );
1238  }
1239  else
1240  {
1241  val = componentRange() - componentRange() * ( point.y() - mMargin ) / ( height() - 2 * mMargin );
1242  }
1243  val = std::max( 0, std::min( val, componentRange() ) );
1244  setComponentValue( val );
1245 
1246  if ( componentValue() != oldValue )
1247  {
1248  //value has changed
1249  emit colorChanged( mCurrentColor );
1250  emit valueChanged( componentValue() );
1251  }
1252 }
1253 
1254 //
1255 // QgsColorSliderWidget
1256 //
1257 
1259  : QgsColorWidget( parent, component )
1260 
1261 {
1262  QHBoxLayout *hLayout = new QHBoxLayout();
1263  hLayout->setMargin( 0 );
1264  hLayout->setSpacing( 5 );
1265 
1266  mRampWidget = new QgsColorRampWidget( nullptr, component );
1267  mRampWidget->setColor( mCurrentColor );
1268  hLayout->addWidget( mRampWidget, 1 );
1269 
1270  mSpinBox = new QSpinBox();
1271  //set spinbox to a reasonable width
1272  int largestCharWidth = mSpinBox->fontMetrics().width( QStringLiteral( "888%" ) );
1273  mSpinBox->setMinimumWidth( largestCharWidth + 35 );
1274  mSpinBox->setMinimum( 0 );
1275  mSpinBox->setMaximum( convertRealToDisplay( componentRange() ) );
1276  mSpinBox->setValue( convertRealToDisplay( componentValue() ) );
1277  if ( component == QgsColorWidget::Hue )
1278  {
1279  //degrees suffix for hue
1280  mSpinBox->setSuffix( QChar( 176 ) );
1281  }
1282  else if ( component == QgsColorWidget::Saturation || component == QgsColorWidget::Value || component == QgsColorWidget::Alpha )
1283  {
1284  mSpinBox->setSuffix( tr( "%" ) );
1285  }
1286  hLayout->addWidget( mSpinBox );
1287  setLayout( hLayout );
1288 
1289  connect( mRampWidget, &QgsColorRampWidget::valueChanged, this, &QgsColorSliderWidget::rampChanged );
1290  connect( mRampWidget, &QgsColorWidget::colorChanged, this, &QgsColorSliderWidget::rampColorChanged );
1291  connect( mSpinBox, static_cast < void ( QSpinBox::* )( int ) > ( &QSpinBox::valueChanged ), this, &QgsColorSliderWidget::spinChanged );
1292 }
1293 
1295 {
1296  QgsColorWidget::setComponent( component );
1297  mRampWidget->setComponent( component );
1298  mSpinBox->setMaximum( convertRealToDisplay( componentRange() ) );
1299  if ( component == QgsColorWidget::Hue )
1300  {
1301  //degrees suffix for hue
1302  mSpinBox->setSuffix( QChar( 176 ) );
1303  }
1304  else if ( component == QgsColorWidget::Saturation || component == QgsColorWidget::Value || component == QgsColorWidget::Alpha )
1305  {
1306  //saturation, value and alpha are in %
1307  mSpinBox->setSuffix( tr( "%" ) );
1308  }
1309  else
1310  {
1311  //clear suffix
1312  mSpinBox->setSuffix( QString() );
1313  }
1314 }
1315 
1317 {
1319  mRampWidget->blockSignals( true );
1320  mRampWidget->setComponentValue( value );
1321  mRampWidget->blockSignals( false );
1322  mSpinBox->blockSignals( true );
1323  mSpinBox->setValue( convertRealToDisplay( value ) );
1324  mSpinBox->blockSignals( false );
1325 }
1326 
1327 void QgsColorSliderWidget::setColor( const QColor &color, bool emitSignals )
1328 {
1329  QgsColorWidget::setColor( color, emitSignals );
1330  mRampWidget->setColor( color );
1331  mSpinBox->blockSignals( true );
1332  mSpinBox->setValue( convertRealToDisplay( componentValue() ) );
1333  mSpinBox->blockSignals( false );
1334 }
1335 
1336 void QgsColorSliderWidget::rampColorChanged( const QColor &color )
1337 {
1338  emit colorChanged( color );
1339 }
1340 
1341 void QgsColorSliderWidget::spinChanged( int value )
1342 {
1343  int convertedValue = convertDisplayToReal( value );
1344  QgsColorWidget::setComponentValue( convertedValue );
1345  mRampWidget->setComponentValue( convertedValue );
1346  emit colorChanged( mCurrentColor );
1347 }
1348 
1349 void QgsColorSliderWidget::rampChanged( int value )
1350 {
1351  mSpinBox->blockSignals( true );
1352  mSpinBox->setValue( convertRealToDisplay( value ) );
1353  mSpinBox->blockSignals( false );
1354 }
1355 
1356 
1357 int QgsColorSliderWidget::convertRealToDisplay( const int realValue ) const
1358 {
1359  //scale saturation, value or alpha to 0->100 range. This makes more sense for users
1360  //for whom "255" is a totally arbitrary value!
1362  {
1363  return std::round( 100.0 * realValue / 255.0 );
1364  }
1365 
1366  //leave all other values intact
1367  return realValue;
1368 }
1369 
1370 int QgsColorSliderWidget::convertDisplayToReal( const int displayValue ) const
1371 {
1372  //scale saturation, value or alpha from 0->100 range (see note in convertRealToDisplay)
1374  {
1375  return std::round( 255.0 * displayValue / 100.0 );
1376  }
1377 
1378  //leave all other values intact
1379  return displayValue;
1380 }
1381 
1382 //
1383 // QgsColorTextWidget
1384 //
1385 
1387  : QgsColorWidget( parent )
1388 {
1389  QHBoxLayout *hLayout = new QHBoxLayout();
1390  hLayout->setMargin( 0 );
1391  hLayout->setSpacing( 0 );
1392 
1393  mLineEdit = new QLineEdit( nullptr );
1394  hLayout->addWidget( mLineEdit );
1395 
1396  mMenuButton = new QToolButton( mLineEdit );
1397  mMenuButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mIconDropDownMenu.svg" ) ) );
1398  mMenuButton->setCursor( Qt::ArrowCursor );
1399  mMenuButton->setFocusPolicy( Qt::NoFocus );
1400  mMenuButton->setStyleSheet( QStringLiteral( "QToolButton { border: none; padding: 0px; }" ) );
1401 
1402  setLayout( hLayout );
1403 
1404  int frameWidth = mLineEdit->style()->pixelMetric( QStyle::PM_DefaultFrameWidth );
1405  mLineEdit->setStyleSheet( QStringLiteral( "QLineEdit { padding-right: %1px; } " )
1406  .arg( mMenuButton->sizeHint().width() + frameWidth + 1 ) );
1407 
1408  connect( mLineEdit, &QLineEdit::editingFinished, this, &QgsColorTextWidget::textChanged );
1409  connect( mMenuButton, &QAbstractButton::clicked, this, &QgsColorTextWidget::showMenu );
1410 
1411  //restore format setting
1412  QgsSettings settings;
1413  mFormat = settings.enumValue( QStringLiteral( "ColorWidgets/textWidgetFormat" ), HexRgb );
1414 
1415  updateText();
1416 }
1417 
1418 void QgsColorTextWidget::setColor( const QColor &color, const bool emitSignals )
1419 {
1420  QgsColorWidget::setColor( color, emitSignals );
1421  updateText();
1422 }
1423 
1424 void QgsColorTextWidget::resizeEvent( QResizeEvent *event )
1425 {
1426  Q_UNUSED( event );
1427  QSize sz = mMenuButton->sizeHint();
1428  int frameWidth = style()->pixelMetric( QStyle::PM_DefaultFrameWidth );
1429  mMenuButton->move( mLineEdit->rect().right() - frameWidth - sz.width(),
1430  ( mLineEdit->rect().bottom() + 1 - sz.height() ) / 2 );
1431 }
1432 
1433 void QgsColorTextWidget::updateText()
1434 {
1435  switch ( mFormat )
1436  {
1437  case HexRgb:
1438  mLineEdit->setText( mCurrentColor.name() );
1439  break;
1440  case HexRgbA:
1441  mLineEdit->setText( mCurrentColor.name() + QStringLiteral( "%1" ).arg( mCurrentColor.alpha(), 2, 16, QChar( '0' ) ) );
1442  break;
1443  case Rgb:
1444  mLineEdit->setText( QString( tr( "rgb( %1, %2, %3 )" ) ).arg( mCurrentColor.red() ).arg( mCurrentColor.green() ).arg( mCurrentColor.blue() ) );
1445  break;
1446  case Rgba:
1447  mLineEdit->setText( QString( tr( "rgba( %1, %2, %3, %4 )" ) ).arg( mCurrentColor.red() ).arg( mCurrentColor.green() ).arg( mCurrentColor.blue() ).arg( QString::number( mCurrentColor.alphaF(), 'f', 2 ) ) );
1448  break;
1449  }
1450 }
1451 
1452 void QgsColorTextWidget::textChanged()
1453 {
1454  QString testString = mLineEdit->text();
1455  bool containsAlpha;
1456  QColor color = QgsSymbolLayerUtils::parseColorWithAlpha( testString, containsAlpha );
1457  if ( !color.isValid() )
1458  {
1459  //bad color string
1460  updateText();
1461  return;
1462  }
1463 
1464  //good color string
1465  if ( color != mCurrentColor )
1466  {
1467  //retain alpha if no explicit alpha set
1468  if ( !containsAlpha )
1469  {
1470  color.setAlpha( mCurrentColor.alpha() );
1471  }
1472  //color has changed
1473  mCurrentColor = color;
1474  emit colorChanged( mCurrentColor );
1475  }
1476  updateText();
1477 }
1478 
1479 void QgsColorTextWidget::showMenu()
1480 {
1481  QMenu colorContextMenu;
1482 
1483  QAction *hexRgbAction = new QAction( tr( "#RRGGBB" ), nullptr );
1484  colorContextMenu.addAction( hexRgbAction );
1485  QAction *hexRgbaAction = new QAction( tr( "#RRGGBBAA" ), nullptr );
1486  colorContextMenu.addAction( hexRgbaAction );
1487  QAction *rgbAction = new QAction( tr( "rgb( r, g, b )" ), nullptr );
1488  colorContextMenu.addAction( rgbAction );
1489  QAction *rgbaAction = new QAction( tr( "rgba( r, g, b, a )" ), nullptr );
1490  colorContextMenu.addAction( rgbaAction );
1491 
1492  QAction *selectedAction = colorContextMenu.exec( QCursor::pos() );
1493  if ( selectedAction == hexRgbAction )
1494  {
1495  mFormat = QgsColorTextWidget::HexRgb;
1496  }
1497  else if ( selectedAction == hexRgbaAction )
1498  {
1499  mFormat = QgsColorTextWidget::HexRgbA;
1500  }
1501  else if ( selectedAction == rgbAction )
1502  {
1503  mFormat = QgsColorTextWidget::Rgb;
1504  }
1505  else if ( selectedAction == rgbaAction )
1506  {
1507  mFormat = QgsColorTextWidget::Rgba;
1508  }
1509 
1510  //save format setting
1511  QgsSettings settings;
1512  settings.setValue( QStringLiteral( "ColorWidgets/textWidgetFormat" ), ( int )mFormat );
1513 
1514  updateText();
1515 }
1516 
1517 
1518 //
1519 // QgsColorPreviewWidget
1520 //
1521 
1523  : QgsColorWidget( parent )
1524  , mColor2( QColor() )
1525 {
1526 
1527 }
1528 
1529 void QgsColorPreviewWidget::drawColor( const QColor &color, QRect rect, QPainter &painter )
1530 {
1531  painter.setPen( Qt::NoPen );
1532  //if color has an alpha, start with a checkboard pattern
1533  if ( color.alpha() < 255 )
1534  {
1535  QBrush checkBrush = QBrush( transparentBackground() );
1536  painter.setBrush( checkBrush );
1537  painter.drawRect( rect );
1538 
1539  //draw half of widget showing solid color, the other half showing color with alpha
1540 
1541  //ensure at least a 1px overlap to avoid artifacts
1542  QBrush colorBrush = QBrush( color );
1543  painter.setBrush( colorBrush );
1544  painter.drawRect( std::floor( rect.width() / 2.0 ) + rect.left(), rect.top(), rect.width() - std::floor( rect.width() / 2.0 ), rect.height() );
1545 
1546  QColor opaqueColor = QColor( color );
1547  opaqueColor.setAlpha( 255 );
1548  QBrush opaqueBrush = QBrush( opaqueColor );
1549  painter.setBrush( opaqueBrush );
1550  painter.drawRect( rect.left(), rect.top(), std::ceil( rect.width() / 2.0 ), rect.height() );
1551  }
1552  else
1553  {
1554  //no alpha component, just draw a solid rectangle
1555  QBrush brush = QBrush( color );
1556  painter.setBrush( brush );
1557  painter.drawRect( rect );
1558  }
1559 }
1560 
1561 void QgsColorPreviewWidget::paintEvent( QPaintEvent *event )
1562 {
1563  Q_UNUSED( event );
1564  QPainter painter( this );
1565 
1566  if ( mColor2.isValid() )
1567  {
1568  //drawing with two color sections
1569  int verticalSplit = std::round( height() / 2.0 );
1570  drawColor( mCurrentColor, QRect( 0, 0, width(), verticalSplit ), painter );
1571  drawColor( mColor2, QRect( 0, verticalSplit, width(), height() - verticalSplit ), painter );
1572  }
1573  else if ( mCurrentColor.isValid() )
1574  {
1575  drawColor( mCurrentColor, QRect( 0, 0, width(), height() ), painter );
1576  }
1577 
1578  painter.end();
1579 }
1580 
1582 {
1583  return QSize( Qgis::UI_SCALE_FACTOR * fontMetrics().width( QStringLiteral( "XXXXXXXXXXXXXXXXXXXXXX" ) ), Qgis::UI_SCALE_FACTOR * fontMetrics().width( QStringLiteral( "XXXXXXXXXXXXXXXXXXXXXX" ) ) * 0.75 );
1584 }
1585 
1587 {
1588  if ( color == mColor2 )
1589  {
1590  return;
1591  }
1592  mColor2 = color;
1593  update();
1594 }
1595 
1597 {
1598  if ( e->button() == Qt::LeftButton )
1599  {
1600  mDragStartPosition = e->pos();
1601  }
1603 }
1604 
1606 {
1607  if ( ( e->pos() - mDragStartPosition ).manhattanLength() >= QApplication::startDragDistance() )
1608  {
1609  //mouse moved, so a drag. nothing to do here
1611  return;
1612  }
1613 
1614  //work out which color was clicked
1615  QColor clickedColor = mCurrentColor;
1616  if ( mColor2.isValid() )
1617  {
1618  //two color sections, check if dragged color was the second color
1619  int verticalSplit = std::round( height() / 2.0 );
1620  if ( mDragStartPosition.y() >= verticalSplit )
1621  {
1622  clickedColor = mColor2;
1623  }
1624  }
1625  emit colorChanged( clickedColor );
1626 
1627 }
1628 
1630 {
1631  //handle dragging colors from button
1632 
1633  if ( !( e->buttons() & Qt::LeftButton ) )
1634  {
1635  //left button not depressed, so not a drag
1637  return;
1638  }
1639 
1640  if ( ( e->pos() - mDragStartPosition ).manhattanLength() < QApplication::startDragDistance() )
1641  {
1642  //mouse not moved, so not a drag
1644  return;
1645  }
1646 
1647  //user is dragging color
1648 
1649  //work out which color is being dragged
1650  QColor dragColor = mCurrentColor;
1651  if ( mColor2.isValid() )
1652  {
1653  //two color sections, check if dragged color was the second color
1654  int verticalSplit = std::round( height() / 2.0 );
1655  if ( mDragStartPosition.y() >= verticalSplit )
1656  {
1657  dragColor = mColor2;
1658  }
1659  }
1660 
1661  QDrag *drag = new QDrag( this );
1662  drag->setMimeData( QgsSymbolLayerUtils::colorToMimeData( dragColor ) );
1663  drag->setPixmap( createDragIcon( dragColor ) );
1664  drag->exec( Qt::CopyAction );
1665 }
1666 
1667 
1668 //
1669 // QgsColorWidgetAction
1670 //
1671 
1672 QgsColorWidgetAction::QgsColorWidgetAction( QgsColorWidget *colorWidget, QMenu *menu, QWidget *parent )
1673  : QWidgetAction( parent )
1674  , mMenu( menu )
1675  , mColorWidget( colorWidget )
1676  , mSuppressRecurse( false )
1677  , mDismissOnColorSelection( true )
1678 {
1679  setDefaultWidget( mColorWidget );
1680  connect( mColorWidget, &QgsColorWidget::colorChanged, this, &QgsColorWidgetAction::setColor );
1681 
1682  connect( this, &QAction::hovered, this, &QgsColorWidgetAction::onHover );
1683  connect( mColorWidget, &QgsColorWidget::hovered, this, &QgsColorWidgetAction::onHover );
1684 }
1685 
1686 void QgsColorWidgetAction::onHover()
1687 {
1688  //see https://bugreports.qt-project.org/browse/QTBUG-10427?focusedCommentId=185610&page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel#comment-185610
1689  if ( mSuppressRecurse )
1690  {
1691  return;
1692  }
1693 
1694  if ( mMenu )
1695  {
1696  mSuppressRecurse = true;
1697  mMenu->setActiveAction( this );
1698  mSuppressRecurse = false;
1699  }
1700 }
1701 
1702 void QgsColorWidgetAction::setColor( const QColor &color )
1703 {
1704  emit colorChanged( color );
1705  QAction::trigger();
1706  if ( mMenu && mDismissOnColorSelection )
1707  {
1708  mMenu->hide();
1709  }
1710 }
T enumValue(const QString &key, const T &defaultValue, const Section section=NoSection, bool flag=false) const
Return the setting value for a setting based on an enum.
Definition: qgssettings.h:231
void paintEvent(QPaintEvent *event) override
bool showFrame() const
Fetches whether the ramp is drawn within a frame.
A base class for interactive color widgets.
Orientation
Specifies the orientation of a color ramp.
void mouseReleaseEvent(QMouseEvent *e) override
Value component of color (based on HSV model)
void hovered()
Emitted when mouse hovers over widget.
static const double UI_SCALE_FACTOR
UI scaling factor.
Definition: qgis.h:151
This class is a composition of two QSettings instances:
Definition: qgssettings.h:57
QgsColorSliderWidget(QWidget *parent=nullptr, const ColorComponent component=QgsColorWidget::Red)
Construct a new color slider widget.
Rgb( r, g, b ) format.
void valueChanged(const int value)
Emitted when the widget&#39;s color component value changes.
int hue() const
Returns the hue for the widget.
Widget alters multiple color components.
ColorComponent component() const
Returns the color component which the widget controls.
void setMarkerSize(const int markerSize)
Sets the size for drawing the triangular markers on the ramp.
void paintEvent(QPaintEvent *event) override
void mousePressEvent(QMouseEvent *e) override
static QIcon getThemeIcon(const QString &name)
Helper to get a theme icon.
void mouseReleaseEvent(QMouseEvent *event) override
QSize sizeHint() const override
static const QPixmap & transparentBackground()
Generates a checkboard pattern pixmap for use as a background to transparent colors.
void mousePressEvent(QMouseEvent *event) override
double ANALYSIS_EXPORT angle(QgsPoint *p1, QgsPoint *p2, QgsPoint *p3, QgsPoint *p4)
Calculates the angle between two segments (in 2 dimension, z-values are ignored)
Definition: MathUtils.cpp:786
static QPixmap getThemePixmap(const QString &name)
Helper to get a theme icon as a pixmap.
void setComponentValue(const int value) override
Alters the widget&#39;s color by setting the value for the widget&#39;s color component.
void setColor(const QColor &color, const bool emitSignals=false) override
Sets the color for the widget.
void mousePressEvent(QMouseEvent *event) override
QgsColorWidgetAction(QgsColorWidget *colorWidget, QMenu *menu=nullptr, QWidget *parent=nullptr)
Construct a new color widget action.
Green component of color.
void setComponent(const ColorComponent component) override
Sets the color component which the widget controls.
void setValue(const QString &key, const QVariant &value, const QgsSettings::Section section=QgsSettings::NoSection)
Sets the value of setting key to value.
~QgsColorBox() override
void mousePressEvent(QMouseEvent *event) override
Red component of color.
~QgsColorWheel() override
void setOrientation(const Orientation orientation)
Sets the orientation for the color ramp.
QSize sizeHint() const override
static QColor parseColorWithAlpha(const QString &colorStr, bool &containsAlpha, bool strictEval=false)
Attempts to parse a string as a color using a variety of common formats, including hex codes...
Alpha component (opacity) of color.
QColor color() const
Returns the current color for the widget.
void setColor(const QColor &color, const bool emitSignals=false) override
void resizeEvent(QResizeEvent *event) override
A color ramp widget.
QgsColorWheel(QWidget *parent=nullptr)
Constructs a new color wheel widget.
void mouseMoveEvent(QMouseEvent *e) override
Blue component of color.
#RRGGBBAA in hexadecimal, with alpha
void dragEnterEvent(QDragEnterEvent *e) override
void wheelEvent(QWheelEvent *event) override
void mouseReleaseEvent(QMouseEvent *e) override
Hue component of color (based on HSV model)
QgsColorPreviewWidget(QWidget *parent=nullptr)
Construct a new color preview widget.
#RRGGBB in hexadecimal
void alterColor(QColor &color, const QgsColorWidget::ColorComponent component, const int newValue) const
Alters a color by modifiying the value of a specific color component.
void paintEvent(QPaintEvent *event) override
void setComponent(const ColorComponent component) override
Sets the color component which the widget controls.
void resizeEvent(QResizeEvent *event) override
QgsColorBox(QWidget *parent=nullptr, const ColorComponent component=Value)
Construct a new color box widget.
virtual void setComponentValue(const int value)
Alters the widget&#39;s color by setting the value for the widget&#39;s color component.
void setInteriorMargin(const int margin)
Sets the margin between the edge of the widget and the ramp.
ColorComponent
Specifies the color component which the widget alters.
QgsColorWidget(QWidget *parent=nullptr, const ColorComponent component=Multiple)
Construct a new color widget.
Saturation component of color (based on HSV model)
void dropEvent(QDropEvent *e) override
void mouseMoveEvent(QMouseEvent *event) override
void colorChanged(const QColor &color)
Emitted when the widget&#39;s color changes.
static QMimeData * colorToMimeData(const QColor &color)
Creates mime data from a color.
QgsColorTextWidget(QWidget *parent=nullptr)
Construct a new color line edit widget.
int componentRange() const
Returns the range of valid values for the color widget&#39;s component.
void setColor(const QColor &color, const bool emitSignals=false) override
Sets the color for the widget.
QSize sizeHint() const override
Orientation orientation() const
Fetches the orientation for the color ramp.
void setColor(const QColor &color, const bool emitSignals=false) override
void paintEvent(QPaintEvent *event) override
virtual void setComponent(const QgsColorWidget::ColorComponent component)
Sets the color component which the widget controls.
int mExplicitHue
QColor wipes the hue information when it is ambiguous (e.g., for saturation = 0). ...
void mouseMoveEvent(QMouseEvent *event) override
ColorComponent mComponent
QgsColorRampWidget(QWidget *parent=nullptr, const ColorComponent component=QgsColorWidget::Red, const Orientation orientation=QgsColorRampWidget::Horizontal)
Construct a new color ramp widget.
void mouseMoveEvent(QMouseEvent *event) override
void resizeEvent(QResizeEvent *event) override
static QPixmap createDragIcon(const QColor &color)
Create an icon for dragging colors.
virtual void setColor2(const QColor &color)
Sets the second color for the widget.
void mousePressEvent(QMouseEvent *e) override
QSize sizeHint() const override
Rgba( r, g, b, a ) format, with alpha.
virtual void setColor(const QColor &color, const bool emitSignals=false)
Sets the color for the widget.
static QColor colorFromMimeData(const QMimeData *data, bool &hasAlpha)
Attempts to parse mime data as a color.
void keyPressEvent(QKeyEvent *event) override
void colorChanged(const QColor &color)
Emitted when a color has been selected from the widget.
int componentValue() const
Returns the current value of the widget&#39;s color component.
void mouseMoveEvent(QMouseEvent *e) override
void setShowFrame(const bool showFrame)
Sets whether the ramp should be drawn within a frame.