QGIS API Documentation  3.12.1-BucureČ™ti (121cc00ff0)
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 {
112  if ( component == QgsColorWidget::Multiple )
113  {
114  //no component
115  return -1;
116  }
117 
118  if ( component == QgsColorWidget::Hue )
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 #if QT_VERSION < QT_VERSION_CHECK(5, 11, 0)
396  int size = Qgis::UI_SCALE_FACTOR * fontMetrics().width( QStringLiteral( "XXXXXXXXXXXXXXXXXXXXXX" ) );
397 #else
398  int size = Qgis::UI_SCALE_FACTOR * fontMetrics().horizontalAdvance( 'X' ) * 22;
399 #endif
400  return QSize( size, size );
401 }
402 
403 void QgsColorWheel::paintEvent( QPaintEvent *event )
404 {
405  Q_UNUSED( event )
406  QPainter painter( this );
407 
408  if ( !mWidgetImage || !mWheelImage || !mTriangleImage )
409  {
410  createImages( size() );
411  }
412 
413  //draw everything in an image
414  mWidgetImage->fill( Qt::transparent );
415  QPainter imagePainter( mWidgetImage );
416  imagePainter.setRenderHint( QPainter::Antialiasing );
417 
418  if ( mWheelDirty )
419  {
420  //need to redraw the wheel image
421  createWheel();
422  }
423 
424  //draw wheel centered on widget
425  QPointF center = QPointF( width() / 2.0, height() / 2.0 );
426  imagePainter.drawImage( QPointF( center.x() - ( mWheelImage->width() / 2.0 ), center.y() - ( mWheelImage->height() / 2.0 ) ), *mWheelImage );
427 
428  //draw hue marker
429  int h = hue();
430  double length = mWheelImage->width() / 2.0;
431  QLineF hueMarkerLine = QLineF( center.x(), center.y(), center.x() + length, center.y() );
432  hueMarkerLine.setAngle( h );
433  imagePainter.save();
434  //use sourceIn mode for nicer antialiasing
435  imagePainter.setCompositionMode( QPainter::CompositionMode_SourceIn );
436  QPen pen;
437  pen.setWidth( 2 );
438  //adapt pen color for hue
439  pen.setColor( h > 20 && h < 200 ? Qt::black : Qt::white );
440  imagePainter.setPen( pen );
441  imagePainter.drawLine( hueMarkerLine );
442  imagePainter.restore();
443 
444  //draw triangle
445  if ( mTriangleDirty )
446  {
447  createTriangle();
448  }
449  imagePainter.drawImage( QPointF( center.x() - ( mWheelImage->width() / 2.0 ), center.y() - ( mWheelImage->height() / 2.0 ) ), *mTriangleImage );
450 
451  //draw current color marker
452  double triangleRadius = length - mWheelThickness - 1;
453 
454  //adapted from equations at https://github.com/timjb/colortriangle/blob/master/colortriangle.js by Tim Baumann
455  double lightness = mCurrentColor.lightnessF();
456  double hueRadians = ( h * M_PI / 180.0 );
457  double hx = std::cos( hueRadians ) * triangleRadius;
458  double hy = -std::sin( hueRadians ) * triangleRadius;
459  double sx = -std::cos( -hueRadians + ( M_PI / 3.0 ) ) * triangleRadius;
460  double sy = -std::sin( -hueRadians + ( M_PI / 3.0 ) ) * triangleRadius;
461  double vx = -std::cos( hueRadians + ( M_PI / 3.0 ) ) * triangleRadius;
462  double vy = std::sin( hueRadians + ( M_PI / 3.0 ) ) * triangleRadius;
463  double mx = ( sx + vx ) / 2.0;
464  double my = ( sy + vy ) / 2.0;
465 
466  double a = ( 1 - 2.0 * std::fabs( lightness - 0.5 ) ) * mCurrentColor.hslSaturationF();
467  double x = sx + ( vx - sx ) * lightness + ( hx - mx ) * a;
468  double y = sy + ( vy - sy ) * lightness + ( hy - my ) * a;
469 
470  //adapt pen color for lightness
471  pen.setColor( lightness > 0.7 ? Qt::black : Qt::white );
472  imagePainter.setPen( pen );
473  imagePainter.setBrush( Qt::NoBrush );
474  imagePainter.drawEllipse( QPointF( x + center.x(), y + center.y() ), 4.0, 4.0 );
475  imagePainter.end();
476 
477  //draw image onto widget
478  painter.drawImage( QPoint( 0, 0 ), *mWidgetImage );
479  painter.end();
480 }
481 
482 void QgsColorWheel::setColor( const QColor &color, const bool emitSignals )
483 {
484  if ( color.hue() >= 0 && color.hue() != hue() )
485  {
486  //hue has changed, need to redraw the triangle
487  mTriangleDirty = true;
488  }
489 
490  QgsColorWidget::setColor( color, emitSignals );
491 }
492 
493 void QgsColorWheel::createImages( const QSizeF size )
494 {
495  double wheelSize = std::min( size.width(), size.height() ) - mMargin * 2.0;
496  mWheelThickness = wheelSize / 15.0;
497 
498  //recreate cache images at correct size
499  delete mWheelImage;
500  mWheelImage = new QImage( wheelSize, wheelSize, QImage::Format_ARGB32 );
501  delete mTriangleImage;
502  mTriangleImage = new QImage( wheelSize, wheelSize, QImage::Format_ARGB32 );
503  delete mWidgetImage;
504  mWidgetImage = new QImage( size.width(), size.height(), QImage::Format_ARGB32 );
505 
506  //trigger a redraw for the images
507  mWheelDirty = true;
508  mTriangleDirty = true;
509 }
510 
511 void QgsColorWheel::resizeEvent( QResizeEvent *event )
512 {
513  //recreate images for new size
514  createImages( event->size() );
515  QgsColorWidget::resizeEvent( event );
516 }
517 
518 void QgsColorWheel::setColorFromPos( const QPointF pos )
519 {
520  QPointF center = QPointF( width() / 2.0, height() / 2.0 );
521  //line from center to mouse position
522  QLineF line = QLineF( center.x(), center.y(), pos.x(), pos.y() );
523 
524  QColor newColor = QColor();
525 
526  int h, s, l, alpha;
527  mCurrentColor.getHsl( &h, &s, &l, &alpha );
528  //override hue with explicit hue, so we don't get -1 values from QColor for hue
529  h = hue();
530 
531  if ( mClickedPart == QgsColorWheel::Triangle )
532  {
533  //adapted from equations at https://github.com/timjb/colortriangle/blob/master/colortriangle.js by Tim Baumann
534 
535  //position of event relative to triangle center
536  double x = pos.x() - center.x();
537  double y = pos.y() - center.y();
538 
539  double eventAngleRadians = line.angle() * M_PI / 180.0;
540  double hueRadians = h * M_PI / 180.0;
541  double rad0 = std::fmod( eventAngleRadians + 2.0 * M_PI - hueRadians, 2.0 * M_PI );
542  double rad1 = std::fmod( rad0, ( ( 2.0 / 3.0 ) * M_PI ) ) - ( M_PI / 3.0 );
543  double length = mWheelImage->width() / 2.0;
544  double triangleLength = length - mWheelThickness - 1;
545 
546  double a = 0.5 * triangleLength;
547  double b = std::tan( rad1 ) * a;
548  double r = std::sqrt( x * x + y * y );
549  double maxR = std::sqrt( a * a + b * b );
550 
551  if ( r > maxR )
552  {
553  double dx = std::tan( rad1 ) * r;
554  double rad2 = std::atan( dx / maxR );
555  rad2 = std::min( rad2, M_PI / 3.0 );
556  rad2 = std::max( rad2, -M_PI / 3.0 );
557  eventAngleRadians += rad2 - rad1;
558  rad0 = std::fmod( eventAngleRadians + 2.0 * M_PI - hueRadians, 2.0 * M_PI );
559  rad1 = std::fmod( rad0, ( ( 2.0 / 3.0 ) * M_PI ) ) - ( M_PI / 3.0 );
560  b = std::tan( rad1 ) * a;
561  r = std::sqrt( a * a + b * b );
562  }
563 
564  double triangleSideLength = std::sqrt( 3.0 ) * triangleLength;
565  double newL = ( ( -std::sin( rad0 ) * r ) / triangleSideLength ) + 0.5;
566  double widthShare = 1.0 - ( std::fabs( newL - 0.5 ) * 2.0 );
567  double newS = ( ( ( std::cos( rad0 ) * r ) + ( triangleLength / 2.0 ) ) / ( 1.5 * triangleLength ) ) / widthShare;
568  s = std::min( static_cast< int >( std::round( std::max( 0.0, newS ) * 255.0 ) ), 255 );
569  l = std::min( static_cast< int >( std::round( std::max( 0.0, newL ) * 255.0 ) ), 255 );
570  newColor = QColor::fromHsl( h, s, l );
571  //explicitly set the hue again, so that it's exact
572  newColor.setHsv( h, newColor.hsvSaturation(), newColor.value(), alpha );
573  }
574  else if ( mClickedPart == QgsColorWheel::Wheel )
575  {
576  //use hue angle
577  s = mCurrentColor.hsvSaturation();
578  int v = mCurrentColor.value();
579  int newHue = line.angle();
580  newColor = QColor::fromHsv( newHue, s, v, alpha );
581  //hue has changed, need to redraw triangle
582  mTriangleDirty = true;
583  }
584 
585  if ( newColor.isValid() && newColor != mCurrentColor )
586  {
587  //color has changed
588  mCurrentColor = QColor( newColor );
589 
590  if ( mCurrentColor.hue() >= 0 )
591  {
592  //color has a valid hue, so update the QgsColorWidget's explicit hue
593  mExplicitHue = mCurrentColor.hue();
594  }
595 
596  update();
597  emit colorChanged( mCurrentColor );
598  }
599 }
600 
601 void QgsColorWheel::mouseMoveEvent( QMouseEvent *event )
602 {
603  setColorFromPos( event->pos() );
605 }
606 
607 void QgsColorWheel::mousePressEvent( QMouseEvent *event )
608 {
609  //calculate where the event occurred -- on the wheel or inside the triangle?
610 
611  //create a line from the widget's center to the event
612  QLineF line = QLineF( width() / 2.0, height() / 2.0, event->pos().x(), event->pos().y() );
613 
614  double innerLength = mWheelImage->width() / 2.0 - mWheelThickness;
615  if ( line.length() < innerLength )
616  {
617  mClickedPart = QgsColorWheel::Triangle;
618  }
619  else
620  {
621  mClickedPart = QgsColorWheel::Wheel;
622  }
623  setColorFromPos( event->pos() );
624 }
625 
626 void QgsColorWheel::mouseReleaseEvent( QMouseEvent *event )
627 {
628  Q_UNUSED( event )
629  mClickedPart = QgsColorWheel::None;
630 }
631 
632 void QgsColorWheel::createWheel()
633 {
634  if ( !mWheelImage )
635  {
636  return;
637  }
638 
639  int maxSize = std::min( mWheelImage->width(), mWheelImage->height() );
640  double wheelRadius = maxSize / 2.0;
641 
642  mWheelImage->fill( Qt::transparent );
643  QPainter p( mWheelImage );
644  p.setRenderHint( QPainter::Antialiasing );
645  p.setBrush( mWheelBrush );
646  p.setPen( Qt::NoPen );
647 
648  //draw hue wheel as a circle
649  p.translate( wheelRadius, wheelRadius );
650  p.drawEllipse( QPointF( 0.0, 0.0 ), wheelRadius, wheelRadius );
651 
652  //cut hole in center of circle to make a ring
653  p.setCompositionMode( QPainter::CompositionMode_DestinationOut );
654  p.setBrush( QBrush( Qt::black ) );
655  p.drawEllipse( QPointF( 0.0, 0.0 ), wheelRadius - mWheelThickness, wheelRadius - mWheelThickness );
656  p.end();
657 
658  mWheelDirty = false;
659 }
660 
661 void QgsColorWheel::createTriangle()
662 {
663  if ( !mWheelImage || !mTriangleImage )
664  {
665  return;
666  }
667 
668  QPointF center = QPointF( mWheelImage->width() / 2.0, mWheelImage->height() / 2.0 );
669  mTriangleImage->fill( Qt::transparent );
670 
671  QPainter imagePainter( mTriangleImage );
672  imagePainter.setRenderHint( QPainter::Antialiasing );
673 
674  int angle = hue();
675  double wheelRadius = mWheelImage->width() / 2.0;
676  double triangleRadius = wheelRadius - mWheelThickness - 1;
677 
678  //pure version of hue (at full saturation and value)
679  QColor pureColor = QColor::fromHsv( angle, 255, 255 );
680  //create copy of color but with 0 alpha
681  QColor alphaColor = QColor( pureColor );
682  alphaColor.setAlpha( 0 );
683 
684  //some rather ugly shortcuts to obtain corners and midpoints of triangle
685  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 ) );
686  QLineF line2 = QLineF( center.x(), center.y(), center.x() + triangleRadius, center.y() );
687  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 ) );
688  QLineF line4 = QLineF( center.x(), center.y(), center.x() - triangleRadius * std::cos( M_PI / 3.0 ), center.y() );
689  QLineF line5 = QLineF( center.x(), center.y(), ( line2.p2().x() + line1.p2().x() ) / 2.0, ( line2.p2().y() + line1.p2().y() ) / 2.0 );
690  line1.setAngle( line1.angle() + angle );
691  line2.setAngle( line2.angle() + angle );
692  line3.setAngle( line3.angle() + angle );
693  line4.setAngle( line4.angle() + angle );
694  line5.setAngle( line5.angle() + angle );
695  QPointF p1 = line1.p2();
696  QPointF p2 = line2.p2();
697  QPointF p3 = line3.p2();
698  QPointF p4 = line4.p2();
699  QPointF p5 = line5.p2();
700 
701  //inspired by Tim Baumann's work at https://github.com/timjb/colortriangle/blob/master/colortriangle.js
702  QLinearGradient colorGrad = QLinearGradient( p4.x(), p4.y(), p2.x(), p2.y() );
703  colorGrad.setColorAt( 0, alphaColor );
704  colorGrad.setColorAt( 1, pureColor );
705  QLinearGradient whiteGrad = QLinearGradient( p3.x(), p3.y(), p5.x(), p5.y() );
706  whiteGrad.setColorAt( 0, QColor( 255, 255, 255, 255 ) );
707  whiteGrad.setColorAt( 1, QColor( 255, 255, 255, 0 ) );
708 
709  QPolygonF triangle;
710  triangle << p2 << p1 << p3 << p2;
711  imagePainter.setPen( Qt::NoPen );
712  //start with a black triangle
713  imagePainter.setBrush( QBrush( Qt::black ) );
714  imagePainter.drawPolygon( triangle );
715  //draw a gradient from transparent to the pure color at the triangle's tip
716  imagePainter.setBrush( QBrush( colorGrad ) );
717  imagePainter.drawPolygon( triangle );
718  //draw a white gradient using additive composition mode
719  imagePainter.setCompositionMode( QPainter::CompositionMode_Plus );
720  imagePainter.setBrush( QBrush( whiteGrad ) );
721  imagePainter.drawPolygon( triangle );
722 
723  //above process results in some small artifacts on the edge of the triangle. Let's clear these up
724  //use source composition mode and draw an outline using a transparent pen
725  //this clears the edge pixels and leaves a nice smooth image
726  imagePainter.setCompositionMode( QPainter::CompositionMode_Source );
727  imagePainter.setBrush( Qt::NoBrush );
728  imagePainter.setPen( QPen( Qt::transparent ) );
729  imagePainter.drawPolygon( triangle );
730 
731  imagePainter.end();
732  mTriangleDirty = false;
733 }
734 
735 
736 
737 //
738 // QgsColorBox
739 //
740 
742  : QgsColorWidget( parent, component )
743 {
744  setFocusPolicy( Qt::StrongFocus );
745  setSizePolicy( QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding );
746 
747  mBoxImage = new QImage( width() - mMargin * 2, height() - mMargin * 2, QImage::Format_RGB32 );
748 }
749 
751 {
752  delete mBoxImage;
753 }
754 
756 {
757 #if QT_VERSION < QT_VERSION_CHECK(5, 11, 0)
758  int size = Qgis::UI_SCALE_FACTOR * fontMetrics().width( QStringLiteral( "XXXXXXXXXXXXXXXXXXXXXX" ) );
759 #else
760  int size = Qgis::UI_SCALE_FACTOR * fontMetrics().horizontalAdvance( 'X' ) * 22;
761 #endif
762  return QSize( size, size );
763 }
764 
765 void QgsColorBox::paintEvent( QPaintEvent *event )
766 {
767  Q_UNUSED( event )
768  QPainter painter( this );
769 
770  QStyleOptionFrame option;
771  option.initFrom( this );
772  option.state = hasFocus() ? QStyle::State_Active : QStyle::State_None;
773  style()->drawPrimitive( QStyle::PE_Frame, &option, &painter );
774 
775  if ( mDirty )
776  {
777  createBox();
778  }
779 
780  //draw background image
781  painter.drawImage( QPoint( mMargin, mMargin ), *mBoxImage );
782 
783  //draw cross lines
784  double xPos = mMargin + ( width() - 2 * mMargin - 1 ) * static_cast<double>( xComponentValue() ) / static_cast<double>( valueRangeX() );
785  double yPos = mMargin + ( height() - 2 * mMargin - 1 ) - ( height() - 2 * mMargin - 1 ) * static_cast<double>( yComponentValue() ) / static_cast<double>( valueRangeY() );
786 
787  painter.setBrush( Qt::white );
788  painter.setPen( Qt::NoPen );
789 
790  painter.drawRect( xPos - 1, mMargin, 3, height() - 2 * mMargin - 1 );
791  painter.drawRect( mMargin, yPos - 1, width() - 2 * mMargin - 1, 3 );
792  painter.setPen( Qt::black );
793  painter.drawLine( xPos, mMargin, xPos, height() - mMargin - 1 );
794  painter.drawLine( mMargin, yPos, width() - mMargin - 1, yPos );
795 
796  painter.end();
797 }
798 
800 {
801  if ( component != mComponent )
802  {
803  //need to redraw
804  mDirty = true;
805  }
806  QgsColorWidget::setComponent( component );
807 }
808 
809 void QgsColorBox::setColor( const QColor &color, const bool emitSignals )
810 {
811  //check if we need to redraw the box image
812  if ( mComponent == QgsColorWidget::Red && mCurrentColor.red() != color.red() )
813  {
814  mDirty = true;
815  }
816  else if ( mComponent == QgsColorWidget::Green && mCurrentColor.green() != color.green() )
817  {
818  mDirty = true;
819  }
820  else if ( mComponent == QgsColorWidget::Blue && mCurrentColor.blue() != color.blue() )
821  {
822  mDirty = true;
823  }
824  else if ( mComponent == QgsColorWidget::Hue && color.hsvHue() >= 0 && hue() != color.hsvHue() )
825  {
826  mDirty = true;
827  }
828  else if ( mComponent == QgsColorWidget::Saturation && mCurrentColor.hsvSaturation() != color.hsvSaturation() )
829  {
830  mDirty = true;
831  }
832  else if ( mComponent == QgsColorWidget::Value && mCurrentColor.value() != color.value() )
833  {
834  mDirty = true;
835  }
836  QgsColorWidget::setColor( color, emitSignals );
837 }
838 
839 void QgsColorBox::resizeEvent( QResizeEvent *event )
840 {
841  mDirty = true;
842  delete mBoxImage;
843  mBoxImage = new QImage( event->size().width() - mMargin * 2, event->size().height() - mMargin * 2, QImage::Format_RGB32 );
844  QgsColorWidget::resizeEvent( event );
845 }
846 
847 void QgsColorBox::mouseMoveEvent( QMouseEvent *event )
848 {
849  setColorFromPoint( event->pos() );
851 }
852 
853 void QgsColorBox::mousePressEvent( QMouseEvent *event )
854 {
855  setColorFromPoint( event->pos() );
856 }
857 
858 void QgsColorBox::createBox()
859 {
860  int maxValueX = mBoxImage->width();
861  int maxValueY = mBoxImage->height();
862 
863  //create a temporary color object
864  QColor currentColor = QColor( mCurrentColor );
865  int colorComponentValue;
866 
867  for ( int y = 0; y < maxValueY; ++y )
868  {
869  QRgb *scanLine = ( QRgb * )mBoxImage->scanLine( y );
870 
871  colorComponentValue = int( valueRangeY() - valueRangeY() * ( double( y ) / maxValueY ) );
872  alterColor( currentColor, yComponent(), colorComponentValue );
873  for ( int x = 0; x < maxValueX; ++x )
874  {
875  colorComponentValue = int( valueRangeX() * ( double( x ) / maxValueX ) );
876  alterColor( currentColor, xComponent(), colorComponentValue );
877  scanLine[x] = currentColor.rgb();
878  }
879  }
880  mDirty = false;
881 }
882 
883 int QgsColorBox::valueRangeX() const
884 {
885  return componentRange( xComponent() );
886 }
887 
888 int QgsColorBox::valueRangeY() const
889 {
890  return componentRange( yComponent() );
891 }
892 
893 QgsColorWidget::ColorComponent QgsColorBox::yComponent() const
894 {
895  switch ( mComponent )
896  {
897  case QgsColorWidget::Red:
898  return QgsColorWidget::Green;
901  return QgsColorWidget::Red;
902  case QgsColorWidget::Hue:
906  return QgsColorWidget::Hue;
907  default:
908  //should not occur
909  return QgsColorWidget::Red;
910  }
911 }
912 
913 int QgsColorBox::yComponentValue() const
914 {
915  return componentValue( yComponent() );
916 }
917 
918 QgsColorWidget::ColorComponent QgsColorBox::xComponent() const
919 {
920  switch ( mComponent )
921  {
922  case QgsColorWidget::Red:
924  return QgsColorWidget::Blue;
926  return QgsColorWidget::Green;
927  case QgsColorWidget::Hue:
929  return QgsColorWidget:: Value;
932  default:
933  //should not occur
934  return QgsColorWidget::Red;
935  }
936 }
937 
938 int QgsColorBox::xComponentValue() const
939 {
940  return componentValue( xComponent() );
941 }
942 
943 void QgsColorBox::setColorFromPoint( QPoint point )
944 {
945  int valX = valueRangeX() * ( point.x() - mMargin ) / ( width() - 2 * mMargin - 1 );
946  valX = std::min( std::max( valX, 0 ), valueRangeX() );
947 
948  int valY = valueRangeY() - valueRangeY() * ( point.y() - mMargin ) / ( height() - 2 * mMargin - 1 );
949  valY = std::min( std::max( valY, 0 ), valueRangeY() );
950 
951  QColor color = QColor( mCurrentColor );
952  alterColor( color, xComponent(), valX );
953  alterColor( color, yComponent(), valY );
954 
955  if ( color == mCurrentColor )
956  {
957  return;
958  }
959 
960  if ( color.hue() >= 0 )
961  {
962  mExplicitHue = color.hue();
963  }
964 
966  update();
967  emit colorChanged( color );
968 }
969 
970 
971 //
972 // QgsColorRampWidget
973 //
974 
977  const Orientation orientation )
978  : QgsColorWidget( parent, component )
979 {
980  setFocusPolicy( Qt::StrongFocus );
981  setOrientation( orientation );
982 
983  //create triangle polygons
984  setMarkerSize( 5 );
985 }
986 
988 {
989  if ( mOrientation == QgsColorRampWidget::Horizontal )
990  {
991  //horizontal
992 #if QT_VERSION < QT_VERSION_CHECK(5, 11, 0)
993  return QSize( Qgis::UI_SCALE_FACTOR * fontMetrics().width( QStringLiteral( "XXXXXXXXXXXXXXXXXXXXXX" ) ), Qgis::UI_SCALE_FACTOR * fontMetrics().height() * 1.3 );
994 #else
995  return QSize( Qgis::UI_SCALE_FACTOR * fontMetrics().horizontalAdvance( 'X' ) * 22, Qgis::UI_SCALE_FACTOR * fontMetrics().height() * 1.3 );
996 #endif
997  }
998  else
999  {
1000  //vertical
1001 #if QT_VERSION < QT_VERSION_CHECK(5, 11, 0)
1002  return QSize( Qgis::UI_SCALE_FACTOR * fontMetrics().height() * 1.3, Qgis::UI_SCALE_FACTOR * fontMetrics().width( QStringLiteral( "XXXXXXXXXXXXXXXXXXXXXX" ) ) );
1003 #else
1004  return QSize( Qgis::UI_SCALE_FACTOR * fontMetrics().height() * 1.3, Qgis::UI_SCALE_FACTOR * fontMetrics().horizontalAdvance( 'X' ) * 22 );
1005 #endif
1006  }
1007 }
1008 
1009 void QgsColorRampWidget::paintEvent( QPaintEvent *event )
1010 {
1011  Q_UNUSED( event )
1012  QPainter painter( this );
1013 
1014  if ( mShowFrame )
1015  {
1016  //draw frame
1017  QStyleOptionFrame option;
1018  option.initFrom( this );
1019  option.state = hasFocus() ? QStyle::State_KeyboardFocusChange : QStyle::State_None;
1020  style()->drawPrimitive( QStyle::PE_Frame, &option, &painter );
1021  }
1022 
1023  if ( hasFocus() )
1024  {
1025  //draw focus rect
1026  QStyleOptionFocusRect option;
1027  option.initFrom( this );
1028  option.state = QStyle::State_KeyboardFocusChange;
1029  style()->drawPrimitive( QStyle::PE_FrameFocusRect, &option, &painter );
1030  }
1031 
1033  {
1034  int maxValue = ( mOrientation == QgsColorRampWidget::Horizontal ? width() : height() ) - 1 - 2 * mMargin;
1035  QColor color = QColor( mCurrentColor );
1036  color.setAlpha( 255 );
1037  QPen pen;
1038  // we need to set pen width to 1,
1039  // since on retina displays
1040  // pen.setWidth(0) <=> pen.width = 0.5
1041  // see https://github.com/qgis/QGIS/issues/23900
1042  pen.setWidth( 1 );
1043  painter.setPen( pen );
1044  painter.setBrush( Qt::NoBrush );
1045 
1046  //draw background ramp
1047  for ( int c = 0; c <= maxValue; ++c )
1048  {
1049  int colorVal = static_cast<int>( componentRange() * static_cast<double>( c ) / maxValue );
1050  //vertical sliders are reversed
1051  if ( mOrientation == QgsColorRampWidget::Vertical )
1052  {
1053  colorVal = componentRange() - colorVal;
1054  }
1055  alterColor( color, mComponent, colorVal );
1056  if ( color.hue() < 0 )
1057  {
1058  color.setHsv( hue(), color.saturation(), color.value() );
1059  }
1060  pen.setColor( color );
1061  painter.setPen( pen );
1062  if ( mOrientation == QgsColorRampWidget::Horizontal )
1063  {
1064  //horizontal
1065  painter.drawLine( QLineF( c + mMargin, mMargin, c + mMargin, height() - mMargin - 1 ) );
1066  }
1067  else
1068  {
1069  //vertical
1070  painter.drawLine( QLineF( mMargin, c + mMargin, width() - mMargin - 1, c + mMargin ) );
1071  }
1072  }
1073  }
1074  else
1075  {
1076  //alpha ramps are drawn differently
1077  //start with the checkboard pattern
1078  QBrush checkBrush = QBrush( transparentBackground() );
1079  painter.setBrush( checkBrush );
1080  painter.setPen( Qt::NoPen );
1081  painter.drawRect( QRectF( mMargin, mMargin, width() - 2 * mMargin - 1, height() - 2 * mMargin - 1 ) );
1082  QLinearGradient colorGrad;
1083  if ( mOrientation == QgsColorRampWidget::Horizontal )
1084  {
1085  //horizontal
1086  colorGrad = QLinearGradient( mMargin, 0, width() - mMargin - 1, 0 );
1087  }
1088  else
1089  {
1090  //vertical
1091  colorGrad = QLinearGradient( 0, mMargin, 0, height() - mMargin - 1 );
1092  }
1093  QColor transparent = QColor( mCurrentColor );
1094  transparent.setAlpha( 0 );
1095  colorGrad.setColorAt( 0, transparent );
1096  QColor opaque = QColor( mCurrentColor );
1097  opaque.setAlpha( 255 );
1098  colorGrad.setColorAt( 1, opaque );
1099  QBrush colorBrush = QBrush( colorGrad );
1100  painter.setBrush( colorBrush );
1101  painter.drawRect( QRectF( mMargin, mMargin, width() - 2 * mMargin - 1, height() - 2 * mMargin - 1 ) );
1102  }
1103 
1104  if ( mOrientation == QgsColorRampWidget::Horizontal )
1105  {
1106  //draw marker triangles for horizontal ramps
1107  painter.setRenderHint( QPainter::Antialiasing );
1108  painter.setBrush( QBrush( Qt::black ) );
1109  painter.setPen( Qt::NoPen );
1110  painter.translate( mMargin + ( width() - 2 * mMargin ) * static_cast<double>( componentValue() ) / componentRange(), mMargin - 1 );
1111  painter.drawPolygon( mTopTriangle );
1112  painter.translate( 0, height() - mMargin - 2 );
1113  painter.setBrush( QBrush( Qt::white ) );
1114  painter.drawPolygon( mBottomTriangle );
1115  painter.end();
1116  }
1117  else
1118  {
1119  //draw cross lines for vertical ramps
1120  double ypos = mMargin + ( height() - 2 * mMargin - 1 ) - ( height() - 2 * mMargin - 1 ) * static_cast<double>( componentValue() ) / componentRange();
1121  painter.setBrush( Qt::white );
1122  painter.setPen( Qt::NoPen );
1123  painter.drawRect( QRectF( mMargin, ypos - 1, width() - 2 * mMargin - 1, 3 ) );
1124  painter.setPen( Qt::black );
1125  painter.drawLine( QLineF( mMargin, ypos, width() - mMargin - 1, ypos ) );
1126  }
1127 }
1128 
1130 {
1131  mOrientation = orientation;
1132  if ( orientation == QgsColorRampWidget::Horizontal )
1133  {
1134  //horizontal
1135  setSizePolicy( QSizePolicy::MinimumExpanding, QSizePolicy::Fixed );
1136  }
1137  else
1138  {
1139  //vertical
1140  setSizePolicy( QSizePolicy::Fixed, QSizePolicy::MinimumExpanding );
1141  }
1142  updateGeometry();
1143 }
1144 
1146 {
1147  if ( margin == mMargin )
1148  {
1149  return;
1150  }
1151  mMargin = margin;
1152  update();
1153 }
1154 
1156 {
1157  if ( showFrame == mShowFrame )
1158  {
1159  return;
1160  }
1161  mShowFrame = showFrame;
1162  update();
1163 }
1164 
1165 void QgsColorRampWidget::setMarkerSize( const int markerSize )
1166 {
1167  //create triangle polygons
1168  mTopTriangle << QPoint( -markerSize, 0 ) << QPoint( markerSize, 0 ) << QPoint( 0, markerSize );
1169  mBottomTriangle << QPoint( -markerSize, 0 ) << QPoint( markerSize, 0 ) << QPoint( 0, -markerSize );
1170  update();
1171 }
1172 
1173 void QgsColorRampWidget::mouseMoveEvent( QMouseEvent *event )
1174 {
1175  setColorFromPoint( event->pos() );
1177 }
1178 
1179 void QgsColorRampWidget::wheelEvent( QWheelEvent *event )
1180 {
1181  int oldValue = componentValue();
1182 
1183  if ( event->delta() > 0 )
1184  {
1186  }
1187  else
1188  {
1190  }
1191 
1192  if ( componentValue() != oldValue )
1193  {
1194  //value has changed
1195  emit colorChanged( mCurrentColor );
1196  emit valueChanged( componentValue() );
1197  }
1198 
1199  event->accept();
1200 }
1201 
1202 void QgsColorRampWidget::mousePressEvent( QMouseEvent *event )
1203 {
1204  setColorFromPoint( event->pos() );
1205 }
1206 
1207 void QgsColorRampWidget::keyPressEvent( QKeyEvent *event )
1208 {
1209  int oldValue = componentValue();
1210  if ( ( mOrientation == QgsColorRampWidget::Horizontal && ( event->key() == Qt::Key_Right || event->key() == Qt::Key_Up ) )
1211  || ( mOrientation == QgsColorRampWidget::Vertical && ( event->key() == Qt::Key_Left || event->key() == Qt::Key_Up ) ) )
1212  {
1214  }
1215  else if ( ( mOrientation == QgsColorRampWidget::Horizontal && ( event->key() == Qt::Key_Left || event->key() == Qt::Key_Down ) )
1216  || ( mOrientation == QgsColorRampWidget::Vertical && ( event->key() == Qt::Key_Right || event->key() == Qt::Key_Down ) ) )
1217  {
1219  }
1220  else if ( ( mOrientation == QgsColorRampWidget::Horizontal && event->key() == Qt::Key_PageDown )
1221  || ( mOrientation == QgsColorRampWidget::Vertical && event->key() == Qt::Key_PageUp ) )
1222  {
1224  }
1225  else if ( ( mOrientation == QgsColorRampWidget::Horizontal && event->key() == Qt::Key_PageUp )
1226  || ( mOrientation == QgsColorRampWidget::Vertical && event->key() == Qt::Key_PageDown ) )
1227  {
1229  }
1230  else if ( ( mOrientation == QgsColorRampWidget::Horizontal && event->key() == Qt::Key_Home )
1231  || ( mOrientation == QgsColorRampWidget::Vertical && event->key() == Qt::Key_End ) )
1232  {
1233  setComponentValue( 0 );
1234  }
1235  else if ( ( mOrientation == QgsColorRampWidget::Horizontal && event->key() == Qt::Key_End )
1236  || ( mOrientation == QgsColorRampWidget::Vertical && event->key() == Qt::Key_Home ) )
1237  {
1238  //set to maximum value
1240  }
1241  else
1242  {
1243  QgsColorWidget::keyPressEvent( event );
1244  return;
1245  }
1246 
1247  if ( componentValue() != oldValue )
1248  {
1249  //value has changed
1250  emit colorChanged( mCurrentColor );
1251  emit valueChanged( componentValue() );
1252  }
1253 }
1254 
1255 void QgsColorRampWidget::setColorFromPoint( QPointF point )
1256 {
1257  int oldValue = componentValue();
1258  int val;
1259  if ( mOrientation == QgsColorRampWidget::Horizontal )
1260  {
1261  val = componentRange() * ( point.x() - mMargin ) / ( width() - 2 * mMargin );
1262  }
1263  else
1264  {
1265  val = componentRange() - componentRange() * ( point.y() - mMargin ) / ( height() - 2 * mMargin );
1266  }
1267  val = std::max( 0, std::min( val, componentRange() ) );
1268  setComponentValue( val );
1269 
1270  if ( componentValue() != oldValue )
1271  {
1272  //value has changed
1273  emit colorChanged( mCurrentColor );
1274  emit valueChanged( componentValue() );
1275  }
1276 }
1277 
1278 //
1279 // QgsColorSliderWidget
1280 //
1281 
1283  : QgsColorWidget( parent, component )
1284 
1285 {
1286  QHBoxLayout *hLayout = new QHBoxLayout();
1287  hLayout->setMargin( 0 );
1288  hLayout->setSpacing( 5 );
1289 
1290  mRampWidget = new QgsColorRampWidget( nullptr, component );
1291  mRampWidget->setColor( mCurrentColor );
1292  hLayout->addWidget( mRampWidget, 1 );
1293 
1294  mSpinBox = new QSpinBox();
1295  //set spinbox to a reasonable width
1296 #if QT_VERSION < QT_VERSION_CHECK(5, 11, 0)
1297  int largestCharWidth = mSpinBox->fontMetrics().width( QStringLiteral( "888%" ) );
1298 #else
1299  int largestCharWidth = mSpinBox->fontMetrics().horizontalAdvance( QStringLiteral( "888%" ) );
1300 #endif
1301  mSpinBox->setMinimumWidth( largestCharWidth + 35 );
1302  mSpinBox->setMinimum( 0 );
1303  mSpinBox->setMaximum( convertRealToDisplay( componentRange() ) );
1304  mSpinBox->setValue( convertRealToDisplay( componentValue() ) );
1305  if ( component == QgsColorWidget::Hue )
1306  {
1307  //degrees suffix for hue
1308  mSpinBox->setSuffix( QChar( 176 ) );
1309  }
1310  else if ( component == QgsColorWidget::Saturation || component == QgsColorWidget::Value || component == QgsColorWidget::Alpha )
1311  {
1312  mSpinBox->setSuffix( tr( "%" ) );
1313  }
1314  hLayout->addWidget( mSpinBox );
1315  setLayout( hLayout );
1316 
1317  connect( mRampWidget, &QgsColorRampWidget::valueChanged, this, &QgsColorSliderWidget::rampChanged );
1318  connect( mRampWidget, &QgsColorWidget::colorChanged, this, &QgsColorSliderWidget::rampColorChanged );
1319  connect( mSpinBox, static_cast < void ( QSpinBox::* )( int ) > ( &QSpinBox::valueChanged ), this, &QgsColorSliderWidget::spinChanged );
1320 }
1321 
1323 {
1324  QgsColorWidget::setComponent( component );
1325  mRampWidget->setComponent( component );
1326  mSpinBox->setMaximum( convertRealToDisplay( componentRange() ) );
1327  if ( component == QgsColorWidget::Hue )
1328  {
1329  //degrees suffix for hue
1330  mSpinBox->setSuffix( QChar( 176 ) );
1331  }
1332  else if ( component == QgsColorWidget::Saturation || component == QgsColorWidget::Value || component == QgsColorWidget::Alpha )
1333  {
1334  //saturation, value and alpha are in %
1335  mSpinBox->setSuffix( tr( "%" ) );
1336  }
1337  else
1338  {
1339  //clear suffix
1340  mSpinBox->setSuffix( QString() );
1341  }
1342 }
1343 
1345 {
1347  mRampWidget->blockSignals( true );
1348  mRampWidget->setComponentValue( value );
1349  mRampWidget->blockSignals( false );
1350  mSpinBox->blockSignals( true );
1351  mSpinBox->setValue( convertRealToDisplay( value ) );
1352  mSpinBox->blockSignals( false );
1353 }
1354 
1355 void QgsColorSliderWidget::setColor( const QColor &color, bool emitSignals )
1356 {
1357  QgsColorWidget::setColor( color, emitSignals );
1358  mRampWidget->setColor( color );
1359  mSpinBox->blockSignals( true );
1360  mSpinBox->setValue( convertRealToDisplay( componentValue() ) );
1361  mSpinBox->blockSignals( false );
1362 }
1363 
1364 void QgsColorSliderWidget::rampColorChanged( const QColor &color )
1365 {
1366  emit colorChanged( color );
1367 }
1368 
1369 void QgsColorSliderWidget::spinChanged( int value )
1370 {
1371  int convertedValue = convertDisplayToReal( value );
1372  QgsColorWidget::setComponentValue( convertedValue );
1373  mRampWidget->setComponentValue( convertedValue );
1374  emit colorChanged( mCurrentColor );
1375 }
1376 
1377 void QgsColorSliderWidget::rampChanged( int value )
1378 {
1379  mSpinBox->blockSignals( true );
1380  mSpinBox->setValue( convertRealToDisplay( value ) );
1381  mSpinBox->blockSignals( false );
1382 }
1383 
1384 
1385 int QgsColorSliderWidget::convertRealToDisplay( const int realValue ) const
1386 {
1387  //scale saturation, value or alpha to 0->100 range. This makes more sense for users
1388  //for whom "255" is a totally arbitrary value!
1390  {
1391  return std::round( 100.0 * realValue / 255.0 );
1392  }
1393 
1394  //leave all other values intact
1395  return realValue;
1396 }
1397 
1398 int QgsColorSliderWidget::convertDisplayToReal( const int displayValue ) const
1399 {
1400  //scale saturation, value or alpha from 0->100 range (see note in convertRealToDisplay)
1402  {
1403  return std::round( 255.0 * displayValue / 100.0 );
1404  }
1405 
1406  //leave all other values intact
1407  return displayValue;
1408 }
1409 
1410 //
1411 // QgsColorTextWidget
1412 //
1413 
1415  : QgsColorWidget( parent )
1416 {
1417  QHBoxLayout *hLayout = new QHBoxLayout();
1418  hLayout->setMargin( 0 );
1419  hLayout->setSpacing( 0 );
1420 
1421  mLineEdit = new QLineEdit( nullptr );
1422  hLayout->addWidget( mLineEdit );
1423 
1424  mMenuButton = new QToolButton( mLineEdit );
1425  mMenuButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mIconDropDownMenu.svg" ) ) );
1426  mMenuButton->setCursor( Qt::ArrowCursor );
1427  mMenuButton->setFocusPolicy( Qt::NoFocus );
1428  mMenuButton->setStyleSheet( QStringLiteral( "QToolButton { border: none; padding: 0px; }" ) );
1429 
1430  setLayout( hLayout );
1431 
1432  int frameWidth = mLineEdit->style()->pixelMetric( QStyle::PM_DefaultFrameWidth );
1433  mLineEdit->setStyleSheet( QStringLiteral( "QLineEdit { padding-right: %1px; } " )
1434  .arg( mMenuButton->sizeHint().width() + frameWidth + 1 ) );
1435 
1436  connect( mLineEdit, &QLineEdit::editingFinished, this, &QgsColorTextWidget::textChanged );
1437  connect( mMenuButton, &QAbstractButton::clicked, this, &QgsColorTextWidget::showMenu );
1438 
1439  //restore format setting
1440  QgsSettings settings;
1441  mFormat = settings.enumValue( QStringLiteral( "ColorWidgets/textWidgetFormat" ), HexRgb );
1442 
1443  updateText();
1444 }
1445 
1446 void QgsColorTextWidget::setColor( const QColor &color, const bool emitSignals )
1447 {
1448  QgsColorWidget::setColor( color, emitSignals );
1449  updateText();
1450 }
1451 
1452 void QgsColorTextWidget::resizeEvent( QResizeEvent *event )
1453 {
1454  Q_UNUSED( event )
1455  QSize sz = mMenuButton->sizeHint();
1456  int frameWidth = style()->pixelMetric( QStyle::PM_DefaultFrameWidth );
1457  mMenuButton->move( mLineEdit->rect().right() - frameWidth - sz.width(),
1458  ( mLineEdit->rect().bottom() + 1 - sz.height() ) / 2 );
1459 }
1460 
1461 void QgsColorTextWidget::updateText()
1462 {
1463  switch ( mFormat )
1464  {
1465  case HexRgb:
1466  mLineEdit->setText( mCurrentColor.name() );
1467  break;
1468  case HexRgbA:
1469  mLineEdit->setText( mCurrentColor.name() + QStringLiteral( "%1" ).arg( mCurrentColor.alpha(), 2, 16, QChar( '0' ) ) );
1470  break;
1471  case Rgb:
1472  mLineEdit->setText( QString( tr( "rgb( %1, %2, %3 )" ) ).arg( mCurrentColor.red() ).arg( mCurrentColor.green() ).arg( mCurrentColor.blue() ) );
1473  break;
1474  case Rgba:
1475  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 ) ) );
1476  break;
1477  }
1478 }
1479 
1480 void QgsColorTextWidget::textChanged()
1481 {
1482  QString testString = mLineEdit->text();
1483  bool containsAlpha;
1484  QColor color = QgsSymbolLayerUtils::parseColorWithAlpha( testString, containsAlpha );
1485  if ( !color.isValid() )
1486  {
1487  //bad color string
1488  updateText();
1489  return;
1490  }
1491 
1492  //good color string
1493  if ( color != mCurrentColor )
1494  {
1495  //retain alpha if no explicit alpha set
1496  if ( !containsAlpha )
1497  {
1498  color.setAlpha( mCurrentColor.alpha() );
1499  }
1500  //color has changed
1501  mCurrentColor = color;
1502  emit colorChanged( mCurrentColor );
1503  }
1504  updateText();
1505 }
1506 
1507 void QgsColorTextWidget::showMenu()
1508 {
1509  QMenu colorContextMenu;
1510 
1511  QAction *hexRgbAction = new QAction( tr( "#RRGGBB" ), nullptr );
1512  colorContextMenu.addAction( hexRgbAction );
1513  QAction *hexRgbaAction = new QAction( tr( "#RRGGBBAA" ), nullptr );
1514  colorContextMenu.addAction( hexRgbaAction );
1515  QAction *rgbAction = new QAction( tr( "rgb( r, g, b )" ), nullptr );
1516  colorContextMenu.addAction( rgbAction );
1517  QAction *rgbaAction = new QAction( tr( "rgba( r, g, b, a )" ), nullptr );
1518  colorContextMenu.addAction( rgbaAction );
1519 
1520  QAction *selectedAction = colorContextMenu.exec( QCursor::pos() );
1521  if ( selectedAction == hexRgbAction )
1522  {
1523  mFormat = QgsColorTextWidget::HexRgb;
1524  }
1525  else if ( selectedAction == hexRgbaAction )
1526  {
1527  mFormat = QgsColorTextWidget::HexRgbA;
1528  }
1529  else if ( selectedAction == rgbAction )
1530  {
1531  mFormat = QgsColorTextWidget::Rgb;
1532  }
1533  else if ( selectedAction == rgbaAction )
1534  {
1535  mFormat = QgsColorTextWidget::Rgba;
1536  }
1537 
1538  //save format setting
1539  QgsSettings settings;
1540  settings.setEnumValue( QStringLiteral( "ColorWidgets/textWidgetFormat" ), mFormat );
1541 
1542  updateText();
1543 }
1544 
1545 
1546 //
1547 // QgsColorPreviewWidget
1548 //
1549 
1551  : QgsColorWidget( parent )
1552  , mColor2( QColor() )
1553 {
1554 
1555 }
1556 
1557 void QgsColorPreviewWidget::drawColor( const QColor &color, QRect rect, QPainter &painter )
1558 {
1559  painter.setPen( Qt::NoPen );
1560  //if color has an alpha, start with a checkboard pattern
1561  if ( color.alpha() < 255 )
1562  {
1563  QBrush checkBrush = QBrush( transparentBackground() );
1564  painter.setBrush( checkBrush );
1565  painter.drawRect( rect );
1566 
1567  //draw half of widget showing solid color, the other half showing color with alpha
1568 
1569  //ensure at least a 1px overlap to avoid artifacts
1570  QBrush colorBrush = QBrush( color );
1571  painter.setBrush( colorBrush );
1572  painter.drawRect( std::floor( rect.width() / 2.0 ) + rect.left(), rect.top(), rect.width() - std::floor( rect.width() / 2.0 ), rect.height() );
1573 
1574  QColor opaqueColor = QColor( color );
1575  opaqueColor.setAlpha( 255 );
1576  QBrush opaqueBrush = QBrush( opaqueColor );
1577  painter.setBrush( opaqueBrush );
1578  painter.drawRect( rect.left(), rect.top(), std::ceil( rect.width() / 2.0 ), rect.height() );
1579  }
1580  else
1581  {
1582  //no alpha component, just draw a solid rectangle
1583  QBrush brush = QBrush( color );
1584  painter.setBrush( brush );
1585  painter.drawRect( rect );
1586  }
1587 }
1588 
1589 void QgsColorPreviewWidget::paintEvent( QPaintEvent *event )
1590 {
1591  Q_UNUSED( event )
1592  QPainter painter( this );
1593 
1594  if ( mColor2.isValid() )
1595  {
1596  //drawing with two color sections
1597  int verticalSplit = std::round( height() / 2.0 );
1598  drawColor( mCurrentColor, QRect( 0, 0, width(), verticalSplit ), painter );
1599  drawColor( mColor2, QRect( 0, verticalSplit, width(), height() - verticalSplit ), painter );
1600  }
1601  else if ( mCurrentColor.isValid() )
1602  {
1603  drawColor( mCurrentColor, QRect( 0, 0, width(), height() ), painter );
1604  }
1605 
1606  painter.end();
1607 }
1608 
1610 {
1611 #if QT_VERSION < QT_VERSION_CHECK(5, 11, 0)
1612  return QSize( Qgis::UI_SCALE_FACTOR * fontMetrics().width( QStringLiteral( "XXXXXXXXXXXXXXXXXXXXXX" ) ), Qgis::UI_SCALE_FACTOR * fontMetrics().width( QStringLiteral( "XXXXXXXXXXXXXXXXXXXXXX" ) ) * 0.75 );
1613 #else
1614  return QSize( Qgis::UI_SCALE_FACTOR * fontMetrics().horizontalAdvance( 'X' ) * 22, Qgis::UI_SCALE_FACTOR * fontMetrics().horizontalAdvance( 'X' ) * 22 * 0.75 );
1615 #endif
1616 }
1617 
1619 {
1620  if ( color == mColor2 )
1621  {
1622  return;
1623  }
1624  mColor2 = color;
1625  update();
1626 }
1627 
1629 {
1630  if ( e->button() == Qt::LeftButton )
1631  {
1632  mDragStartPosition = e->pos();
1633  }
1635 }
1636 
1638 {
1639  if ( ( e->pos() - mDragStartPosition ).manhattanLength() >= QApplication::startDragDistance() )
1640  {
1641  //mouse moved, so a drag. nothing to do here
1643  return;
1644  }
1645 
1646  //work out which color was clicked
1647  QColor clickedColor = mCurrentColor;
1648  if ( mColor2.isValid() )
1649  {
1650  //two color sections, check if dragged color was the second color
1651  int verticalSplit = std::round( height() / 2.0 );
1652  if ( mDragStartPosition.y() >= verticalSplit )
1653  {
1654  clickedColor = mColor2;
1655  }
1656  }
1657  emit colorChanged( clickedColor );
1658 
1659 }
1660 
1662 {
1663  //handle dragging colors from button
1664 
1665  if ( !( e->buttons() & Qt::LeftButton ) )
1666  {
1667  //left button not depressed, so not a drag
1669  return;
1670  }
1671 
1672  if ( ( e->pos() - mDragStartPosition ).manhattanLength() < QApplication::startDragDistance() )
1673  {
1674  //mouse not moved, so not a drag
1676  return;
1677  }
1678 
1679  //user is dragging color
1680 
1681  //work out which color is being dragged
1682  QColor dragColor = mCurrentColor;
1683  if ( mColor2.isValid() )
1684  {
1685  //two color sections, check if dragged color was the second color
1686  int verticalSplit = std::round( height() / 2.0 );
1687  if ( mDragStartPosition.y() >= verticalSplit )
1688  {
1689  dragColor = mColor2;
1690  }
1691  }
1692 
1693  QDrag *drag = new QDrag( this );
1694  drag->setMimeData( QgsSymbolLayerUtils::colorToMimeData( dragColor ) );
1695  drag->setPixmap( createDragIcon( dragColor ) );
1696  drag->exec( Qt::CopyAction );
1697 }
1698 
1699 
1700 //
1701 // QgsColorWidgetAction
1702 //
1703 
1704 QgsColorWidgetAction::QgsColorWidgetAction( QgsColorWidget *colorWidget, QMenu *menu, QWidget *parent )
1705  : QWidgetAction( parent )
1706  , mMenu( menu )
1707  , mColorWidget( colorWidget )
1708  , mSuppressRecurse( false )
1709  , mDismissOnColorSelection( true )
1710 {
1711  setDefaultWidget( mColorWidget );
1712  connect( mColorWidget, &QgsColorWidget::colorChanged, this, &QgsColorWidgetAction::setColor );
1713 
1714  connect( this, &QAction::hovered, this, &QgsColorWidgetAction::onHover );
1715  connect( mColorWidget, &QgsColorWidget::hovered, this, &QgsColorWidgetAction::onHover );
1716 }
1717 
1718 void QgsColorWidgetAction::onHover()
1719 {
1720  //see https://bugreports.qt.io/browse/QTBUG-10427?focusedCommentId=185610&page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel#comment-185610
1721  if ( mSuppressRecurse )
1722  {
1723  return;
1724  }
1725 
1726  if ( mMenu )
1727  {
1728  mSuppressRecurse = true;
1729  mMenu->setActiveAction( this );
1730  mSuppressRecurse = false;
1731  }
1732 }
1733 
1734 void QgsColorWidgetAction::setColor( const QColor &color )
1735 {
1736  emit colorChanged( color );
1737  QAction::trigger();
1738  if ( mMenu && mDismissOnColorSelection )
1739  {
1740  mMenu->hide();
1741  }
1742 }
QgsColorRampWidget(QWidget *parent=nullptr, ColorComponent component=QgsColorWidget::Red, Orientation orientation=QgsColorRampWidget::Horizontal)
Construct a new color ramp widget.
void paintEvent(QPaintEvent *event) override
QgsColorSliderWidget(QWidget *parent=nullptr, ColorComponent component=QgsColorWidget::Red)
Construct a new color slider widget.
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:182
This class is a composition of two QSettings instances:
Definition: qgssettings.h:58
void setColor(const QColor &color, bool emitSignals=false) override
Sets the color for the widget.
virtual void setColor(const QColor &color, bool emitSignals=false)
Sets the color for the widget.
int scaleIconSize(int standardSize)
Scales an icon size to compensate for display pixel density, making the icon size hi-dpi friendly...
void valueChanged(int value)
Emitted when the widget&#39;s color component value changes.
Rgb( r, g, b ) format.
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 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
virtual void setComponent(QgsColorWidget::ColorComponent component)
Sets the color component which the widget controls.
static const QPixmap & transparentBackground()
Generates a checkboard pattern pixmap for use as a background to transparent colors.
void alterColor(QColor &color, QgsColorWidget::ColorComponent component, int newValue) const
Alters a color by modifying the value of a specific color component.
void setMarkerSize(int markerSize)
Sets the size for drawing the triangular markers on the ramp.
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 mousePressEvent(QMouseEvent *event) override
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
QgsColorWidgetAction(QgsColorWidget *colorWidget, QMenu *menu=nullptr, QWidget *parent=nullptr)
Construct a new color widget action.
Green component of color.
~QgsColorBox() override
void mousePressEvent(QMouseEvent *event) override
virtual void setComponentValue(int value)
Alters the widget&#39;s color by setting the value for the widget&#39;s color component.
Red component of color.
~QgsColorWheel() override
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 setComponent(ColorComponent component) override
Sets the color component which the widget controls.
void resizeEvent(QResizeEvent *event) override
A color ramp widget.
QgsColorWheel(QWidget *parent=nullptr)
Constructs a new color wheel widget.
void setColor(const QColor &color, bool emitSignals=false) override
void mouseMoveEvent(QMouseEvent *e) override
Blue component of color.
#RRGGBBAA in hexadecimal, with alpha
void dragEnterEvent(QDragEnterEvent *e) override
void setInteriorMargin(int margin)
Sets the margin between the edge of the widget and the ramp.
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 paintEvent(QPaintEvent *event) override
QSize iconSize(bool dockableToolbar)
Returns the user-preferred size of a window&#39;s toolbar icons.
void setComponent(ColorComponent component) override
Sets the color component which the widget controls.
void resizeEvent(QResizeEvent *event) override
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:297
ColorComponent
Specifies the color component which the widget alters.
void setColor(const QColor &color, bool emitSignals=false) override
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.
QSize sizeHint() const override
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:245
Orientation orientation() const
Fetches the orientation for the color ramp.
void paintEvent(QPaintEvent *event) override
void setOrientation(Orientation orientation)
Sets the orientation for the color ramp.
int mExplicitHue
QColor wipes the hue information when it is ambiguous (e.g., for saturation = 0). ...
void mouseMoveEvent(QMouseEvent *event) override
ColorComponent mComponent
QgsColorBox(QWidget *parent=nullptr, ColorComponent component=Value)
Construct a new color box 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 setComponentValue(int value) override
Alters the widget&#39;s color by setting the value for the widget&#39;s color component.
void setShowFrame(bool showFrame)
Sets whether the ramp should be drawn within a frame.
void mousePressEvent(QMouseEvent *e) override
void setColor(const QColor &color, bool emitSignals=false) override
Sets the color for the widget.
QSize sizeHint() const override
Rgba( r, g, b, a ) format, with alpha.
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
QgsColorWidget(QWidget *parent=nullptr, ColorComponent component=Multiple)
Construct a new color widget.