QGIS API Documentation 3.28.0-Firenze (ed3ad0430f)
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 //recreate images for new size
515 createImages( event->size() );
516 QgsColorWidget::resizeEvent( event );
517}
518
519void QgsColorWheel::setColorFromPos( const QPointF pos )
520{
521 const QPointF center = QPointF( width() / 2.0, height() / 2.0 );
522 //line from center to mouse position
523 const QLineF line = QLineF( center.x(), center.y(), pos.x(), pos.y() );
524
525 QColor newColor = QColor();
526
527 int h, s, l, alpha;
528 mCurrentColor.getHsl( &h, &s, &l, &alpha );
529 //override hue with explicit hue, so we don't get -1 values from QColor for hue
530 h = hue();
531
532 if ( mClickedPart == QgsColorWheel::Triangle )
533 {
534 //adapted from equations at https://github.com/timjb/colortriangle/blob/master/colortriangle.js by Tim Baumann
535
536 //position of event relative to triangle center
537 const double x = pos.x() - center.x();
538 const double y = pos.y() - center.y();
539
540 double eventAngleRadians = line.angle() * M_PI / 180.0;
541 const double hueRadians = h * M_PI / 180.0;
542 double rad0 = std::fmod( eventAngleRadians + 2.0 * M_PI - hueRadians, 2.0 * M_PI );
543 double rad1 = std::fmod( rad0, ( ( 2.0 / 3.0 ) * M_PI ) ) - ( M_PI / 3.0 );
544 const double length = mWheelImage->width() / 2.0;
545 const double triangleLength = length - mWheelThickness - 1;
546
547 const double a = 0.5 * triangleLength;
548 double b = std::tan( rad1 ) * a;
549 double r = std::sqrt( x * x + y * y );
550 const double maxR = std::sqrt( a * a + b * b );
551
552 if ( r > maxR )
553 {
554 const double dx = std::tan( rad1 ) * r;
555 double rad2 = std::atan( dx / maxR );
556 rad2 = std::min( rad2, M_PI / 3.0 );
557 rad2 = std::max( rad2, -M_PI / 3.0 );
558 eventAngleRadians += rad2 - rad1;
559 rad0 = std::fmod( eventAngleRadians + 2.0 * M_PI - hueRadians, 2.0 * M_PI );
560 rad1 = std::fmod( rad0, ( ( 2.0 / 3.0 ) * M_PI ) ) - ( M_PI / 3.0 );
561 b = std::tan( rad1 ) * a;
562 r = std::sqrt( a * a + b * b );
563 }
564
565 const double triangleSideLength = std::sqrt( 3.0 ) * triangleLength;
566 const double newL = ( ( -std::sin( rad0 ) * r ) / triangleSideLength ) + 0.5;
567 const double widthShare = 1.0 - ( std::fabs( newL - 0.5 ) * 2.0 );
568 const double newS = ( ( ( std::cos( rad0 ) * r ) + ( triangleLength / 2.0 ) ) / ( 1.5 * triangleLength ) ) / widthShare;
569 s = std::min( static_cast< int >( std::round( std::max( 0.0, newS ) * 255.0 ) ), 255 );
570 l = std::min( static_cast< int >( std::round( std::max( 0.0, newL ) * 255.0 ) ), 255 );
571 newColor = QColor::fromHsl( h, s, l );
572 //explicitly set the hue again, so that it's exact
573 newColor.setHsv( h, newColor.hsvSaturation(), newColor.value(), alpha );
574 }
575 else if ( mClickedPart == QgsColorWheel::Wheel )
576 {
577 //use hue angle
578 s = mCurrentColor.hsvSaturation();
579 const int v = mCurrentColor.value();
580 const int newHue = line.angle();
581 newColor = QColor::fromHsv( newHue, s, v, alpha );
582 //hue has changed, need to redraw triangle
583 mTriangleDirty = true;
584 }
585
586 if ( newColor.isValid() && newColor != mCurrentColor )
587 {
588 //color has changed
589 mCurrentColor = QColor( newColor );
590
591 if ( mCurrentColor.hue() >= 0 )
592 {
593 //color has a valid hue, so update the QgsColorWidget's explicit hue
595 }
596
597 update();
599 }
600}
601
602void QgsColorWheel::mouseMoveEvent( QMouseEvent *event )
603{
604 if ( mIsDragging )
605 setColorFromPos( event->pos() );
606
608}
609
610void QgsColorWheel::mousePressEvent( QMouseEvent *event )
611{
612 if ( event->button() == Qt::LeftButton )
613 {
614 mIsDragging = true;
615 //calculate where the event occurred -- on the wheel or inside the triangle?
616
617 //create a line from the widget's center to the event
618 const QLineF line = QLineF( width() / 2.0, height() / 2.0, event->pos().x(), event->pos().y() );
619
620 const double innerLength = mWheelImage->width() / 2.0 - mWheelThickness;
621 if ( line.length() < innerLength )
622 {
623 mClickedPart = QgsColorWheel::Triangle;
624 }
625 else
626 {
627 mClickedPart = QgsColorWheel::Wheel;
628 }
629 setColorFromPos( event->pos() );
630 }
631 else
632 {
634 }
635}
636
637void QgsColorWheel::mouseReleaseEvent( QMouseEvent *event )
638{
639 if ( event->button() == Qt::LeftButton )
640 {
641 mIsDragging = false;
642 mClickedPart = QgsColorWheel::None;
643 }
644 else
645 {
647 }
648}
649
650void QgsColorWheel::createWheel()
651{
652 if ( !mWheelImage )
653 {
654 return;
655 }
656
657 const int maxSize = std::min( mWheelImage->width(), mWheelImage->height() );
658 const double wheelRadius = maxSize / 2.0;
659
660 mWheelImage->fill( Qt::transparent );
661 QPainter p( mWheelImage );
662 p.setRenderHint( QPainter::Antialiasing );
663 p.setBrush( mWheelBrush );
664 p.setPen( Qt::NoPen );
665
666 //draw hue wheel as a circle
667 p.translate( wheelRadius, wheelRadius );
668 p.drawEllipse( QPointF( 0.0, 0.0 ), wheelRadius, wheelRadius );
669
670 //cut hole in center of circle to make a ring
671 p.setCompositionMode( QPainter::CompositionMode_DestinationOut );
672 p.setBrush( QBrush( Qt::black ) );
673 p.drawEllipse( QPointF( 0.0, 0.0 ), wheelRadius - mWheelThickness, wheelRadius - mWheelThickness );
674 p.end();
675
676 mWheelDirty = false;
677}
678
679void QgsColorWheel::createTriangle()
680{
681 if ( !mWheelImage || !mTriangleImage )
682 {
683 return;
684 }
685
686 const QPointF center = QPointF( mWheelImage->width() / 2.0, mWheelImage->height() / 2.0 );
687 mTriangleImage->fill( Qt::transparent );
688
689 QPainter imagePainter( mTriangleImage );
690 imagePainter.setRenderHint( QPainter::Antialiasing );
691
692 const int angle = hue();
693 const double wheelRadius = mWheelImage->width() / 2.0;
694 const double triangleRadius = wheelRadius - mWheelThickness - 1;
695
696 //pure version of hue (at full saturation and value)
697 const QColor pureColor = QColor::fromHsv( angle, 255, 255 );
698 //create copy of color but with 0 alpha
699 QColor alphaColor = QColor( pureColor );
700 alphaColor.setAlpha( 0 );
701
702 //some rather ugly shortcuts to obtain corners and midpoints of triangle
703 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 ) );
704 QLineF line2 = QLineF( center.x(), center.y(), center.x() + triangleRadius, center.y() );
705 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 ) );
706 QLineF line4 = QLineF( center.x(), center.y(), center.x() - triangleRadius * std::cos( M_PI / 3.0 ), center.y() );
707 QLineF line5 = QLineF( center.x(), center.y(), ( line2.p2().x() + line1.p2().x() ) / 2.0, ( line2.p2().y() + line1.p2().y() ) / 2.0 );
708 line1.setAngle( line1.angle() + angle );
709 line2.setAngle( line2.angle() + angle );
710 line3.setAngle( line3.angle() + angle );
711 line4.setAngle( line4.angle() + angle );
712 line5.setAngle( line5.angle() + angle );
713 const QPointF p1 = line1.p2();
714 const QPointF p2 = line2.p2();
715 const QPointF p3 = line3.p2();
716 const QPointF p4 = line4.p2();
717 const QPointF p5 = line5.p2();
718
719 //inspired by Tim Baumann's work at https://github.com/timjb/colortriangle/blob/master/colortriangle.js
720 QLinearGradient colorGrad = QLinearGradient( p4.x(), p4.y(), p2.x(), p2.y() );
721 colorGrad.setColorAt( 0, alphaColor );
722 colorGrad.setColorAt( 1, pureColor );
723 QLinearGradient whiteGrad = QLinearGradient( p3.x(), p3.y(), p5.x(), p5.y() );
724 whiteGrad.setColorAt( 0, QColor( 255, 255, 255, 255 ) );
725 whiteGrad.setColorAt( 1, QColor( 255, 255, 255, 0 ) );
726
727 QPolygonF triangle;
728 triangle << p2 << p1 << p3 << p2;
729 imagePainter.setPen( Qt::NoPen );
730 //start with a black triangle
731 imagePainter.setBrush( QBrush( Qt::black ) );
732 imagePainter.drawPolygon( triangle );
733 //draw a gradient from transparent to the pure color at the triangle's tip
734 imagePainter.setBrush( QBrush( colorGrad ) );
735 imagePainter.drawPolygon( triangle );
736 //draw a white gradient using additive composition mode
737 imagePainter.setCompositionMode( QPainter::CompositionMode_Plus );
738 imagePainter.setBrush( QBrush( whiteGrad ) );
739 imagePainter.drawPolygon( triangle );
740
741 //above process results in some small artifacts on the edge of the triangle. Let's clear these up
742 //use source composition mode and draw an outline using a transparent pen
743 //this clears the edge pixels and leaves a nice smooth image
744 imagePainter.setCompositionMode( QPainter::CompositionMode_Source );
745 imagePainter.setBrush( Qt::NoBrush );
746 imagePainter.setPen( QPen( Qt::transparent ) );
747 imagePainter.drawPolygon( triangle );
748
749 imagePainter.end();
750 mTriangleDirty = false;
751}
752
753
754
755//
756// QgsColorBox
757//
758
759QgsColorBox::QgsColorBox( QWidget *parent, const ColorComponent component )
760 : QgsColorWidget( parent, component )
761{
762 setFocusPolicy( Qt::StrongFocus );
763 setSizePolicy( QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding );
764
765 mBoxImage = new QImage( width() - mMargin * 2, height() - mMargin * 2, QImage::Format_RGB32 );
766}
767
769{
770 delete mBoxImage;
771}
772
774{
775 const int size = Qgis::UI_SCALE_FACTOR * fontMetrics().horizontalAdvance( 'X' ) * 22;
776 return QSize( size, size );
777}
778
779void QgsColorBox::paintEvent( QPaintEvent *event )
780{
781 Q_UNUSED( event )
782 QPainter painter( this );
783
784 QStyleOptionFrame option;
785 option.initFrom( this );
786 option.state = hasFocus() ? QStyle::State_Active : QStyle::State_None;
787 style()->drawPrimitive( QStyle::PE_Frame, &option, &painter );
788
789 if ( mDirty )
790 {
791 createBox();
792 }
793
794 //draw background image
795 painter.drawImage( QPoint( mMargin, mMargin ), *mBoxImage );
796
797 //draw cross lines
798 const double xPos = mMargin + ( width() - 2 * mMargin - 1 ) * static_cast<double>( xComponentValue() ) / static_cast<double>( valueRangeX() );
799 const double yPos = mMargin + ( height() - 2 * mMargin - 1 ) - ( height() - 2 * mMargin - 1 ) * static_cast<double>( yComponentValue() ) / static_cast<double>( valueRangeY() );
800
801 painter.setBrush( Qt::white );
802 painter.setPen( Qt::NoPen );
803
804 painter.drawRect( xPos - 1, mMargin, 3, height() - 2 * mMargin - 1 );
805 painter.drawRect( mMargin, yPos - 1, width() - 2 * mMargin - 1, 3 );
806 painter.setPen( Qt::black );
807 painter.drawLine( xPos, mMargin, xPos, height() - mMargin - 1 );
808 painter.drawLine( mMargin, yPos, width() - mMargin - 1, yPos );
809
810 painter.end();
811}
812
814{
815 if ( component != mComponent )
816 {
817 //need to redraw
818 mDirty = true;
819 }
821}
822
823void QgsColorBox::setColor( const QColor &color, const bool emitSignals )
824{
825 //check if we need to redraw the box image
826 if ( mComponent == QgsColorWidget::Red && mCurrentColor.red() != color.red() )
827 {
828 mDirty = true;
829 }
830 else if ( mComponent == QgsColorWidget::Green && mCurrentColor.green() != color.green() )
831 {
832 mDirty = true;
833 }
834 else if ( mComponent == QgsColorWidget::Blue && mCurrentColor.blue() != color.blue() )
835 {
836 mDirty = true;
837 }
838 else if ( mComponent == QgsColorWidget::Hue && color.hsvHue() >= 0 && hue() != color.hsvHue() )
839 {
840 mDirty = true;
841 }
842 else if ( mComponent == QgsColorWidget::Saturation && mCurrentColor.hsvSaturation() != color.hsvSaturation() )
843 {
844 mDirty = true;
845 }
846 else if ( mComponent == QgsColorWidget::Value && mCurrentColor.value() != color.value() )
847 {
848 mDirty = true;
849 }
850 QgsColorWidget::setColor( color, emitSignals );
851}
852
853void QgsColorBox::resizeEvent( QResizeEvent *event )
854{
855 mDirty = true;
856 delete mBoxImage;
857 mBoxImage = new QImage( event->size().width() - mMargin * 2, event->size().height() - mMargin * 2, QImage::Format_RGB32 );
858 QgsColorWidget::resizeEvent( event );
859}
860
861void QgsColorBox::mouseMoveEvent( QMouseEvent *event )
862{
863 if ( mIsDragging )
864 {
865 setColorFromPoint( event->pos() );
866 }
868}
869
870void QgsColorBox::mousePressEvent( QMouseEvent *event )
871{
872 if ( event->button() == Qt::LeftButton )
873 {
874 mIsDragging = true;
875 setColorFromPoint( event->pos() );
876 }
877 else
878 {
880 }
881}
882
883void QgsColorBox::mouseReleaseEvent( QMouseEvent *event )
884{
885 if ( event->button() == Qt::LeftButton )
886 {
887 mIsDragging = false;
888 }
889 else
890 {
892 }
893}
894
895void QgsColorBox::createBox()
896{
897 const int maxValueX = mBoxImage->width();
898 const int maxValueY = mBoxImage->height();
899
900 //create a temporary color object
901 QColor currentColor = QColor( mCurrentColor );
902 int colorComponentValue;
903
904 for ( int y = 0; y < maxValueY; ++y )
905 {
906 QRgb *scanLine = ( QRgb * )mBoxImage->scanLine( y );
907
908 colorComponentValue = int( valueRangeY() - valueRangeY() * ( double( y ) / maxValueY ) );
909 alterColor( currentColor, yComponent(), colorComponentValue );
910 for ( int x = 0; x < maxValueX; ++x )
911 {
912 colorComponentValue = int( valueRangeX() * ( double( x ) / maxValueX ) );
913 alterColor( currentColor, xComponent(), colorComponentValue );
914 scanLine[x] = currentColor.rgb();
915 }
916 }
917 mDirty = false;
918}
919
920int QgsColorBox::valueRangeX() const
921{
922 return componentRange( xComponent() );
923}
924
925int QgsColorBox::valueRangeY() const
926{
927 return componentRange( yComponent() );
928}
929
930QgsColorWidget::ColorComponent QgsColorBox::yComponent() const
931{
932 switch ( mComponent )
933 {
938 return QgsColorWidget::Red;
943 return QgsColorWidget::Hue;
944 default:
945 //should not occur
946 return QgsColorWidget::Red;
947 }
948}
949
950int QgsColorBox::yComponentValue() const
951{
952 return componentValue( yComponent() );
953}
954
955QgsColorWidget::ColorComponent QgsColorBox::xComponent() const
956{
957 switch ( mComponent )
958 {
969 default:
970 //should not occur
971 return QgsColorWidget::Red;
972 }
973}
974
975int QgsColorBox::xComponentValue() const
976{
977 return componentValue( xComponent() );
978}
979
980void QgsColorBox::setColorFromPoint( QPoint point )
981{
982 int valX = valueRangeX() * ( point.x() - mMargin ) / ( width() - 2 * mMargin - 1 );
983 valX = std::min( std::max( valX, 0 ), valueRangeX() );
984
985 int valY = valueRangeY() - valueRangeY() * ( point.y() - mMargin ) / ( height() - 2 * mMargin - 1 );
986 valY = std::min( std::max( valY, 0 ), valueRangeY() );
987
988 QColor color = QColor( mCurrentColor );
989 alterColor( color, xComponent(), valX );
990 alterColor( color, yComponent(), valY );
991
992 if ( color == mCurrentColor )
993 {
994 return;
995 }
996
997 if ( color.hue() >= 0 )
998 {
999 mExplicitHue = color.hue();
1000 }
1001
1003 update();
1004 emit colorChanged( color );
1005}
1006
1007
1008//
1009// QgsColorRampWidget
1010//
1011
1013 const QgsColorWidget::ColorComponent component,
1014 const Orientation orientation )
1015 : QgsColorWidget( parent, component )
1016{
1017 setFocusPolicy( Qt::StrongFocus );
1019
1020 //create triangle polygons
1021 setMarkerSize( 5 );
1022}
1023
1025{
1026 if ( mOrientation == QgsColorRampWidget::Horizontal )
1027 {
1028 //horizontal
1029 return QSize( Qgis::UI_SCALE_FACTOR * fontMetrics().horizontalAdvance( 'X' ) * 22, Qgis::UI_SCALE_FACTOR * fontMetrics().height() * 1.3 );
1030 }
1031 else
1032 {
1033 //vertical
1034 return QSize( Qgis::UI_SCALE_FACTOR * fontMetrics().height() * 1.3, Qgis::UI_SCALE_FACTOR * fontMetrics().horizontalAdvance( 'X' ) * 22 );
1035 }
1036}
1037
1038void QgsColorRampWidget::paintEvent( QPaintEvent *event )
1039{
1040 Q_UNUSED( event )
1041 QPainter painter( this );
1042
1043 if ( mShowFrame )
1044 {
1045 //draw frame
1046 QStyleOptionFrame option;
1047 option.initFrom( this );
1048 option.state = hasFocus() ? QStyle::State_KeyboardFocusChange : QStyle::State_None;
1049 style()->drawPrimitive( QStyle::PE_Frame, &option, &painter );
1050 }
1051
1052 if ( hasFocus() )
1053 {
1054 //draw focus rect
1055 QStyleOptionFocusRect option;
1056 option.initFrom( this );
1057 option.state = QStyle::State_KeyboardFocusChange;
1058 style()->drawPrimitive( QStyle::PE_FrameFocusRect, &option, &painter );
1059 }
1060
1062 {
1063 const int maxValue = ( mOrientation == QgsColorRampWidget::Horizontal ? width() : height() ) - 1 - 2 * mMargin;
1064 QColor color = QColor( mCurrentColor );
1065 color.setAlpha( 255 );
1066 QPen pen;
1067 // we need to set pen width to 1,
1068 // since on retina displays
1069 // pen.setWidth(0) <=> pen.width = 0.5
1070 // see https://github.com/qgis/QGIS/issues/23900
1071 pen.setWidth( 1 );
1072 painter.setPen( pen );
1073 painter.setBrush( Qt::NoBrush );
1074
1075 //draw background ramp
1076 for ( int c = 0; c <= maxValue; ++c )
1077 {
1078 int colorVal = static_cast<int>( componentRange() * static_cast<double>( c ) / maxValue );
1079 //vertical sliders are reversed
1080 if ( mOrientation == QgsColorRampWidget::Vertical )
1081 {
1082 colorVal = componentRange() - colorVal;
1083 }
1084 alterColor( color, mComponent, colorVal );
1085 if ( color.hue() < 0 )
1086 {
1087 color.setHsv( hue(), color.saturation(), color.value() );
1088 }
1089 pen.setColor( color );
1090 painter.setPen( pen );
1091 if ( mOrientation == QgsColorRampWidget::Horizontal )
1092 {
1093 //horizontal
1094 painter.drawLine( QLineF( c + mMargin, mMargin, c + mMargin, height() - mMargin - 1 ) );
1095 }
1096 else
1097 {
1098 //vertical
1099 painter.drawLine( QLineF( mMargin, c + mMargin, width() - mMargin - 1, c + mMargin ) );
1100 }
1101 }
1102 }
1103 else
1104 {
1105 //alpha ramps are drawn differently
1106 //start with the checkboard pattern
1107 const QBrush checkBrush = QBrush( transparentBackground() );
1108 painter.setBrush( checkBrush );
1109 painter.setPen( Qt::NoPen );
1110 painter.drawRect( QRectF( mMargin, mMargin, width() - 2 * mMargin - 1, height() - 2 * mMargin - 1 ) );
1111 QLinearGradient colorGrad;
1112 if ( mOrientation == QgsColorRampWidget::Horizontal )
1113 {
1114 //horizontal
1115 colorGrad = QLinearGradient( mMargin, 0, width() - mMargin - 1, 0 );
1116 }
1117 else
1118 {
1119 //vertical
1120 colorGrad = QLinearGradient( 0, mMargin, 0, height() - mMargin - 1 );
1121 }
1122 QColor transparent = QColor( mCurrentColor );
1123 transparent.setAlpha( 0 );
1124 colorGrad.setColorAt( 0, transparent );
1125 QColor opaque = QColor( mCurrentColor );
1126 opaque.setAlpha( 255 );
1127 colorGrad.setColorAt( 1, opaque );
1128 const QBrush colorBrush = QBrush( colorGrad );
1129 painter.setBrush( colorBrush );
1130 painter.drawRect( QRectF( mMargin, mMargin, width() - 2 * mMargin - 1, height() - 2 * mMargin - 1 ) );
1131 }
1132
1133 if ( mOrientation == QgsColorRampWidget::Horizontal )
1134 {
1135 //draw marker triangles for horizontal ramps
1136 painter.setRenderHint( QPainter::Antialiasing );
1137 painter.setBrush( QBrush( Qt::black ) );
1138 painter.setPen( Qt::NoPen );
1139 painter.translate( mMargin + ( width() - 2 * mMargin ) * static_cast<double>( componentValue() ) / componentRange(), mMargin - 1 );
1140 painter.drawPolygon( mTopTriangle );
1141 painter.translate( 0, height() - mMargin - 2 );
1142 painter.setBrush( QBrush( Qt::white ) );
1143 painter.drawPolygon( mBottomTriangle );
1144 painter.end();
1145 }
1146 else
1147 {
1148 //draw cross lines for vertical ramps
1149 const double ypos = mMargin + ( height() - 2 * mMargin - 1 ) - ( height() - 2 * mMargin - 1 ) * static_cast<double>( componentValue() ) / componentRange();
1150 painter.setBrush( Qt::white );
1151 painter.setPen( Qt::NoPen );
1152 painter.drawRect( QRectF( mMargin, ypos - 1, width() - 2 * mMargin - 1, 3 ) );
1153 painter.setPen( Qt::black );
1154 painter.drawLine( QLineF( mMargin, ypos, width() - mMargin - 1, ypos ) );
1155 }
1156}
1157
1159{
1160 mOrientation = orientation;
1162 {
1163 //horizontal
1164 setSizePolicy( QSizePolicy::MinimumExpanding, QSizePolicy::Fixed );
1165 }
1166 else
1167 {
1168 //vertical
1169 setSizePolicy( QSizePolicy::Fixed, QSizePolicy::MinimumExpanding );
1170 }
1171 updateGeometry();
1172}
1173
1175{
1176 if ( margin == mMargin )
1177 {
1178 return;
1179 }
1180 mMargin = margin;
1181 update();
1182}
1183
1184void QgsColorRampWidget::setShowFrame( const bool showFrame )
1185{
1186 if ( showFrame == mShowFrame )
1187 {
1188 return;
1189 }
1190 mShowFrame = showFrame;
1191 update();
1192}
1193
1194void QgsColorRampWidget::setMarkerSize( const int markerSize )
1195{
1196 //create triangle polygons
1197 mTopTriangle << QPoint( -markerSize, 0 ) << QPoint( markerSize, 0 ) << QPoint( 0, markerSize );
1198 mBottomTriangle << QPoint( -markerSize, 0 ) << QPoint( markerSize, 0 ) << QPoint( 0, -markerSize );
1199 update();
1200}
1201
1202void QgsColorRampWidget::mouseMoveEvent( QMouseEvent *event )
1203{
1204 if ( mIsDragging )
1205 {
1206 setColorFromPoint( event->pos() );
1207 }
1208
1210}
1211
1212void QgsColorRampWidget::wheelEvent( QWheelEvent *event )
1213{
1214 const int oldValue = componentValue();
1215
1216 if ( event->angleDelta().y() > 0 )
1217 {
1219 }
1220 else
1221 {
1223 }
1224
1225 if ( componentValue() != oldValue )
1226 {
1227 //value has changed
1229 emit valueChanged( componentValue() );
1230 }
1231
1232 event->accept();
1233}
1234
1235void QgsColorRampWidget::mousePressEvent( QMouseEvent *event )
1236{
1237 if ( event->button() == Qt::LeftButton )
1238 {
1239 mIsDragging = true;
1240 setColorFromPoint( event->pos() );
1241 }
1242 else
1243 {
1245 }
1246}
1247
1249{
1250 if ( event->button() == Qt::LeftButton )
1251 {
1252 mIsDragging = false;
1253 }
1254 else
1255 {
1257 }
1258}
1259
1261{
1262 const int oldValue = componentValue();
1263 if ( ( mOrientation == QgsColorRampWidget::Horizontal && ( event->key() == Qt::Key_Right || event->key() == Qt::Key_Up ) )
1264 || ( mOrientation == QgsColorRampWidget::Vertical && ( event->key() == Qt::Key_Left || event->key() == Qt::Key_Up ) ) )
1265 {
1267 }
1268 else if ( ( mOrientation == QgsColorRampWidget::Horizontal && ( event->key() == Qt::Key_Left || event->key() == Qt::Key_Down ) )
1269 || ( mOrientation == QgsColorRampWidget::Vertical && ( event->key() == Qt::Key_Right || event->key() == Qt::Key_Down ) ) )
1270 {
1272 }
1273 else if ( ( mOrientation == QgsColorRampWidget::Horizontal && event->key() == Qt::Key_PageDown )
1274 || ( mOrientation == QgsColorRampWidget::Vertical && event->key() == Qt::Key_PageUp ) )
1275 {
1277 }
1278 else if ( ( mOrientation == QgsColorRampWidget::Horizontal && event->key() == Qt::Key_PageUp )
1279 || ( mOrientation == QgsColorRampWidget::Vertical && event->key() == Qt::Key_PageDown ) )
1280 {
1282 }
1283 else if ( ( mOrientation == QgsColorRampWidget::Horizontal && event->key() == Qt::Key_Home )
1284 || ( mOrientation == QgsColorRampWidget::Vertical && event->key() == Qt::Key_End ) )
1285 {
1286 setComponentValue( 0 );
1287 }
1288 else if ( ( mOrientation == QgsColorRampWidget::Horizontal && event->key() == Qt::Key_End )
1289 || ( mOrientation == QgsColorRampWidget::Vertical && event->key() == Qt::Key_Home ) )
1290 {
1291 //set to maximum value
1293 }
1294 else
1295 {
1296 QgsColorWidget::keyPressEvent( event );
1297 return;
1298 }
1299
1300 if ( componentValue() != oldValue )
1301 {
1302 //value has changed
1304 emit valueChanged( componentValue() );
1305 }
1306}
1307
1308void QgsColorRampWidget::setColorFromPoint( QPointF point )
1309{
1310 const int oldValue = componentValue();
1311 int val;
1312 if ( mOrientation == QgsColorRampWidget::Horizontal )
1313 {
1314 val = componentRange() * ( point.x() - mMargin ) / ( width() - 2 * mMargin );
1315 }
1316 else
1317 {
1318 val = componentRange() - componentRange() * ( point.y() - mMargin ) / ( height() - 2 * mMargin );
1319 }
1320 val = std::max( 0, std::min( val, componentRange() ) );
1321 setComponentValue( val );
1322
1323 if ( componentValue() != oldValue )
1324 {
1325 //value has changed
1327 emit valueChanged( componentValue() );
1328 }
1329}
1330
1331//
1332// QgsColorSliderWidget
1333//
1334
1336 : QgsColorWidget( parent, component )
1337
1338{
1339 QHBoxLayout *hLayout = new QHBoxLayout();
1340 hLayout->setContentsMargins( 0, 0, 0, 0 );
1341 hLayout->setSpacing( 5 );
1342
1343 mRampWidget = new QgsColorRampWidget( nullptr, component );
1344 mRampWidget->setColor( mCurrentColor );
1345 hLayout->addWidget( mRampWidget, 1 );
1346
1347 mSpinBox = new QSpinBox();
1348 //set spinbox to a reasonable width
1349 const int largestCharWidth = mSpinBox->fontMetrics().horizontalAdvance( QStringLiteral( "888%" ) );
1350 mSpinBox->setMinimumWidth( largestCharWidth + 35 );
1351 mSpinBox->setMinimum( 0 );
1352 mSpinBox->setMaximum( convertRealToDisplay( componentRange() ) );
1353 mSpinBox->setValue( convertRealToDisplay( componentValue() ) );
1355 {
1356 //degrees suffix for hue
1357 mSpinBox->setSuffix( QChar( 176 ) );
1358 }
1360 {
1361 mSpinBox->setSuffix( tr( "%" ) );
1362 }
1363 hLayout->addWidget( mSpinBox );
1364 setLayout( hLayout );
1365
1366 connect( mRampWidget, &QgsColorRampWidget::valueChanged, this, &QgsColorSliderWidget::rampChanged );
1367 connect( mRampWidget, &QgsColorWidget::colorChanged, this, &QgsColorSliderWidget::rampColorChanged );
1368 connect( mSpinBox, static_cast < void ( QSpinBox::* )( int ) > ( &QSpinBox::valueChanged ), this, &QgsColorSliderWidget::spinChanged );
1369}
1370
1372{
1374 mRampWidget->setComponent( component );
1375 mSpinBox->setMaximum( convertRealToDisplay( componentRange() ) );
1377 {
1378 //degrees suffix for hue
1379 mSpinBox->setSuffix( QChar( 176 ) );
1380 }
1382 {
1383 //saturation, value and alpha are in %
1384 mSpinBox->setSuffix( tr( "%" ) );
1385 }
1386 else
1387 {
1388 //clear suffix
1389 mSpinBox->setSuffix( QString() );
1390 }
1391}
1392
1394{
1396 mRampWidget->blockSignals( true );
1397 mRampWidget->setComponentValue( value );
1398 mRampWidget->blockSignals( false );
1399 mSpinBox->blockSignals( true );
1400 mSpinBox->setValue( convertRealToDisplay( value ) );
1401 mSpinBox->blockSignals( false );
1402}
1403
1404void QgsColorSliderWidget::setColor( const QColor &color, bool emitSignals )
1405{
1406 QgsColorWidget::setColor( color, emitSignals );
1407 mRampWidget->setColor( color );
1408 mSpinBox->blockSignals( true );
1409 mSpinBox->setValue( convertRealToDisplay( componentValue() ) );
1410 mSpinBox->blockSignals( false );
1411}
1412
1413void QgsColorSliderWidget::rampColorChanged( const QColor &color )
1414{
1415 emit colorChanged( color );
1416}
1417
1418void QgsColorSliderWidget::spinChanged( int value )
1419{
1420 const int convertedValue = convertDisplayToReal( value );
1421 QgsColorWidget::setComponentValue( convertedValue );
1422 mRampWidget->setComponentValue( convertedValue );
1424}
1425
1426void QgsColorSliderWidget::rampChanged( int value )
1427{
1428 mSpinBox->blockSignals( true );
1429 mSpinBox->setValue( convertRealToDisplay( value ) );
1430 mSpinBox->blockSignals( false );
1431}
1432
1433
1434int QgsColorSliderWidget::convertRealToDisplay( const int realValue ) const
1435{
1436 //scale saturation, value or alpha to 0->100 range. This makes more sense for users
1437 //for whom "255" is a totally arbitrary value!
1439 {
1440 return std::round( 100.0 * realValue / 255.0 );
1441 }
1442
1443 //leave all other values intact
1444 return realValue;
1445}
1446
1447int QgsColorSliderWidget::convertDisplayToReal( const int displayValue ) const
1448{
1449 //scale saturation, value or alpha from 0->100 range (see note in convertRealToDisplay)
1451 {
1452 return std::round( 255.0 * displayValue / 100.0 );
1453 }
1454
1455 //leave all other values intact
1456 return displayValue;
1457}
1458
1459//
1460// QgsColorTextWidget
1461//
1462
1464 : QgsColorWidget( parent )
1465{
1466 QHBoxLayout *hLayout = new QHBoxLayout();
1467 hLayout->setContentsMargins( 0, 0, 0, 0 );
1468 hLayout->setSpacing( 0 );
1469
1470 mLineEdit = new QLineEdit( nullptr );
1471 hLayout->addWidget( mLineEdit );
1472
1473 mMenuButton = new QToolButton( mLineEdit );
1474 mMenuButton->setIcon( QgsApplication::getThemeIcon( QStringLiteral( "/mIconDropDownMenu.svg" ) ) );
1475 mMenuButton->setCursor( Qt::ArrowCursor );
1476 mMenuButton->setFocusPolicy( Qt::NoFocus );
1477 mMenuButton->setStyleSheet( QStringLiteral( "QToolButton { border: none; padding: 0px; }" ) );
1478
1479 setLayout( hLayout );
1480
1481 const int frameWidth = mLineEdit->style()->pixelMetric( QStyle::PM_DefaultFrameWidth );
1482 mLineEdit->setStyleSheet( QStringLiteral( "QLineEdit { padding-right: %1px; } " )
1483 .arg( mMenuButton->sizeHint().width() + frameWidth + 1 ) );
1484
1485 connect( mLineEdit, &QLineEdit::editingFinished, this, &QgsColorTextWidget::textChanged );
1486 connect( mMenuButton, &QAbstractButton::clicked, this, &QgsColorTextWidget::showMenu );
1487
1488 //restore format setting
1489 QgsSettings settings;
1490 mFormat = settings.enumValue( QStringLiteral( "ColorWidgets/textWidgetFormat" ), HexRgb );
1491
1492 updateText();
1493}
1494
1495void QgsColorTextWidget::setColor( const QColor &color, const bool emitSignals )
1496{
1497 QgsColorWidget::setColor( color, emitSignals );
1498 updateText();
1499}
1500
1501void QgsColorTextWidget::resizeEvent( QResizeEvent *event )
1502{
1503 Q_UNUSED( event )
1504 const QSize sz = mMenuButton->sizeHint();
1505 const int frameWidth = style()->pixelMetric( QStyle::PM_DefaultFrameWidth );
1506 mMenuButton->move( mLineEdit->rect().right() - frameWidth - sz.width(),
1507 ( mLineEdit->rect().bottom() + 1 - sz.height() ) / 2 );
1508}
1509
1510void QgsColorTextWidget::updateText()
1511{
1512 switch ( mFormat )
1513 {
1514 case HexRgb:
1515 mLineEdit->setText( mCurrentColor.name() );
1516 break;
1517 case HexRgbA:
1518 mLineEdit->setText( mCurrentColor.name() + QStringLiteral( "%1" ).arg( mCurrentColor.alpha(), 2, 16, QChar( '0' ) ) );
1519 break;
1520 case Rgb:
1521 mLineEdit->setText( tr( "rgb( %1, %2, %3 )" ).arg( mCurrentColor.red() ).arg( mCurrentColor.green() ).arg( mCurrentColor.blue() ) );
1522 break;
1523 case Rgba:
1524 mLineEdit->setText( tr( "rgba( %1, %2, %3, %4 )" ).arg( mCurrentColor.red() ).arg( mCurrentColor.green() ).arg( mCurrentColor.blue() ).arg( QString::number( mCurrentColor.alphaF(), 'f', 2 ) ) );
1525 break;
1526 }
1527}
1528
1529void QgsColorTextWidget::textChanged()
1530{
1531 const QString testString = mLineEdit->text();
1532 bool containsAlpha;
1533 QColor color = QgsSymbolLayerUtils::parseColorWithAlpha( testString, containsAlpha );
1534 if ( !color.isValid() )
1535 {
1536 //bad color string
1537 updateText();
1538 return;
1539 }
1540
1541 //good color string
1542 if ( color != mCurrentColor )
1543 {
1544 //retain alpha if no explicit alpha set
1545 if ( !containsAlpha )
1546 {
1547 color.setAlpha( mCurrentColor.alpha() );
1548 }
1549 //color has changed
1552 }
1553 updateText();
1554}
1555
1556void QgsColorTextWidget::showMenu()
1557{
1558 QMenu colorContextMenu;
1559 QAction *hexRgbaAction = nullptr;
1560 QAction *rgbaAction = nullptr;
1561
1562 QAction *hexRgbAction = new QAction( tr( "#RRGGBB" ), nullptr );
1563 colorContextMenu.addAction( hexRgbAction );
1564 if ( mAllowAlpha )
1565 {
1566 hexRgbaAction = new QAction( tr( "#RRGGBBAA" ), nullptr );
1567 colorContextMenu.addAction( hexRgbaAction );
1568 }
1569 QAction *rgbAction = new QAction( tr( "rgb( r, g, b )" ), nullptr );
1570 colorContextMenu.addAction( rgbAction );
1571 if ( mAllowAlpha )
1572 {
1573 rgbaAction = new QAction( tr( "rgba( r, g, b, a )" ), nullptr );
1574 colorContextMenu.addAction( rgbaAction );
1575 }
1576
1577 QAction *selectedAction = colorContextMenu.exec( QCursor::pos() );
1578 if ( selectedAction == hexRgbAction )
1579 {
1581 }
1582 else if ( hexRgbaAction && selectedAction == hexRgbaAction )
1583 {
1585 }
1586 else if ( selectedAction == rgbAction )
1587 {
1588 mFormat = QgsColorTextWidget::Rgb;
1589 }
1590 else if ( rgbaAction && selectedAction == rgbaAction )
1591 {
1592 mFormat = QgsColorTextWidget::Rgba;
1593 }
1594
1595 //save format setting
1596 QgsSettings settings;
1597 settings.setEnumValue( QStringLiteral( "ColorWidgets/textWidgetFormat" ), mFormat );
1598
1599 updateText();
1600}
1601
1602void QgsColorTextWidget::setAllowOpacity( const bool allowOpacity )
1603{
1604 mAllowAlpha = allowOpacity;
1605}
1606
1607//
1608// QgsColorPreviewWidget
1609//
1610
1612 : QgsColorWidget( parent )
1613 , mColor2( QColor() )
1614{
1615
1616}
1617
1618void QgsColorPreviewWidget::drawColor( const QColor &color, QRect rect, QPainter &painter )
1619{
1620 painter.setPen( Qt::NoPen );
1621 //if color has an alpha, start with a checkboard pattern
1622 if ( color.alpha() < 255 )
1623 {
1624 const QBrush checkBrush = QBrush( transparentBackground() );
1625 painter.setBrush( checkBrush );
1626 painter.drawRect( rect );
1627
1628 //draw half of widget showing solid color, the other half showing color with alpha
1629
1630 //ensure at least a 1px overlap to avoid artifacts
1631 const QBrush colorBrush = QBrush( color );
1632 painter.setBrush( colorBrush );
1633 painter.drawRect( std::floor( rect.width() / 2.0 ) + rect.left(), rect.top(), rect.width() - std::floor( rect.width() / 2.0 ), rect.height() );
1634
1635 QColor opaqueColor = QColor( color );
1636 opaqueColor.setAlpha( 255 );
1637 const QBrush opaqueBrush = QBrush( opaqueColor );
1638 painter.setBrush( opaqueBrush );
1639 painter.drawRect( rect.left(), rect.top(), std::ceil( rect.width() / 2.0 ), rect.height() );
1640 }
1641 else
1642 {
1643 //no alpha component, just draw a solid rectangle
1644 const QBrush brush = QBrush( color );
1645 painter.setBrush( brush );
1646 painter.drawRect( rect );
1647 }
1648}
1649
1650void QgsColorPreviewWidget::paintEvent( QPaintEvent *event )
1651{
1652 Q_UNUSED( event )
1653 QPainter painter( this );
1654
1655 if ( mColor2.isValid() )
1656 {
1657 //drawing with two color sections
1658 const int verticalSplit = std::round( height() / 2.0 );
1659 drawColor( mCurrentColor, QRect( 0, 0, width(), verticalSplit ), painter );
1660 drawColor( mColor2, QRect( 0, verticalSplit, width(), height() - verticalSplit ), painter );
1661 }
1662 else if ( mCurrentColor.isValid() )
1663 {
1664 drawColor( mCurrentColor, QRect( 0, 0, width(), height() ), painter );
1665 }
1666
1667 painter.end();
1668}
1669
1671{
1672 return QSize( Qgis::UI_SCALE_FACTOR * fontMetrics().horizontalAdvance( 'X' ) * 22, Qgis::UI_SCALE_FACTOR * fontMetrics().horizontalAdvance( 'X' ) * 22 * 0.75 );
1673}
1674
1675void QgsColorPreviewWidget::setColor2( const QColor &color )
1676{
1677 if ( color == mColor2 )
1678 {
1679 return;
1680 }
1681 mColor2 = color;
1682 update();
1683}
1684
1686{
1687 if ( e->button() == Qt::LeftButton )
1688 {
1689 mDragStartPosition = e->pos();
1690 }
1692}
1693
1695{
1696 if ( ( e->pos() - mDragStartPosition ).manhattanLength() >= QApplication::startDragDistance() )
1697 {
1698 //mouse moved, so a drag. nothing to do here
1700 return;
1701 }
1702
1703 //work out which color was clicked
1704 QColor clickedColor = mCurrentColor;
1705 if ( mColor2.isValid() )
1706 {
1707 //two color sections, check if dragged color was the second color
1708 const int verticalSplit = std::round( height() / 2.0 );
1709 if ( mDragStartPosition.y() >= verticalSplit )
1710 {
1711 clickedColor = mColor2;
1712 }
1713 }
1714 emit colorChanged( clickedColor );
1715
1716}
1717
1719{
1720 //handle dragging colors from button
1721
1722 if ( !( e->buttons() & Qt::LeftButton ) )
1723 {
1724 //left button not depressed, so not a drag
1726 return;
1727 }
1728
1729 if ( ( e->pos() - mDragStartPosition ).manhattanLength() < QApplication::startDragDistance() )
1730 {
1731 //mouse not moved, so not a drag
1733 return;
1734 }
1735
1736 //user is dragging color
1737
1738 //work out which color is being dragged
1739 QColor dragColor = mCurrentColor;
1740 if ( mColor2.isValid() )
1741 {
1742 //two color sections, check if dragged color was the second color
1743 const int verticalSplit = std::round( height() / 2.0 );
1744 if ( mDragStartPosition.y() >= verticalSplit )
1745 {
1746 dragColor = mColor2;
1747 }
1748 }
1749
1750 QDrag *drag = new QDrag( this );
1751 drag->setMimeData( QgsSymbolLayerUtils::colorToMimeData( dragColor ) );
1752 drag->setPixmap( createDragIcon( dragColor ) );
1753 drag->exec( Qt::CopyAction );
1754}
1755
1756
1757//
1758// QgsColorWidgetAction
1759//
1760
1761QgsColorWidgetAction::QgsColorWidgetAction( QgsColorWidget *colorWidget, QMenu *menu, QWidget *parent )
1762 : QWidgetAction( parent )
1763 , mMenu( menu )
1764 , mColorWidget( colorWidget )
1765 , mSuppressRecurse( false )
1766 , mDismissOnColorSelection( true )
1767{
1768 setDefaultWidget( mColorWidget );
1769 connect( mColorWidget, &QgsColorWidget::colorChanged, this, &QgsColorWidgetAction::setColor );
1770
1771 connect( this, &QAction::hovered, this, &QgsColorWidgetAction::onHover );
1772 connect( mColorWidget, &QgsColorWidget::hovered, this, &QgsColorWidgetAction::onHover );
1773}
1774
1775void QgsColorWidgetAction::onHover()
1776{
1777 //see https://bugreports.qt.io/browse/QTBUG-10427?focusedCommentId=185610&page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel#comment-185610
1778 if ( mSuppressRecurse )
1779 {
1780 return;
1781 }
1782
1783 if ( mMenu )
1784 {
1785 mSuppressRecurse = true;
1786 mMenu->setActiveAction( this );
1787 mSuppressRecurse = false;
1788 }
1789}
1790
1791void QgsColorWidgetAction::setColor( const QColor &color )
1792{
1793 emit colorChanged( color );
1794 if ( mMenu && mDismissOnColorSelection )
1795 {
1796 QAction::trigger();
1797 mMenu->hide();
1798 }
1799}
static const double UI_SCALE_FACTOR
UI scaling factor.
Definition: qgis.h:2304
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:62
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:336
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:284
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