QGIS API Documentation  2.18.21-Las Palmas (9fba24a)
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 "qgssymbollayerv2utils.h"
19 #include <QResizeEvent>
20 #include <QStyleOptionFrameV3>
21 #include <QPainter>
22 #include <QHBoxLayout>
23 #include <QSpinBox>
24 #include <QLineEdit>
25 #include <QFontMetrics>
26 #include <QToolButton>
27 #include <QMenu>
28 #include <QSettings>
29 #include <QDrag>
30 #include <cmath>
31 #include "qgslogger.h"
32 
33 
34 //
35 // QgsColorWidget
36 //
37 
39  : QWidget( parent )
40  , mCurrentColor( Qt::red )
41  , mComponent( component )
42  , mExplicitHue( 0 )
43 {
44  setAcceptDrops( true );
45 }
46 
48 {
49 
50 }
51 
53 {
54  return componentValue( mComponent );
55 }
56 
58 {
59  //craft a pixmap for the drag icon
60  QPixmap pixmap( 50, 50 );
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, 50, 50 ), 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, 47, 47 ) );
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();
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 
143 {
144  int h, s, v, a;
145  color.getHsv( &h, &s, &v, &a );
146 
147  //clip value to sensible range
148  int clippedValue = qMin( qMax( 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 transpBkgrd;
181 
182  if ( transpBkgrd.isNull() )
183  transpBkgrd = QgsApplication::getThemePixmap( "/transp-background_8x8.png" );
184 
185  return transpBkgrd;
186 }
187 
189 {
190  //is dragged data valid color data?
191  bool hasAlpha;
192  QColor mimeColor = QgsSymbolLayerV2Utils::colorFromMimeData( e->mimeData(), hasAlpha );
193 
194  if ( mimeColor.isValid() )
195  {
196  //if so, we accept the drag
198  }
199 }
200 
202 {
203  //is dropped data valid color data?
204  bool hasAlpha = false;
205  QColor mimeColor = QgsSymbolLayerV2Utils::colorFromMimeData( e->mimeData(), hasAlpha );
206 
207  if ( mimeColor.isValid() )
208  {
209  //accept drop and set new color
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 
226 {
227  emit hovered();
228  e->accept();
229  //don't pass to QWidget::mouseMoveEvent, causes issues with widget used in QWidgetAction
230 }
231 
233 {
234  e->accept();
235  //don't pass to QWidget::mousePressEvent, causes issues with widget used in QWidgetAction
236 }
237 
239 {
240  e->accept();
241  //don't pass to QWidget::mouseReleaseEvent, causes issues with widget used in QWidgetAction
242 }
243 
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 = qMin( value, componentRange() );
269  valueClipped = qMax( 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  {
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  , mMargin( 4 )
373  , mWheelThickness( 18 )
374  , mClickedPart( QgsColorWheel::None )
375  , mWheelImage( nullptr )
376  , mTriangleImage( nullptr )
377  , mWidgetImage( nullptr )
378  , mWheelDirty( true )
379  , mTriangleDirty( true )
380 {
381  //create wheel hue brush - only do this once
382  QConicalGradient wheelGradient = QConicalGradient( 0, 0, 0 );
383  int wheelStops = 20;
384  QColor gradColor = QColor::fromHsvF( 1.0, 1.0, 1.0 );
385  for ( int pos = 0; pos <= wheelStops; ++pos )
386  {
387  double relativePos = ( double )pos / wheelStops;
388  gradColor.setHsvF( relativePos, 1, 1 );
389  wheelGradient.setColorAt( relativePos, gradColor );
390  }
391  mWheelBrush = QBrush( wheelGradient );
392 }
393 
395 {
396  delete mWheelImage;
397  delete mTriangleImage;
398  delete mWidgetImage;
399 }
400 
402 {
403  return QSize( 200, 200 );
404 }
405 
407 {
408  Q_UNUSED( event );
409  QPainter painter( this );
410 
411  //draw a frame
413  option.initFrom( this );
414  option.state = this->hasFocus() ? QStyle::State_Active : QStyle::State_None;
415  style()->drawPrimitive( QStyle::PE_Frame, &option, &painter );
416 
417  if ( !mWidgetImage || !mWheelImage || !mTriangleImage )
418  {
419  createImages( size() );
420  }
421 
422  //draw everything in an image
423  mWidgetImage->fill( Qt::transparent );
424  QPainter imagePainter( mWidgetImage );
425  imagePainter.setRenderHint( QPainter::Antialiasing );
426 
427  if ( mWheelDirty )
428  {
429  //need to redraw the wheel image
430  createWheel();
431  }
432 
433  //draw wheel centered on widget
434  QPointF center = QPointF( width() / 2.0, height() / 2.0 );
435  imagePainter.drawImage( QPointF( center.x() - ( mWheelImage->width() / 2.0 ), center.y() - ( mWheelImage->height() / 2.0 ) ), *mWheelImage );
436 
437  //draw hue marker
438  int h = hue();
439  double length = mWheelImage->width() / 2.0;
440  QLineF hueMarkerLine = QLineF( center.x(), center.y(), center.x() + length, center.y() );
441  hueMarkerLine.setAngle( h );
442  imagePainter.save();
443  //use sourceIn mode for nicer antialiasing
444  imagePainter.setCompositionMode( QPainter::CompositionMode_SourceIn );
445  QPen pen;
446  pen.setWidth( 2 );
447  //adapt pen color for hue
448  pen.setColor( h > 20 && h < 200 ? Qt::black : Qt::white );
449  imagePainter.setPen( pen );
450  imagePainter.drawLine( hueMarkerLine );
451  imagePainter.restore();
452 
453  //draw triangle
454  if ( mTriangleDirty )
455  {
456  createTriangle();
457  }
458  imagePainter.drawImage( QPointF( center.x() - ( mWheelImage->width() / 2.0 ), center.y() - ( mWheelImage->height() / 2.0 ) ), *mTriangleImage );
459 
460  //draw current color marker
461  double triangleRadius = length - mWheelThickness - 1;
462 
463  //adapted from equations at https://github.com/timjb/colortriangle/blob/master/colortriangle.js by Tim Baumann
464  double lightness = mCurrentColor.lightnessF();
465  double hueRadians = ( h * M_PI / 180.0 );
466  double hx = cos( hueRadians ) * triangleRadius;
467  double hy = -sin( hueRadians ) * triangleRadius;
468  double sx = -cos( -hueRadians + ( M_PI / 3.0 ) ) * triangleRadius;
469  double sy = -sin( -hueRadians + ( M_PI / 3.0 ) ) * triangleRadius;
470  double vx = -cos( hueRadians + ( M_PI / 3.0 ) ) * triangleRadius;
471  double vy = sin( hueRadians + ( M_PI / 3.0 ) ) * triangleRadius;
472  double mx = ( sx + vx ) / 2.0;
473  double my = ( sy + vy ) / 2.0;
474 
475  double a = ( 1 - 2.0 * fabs( lightness - 0.5 ) ) * mCurrentColor.hslSaturationF();
476  double x = sx + ( vx - sx ) * lightness + ( hx - mx ) * a;
477  double y = sy + ( vy - sy ) * lightness + ( hy - my ) * a;
478 
479  //adapt pen color for lightness
480  pen.setColor( lightness > 0.7 ? Qt::black : Qt::white );
481  imagePainter.setPen( pen );
482  imagePainter.setBrush( Qt::NoBrush );
483  imagePainter.drawEllipse( QPointF( x + center.x(), y + center.y() ), 4.0, 4.0 );
484  imagePainter.end();
485 
486  //draw image onto widget
487  painter.drawImage( QPoint( 0, 0 ), *mWidgetImage );
488  painter.end();
489 }
490 
491 void QgsColorWheel::setColor( const QColor &color, const bool emitSignals )
492 {
493  if ( color.hue() >= 0 && color.hue() != hue() )
494  {
495  //hue has changed, need to redraw the triangle
496  mTriangleDirty = true;
497  }
498 
499  QgsColorWidget::setColor( color, emitSignals );
500 }
501 
502 void QgsColorWheel::createImages( const QSizeF size )
503 {
504  double wheelSize = qMin( size.width(), size.height() ) - mMargin * 2.0;
505  mWheelThickness = wheelSize / 15.0;
506 
507  //recreate cache images at correct size
508  delete mWheelImage;
509  mWheelImage = new QImage( wheelSize, wheelSize, QImage::Format_ARGB32 );
510  delete mTriangleImage;
511  mTriangleImage = new QImage( wheelSize, wheelSize, QImage::Format_ARGB32 );
512  delete mWidgetImage;
513  mWidgetImage = new QImage( size.width(), size.height(), QImage::Format_ARGB32 );
514 
515  //trigger a redraw for the images
516  mWheelDirty = true;
517  mTriangleDirty = true;
518 }
519 
521 {
522  //recreate images for new size
523  createImages( event->size() );
525 }
526 
527 void QgsColorWheel::setColorFromPos( const QPointF pos )
528 {
529  QPointF center = QPointF( width() / 2.0, height() / 2.0 );
530  //line from center to mouse position
531  QLineF line = QLineF( center.x(), center.y(), pos.x(), pos.y() );
532 
533  QColor newColor = QColor();
534 
535  int h, s, l, alpha;
536  mCurrentColor.getHsl( &h, &s, &l, &alpha );
537  //override hue with explicit hue, so we don't get -1 values from QColor for hue
538  h = hue();
539 
540  if ( mClickedPart == QgsColorWheel::Triangle )
541  {
542  //adapted from equations at https://github.com/timjb/colortriangle/blob/master/colortriangle.js by Tim Baumann
543 
544  //position of event relative to triangle center
545  double x = pos.x() - center.x();
546  double y = pos.y() - center.y();
547 
548  double eventAngleRadians = line.angle() * M_PI / 180.0;
549  double hueRadians = h * M_PI / 180.0;
550  double rad0 = fmod( eventAngleRadians + 2.0 * M_PI - hueRadians, 2.0 * M_PI );
551  double rad1 = fmod( rad0, (( 2.0 / 3.0 ) * M_PI ) ) - ( M_PI / 3.0 );
552  double length = mWheelImage->width() / 2.0;
553  double triangleLength = length - mWheelThickness - 1;
554 
555  double a = 0.5 * triangleLength;
556  double b = tan( rad1 ) * a;
557  double r = sqrt( x * x + y * y );
558  double maxR = sqrt( a * a + b * b );
559 
560  if ( r > maxR )
561  {
562  double dx = tan( rad1 ) * r;
563  double rad2 = atan( dx / maxR );
564  rad2 = qMin( rad2, M_PI / 3.0 );
565  rad2 = qMax( rad2, -M_PI / 3.0 );
566  eventAngleRadians += rad2 - rad1;
567  rad0 = fmod( eventAngleRadians + 2.0 * M_PI - hueRadians, 2.0 * M_PI );
568  rad1 = fmod( rad0, (( 2.0 / 3.0 ) * M_PI ) ) - ( M_PI / 3.0 );
569  b = tan( rad1 ) * a;
570  r = sqrt( a * a + b * b );
571  }
572 
573  double triangleSideLength = sqrt( 3.0 ) * triangleLength;
574  double newL = (( -sin( rad0 ) * r ) / triangleSideLength ) + 0.5;
575  double widthShare = 1.0 - ( fabs( newL - 0.5 ) * 2.0 );
576  double newS = ((( cos( rad0 ) * r ) + ( triangleLength / 2.0 ) ) / ( 1.5 * triangleLength ) ) / widthShare;
577  s = qMin( qRound( qMax( 0.0, newS ) * 255.0 ), 255 );
578  l = qMin( qRound( qMax( 0.0, newL ) * 255.0 ), 255 );
579  newColor = QColor::fromHsl( h, s, l );
580  //explicitly set the hue again, so that it's exact
581  newColor.setHsv( h, newColor.hsvSaturation(), newColor.value(), alpha );
582  }
583  else if ( mClickedPart == QgsColorWheel::Wheel )
584  {
585  //use hue angle
587  int v = mCurrentColor.value();
588  int newHue = line.angle();
589  newColor = QColor::fromHsv( newHue, s, v, alpha );
590  //hue has changed, need to redraw triangle
591  mTriangleDirty = true;
592  }
593 
594  if ( newColor.isValid() && newColor != mCurrentColor )
595  {
596  //color has changed
597  mCurrentColor = QColor( newColor );
598 
599  if ( mCurrentColor.hue() >= 0 )
600  {
601  //color has a valid hue, so update the QgsColorWidget's explicit hue
603  }
604 
605  update();
606  emit colorChanged( mCurrentColor );
607  }
608 }
609 
611 {
612  setColorFromPos( event->posF() );
614 }
615 
617 {
618  //calculate where the event occurred -- on the wheel or inside the triangle?
619 
620  //create a line from the widget's center to the event
621  QLineF line = QLineF( width() / 2.0, height() / 2.0, event->pos().x(), event->pos().y() );
622 
623  double innerLength = mWheelImage->width() / 2.0 - mWheelThickness;
624  if ( line.length() < innerLength )
625  {
626  mClickedPart = QgsColorWheel::Triangle;
627  }
628  else
629  {
630  mClickedPart = QgsColorWheel::Wheel;
631  }
632  setColorFromPos( event->posF() );
633 }
634 
636 {
637  Q_UNUSED( event );
638  mClickedPart = QgsColorWheel::None;
639 }
640 
641 void QgsColorWheel::createWheel()
642 {
643  if ( !mWheelImage )
644  {
645  return;
646  }
647 
648  int maxSize = qMin( mWheelImage->width(), mWheelImage->height() );
649  double wheelRadius = maxSize / 2.0;
650 
651  mWheelImage->fill( Qt::transparent );
652  QPainter p( mWheelImage );
653  p.setRenderHint( QPainter::Antialiasing );
654  p.setBrush( mWheelBrush );
655  p.setPen( Qt::NoPen );
656 
657  //draw hue wheel as a circle
658  p.translate( wheelRadius, wheelRadius );
659  p.drawEllipse( QPointF( 0.0, 0.0 ), wheelRadius, wheelRadius );
660 
661  //cut hole in center of circle to make a ring
662  p.setCompositionMode( QPainter::CompositionMode_DestinationOut );
663  p.setBrush( QBrush( Qt::black ) );
664  p.drawEllipse( QPointF( 0.0, 0.0 ), wheelRadius - mWheelThickness, wheelRadius - mWheelThickness );
665  p.end();
666 
667  mWheelDirty = false;
668 }
669 
670 void QgsColorWheel::createTriangle()
671 {
672  if ( !mWheelImage || !mTriangleImage )
673  {
674  return;
675  }
676 
677  QPointF center = QPointF( mWheelImage->width() / 2.0, mWheelImage->height() / 2.0 );
678  mTriangleImage->fill( Qt::transparent );
679 
680  QPainter imagePainter( mTriangleImage );
681  imagePainter.setRenderHint( QPainter::Antialiasing );
682 
683  int angle = hue();
684  double wheelRadius = mWheelImage->width() / 2.0;
685  double triangleRadius = wheelRadius - mWheelThickness - 1;
686 
687  //pure version of hue (at full saturation and value)
688  QColor pureColor = QColor::fromHsv( angle, 255, 255 );
689  //create copy of color but with 0 alpha
690  QColor alphaColor = QColor( pureColor );
691  alphaColor.setAlpha( 0 );
692 
693  //some rather ugly shortcuts to obtain corners and midpoints of triangle
694  QLineF line1 = QLineF( center.x(), center.y(), center.x() - triangleRadius * cos( M_PI / 3.0 ), center.y() - triangleRadius * sin( M_PI / 3.0 ) );
695  QLineF line2 = QLineF( center.x(), center.y(), center.x() + triangleRadius, center.y() );
696  QLineF line3 = QLineF( center.x(), center.y(), center.x() - triangleRadius * cos( M_PI / 3.0 ), center.y() + triangleRadius * sin( M_PI / 3.0 ) );
697  QLineF line4 = QLineF( center.x(), center.y(), center.x() - triangleRadius * cos( M_PI / 3.0 ), center.y() );
698  QLineF line5 = QLineF( center.x(), center.y(), ( line2.p2().x() + line1.p2().x() ) / 2.0, ( line2.p2().y() + line1.p2().y() ) / 2.0 );
699  line1.setAngle( line1.angle() + angle );
700  line2.setAngle( line2.angle() + angle );
701  line3.setAngle( line3.angle() + angle );
702  line4.setAngle( line4.angle() + angle );
703  line5.setAngle( line5.angle() + angle );
704  QPointF p1 = line1.p2();
705  QPointF p2 = line2.p2();
706  QPointF p3 = line3.p2();
707  QPointF p4 = line4.p2();
708  QPointF p5 = line5.p2();
709 
710  //inspired by Tim Baumann's work at https://github.com/timjb/colortriangle/blob/master/colortriangle.js
711  QLinearGradient colorGrad = QLinearGradient( p4.x(), p4.y(), p2.x(), p2.y() );
712  colorGrad.setColorAt( 0, alphaColor );
713  colorGrad.setColorAt( 1, pureColor );
714  QLinearGradient whiteGrad = QLinearGradient( p3.x(), p3.y(), p5.x(), p5.y() );
715  whiteGrad.setColorAt( 0, QColor( 255, 255, 255, 255 ) );
716  whiteGrad.setColorAt( 1, QColor( 255, 255, 255, 0 ) );
717 
718  QPolygonF triangle;
719  triangle << p2 << p1 << p3 << p2;
720  imagePainter.setPen( Qt::NoPen );
721  //start with a black triangle
722  imagePainter.setBrush( QBrush( Qt::black ) );
723  imagePainter.drawPolygon( triangle );
724  //draw a gradient from transparent to the pure color at the triangle's tip
725  imagePainter.setBrush( QBrush( colorGrad ) );
726  imagePainter.drawPolygon( triangle );
727  //draw a white gradient using additive composition mode
728  imagePainter.setCompositionMode( QPainter::CompositionMode_Plus );
729  imagePainter.setBrush( QBrush( whiteGrad ) );
730  imagePainter.drawPolygon( triangle );
731 
732  //above process results in some small artifacts on the edge of the triangle. Let's clear these up
733  //use source composition mode and draw an outline using a transparent pen
734  //this clears the edge pixels and leaves a nice smooth image
735  imagePainter.setCompositionMode( QPainter::CompositionMode_Source );
736  imagePainter.setBrush( Qt::NoBrush );
737  imagePainter.setPen( QPen( Qt::transparent ) );
738  imagePainter.drawPolygon( triangle );
739 
740  imagePainter.end();
741  mTriangleDirty = false;
742 }
743 
744 
745 
746 //
747 // QgsColorBox
748 //
749 
751  : QgsColorWidget( parent, component )
752  , mMargin( 2 )
753  , mBoxImage( nullptr )
754  , mDirty( true )
755 {
756  setFocusPolicy( Qt::StrongFocus );
757  setSizePolicy( QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding );
758 
759  mBoxImage = new QImage( width() - mMargin * 2, height() - mMargin * 2, QImage::Format_RGB32 );
760 }
761 
763 {
764  delete mBoxImage;
765 }
766 
768 {
769  return QSize( 200, 200 );
770 }
771 
773 {
774  Q_UNUSED( event );
775  QPainter painter( this );
776 
777  QStyleOptionFrameV3 option;
778  option.initFrom( this );
779  option.state = hasFocus() ? QStyle::State_Active : QStyle::State_None;
780  style()->drawPrimitive( QStyle::PE_Frame, &option, &painter );
781 
782  if ( mDirty )
783  {
784  createBox();
785  }
786 
787  //draw background image
788  painter.drawImage( QPoint( mMargin, mMargin ), *mBoxImage );
789 
790  //draw cross lines
791  double xPos = mMargin + ( width() - 2 * mMargin - 1 ) * ( double )xComponentValue() / ( double )valueRangeX();
792  double yPos = mMargin + ( height() - 2 * mMargin - 1 ) - ( height() - 2 * mMargin - 1 ) * ( double )yComponentValue() / ( double )valueRangeY();
793 
794  painter.setBrush( Qt::white );
795  painter.setPen( Qt::NoPen );
796 
797  painter.drawRect( xPos - 1, mMargin, 3, height() - 2 * mMargin - 1 );
798  painter.drawRect( mMargin, yPos - 1, width() - 2 * mMargin - 1, 3 );
799  painter.setPen( Qt::black );
800  painter.drawLine( xPos, mMargin, xPos, height() - mMargin - 1 );
801  painter.drawLine( mMargin, yPos, width() - mMargin - 1, yPos );
802 
803  painter.end();
804 }
805 
807 {
808  if ( component != mComponent )
809  {
810  //need to redraw
811  mDirty = true;
812  }
813  QgsColorWidget::setComponent( component );
814 }
815 
816 void QgsColorBox::setColor( const QColor &color, const bool emitSignals )
817 {
818  //check if we need to redraw the box image
819  if ( mComponent == QgsColorWidget::Red && mCurrentColor.red() != color.red() )
820  {
821  mDirty = true;
822  }
823  else if ( mComponent == QgsColorWidget::Green && mCurrentColor.green() != color.green() )
824  {
825  mDirty = true;
826  }
827  else if ( mComponent == QgsColorWidget::Blue && mCurrentColor.blue() != color.blue() )
828  {
829  mDirty = true;
830  }
831  else if ( mComponent == QgsColorWidget::Hue && color.hsvHue() >= 0 && hue() != color.hsvHue() )
832  {
833  mDirty = true;
834  }
836  {
837  mDirty = true;
838  }
839  else if ( mComponent == QgsColorWidget::Value && mCurrentColor.value() != color.value() )
840  {
841  mDirty = true;
842  }
843  QgsColorWidget::setColor( color, emitSignals );
844 }
845 
847 {
848  mDirty = true;
849  delete mBoxImage;
850  mBoxImage = new QImage( event->size().width() - mMargin * 2, event->size().height() - mMargin * 2, QImage::Format_RGB32 );
852 }
853 
855 {
856  setColorFromPoint( event->pos() );
858 }
859 
861 {
862  setColorFromPoint( event->pos() );
863 }
864 
865 void QgsColorBox::createBox()
866 {
867  int maxValueX = mBoxImage->width();
868  int maxValueY = mBoxImage->height();
869 
870  //create a temporary color object
871  QColor currentColor = QColor( mCurrentColor );
872  int colorComponentValue;
873 
874  for ( int y = 0; y < maxValueY; ++y )
875  {
876  QRgb* scanLine = ( QRgb* )mBoxImage->scanLine( y );
877 
878  colorComponentValue = int( valueRangeY() - valueRangeY() * ( double( y ) / maxValueY ) );
879  alterColor( currentColor, yComponent(), colorComponentValue );
880  for ( int x = 0; x < maxValueX; ++x )
881  {
882  colorComponentValue = int( valueRangeX() * ( double( x ) / maxValueX ) );
883  alterColor( currentColor, xComponent(), colorComponentValue );
884  scanLine[x] = currentColor.rgb();
885  }
886  }
887  mDirty = false;
888 }
889 
890 int QgsColorBox::valueRangeX() const
891 {
892  return componentRange( xComponent() );
893 }
894 
895 int QgsColorBox::valueRangeY() const
896 {
897  return componentRange( yComponent() );
898 }
899 
900 QgsColorWidget::ColorComponent QgsColorBox::yComponent() const
901 {
902  switch ( mComponent )
903  {
904  case QgsColorWidget::Red:
905  return QgsColorWidget::Green;
908  return QgsColorWidget::Red;
909  case QgsColorWidget::Hue:
913  return QgsColorWidget::Hue;
914  default:
915  //should not occur
916  return QgsColorWidget::Red;
917  }
918 }
919 
920 int QgsColorBox::yComponentValue() const
921 {
922  return componentValue( yComponent() );
923 }
924 
925 QgsColorWidget::ColorComponent QgsColorBox::xComponent() const
926 {
927  switch ( mComponent )
928  {
929  case QgsColorWidget::Red:
931  return QgsColorWidget::Blue;
933  return QgsColorWidget::Green;
934  case QgsColorWidget::Hue:
936  return QgsColorWidget:: Value;
939  default:
940  //should not occur
941  return QgsColorWidget::Red;
942  }
943 }
944 
945 int QgsColorBox::xComponentValue() const
946 {
947  return componentValue( xComponent() );
948 }
949 
950 void QgsColorBox::setColorFromPoint( QPoint point )
951 {
952  int valX = valueRangeX() * ( point.x() - mMargin ) / ( width() - 2 * mMargin - 1 );
953  valX = qMin( qMax( valX, 0 ), valueRangeX() );
954 
955  int valY = valueRangeY() - valueRangeY() * ( point.y() - mMargin ) / ( height() - 2 * mMargin - 1 );
956  valY = qMin( qMax( valY, 0 ), valueRangeY() );
957 
959  alterColor( color, xComponent(), valX );
960  alterColor( color, yComponent(), valY );
961 
962  if ( color == mCurrentColor )
963  {
964  return;
965  }
966 
967  if ( color.hue() >= 0 )
968  {
969  mExplicitHue = color.hue();
970  }
971 
973  update();
974  emit colorChanged( color );
975 }
976 
977 
978 //
979 // QgsColorRampWidget
980 //
981 
984  const Orientation orientation )
985  : QgsColorWidget( parent, component )
986  , mMargin( 4 )
987  , mShowFrame( false )
988 {
989  setFocusPolicy( Qt::StrongFocus );
990  setOrientation( orientation );
991 
992  //create triangle polygons
993  setMarkerSize( 5 );
994 }
995 
997 {
998 
999 }
1000 
1002 {
1003  if ( mOrientation == QgsColorRampWidget::Horizontal )
1004  {
1005  //horizontal
1006  return QSize( 200, 28 );
1007  }
1008  else
1009  {
1010  //vertical
1011  return QSize( 18, 200 );
1012  }
1013 }
1014 
1016 {
1017  Q_UNUSED( event );
1018  QPainter painter( this );
1019 
1020  if ( mShowFrame )
1021  {
1022  //draw frame
1023  QStyleOptionFrameV3 option;
1024  option.initFrom( this );
1025  option.state = hasFocus() ? QStyle::State_KeyboardFocusChange : QStyle::State_None;
1026  style()->drawPrimitive( QStyle::PE_Frame, &option, &painter );
1027  }
1028 
1029  if ( hasFocus() )
1030  {
1031  //draw focus rect
1032  QStyleOptionFocusRect option;
1033  option.initFrom( this );
1034  option.state = QStyle::State_KeyboardFocusChange;
1035  style()->drawPrimitive( QStyle::PE_FrameFocusRect, &option, &painter );
1036  }
1037 
1039  {
1040  int maxValue = ( mOrientation == QgsColorRampWidget::Horizontal ? width() : height() ) - 1 - 2 * mMargin;
1042  color.setAlpha( 255 );
1043  QPen pen;
1044  pen.setWidth( 0 );
1045  painter.setPen( pen );
1046  painter.setBrush( Qt::NoBrush );
1047 
1048  //draw background ramp
1049  for ( int c = 0; c <= maxValue; ++c )
1050  {
1051  int colorVal = componentRange() * ( double )c / maxValue;
1052  //vertical sliders are reversed
1053  if ( mOrientation == QgsColorRampWidget::Vertical )
1054  {
1055  colorVal = componentRange() - colorVal;
1056  }
1057  alterColor( color, mComponent, colorVal );
1058  if ( color.hue() < 0 )
1059  {
1060  color.setHsv( hue(), color.saturation(), color.value() );
1061  }
1062  pen.setColor( color );
1063  painter.setPen( pen );
1064  if ( mOrientation == QgsColorRampWidget::Horizontal )
1065  {
1066  //horizontal
1067  painter.drawLine( c + mMargin, mMargin, c + mMargin, height() - mMargin - 1 );
1068  }
1069  else
1070  {
1071  //vertical
1072  painter.drawLine( mMargin, c + mMargin, width() - mMargin - 1, c + mMargin );
1073  }
1074  }
1075  }
1076  else
1077  {
1078  //alpha ramps are drawn differently
1079  //start with the checkboard pattern
1080  QBrush checkBrush = QBrush( transparentBackground() );
1081  painter.setBrush( checkBrush );
1082  painter.setPen( Qt::NoPen );
1083  painter.drawRect( mMargin, mMargin, width() - 2 * mMargin - 1, height() - 2 * mMargin - 1 );
1084  QLinearGradient colorGrad;
1085  if ( mOrientation == QgsColorRampWidget::Horizontal )
1086  {
1087  //horizontal
1088  colorGrad = QLinearGradient( mMargin, 0, width() - mMargin - 1, 0 );
1089  }
1090  else
1091  {
1092  //vertical
1093  colorGrad = QLinearGradient( 0, mMargin, 0, height() - mMargin - 1 );
1094  }
1095  QColor transparent = QColor( mCurrentColor );
1096  transparent.setAlpha( 0 );
1097  colorGrad.setColorAt( 0, transparent );
1098  QColor opaque = QColor( mCurrentColor );
1099  opaque.setAlpha( 255 );
1100  colorGrad.setColorAt( 1, opaque );
1101  QBrush colorBrush = QBrush( colorGrad );
1102  painter.setBrush( colorBrush );
1103  painter.drawRect( mMargin, mMargin, width() - 2 * mMargin - 1, height() - 2 * mMargin - 1 );
1104  }
1105 
1106  if ( mOrientation == QgsColorRampWidget::Horizontal )
1107  {
1108  //draw marker triangles for horizontal ramps
1109  painter.setRenderHint( QPainter::Antialiasing );
1110  painter.setBrush( QBrush( Qt::black ) );
1111  painter.setPen( Qt::NoPen );
1112  painter.translate( mMargin + ( width() - 2 * mMargin ) * ( double )componentValue() / componentRange(), mMargin - 1 );
1113  painter.drawPolygon( mTopTriangle );
1114  painter.translate( 0, height() - mMargin - 2 );
1115  painter.setBrush( QBrush( Qt::white ) );
1116  painter.drawPolygon( mBottomTriangle );
1117  painter.end();
1118  }
1119  else
1120  {
1121  //draw cross lines for vertical ramps
1122  int ypos = mMargin + ( height() - 2 * mMargin - 1 ) - ( height() - 2 * mMargin - 1 ) * ( double )componentValue() / componentRange();
1123  painter.setBrush( Qt::white );
1124  painter.setPen( Qt::NoPen );
1125  painter.drawRect( mMargin, ypos - 1, width() - 2 * mMargin - 1, 3 );
1126  painter.setPen( Qt::black );
1127  painter.drawLine( mMargin, ypos, width() - mMargin - 1, ypos );
1128  }
1129 }
1130 
1132 {
1133  mOrientation = orientation;
1134  if ( orientation == QgsColorRampWidget::Horizontal )
1135  {
1136  //horizontal
1137  setSizePolicy( QSizePolicy::MinimumExpanding, QSizePolicy::Fixed );
1138  }
1139  else
1140  {
1141  //vertical
1142  setSizePolicy( QSizePolicy::Fixed, QSizePolicy::MinimumExpanding );
1143  }
1144  updateGeometry();
1145 }
1146 
1148 {
1149  if ( margin == mMargin )
1150  {
1151  return;
1152  }
1153  mMargin = margin;
1154  update();
1155 }
1156 
1158 {
1159  if ( showFrame == mShowFrame )
1160  {
1161  return;
1162  }
1163  mShowFrame = showFrame;
1164  update();
1165 }
1166 
1167 void QgsColorRampWidget::setMarkerSize( const int markerSize )
1168 {
1169  //create triangle polygons
1170  mTopTriangle << QPoint( -markerSize, 0 ) << QPoint( markerSize, 0 ) << QPoint( 0, markerSize );
1171  mBottomTriangle << QPoint( -markerSize, 0 ) << QPoint( markerSize, 0 ) << QPoint( 0, -markerSize );
1172  update();
1173 }
1174 
1176 {
1177  setColorFromPoint( event->posF() );
1179 }
1180 
1182 {
1183  int oldValue = componentValue();
1184 
1185  if ( event->delta() > 0 )
1186  {
1188  }
1189  else
1190  {
1192  }
1193 
1194  if ( componentValue() != oldValue )
1195  {
1196  //value has changed
1197  emit colorChanged( mCurrentColor );
1198  emit valueChanged( componentValue() );
1199  }
1200 }
1201 
1203 {
1204  setColorFromPoint( event->posF() );
1205 }
1206 
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  {
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 = qMax( 0, qMin( 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  , mRampWidget( nullptr )
1285  , mSpinBox( nullptr )
1286 {
1287  QHBoxLayout* hLayout = new QHBoxLayout();
1288  hLayout->setMargin( 0 );
1289  hLayout->setSpacing( 5 );
1290 
1291  mRampWidget = new QgsColorRampWidget( nullptr, component );
1292  mRampWidget->setColor( mCurrentColor );
1293  hLayout->addWidget( mRampWidget, 1 );
1294 
1295  mSpinBox = new QSpinBox();
1296  //set spinbox to a reasonable width
1297  int largestCharWidth = mSpinBox->fontMetrics().width( "888%" );
1298  mSpinBox->setMinimumWidth( largestCharWidth + 35 );
1299  mSpinBox->setMinimum( 0 );
1300  mSpinBox->setMaximum( convertRealToDisplay( componentRange() ) );
1301  mSpinBox->setValue( convertRealToDisplay( componentValue() ) );
1302  if ( component == QgsColorWidget::Hue )
1303  {
1304  //degrees suffix for hue
1305  mSpinBox->setSuffix( QChar( 176 ) );
1306  }
1307  else if ( component == QgsColorWidget::Saturation || component == QgsColorWidget::Value || component == QgsColorWidget::Alpha )
1308  {
1309  mSpinBox->setSuffix( tr( "%" ) );
1310  }
1311  hLayout->addWidget( mSpinBox );
1312  setLayout( hLayout );
1313 
1314  connect( mRampWidget, SIGNAL( valueChanged( int ) ), this, SLOT( rampChanged( int ) ) );
1315  connect( mRampWidget, SIGNAL( colorChanged( const QColor ) ), this, SLOT( rampColorChanged( const QColor ) ) );
1316  connect( mSpinBox, SIGNAL( valueChanged( int ) ), this, SLOT( spinChanged( int ) ) );
1317 }
1318 
1320 {
1321 }
1322 
1324 {
1325  QgsColorWidget::setComponent( component );
1326  mRampWidget->setComponent( component );
1327  mSpinBox->setMaximum( convertRealToDisplay( componentRange() ) );
1328  if ( component == QgsColorWidget::Hue )
1329  {
1330  //degrees suffix for hue
1331  mSpinBox->setSuffix( QChar( 176 ) );
1332  }
1333  else if ( component == QgsColorWidget::Saturation || component == QgsColorWidget::Value || component == QgsColorWidget::Alpha )
1334  {
1335  //saturation, value and alpha are in %
1336  mSpinBox->setSuffix( tr( "%" ) );
1337  }
1338  else
1339  {
1340  //clear suffix
1341  mSpinBox->setSuffix( QString() );
1342  }
1343 }
1344 
1346 {
1348  mRampWidget->blockSignals( true );
1349  mRampWidget->setComponentValue( value );
1350  mRampWidget->blockSignals( false );
1351  mSpinBox->blockSignals( true );
1352  mSpinBox->setValue( convertRealToDisplay( value ) );
1353  mSpinBox->blockSignals( false );
1354 }
1355 
1356 void QgsColorSliderWidget::setColor( const QColor &color, bool emitSignals )
1357 {
1358  QgsColorWidget::setColor( color, emitSignals );
1359  mRampWidget->setColor( color );
1360  mSpinBox->blockSignals( true );
1361  mSpinBox->setValue( convertRealToDisplay( componentValue() ) );
1362  mSpinBox->blockSignals( false );
1363 }
1364 
1365 void QgsColorSliderWidget::rampColorChanged( const QColor &color )
1366 {
1367  emit colorChanged( color );
1368 }
1369 
1370 void QgsColorSliderWidget::spinChanged( int value )
1371 {
1372  int convertedValue = convertDisplayToReal( value );
1373  QgsColorWidget::setComponentValue( convertedValue );
1374  mRampWidget->setComponentValue( convertedValue );
1375  emit colorChanged( mCurrentColor );
1376 }
1377 
1378 void QgsColorSliderWidget::rampChanged( int value )
1379 {
1380  mSpinBox->blockSignals( true );
1381  mSpinBox->setValue( convertRealToDisplay( value ) );
1382  mSpinBox->blockSignals( false );
1383 }
1384 
1385 
1386 int QgsColorSliderWidget::convertRealToDisplay( const int realValue ) const
1387 {
1388  //scale saturation, value or alpha to 0->100 range. This makes more sense for users
1389  //for whom "255" is a totally arbitrary value!
1391  {
1392  return qRound( 100.0 * realValue / 255.0 );
1393  }
1394 
1395  //leave all other values intact
1396  return realValue;
1397 }
1398 
1399 int QgsColorSliderWidget::convertDisplayToReal( const int displayValue ) const
1400 {
1401  //scale saturation, value or alpha from 0->100 range (see note in convertRealToDisplay)
1403  {
1404  return qRound( 255.0 * displayValue / 100.0 );
1405  }
1406 
1407  //leave all other values intact
1408  return displayValue;
1409 }
1410 
1411 //
1412 // QgsColorTextWidget
1413 //
1414 
1416  : QgsColorWidget( parent )
1417  , mLineEdit( nullptr )
1418  , mMenuButton( nullptr )
1419  , mFormat( QgsColorTextWidget::HexRgb )
1420 {
1421  QHBoxLayout* hLayout = new QHBoxLayout();
1422  hLayout->setMargin( 0 );
1423  hLayout->setSpacing( 0 );
1424 
1425  mLineEdit = new QLineEdit( nullptr );
1426  hLayout->addWidget( mLineEdit );
1427 
1428  mMenuButton = new QToolButton( mLineEdit );
1429  mMenuButton->setIcon( QgsApplication::getThemeIcon( "/mIconDropDownMenu.svg" ) );
1430  mMenuButton->setCursor( Qt::ArrowCursor );
1431  mMenuButton->setFocusPolicy( Qt::NoFocus );
1432  mMenuButton->setStyleSheet( "QToolButton { border: none; padding: 0px; }" );
1433 
1434  setLayout( hLayout );
1435 
1436  int frameWidth = mLineEdit->style()->pixelMetric( QStyle::PM_DefaultFrameWidth );
1437  mLineEdit->setStyleSheet( QString( "QLineEdit { padding-right: %1px; } " )
1438  .arg( mMenuButton->sizeHint().width() + frameWidth + 1 ) );
1439 
1440  connect( mLineEdit, SIGNAL( editingFinished() ), this, SLOT( textChanged() ) );
1441  connect( mMenuButton, SIGNAL( clicked() ), this, SLOT( showMenu() ) );
1442 
1443  //restore format setting
1444  QSettings settings;
1445  mFormat = ( ColorTextFormat )settings.value( "/ColorWidgets/textWidgetFormat", 0 ).toInt();
1446 
1447  updateText();
1448 }
1449 
1451 {
1452 
1453 }
1454 
1455 void QgsColorTextWidget::setColor( const QColor &color, const bool emitSignals )
1456 {
1457  QgsColorWidget::setColor( color, emitSignals );
1458  updateText();
1459 }
1460 
1462 {
1463  Q_UNUSED( event );
1464  QSize sz = mMenuButton->sizeHint();
1465  int frameWidth = style()->pixelMetric( QStyle::PM_DefaultFrameWidth );
1466  mMenuButton->move( mLineEdit->rect().right() - frameWidth - sz.width(),
1467  ( mLineEdit->rect().bottom() + 1 - sz.height() ) / 2 );
1468 }
1469 
1470 void QgsColorTextWidget::updateText()
1471 {
1472  switch ( mFormat )
1473  {
1474  case HexRgb:
1475  mLineEdit->setText( mCurrentColor.name() );
1476  break;
1477  case HexRgbA:
1478  mLineEdit->setText( mCurrentColor.name() + QString( "%1" ).arg( mCurrentColor.alpha(), 2, 16, QChar( '0' ) ) );
1479  break;
1480  case Rgb:
1481  mLineEdit->setText( QString( tr( "rgb( %1, %2, %3 )" ) ).arg( mCurrentColor.red() ).arg( mCurrentColor.green() ).arg( mCurrentColor.blue() ) );
1482  break;
1483  case Rgba:
1484  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 ) ) );
1485  break;
1486  }
1487 }
1488 
1489 void QgsColorTextWidget::textChanged()
1490 {
1491  QString testString = mLineEdit->text();
1492  bool containsAlpha;
1493  QColor color = QgsSymbolLayerV2Utils::parseColorWithAlpha( testString, containsAlpha );
1494  if ( !color.isValid() )
1495  {
1496  //bad color string
1497  updateText();
1498  return;
1499  }
1500 
1501  //good color string
1502  if ( color != mCurrentColor )
1503  {
1504  //retain alpha if no explicit alpha set
1505  if ( !containsAlpha )
1506  {
1507  color.setAlpha( mCurrentColor.alpha() );
1508  }
1509  //color has changed
1510  mCurrentColor = color;
1511  emit colorChanged( mCurrentColor );
1512  }
1513  updateText();
1514 }
1515 
1516 void QgsColorTextWidget::showMenu()
1517 {
1518  QMenu colorContextMenu;
1519 
1520  QAction* hexRgbAction = new QAction( tr( "#RRGGBB" ), nullptr );
1521  colorContextMenu.addAction( hexRgbAction );
1522  QAction* hexRgbaAction = new QAction( tr( "#RRGGBBAA" ), nullptr );
1523  colorContextMenu.addAction( hexRgbaAction );
1524  QAction* rgbAction = new QAction( tr( "rgb( r, g, b )" ), nullptr );
1525  colorContextMenu.addAction( rgbAction );
1526  QAction* rgbaAction = new QAction( tr( "rgba( r, g, b, a )" ), nullptr );
1527  colorContextMenu.addAction( rgbaAction );
1528 
1529  QAction* selectedAction = colorContextMenu.exec( QCursor::pos() );
1530  if ( selectedAction == hexRgbAction )
1531  {
1532  mFormat = QgsColorTextWidget::HexRgb;
1533  }
1534  else if ( selectedAction == hexRgbaAction )
1535  {
1536  mFormat = QgsColorTextWidget::HexRgbA;
1537  }
1538  else if ( selectedAction == rgbAction )
1539  {
1540  mFormat = QgsColorTextWidget::Rgb;
1541  }
1542  else if ( selectedAction == rgbaAction )
1543  {
1544  mFormat = QgsColorTextWidget::Rgba;
1545  }
1546 
1547  //save format setting
1548  QSettings settings;
1549  settings.setValue( "/ColorWidgets/textWidgetFormat", ( int )mFormat );
1550 
1551  updateText();
1552 }
1553 
1554 
1555 //
1556 // QgsColorPreviewWidget
1557 //
1558 
1560  : QgsColorWidget( parent )
1561  , mColor2( QColor() )
1562 {
1563 
1564 }
1565 
1567 {
1568 
1569 }
1570 
1571 void QgsColorPreviewWidget::drawColor( const QColor &color, QRect rect, QPainter& painter )
1572 {
1573  painter.setPen( Qt::NoPen );
1574  //if color has an alpha, start with a checkboard pattern
1575  if ( color.alpha() < 255 )
1576  {
1577  QBrush checkBrush = QBrush( transparentBackground() );
1578  painter.setBrush( checkBrush );
1579  painter.drawRect( rect );
1580 
1581  //draw half of widget showing solid color, the other half showing color with alpha
1582 
1583  //ensure at least a 1px overlap to avoid artifacts
1584  QBrush colorBrush = QBrush( color );
1585  painter.setBrush( colorBrush );
1586  painter.drawRect( floor( rect.width() / 2.0 ) + rect.left(), rect.top(), rect.width() - floor( rect.width() / 2.0 ), rect.height() );
1587 
1588  QColor opaqueColor = QColor( color );
1589  opaqueColor.setAlpha( 255 );
1590  QBrush opaqueBrush = QBrush( opaqueColor );
1591  painter.setBrush( opaqueBrush );
1592  painter.drawRect( rect.left(), rect.top(), ceil( rect.width() / 2.0 ), rect.height() );
1593  }
1594  else
1595  {
1596  //no alpha component, just draw a solid rectangle
1597  QBrush brush = QBrush( color );
1598  painter.setBrush( brush );
1599  painter.drawRect( rect );
1600  }
1601 }
1602 
1604 {
1605  Q_UNUSED( event );
1606  QPainter painter( this );
1607 
1608  if ( mColor2.isValid() )
1609  {
1610  //drawing with two color sections
1611  int verticalSplit = qRound( height() / 2.0 );
1612  drawColor( mCurrentColor, QRect( 0, 0, width(), verticalSplit ), painter );
1613  drawColor( mColor2, QRect( 0, verticalSplit, width(), height() - verticalSplit ), painter );
1614  }
1615  else if ( mCurrentColor.isValid() )
1616  {
1617  drawColor( mCurrentColor, QRect( 0, 0, width(), height() ), painter );
1618  }
1619 
1620  painter.end();
1621 }
1622 
1624 {
1625  return QSize( 200, 150 );
1626 }
1627 
1629 {
1630  if ( color == mColor2 )
1631  {
1632  return;
1633  }
1634  mColor2 = color;
1635  update();
1636 }
1637 
1639 {
1640  if ( e->button() == Qt::LeftButton )
1641  {
1642  mDragStartPosition = e->pos();
1643  }
1645 }
1646 
1648 {
1649  if (( e->pos() - mDragStartPosition ).manhattanLength() >= QApplication::startDragDistance() )
1650  {
1651  //mouse moved, so a drag. nothing to do here
1653  return;
1654  }
1655 
1656  //work out which color was clicked
1657  QColor clickedColor = mCurrentColor;
1658  if ( mColor2.isValid() )
1659  {
1660  //two color sections, check if dragged color was the second color
1661  int verticalSplit = qRound( height() / 2.0 );
1662  if ( mDragStartPosition.y() >= verticalSplit )
1663  {
1664  clickedColor = mColor2;
1665  }
1666  }
1667  emit colorChanged( clickedColor );
1668 
1669 }
1670 
1672 {
1673  //handle dragging colors from button
1674 
1675  if ( !( e->buttons() & Qt::LeftButton ) )
1676  {
1677  //left button not depressed, so not a drag
1679  return;
1680  }
1681 
1682  if (( e->pos() - mDragStartPosition ).manhattanLength() < QApplication::startDragDistance() )
1683  {
1684  //mouse not moved, so not a drag
1686  return;
1687  }
1688 
1689  //user is dragging color
1690 
1691  //work out which color is being dragged
1692  QColor dragColor = mCurrentColor;
1693  if ( mColor2.isValid() )
1694  {
1695  //two color sections, check if dragged color was the second color
1696  int verticalSplit = qRound( height() / 2.0 );
1697  if ( mDragStartPosition.y() >= verticalSplit )
1698  {
1699  dragColor = mColor2;
1700  }
1701  }
1702 
1703  QDrag *drag = new QDrag( this );
1705  drag->setPixmap( createDragIcon( dragColor ) );
1706  drag->exec( Qt::CopyAction );
1707 }
1708 
1709 
1710 //
1711 // QgsColorWidgetAction
1712 //
1713 
1715  : QWidgetAction( parent )
1716  , mMenu( menu )
1717  , mColorWidget( colorWidget )
1718  , mSuppressRecurse( false )
1719  , mDismissOnColorSelection( true )
1720 {
1721  setDefaultWidget( mColorWidget );
1722  connect( mColorWidget, SIGNAL( colorChanged( QColor ) ), this, SLOT( setColor( QColor ) ) );
1723 
1724  connect( this, SIGNAL( hovered() ), this, SLOT( onHover() ) );
1725  connect( mColorWidget, SIGNAL( hovered() ), this, SLOT( onHover() ) );
1726 }
1727 
1729 {
1730 
1731 }
1732 
1733 void QgsColorWidgetAction::onHover()
1734 {
1735  //see https://bugreports.qt-project.org/browse/QTBUG-10427?focusedCommentId=185610&page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel#comment-185610
1736  if ( mSuppressRecurse )
1737  {
1738  return;
1739  }
1740 
1741  if ( mMenu )
1742  {
1743  mSuppressRecurse = true;
1744  mMenu->setActiveAction( this );
1745  mSuppressRecurse = false;
1746  }
1747 }
1748 
1749 void QgsColorWidgetAction::setColor( const QColor& color )
1750 {
1751  emit colorChanged( color );
1752  QAction::trigger();
1753  if ( mMenu && mDismissOnColorSelection )
1754  {
1755  mMenu->hide();
1756  }
1757 }
uchar * scanLine(int i)
void paintEvent(QPaintEvent *event) override
void setStyleSheet(const QString &styleSheet)
void setMinimum(int min)
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
int width() const
bool end()
void setCursor(const QCursor &)
virtual QSize sizeHint() const
const QMimeData * mimeData() const
static QMimeData * colorToMimeData(const QColor &color)
Creates mime data from a color.
void fillRect(const QRectF &rectangle, const QBrush &brush)
void setCompositionMode(CompositionMode mode)
qreal alphaF() const
void setRenderHint(RenderHint hint, bool on)
void hovered()
Emitted when mouse hovers over widget.
void updateGeometry()
void fill(const QColor &color)
void setSuffix(const QString &suffix)
void setMimeData(QMimeData *data)
QgsColorSliderWidget(QWidget *parent=nullptr, const ColorComponent component=QgsColorWidget::Red)
Construct a new color slider widget.
QString name() const
void setText(const QString &)
void setFocusPolicy(Qt::FocusPolicy policy)
void setColorAt(qreal position, const QColor &color)
void valueChanged(const int value)
Emitted when the widget&#39;s color component value changes.
int value() const
int hue() const
Returns the hue for the widget.
QStyle * style() const
static QIcon getThemeIcon(const QString &theName)
Helper to get a theme icon.
virtual int pixelMetric(PixelMetric metric, const QStyleOption *option, const QWidget *widget) const=0
void setActiveAction(QAction *act)
void setPixmap(const QPixmap &pixmap)
void setMinimumWidth(int minw)
ColorComponent component() const
Returns the color component which the widget controls.
void addAction(QAction *action)
void setMarkerSize(const int markerSize)
Sets the size for drawing the triangular markers on the ramp.
static QColor colorFromMimeData(const QMimeData *data, bool &hasAlpha)
Attempts to parse mime data as a color.
int y() const
void save()
void paintEvent(QPaintEvent *event) override
void mousePressEvent(QMouseEvent *e) override
void drawPolygon(const QPointF *points, int pointCount, Qt::FillRule fillRule)
void setBlue(int blue)
void setAlpha(int alpha)
virtual void mouseReleaseEvent(QMouseEvent *event) override
virtual QSize sizeHint() const override
int height() const
bool hasFocus() const
static const QPixmap & transparentBackground()
Generates a checkboard pattern pixmap for use as a background to transparent colors.
Qt::MouseButtons buttons() const
virtual void mousePressEvent(QMouseEvent *event) override
void drawLine(const QLineF &line)
void acceptProposedAction()
void setRed(int red)
int hue() const
void setIcon(const QIcon &icon)
QString tr(const char *sourceText, const char *disambiguation, int n)
static QPixmap getThemePixmap(const QString &theName)
Helper to get a theme icon as a pixmap.
void getHsl(int *h, int *s, int *l, int *a) const
void getHsv(int *h, int *s, int *v, int *a) const
void update()
int x() const
int y() const
void trigger()
virtual void setComponentValue(const int value) override
Alters the widget&#39;s color by setting the value for the widget&#39;s color component.
QColor fromHsl(int h, int s, int l, int a)
QColor fromHsv(int h, int s, int v, int a)
virtual void setColor(const QColor &color, const bool emitSignals=false) override
Sets the color for the widget.
virtual void mousePressEvent(QMouseEvent *event) override
void initFrom(const QWidget *widget)
int width() const
Qt::DropAction exec(QFlags< Qt::DropAction > supportedActions)
QgsColorWidgetAction(QgsColorWidget *colorWidget, QMenu *menu=nullptr, QWidget *parent=nullptr)
Construct a new color widget action.
void setValue(const QString &key, const QVariant &value)
QSize size() const
void drawRect(const QRectF &rectangle)
virtual void setComponent(const ColorComponent component) override
Sets the color component which the widget controls.
qreal length() const
void addWidget(QWidget *widget, int stretch, QFlags< Qt::AlignmentFlag > alignment)
virtual void mousePressEvent(QMouseEvent *event) override
QString number(int n, int base)
qreal x() const
qreal y() const
void setGreen(int green)
QPointF p2() const
void setLayout(QLayout *layout)
A color wheel widget.
int toInt(bool *ok) const
int x() const
void fill(uint pixelValue)
void setOrientation(const Orientation orientation)
Sets the orientation for the color ramp.
int top() const
QRgb rgb() const
virtual QSize sizeHint() const override
int red() const
void setPen(const QColor &color)
int width() const
void drawEllipse(const QRectF &rectangle)
int left() const
Qt::MouseButton button() const
QPointF posF() const
void setAngle(qreal angle)
QColor color() const
Returns the current color for the widget.
virtual void setColor(const QColor &color, const bool emitSignals=false) override
void resizeEvent(QResizeEvent *event) override
#define M_PI
qreal lightnessF() const
A color ramp widget.
QgsColorWheel(QWidget *parent=nullptr)
Constructs a new color wheel widget.
QPoint pos() const
void setBrush(const QBrush &brush)
void mouseMoveEvent(QMouseEvent *e) override
void dragEnterEvent(QDragEnterEvent *e) override
virtual void wheelEvent(QWheelEvent *event) override
void hide()
void setColor(const QColor &color)
void mouseReleaseEvent(QMouseEvent *e) override
QgsColorPreviewWidget(QWidget *parent=nullptr)
Construct a new color preview widget.
void alterColor(QColor &color, const QgsColorWidget::ColorComponent component, const int newValue) const
Alters a color by modifiying the value of a specific color component.
void paintEvent(QPaintEvent *event) override
virtual void setComponent(const ColorComponent component) override
Sets the color component which the widget controls.
void setMargin(int margin)
QAction * exec()
void setSizePolicy(QSizePolicy)
int alpha() const
void setAcceptDrops(bool on)
int green() const
double ANALYSIS_EXPORT angle(Point3D *p1, Point3D *p2, Point3D *p3, Point3D *p4)
Calculates the angle between two segments (in 2 dimension, z-values are ignored)
int key() const
virtual void resizeEvent(QResizeEvent *event) override
void accept()
bool blockSignals(bool block)
const QSize & size() const
bool isNull() const
QgsColorBox(QWidget *parent=nullptr, const ColorComponent component=Value)
Construct a new color box widget.
void setHsvF(qreal h, qreal s, qreal v, qreal a)
int width(const QString &text, int len) const
int delta() const
virtual void setComponentValue(const int value)
Alters the widget&#39;s color by setting the value for the widget&#39;s color component.
void restore()
void setMaximum(int max)
void setInteriorMargin(const int margin)
Sets the margin between the edge of the widget and the ramp.
ColorComponent
Specifies the color component which the widget alters.
int blue() const
QVariant value(const QString &key, const QVariant &defaultValue) const
QgsColorWidget(QWidget *parent=nullptr, const ColorComponent component=Multiple)
Construct a new color widget.
int width() const
void drawImage(const QRectF &target, const QImage &image, const QRectF &source, QFlags< Qt::ImageConversionFlag > flags)
QFontMetrics fontMetrics() const
QPoint pos()
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...
void setWidth(int width)
void dropEvent(QDropEvent *e) override
void setDefaultWidget(QWidget *widget)
void hovered()
void setValue(int val)
virtual void mouseMoveEvent(QMouseEvent *event) override
void colorChanged(const QColor &color)
Emitted when the widget&#39;s color changes.
QgsColorTextWidget(QWidget *parent=nullptr)
Construct a new color line edit widget.
int componentRange() const
Returns the range of valid values for the color widget&#39;s component.
void getRgb(int *r, int *g, int *b, int *a) const
virtual void setColor(const QColor &color, const bool emitSignals=false) override
Sets the color for the widget.
int height() const
virtual void keyPressEvent(QKeyEvent *event)
virtual QSize sizeHint() const override
void translate(const QPointF &offset)
virtual void drawPrimitive(PrimitiveElement element, const QStyleOption *option, QPainter *painter, const QWidget *widget) const=0
qreal hslSaturationF() const
qreal angle() const
void setHsv(int h, int s, int v, int a)
Orientation orientation() const
Fetches the orientation for the color ramp.
virtual void setColor(const QColor &color, const bool emitSignals=false) override
void paintEvent(QPaintEvent *event) override
virtual ~QgsColorWidget()
int height() const
int mExplicitHue
QColor wipes the hue information when it is ambiguous (eg, for saturation = 0).
virtual void mouseMoveEvent(QMouseEvent *event) override
int hsvHue() const
int saturation() const
ColorComponent mComponent
const QPoint & pos() const
virtual void resizeEvent(QResizeEvent *event)
QgsColorRampWidget(QWidget *parent=nullptr, const ColorComponent component=QgsColorWidget::Red, const Orientation orientation=QgsColorRampWidget::Horizontal)
Construct a new color ramp widget.
qreal height() const
virtual void mouseMoveEvent(QMouseEvent *event) override
bool connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
QObject * parent() const
virtual void resizeEvent(QResizeEvent *event) override
bool begin(QPaintDevice *device)
static QPixmap createDragIcon(const QColor &color)
Create an icon for dragging colors.
QString arg(qlonglong a, int fieldWidth, int base, const QChar &fillChar) const
virtual bool event(QEvent *event)
A line edit widget which displays colors as text and accepts string representations of colors...
virtual void setColor2(const QColor &color)
Sets the second color for the widget.
void mousePressEvent(QMouseEvent *e) override
qreal width() const
void setSpacing(int spacing)
virtual ~QgsColorWheel()
int height() const
virtual QSize sizeHint() const override
virtual void setComponent(const ColorComponent component)
Sets the color component which the widget controls.
QColor fromHsvF(qreal h, qreal s, qreal v, qreal a)
int hsvSaturation() const
virtual void setColor(const QColor &color, const bool emitSignals=false)
Sets the color for the widget.
int startDragDistance()
bool isValid() const
virtual void keyPressEvent(QKeyEvent *event) override
void colorChanged(const QColor &color)
Emitted when a color has been selected from the widget.
virtual ~QgsColorBox()
int componentValue() const
Returns the current value of the widget&#39;s color component.
void mouseMoveEvent(QMouseEvent *e) override
void setShowFrame(const bool showFrame)
Sets whether the ramp should be drawn within a frame.