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