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