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