QGIS API Documentation  3.4.15-Madeira (e83d02e274)
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  int size = Qgis::UI_SCALE_FACTOR * fontMetrics().width( QStringLiteral( "XXXXXXXXXXXXXXXXXXXXXX" ) );
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 
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().width( QStringLiteral( "XXXXXXXXXXXXXXXXXXXXXX" ) );
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  }
798  QgsColorWidget::setComponent( component );
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 
969  const Orientation orientation )
970  : QgsColorWidget( parent, component )
971 {
972  setFocusPolicy( Qt::StrongFocus );
973  setOrientation( orientation );
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().width( QStringLiteral( "XXXXXXXXXXXXXXXXXXXXXX" ) ), 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().width( QStringLiteral( "XXXXXXXXXXXXXXXXXXXXXX" ) ) );
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://issues.qgis.org/issues/15984
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;
1116  if ( orientation == QgsColorRampWidget::Horizontal )
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 
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->setMargin( 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().width( 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  }
1290  else if ( component == QgsColorWidget::Saturation || component == QgsColorWidget::Value || component == QgsColorWidget::Alpha )
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 {
1304  QgsColorWidget::setComponent( component );
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  }
1312  else if ( component == QgsColorWidget::Saturation || component == QgsColorWidget::Value || component == QgsColorWidget::Alpha )
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->setMargin( 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( QString( tr( "rgb( %1, %2, %3 )" ) ).arg( mCurrentColor.red() ).arg( mCurrentColor.green() ).arg( mCurrentColor.blue() ) );
1453  break;
1454  case Rgba:
1455  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 ) ) );
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().width( QStringLiteral( "XXXXXXXXXXXXXXXXXXXXXX" ) ), Qgis::UI_SCALE_FACTOR * fontMetrics().width( QStringLiteral( "XXXXXXXXXXXXXXXXXXXXXX" ) ) * 0.75 );
1592 }
1593 
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  QAction::trigger();
1714  if ( mMenu && mDismissOnColorSelection )
1715  {
1716  mMenu->hide();
1717  }
1718 }
int hue() const
Returns the hue for the widget.
QgsColorRampWidget(QWidget *parent=nullptr, ColorComponent component=QgsColorWidget::Red, Orientation orientation=QgsColorRampWidget::Horizontal)
Construct a new color ramp widget.
Orientation orientation() const
Fetches the orientation for the color ramp.
void paintEvent(QPaintEvent *event) override
bool showFrame() const
Fetches whether the ramp is drawn within a frame.
QgsColorSliderWidget(QWidget *parent=nullptr, ColorComponent component=QgsColorWidget::Red)
Construct a new color slider widget.
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)
int componentRange() const
Returns the range of valid values for the color widget&#39;s component.
void hovered()
Emitted when mouse hovers over widget.
static const double UI_SCALE_FACTOR
UI scaling factor.
Definition: qgis.h:152
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.
Widget alters multiple color components.
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 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.
int componentValue() const
Returns the current value of the widget&#39;s color component.
void setComponent(ColorComponent component) override
Sets the color component which the widget controls.
void resizeEvent(QResizeEvent *event) override
QColor color() const
Returns the current color for the widget.
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
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:287
ColorComponent
Specifies the color component which the widget alters.
void setColor(const QColor &color, bool emitSignals=false) override
void alterColor(QColor &color, QgsColorWidget::ColorComponent component, int newValue) const
Alters a color by modifiying the value of a specific color component.
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.
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:235
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
ColorComponent component() const
Returns the color component which the widget controls.
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.
void mouseMoveEvent(QMouseEvent *e) override
QgsColorWidget(QWidget *parent=nullptr, ColorComponent component=Multiple)
Construct a new color widget.