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