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