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