QGIS API Documentation 3.41.0-Master (3440c17df1d)
Loading...
Searching...
No Matches
qgsgradientstopeditor.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgsgradientstopeditor.cpp
3 -------------------------
4 begin : April 2016
5 copyright : (C) 2016 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
17#include "moc_qgsgradientstopeditor.cpp"
18#include "qgsapplication.h"
19#include "qgssymbollayerutils.h"
20
21#include <QPainter>
22#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
23#include <QStyleOptionFrameV3>
24#else
25#include <QStyleOptionFrame>
26#endif
27#include <QMouseEvent>
28
29#define MARKER_WIDTH 11
30#define MARKER_HEIGHT 14
31#define MARKER_GAP 1.5
32#define MARGIN_BOTTOM ( MARKER_HEIGHT + 2 )
33#define MARGIN_X ( MARKER_WIDTH / 2 )
34#define FRAME_MARGIN 2
35#define CLICK_THRESHOLD ( MARKER_WIDTH / 2 + 3 )
36
38 : QWidget( parent )
39{
40 if ( ramp )
41 mGradient = *ramp;
42 mStops = mGradient.stops();
43
44 if ( sOuterTriangle.isEmpty() )
45 {
46 sOuterTriangle << QPointF( 0, MARKER_HEIGHT ) << QPointF( MARKER_WIDTH, MARKER_HEIGHT )
47 << QPointF( MARKER_WIDTH, MARKER_WIDTH / 2.0 )
48 << QPointF( MARKER_WIDTH / 2.0, 0 )
49 << QPointF( 0, MARKER_WIDTH / 2.0 )
50 << QPointF( 0, MARKER_HEIGHT );
51 }
52 if ( sInnerTriangle.isEmpty() )
53 {
54 sInnerTriangle << QPointF( MARKER_GAP, MARKER_HEIGHT - MARKER_GAP ) << QPointF( MARKER_WIDTH - MARKER_GAP, MARKER_HEIGHT - MARKER_GAP )
55 << QPointF( MARKER_WIDTH - MARKER_GAP, MARKER_WIDTH / 2.0 + 1 )
56 << QPointF( MARKER_WIDTH / 2.0, MARKER_GAP )
57 << QPointF( MARKER_GAP, MARKER_WIDTH / 2.0 + 1 )
58 << QPointF( MARKER_GAP, MARKER_HEIGHT - MARKER_GAP );
59 }
60
61 setFocusPolicy( Qt::StrongFocus );
62 setAcceptDrops( true );
63}
64
66{
67 mGradient = ramp;
68 mStops = mGradient.stops();
69 mSelectedStop = 0;
70 update();
71 emit changed();
72}
73
75{
76 //horizontal
77 return QSize( 200, 80 );
78}
79
80void QgsGradientStopEditor::paintEvent( QPaintEvent *event )
81{
82 Q_UNUSED( event )
83 QPainter painter( this );
84
85 QRect frameRect( rect().x() + MARGIN_X, rect().y(),
86 rect().width() - 2 * MARGIN_X,
87 rect().height() - MARGIN_BOTTOM );
88
89 //draw frame
90 QStyleOptionFrame option;
91 option.initFrom( this );
92 option.state = hasFocus() ? QStyle::State_KeyboardFocusChange : QStyle::State_None;
93 option.rect = frameRect;
94 style()->drawPrimitive( QStyle::PE_Frame, &option, &painter );
95
96 if ( hasFocus() )
97 {
98 //draw focus rect
99 QStyleOptionFocusRect option;
100 option.initFrom( this );
101 option.state = QStyle::State_KeyboardFocusChange;
102 option.rect = frameRect;
103 style()->drawPrimitive( QStyle::PE_FrameFocusRect, &option, &painter );
104 }
105
106 //start with the checkboard pattern
107 QBrush checkBrush = QBrush( transparentBackground() );
108 painter.setBrush( checkBrush );
109 painter.setPen( Qt::NoPen );
110
111 QRect box( frameRect.x() + FRAME_MARGIN, frameRect.y() + FRAME_MARGIN,
112 frameRect.width() - 2 * FRAME_MARGIN,
113 frameRect.height() - 2 * FRAME_MARGIN );
114
115 painter.drawRect( box );
116
117 // draw gradient preview on top of checkerboard
118 for ( int i = 0; i < box.width() + 1; ++i )
119 {
120 QPen pen( mGradient.color( static_cast< double >( i ) / box.width() ) );
121 painter.setPen( pen );
122 painter.drawLine( box.left() + i, box.top(), box.left() + i, box.height() + 1 );
123 }
124
125 // draw stop markers
126 int markerTop = frameRect.bottom() + 1;
127 drawStopMarker( painter, QPoint( box.left(), markerTop ), mGradient.color1(), mSelectedStop == 0 );
128 drawStopMarker( painter, QPoint( box.right(), markerTop ), mGradient.color2(), mSelectedStop == mGradient.count() - 1 );
129 int i = 1;
130 const auto constMStops = mStops;
131 for ( const QgsGradientStop &stop : constMStops )
132 {
133 int x = stop.offset * box.width() + box.left();
134 drawStopMarker( painter, QPoint( x, markerTop ), stop.color, mSelectedStop == i );
135 ++i;
136 }
137
138 painter.end();
139}
140
142{
143 if ( index > 0 && index < mGradient.count() - 1 )
144 {
145 // need to map original stop index across to cached, possibly out of order stop index
146 QgsGradientStop selectedStop = mGradient.stops().at( index - 1 );
147 index = 1;
148 const auto constMStops = mStops;
149 for ( const QgsGradientStop &stop : constMStops )
150 {
151 if ( stop == selectedStop )
152 {
153 break;
154 }
155 index++;
156 }
157 }
158
159 mSelectedStop = index;
161 update();
162}
163
165{
166 if ( mSelectedStop > 0 && mSelectedStop < mGradient.count() - 1 )
167 {
168 return mStops.at( mSelectedStop - 1 );
169 }
170 else if ( mSelectedStop == 0 )
171 {
172 return QgsGradientStop( 0.0, mGradient.color1() );
173 }
174 else
175 {
176 QgsGradientStop stop( 1.0, mGradient.color2() );
177 stop.setColorSpec( mGradient.colorSpec() );
178 stop.setDirection( mGradient.direction() );
179 return stop;
180 }
181}
182
184{
185 if ( mSelectedStop > 0 && mSelectedStop < mGradient.count() - 1 )
186 {
187 mStops[ mSelectedStop - 1 ].color = color;
188 mGradient.setStops( mStops );
189 }
190 else if ( mSelectedStop == 0 )
191 {
192 mGradient.setColor1( color );
193 }
194 else
195 {
196 mGradient.setColor2( color );
197 }
198 update();
199 emit changed();
200}
201
203{
204 if ( mSelectedStop > 0 && mSelectedStop < mGradient.count() - 1 )
205 {
206 mStops[ mSelectedStop - 1 ].offset = offset;
207 mGradient.setStops( mStops );
208 update();
209 emit changed();
210 }
211}
212
214{
215 if ( mSelectedStop > 0 && mSelectedStop < mGradient.count() - 1 )
216 {
217 mStops[ mSelectedStop - 1 ].setColorSpec( spec );
218 mGradient.setStops( mStops );
219 update();
220 emit changed();
221 }
222 else if ( mSelectedStop == mGradient.count() - 1 )
223 {
224 mGradient.setColorSpec( spec );
225 update();
226 emit changed();
227 }
228}
229
231{
232 if ( mSelectedStop > 0 && mSelectedStop < mGradient.count() - 1 )
233 {
234 mStops[ mSelectedStop - 1 ].setDirection( direction );
235 mGradient.setStops( mStops );
236 update();
237 emit changed();
238 }
239 else if ( mSelectedStop == mGradient.count() - 1 )
240 {
241 mGradient.setDirection( direction );
242 update();
243 emit changed();
244 }
245}
246
247void QgsGradientStopEditor::setSelectedStopDetails( const QColor &color, double offset )
248{
249 if ( mSelectedStop > 0 && mSelectedStop < mGradient.count() - 1 )
250 {
251 mStops[ mSelectedStop - 1 ].color = color;
252 mStops[ mSelectedStop - 1 ].offset = offset;
253 mGradient.setStops( mStops );
254 }
255 else if ( mSelectedStop == 0 )
256 {
257 mGradient.setColor1( color );
258 }
259 else
260 {
261 mGradient.setColor2( color );
262 }
263
264 update();
265 emit changed();
266}
267
269{
270 if ( selectedStopIsMovable() )
271 {
272 //delete stop
273 double stopOffset = mStops.at( mSelectedStop - 1 ).offset;
274 mStops.removeAt( mSelectedStop - 1 );
275 mGradient.setStops( mStops );
276
277 int closest = findClosestStop( relativePositionToPoint( stopOffset ) );
278 if ( closest >= 0 )
279 selectStop( closest );
280 update();
281 emit changed();
282 }
283}
284
285void QgsGradientStopEditor::setColor1( const QColor &color )
286{
287 mGradient.setColor1( color );
288 update();
289 emit changed();
290}
291
292void QgsGradientStopEditor::setColor2( const QColor &color )
293{
294 mGradient.setColor2( color );
295 update();
296 emit changed();
297}
298
300{
301 if ( e->buttons() & Qt::LeftButton )
302 {
303 if ( selectedStopIsMovable() )
304 {
305 double offset = pointToRelativePosition( e->pos().x() );
306
307 // have to edit the temporary stop list, as setting stops on the gradient will reorder them
308 // and change which stop corresponds to the selected one;
309 mStops[ mSelectedStop - 1 ].offset = offset;
310
311 mGradient.setStops( mStops );
312 update();
313 emit changed();
314 }
315 }
316 e->accept();
317}
318
319int QgsGradientStopEditor::findClosestStop( int x, int threshold ) const
320{
321 int closestStop = -1;
322 int closestDiff = std::numeric_limits<int>::max();
323 int currentDiff = std::numeric_limits<int>::max();
324
325 // check for matching stops first, so that they take precedence
326 // otherwise it's impossible to select a stop which sits above the first/last stop, making
327 // it impossible to move or delete these
328 int i = 1;
329 const auto constStops = mGradient.stops();
330 for ( const QgsGradientStop &stop : constStops )
331 {
332 currentDiff = std::abs( relativePositionToPoint( stop.offset ) + 1 - x );
333 if ( ( threshold < 0 || currentDiff < threshold ) && currentDiff < closestDiff )
334 {
335 closestStop = i;
336 closestDiff = currentDiff;
337 }
338 i++;
339 }
340
341 //first stop
342 currentDiff = std::abs( relativePositionToPoint( 0.0 ) + 1 - x );
343 if ( ( threshold < 0 || currentDiff < threshold ) && currentDiff < closestDiff )
344 {
345 closestStop = 0;
346 closestDiff = currentDiff;
347 }
348
349 //last stop
350 currentDiff = std::abs( relativePositionToPoint( 1.0 ) + 1 - x );
351 if ( ( threshold < 0 || currentDiff < threshold ) && currentDiff < closestDiff )
352 {
353 closestStop = mGradient.count() - 1;
354 }
355
356 return closestStop;
357}
358
360{
361 if ( e->pos().y() >= rect().height() - MARGIN_BOTTOM - 1 )
362 {
363 // find closest point
364 int closestStop = findClosestStop( e->pos().x(), CLICK_THRESHOLD );
365 if ( closestStop >= 0 )
366 {
367 selectStop( closestStop );
368 }
369 update();
370 }
371 e->accept();
372}
373
375{
376 if ( e->buttons() & Qt::LeftButton )
377 {
378 // add a new stop
379 double offset = pointToRelativePosition( e->pos().x() );
380 mStops << QgsGradientStop( offset, mGradient.color( offset ) );
381 mSelectedStop = mStops.length();
382 mGradient.setStops( mStops );
383 update();
384 emit changed();
385 }
386 e->accept();
387}
388
390{
391 if ( ( e->key() == Qt::Key_Backspace || e->key() == Qt::Key_Delete ) )
392 {
394 e->accept();
395 return;
396 }
397 else if ( e->key() == Qt::Key_Left || e->key() == Qt::Key_Right )
398 {
399 if ( selectedStopIsMovable() )
400 {
401 // calculate offset corresponding to 1 px
402 double offsetDiff = pointToRelativePosition( rect().x() + MARGIN_X + FRAME_MARGIN + 2 ) - pointToRelativePosition( rect().x() + MARGIN_X + FRAME_MARGIN + 1 );
403
404 if ( e->modifiers() & Qt::ShiftModifier )
405 offsetDiff *= 10.0;
406
407 if ( e->key() == Qt::Key_Left )
408 offsetDiff *= -1;
409
410 mStops[ mSelectedStop - 1 ].offset = std::clamp( mStops[ mSelectedStop - 1 ].offset + offsetDiff, 0.0, 1.0 );
411 mGradient.setStops( mStops );
412 update();
413 e->accept();
414 emit changed();
415 return;
416 }
417 }
418
419 QWidget::keyPressEvent( e );
420}
421
422QPixmap QgsGradientStopEditor::transparentBackground()
423{
424 static QPixmap sTranspBkgrd;
425
426 if ( sTranspBkgrd.isNull() )
427 sTranspBkgrd = QgsApplication::getThemePixmap( QStringLiteral( "/transp-background_8x8.png" ) );
428
429 return sTranspBkgrd;
430}
431
432void QgsGradientStopEditor::drawStopMarker( QPainter &painter, QPoint topMiddle, const QColor &color, bool selected )
433{
434 QgsScopedQPainterState painterState( &painter );
435 painter.setRenderHint( QPainter::Antialiasing );
436 painter.setBrush( selected ? QColor( 150, 150, 150 ) : Qt::white );
437 painter.setPen( selected ? Qt::black : QColor( 150, 150, 150 ) );
438 // 0.5 offsets to make edges pixel grid aligned
439 painter.translate( std::round( topMiddle.x() - MARKER_WIDTH / 2.0 ) + 0.5, topMiddle.y() + 0.5 );
440 painter.drawPolygon( sOuterTriangle );
441
442 // draw the checkerboard background for marker
443 painter.setBrush( QBrush( transparentBackground() ) );
444 painter.setPen( Qt::NoPen );
445 painter.drawPolygon( sInnerTriangle );
446
447 // draw color on top
448 painter.setBrush( color );
449 painter.drawPolygon( sInnerTriangle );
450}
451
452double QgsGradientStopEditor::pointToRelativePosition( int x ) const
453{
454 int left = rect().x() + MARGIN_X + FRAME_MARGIN;
455 int right = left + rect().width() - 2 * MARGIN_X - 2 * FRAME_MARGIN;
456
457 if ( x <= left )
458 return 0;
459 else if ( x >= right )
460 return 1.0;
461
462 return static_cast< double >( x - left ) / ( right - left );
463}
464
465int QgsGradientStopEditor::relativePositionToPoint( double position ) const
466{
467 int left = rect().x() + MARGIN_X + FRAME_MARGIN;
468 int right = left + rect().width() - 2 * MARGIN_X - 2 * FRAME_MARGIN;
469
470 if ( position <= 0 )
471 return left;
472 else if ( position >= 1.0 )
473 return right;
474
475 return left + ( right - left ) * position;
476}
477
478bool QgsGradientStopEditor::selectedStopIsMovable() const
479{
480 // first and last stop can't be moved or deleted
481 return mSelectedStop > 0 && mSelectedStop < mGradient.count() - 1;
482}
483
484
486{
487 //is dragged data valid color data?
488 bool hasAlpha;
489 QColor mimeColor = QgsSymbolLayerUtils::colorFromMimeData( e->mimeData(), hasAlpha );
490
491 if ( mimeColor.isValid() )
492 {
493 //if so, we accept the drag
494 e->acceptProposedAction();
495 }
496}
497
499{
500 //is dropped data valid color data?
501 bool hasAlpha = false;
502 QColor mimeColor = QgsSymbolLayerUtils::colorFromMimeData( e->mimeData(), hasAlpha );
503
504 if ( mimeColor.isValid() )
505 {
506 //accept drop and set new color
507 e->acceptProposedAction();
508
509 // add a new stop here
510 double offset = pointToRelativePosition( e->pos().x() );
511 mStops << QgsGradientStop( offset, mimeColor );
512 mSelectedStop = mStops.length();
513 mGradient.setStops( mStops );
514 update();
515 emit changed();
516 }
517
518 //could not get color from mime data
519}
520
521
AngularDirection
Angular directions.
Definition qgis.h:3183
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.
Gradient color ramp, which smoothly interpolates between two colors and also supports optional extra ...
void setColor1(const QColor &color)
Sets the gradient start color.
void setColor2(const QColor &color)
Sets the gradient end color.
void setColorSpec(QColor::Spec spec)
Sets the color specification in which the color component interpolation will occur.
int count() const override
Returns number of defined colors, or -1 if undefined.
QColor::Spec colorSpec() const
Returns the color specification in which the color component interpolation will occur.
QColor color(double value) const override
Returns the color corresponding to a specified value.
void setStops(const QgsGradientStopsList &stops)
Sets the list of intermediate gradient stops for the ramp.
QColor color1() const
Returns the gradient start color.
void setDirection(Qgis::AngularDirection direction)
Sets the direction to traverse the color wheel using when interpolating hue-based color specification...
Qgis::AngularDirection direction() const
Returns the direction to traverse the color wheel using when interpolating hue-based color specificat...
QgsGradientStopsList stops() const
Returns the list of intermediate gradient stops for the ramp.
QColor color2() const
Returns the gradient end color.
void setSelectedStopOffset(double offset)
Sets the offset for the current selected stop.
void setColor2(const QColor &color)
Sets the color for the last stop.
void setSelectedStopColor(const QColor &color)
Sets the color for the current selected stop.
void changed()
Emitted when the gradient ramp is changed by a user.
void mousePressEvent(QMouseEvent *event) override
void mouseDoubleClickEvent(QMouseEvent *event) override
void setSelectedStopColorSpec(QColor::Spec spec)
Sets the color spec for the current selected stop.
QgsGradientStop selectedStop() const
Returns details about the currently selected stop.
void keyPressEvent(QKeyEvent *event) override
void setSelectedStopDetails(const QColor &color, double offset)
Sets the color and offset for the current selected stop.
void deleteSelectedStop()
Deletes the current selected stop.
void paintEvent(QPaintEvent *event) override
void selectStop(int index)
Sets the currently selected stop.
QSize sizeHint() const override
void setColor1(const QColor &color)
Sets the color for the first stop.
void setSelectedStopDirection(Qgis::AngularDirection direction)
Sets the hue angular direction for the current selected stop.
void selectedStopChanged(const QgsGradientStop &stop)
Emitted when the current selected stop changes.
QgsGradientStopEditor(QWidget *parent=nullptr, QgsGradientColorRamp *ramp=nullptr)
Constructor for QgsGradientStopEditor.
void dropEvent(QDropEvent *e) override
void mouseMoveEvent(QMouseEvent *event) override
void setGradientRamp(const QgsGradientColorRamp &ramp)
Sets the current ramp shown in the editor.
void dragEnterEvent(QDragEnterEvent *e) override
Represents a color stop within a QgsGradientColorRamp color ramp.
void setColorSpec(QColor::Spec spec)
Sets the color specification in which the color component interpolation will occur.
void setDirection(Qgis::AngularDirection direction)
Sets the direction to traverse the color wheel using when interpolating hue-based color specification...
Scoped object for saving and restoring a QPainter object's state.
static QColor colorFromMimeData(const QMimeData *data, bool &hasAlpha)
Attempts to parse mime data as a color.
#define CLICK_THRESHOLD
#define MARKER_HEIGHT
#define FRAME_MARGIN
#define MARGIN_X
#define MARKER_WIDTH
#define MARKER_GAP
#define MARGIN_BOTTOM