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