QGIS API Documentation 3.41.0-Master (d5b93354e9c)
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(), rect().width() - 2 * MARGIN_X, rect().height() - MARGIN_BOTTOM );
86
87 //draw frame
88 QStyleOptionFrame option;
89 option.initFrom( this );
90 option.state = hasFocus() ? QStyle::State_KeyboardFocusChange : QStyle::State_None;
91 option.rect = frameRect;
92 style()->drawPrimitive( QStyle::PE_Frame, &option, &painter );
93
94 if ( hasFocus() )
95 {
96 //draw focus rect
97 QStyleOptionFocusRect option;
98 option.initFrom( this );
99 option.state = QStyle::State_KeyboardFocusChange;
100 option.rect = frameRect;
101 style()->drawPrimitive( QStyle::PE_FrameFocusRect, &option, &painter );
102 }
103
104 //start with the checkboard pattern
105 QBrush checkBrush = QBrush( transparentBackground() );
106 painter.setBrush( checkBrush );
107 painter.setPen( Qt::NoPen );
108
109 QRect box( frameRect.x() + FRAME_MARGIN, frameRect.y() + FRAME_MARGIN, frameRect.width() - 2 * FRAME_MARGIN, frameRect.height() - 2 * FRAME_MARGIN );
110
111 painter.drawRect( box );
112
113 // draw gradient preview on top of checkerboard
114 for ( int i = 0; i < box.width() + 1; ++i )
115 {
116 QPen pen( mGradient.color( static_cast<double>( i ) / box.width() ) );
117 painter.setPen( pen );
118 painter.drawLine( box.left() + i, box.top(), box.left() + i, box.height() + 1 );
119 }
120
121 // draw stop markers
122 int markerTop = frameRect.bottom() + 1;
123 drawStopMarker( painter, QPoint( box.left(), markerTop ), mGradient.color1(), mSelectedStop == 0 );
124 drawStopMarker( painter, QPoint( box.right(), markerTop ), mGradient.color2(), mSelectedStop == mGradient.count() - 1 );
125 int i = 1;
126 const auto constMStops = mStops;
127 for ( const QgsGradientStop &stop : constMStops )
128 {
129 int x = stop.offset * box.width() + box.left();
130 drawStopMarker( painter, QPoint( x, markerTop ), stop.color, mSelectedStop == i );
131 ++i;
132 }
133
134 painter.end();
135}
136
138{
139 if ( index > 0 && index < mGradient.count() - 1 )
140 {
141 // need to map original stop index across to cached, possibly out of order stop index
142 QgsGradientStop selectedStop = mGradient.stops().at( index - 1 );
143 index = 1;
144 const auto constMStops = mStops;
145 for ( const QgsGradientStop &stop : constMStops )
146 {
147 if ( stop == selectedStop )
148 {
149 break;
150 }
151 index++;
152 }
153 }
154
155 mSelectedStop = index;
157 update();
158}
159
161{
162 if ( mSelectedStop > 0 && mSelectedStop < mGradient.count() - 1 )
163 {
164 return mStops.at( mSelectedStop - 1 );
165 }
166 else if ( mSelectedStop == 0 )
167 {
168 return QgsGradientStop( 0.0, mGradient.color1() );
169 }
170 else
171 {
172 QgsGradientStop stop( 1.0, mGradient.color2() );
173 stop.setColorSpec( mGradient.colorSpec() );
174 stop.setDirection( mGradient.direction() );
175 return stop;
176 }
177}
178
180{
181 if ( mSelectedStop > 0 && mSelectedStop < mGradient.count() - 1 )
182 {
183 mStops[mSelectedStop - 1].color = color;
184 mGradient.setStops( mStops );
185 }
186 else if ( mSelectedStop == 0 )
187 {
188 mGradient.setColor1( color );
189 }
190 else
191 {
192 mGradient.setColor2( color );
193 }
194 update();
195 emit changed();
196}
197
199{
200 if ( mSelectedStop > 0 && mSelectedStop < mGradient.count() - 1 )
201 {
202 mStops[mSelectedStop - 1].offset = offset;
203 mGradient.setStops( mStops );
204 update();
205 emit changed();
206 }
207}
208
210{
211 if ( mSelectedStop > 0 && mSelectedStop < mGradient.count() - 1 )
212 {
213 mStops[mSelectedStop - 1].setColorSpec( spec );
214 mGradient.setStops( mStops );
215 update();
216 emit changed();
217 }
218 else if ( mSelectedStop == mGradient.count() - 1 )
219 {
220 mGradient.setColorSpec( spec );
221 update();
222 emit changed();
223 }
224}
225
227{
228 if ( mSelectedStop > 0 && mSelectedStop < mGradient.count() - 1 )
229 {
230 mStops[mSelectedStop - 1].setDirection( direction );
231 mGradient.setStops( mStops );
232 update();
233 emit changed();
234 }
235 else if ( mSelectedStop == mGradient.count() - 1 )
236 {
237 mGradient.setDirection( direction );
238 update();
239 emit changed();
240 }
241}
242
243void QgsGradientStopEditor::setSelectedStopDetails( const QColor &color, double offset )
244{
245 if ( mSelectedStop > 0 && mSelectedStop < mGradient.count() - 1 )
246 {
247 mStops[mSelectedStop - 1].color = color;
248 mStops[mSelectedStop - 1].offset = offset;
249 mGradient.setStops( mStops );
250 }
251 else if ( mSelectedStop == 0 )
252 {
253 mGradient.setColor1( color );
254 }
255 else
256 {
257 mGradient.setColor2( color );
258 }
259
260 update();
261 emit changed();
262}
263
265{
266 if ( selectedStopIsMovable() )
267 {
268 //delete stop
269 double stopOffset = mStops.at( mSelectedStop - 1 ).offset;
270 mStops.removeAt( mSelectedStop - 1 );
271 mGradient.setStops( mStops );
272
273 int closest = findClosestStop( relativePositionToPoint( stopOffset ) );
274 if ( closest >= 0 )
275 selectStop( closest );
276 update();
277 emit changed();
278 }
279}
280
281void QgsGradientStopEditor::setColor1( const QColor &color )
282{
283 mGradient.setColor1( color );
284 update();
285 emit changed();
286}
287
288void QgsGradientStopEditor::setColor2( const QColor &color )
289{
290 mGradient.setColor2( color );
291 update();
292 emit changed();
293}
294
296{
297 if ( e->buttons() & Qt::LeftButton )
298 {
299 if ( selectedStopIsMovable() )
300 {
301 double offset = pointToRelativePosition( e->pos().x() );
302
303 // have to edit the temporary stop list, as setting stops on the gradient will reorder them
304 // and change which stop corresponds to the selected one;
305 mStops[mSelectedStop - 1].offset = offset;
306
307 mGradient.setStops( mStops );
308 update();
309 emit changed();
310 }
311 }
312 e->accept();
313}
314
315int QgsGradientStopEditor::findClosestStop( int x, int threshold ) const
316{
317 int closestStop = -1;
318 int closestDiff = std::numeric_limits<int>::max();
319 int currentDiff = std::numeric_limits<int>::max();
320
321 // check for matching stops first, so that they take precedence
322 // otherwise it's impossible to select a stop which sits above the first/last stop, making
323 // it impossible to move or delete these
324 int i = 1;
325 const auto constStops = mGradient.stops();
326 for ( const QgsGradientStop &stop : constStops )
327 {
328 currentDiff = std::abs( relativePositionToPoint( stop.offset ) + 1 - x );
329 if ( ( threshold < 0 || currentDiff < threshold ) && currentDiff < closestDiff )
330 {
331 closestStop = i;
332 closestDiff = currentDiff;
333 }
334 i++;
335 }
336
337 //first stop
338 currentDiff = std::abs( relativePositionToPoint( 0.0 ) + 1 - x );
339 if ( ( threshold < 0 || currentDiff < threshold ) && currentDiff < closestDiff )
340 {
341 closestStop = 0;
342 closestDiff = currentDiff;
343 }
344
345 //last stop
346 currentDiff = std::abs( relativePositionToPoint( 1.0 ) + 1 - x );
347 if ( ( threshold < 0 || currentDiff < threshold ) && currentDiff < closestDiff )
348 {
349 closestStop = mGradient.count() - 1;
350 }
351
352 return closestStop;
353}
354
356{
357 if ( e->pos().y() >= rect().height() - MARGIN_BOTTOM - 1 )
358 {
359 // find closest point
360 int closestStop = findClosestStop( e->pos().x(), CLICK_THRESHOLD );
361 if ( closestStop >= 0 )
362 {
363 selectStop( closestStop );
364 }
365 update();
366 }
367 e->accept();
368}
369
371{
372 if ( e->buttons() & Qt::LeftButton )
373 {
374 // add a new stop
375 double offset = pointToRelativePosition( e->pos().x() );
376 mStops << QgsGradientStop( offset, mGradient.color( offset ) );
377 mSelectedStop = mStops.length();
378 mGradient.setStops( mStops );
379 update();
380 emit changed();
381 }
382 e->accept();
383}
384
386{
387 if ( ( e->key() == Qt::Key_Backspace || e->key() == Qt::Key_Delete ) )
388 {
390 e->accept();
391 return;
392 }
393 else if ( e->key() == Qt::Key_Left || e->key() == Qt::Key_Right )
394 {
395 if ( selectedStopIsMovable() )
396 {
397 // calculate offset corresponding to 1 px
398 double offsetDiff = pointToRelativePosition( rect().x() + MARGIN_X + FRAME_MARGIN + 2 ) - pointToRelativePosition( rect().x() + MARGIN_X + FRAME_MARGIN + 1 );
399
400 if ( e->modifiers() & Qt::ShiftModifier )
401 offsetDiff *= 10.0;
402
403 if ( e->key() == Qt::Key_Left )
404 offsetDiff *= -1;
405
406 mStops[mSelectedStop - 1].offset = std::clamp( mStops[mSelectedStop - 1].offset + offsetDiff, 0.0, 1.0 );
407 mGradient.setStops( mStops );
408 update();
409 e->accept();
410 emit changed();
411 return;
412 }
413 }
414
415 QWidget::keyPressEvent( e );
416}
417
418QPixmap QgsGradientStopEditor::transparentBackground()
419{
420 static QPixmap sTranspBkgrd;
421
422 if ( sTranspBkgrd.isNull() )
423 sTranspBkgrd = QgsApplication::getThemePixmap( QStringLiteral( "/transp-background_8x8.png" ) );
424
425 return sTranspBkgrd;
426}
427
428void QgsGradientStopEditor::drawStopMarker( QPainter &painter, QPoint topMiddle, const QColor &color, bool selected )
429{
430 QgsScopedQPainterState painterState( &painter );
431 painter.setRenderHint( QPainter::Antialiasing );
432 painter.setBrush( selected ? QColor( 150, 150, 150 ) : Qt::white );
433 painter.setPen( selected ? Qt::black : QColor( 150, 150, 150 ) );
434 // 0.5 offsets to make edges pixel grid aligned
435 painter.translate( std::round( topMiddle.x() - MARKER_WIDTH / 2.0 ) + 0.5, topMiddle.y() + 0.5 );
436 painter.drawPolygon( sOuterTriangle );
437
438 // draw the checkerboard background for marker
439 painter.setBrush( QBrush( transparentBackground() ) );
440 painter.setPen( Qt::NoPen );
441 painter.drawPolygon( sInnerTriangle );
442
443 // draw color on top
444 painter.setBrush( color );
445 painter.drawPolygon( sInnerTriangle );
446}
447
448double QgsGradientStopEditor::pointToRelativePosition( int x ) const
449{
450 int left = rect().x() + MARGIN_X + FRAME_MARGIN;
451 int right = left + rect().width() - 2 * MARGIN_X - 2 * FRAME_MARGIN;
452
453 if ( x <= left )
454 return 0;
455 else if ( x >= right )
456 return 1.0;
457
458 return static_cast<double>( x - left ) / ( right - left );
459}
460
461int QgsGradientStopEditor::relativePositionToPoint( double position ) const
462{
463 int left = rect().x() + MARGIN_X + FRAME_MARGIN;
464 int right = left + rect().width() - 2 * MARGIN_X - 2 * FRAME_MARGIN;
465
466 if ( position <= 0 )
467 return left;
468 else if ( position >= 1.0 )
469 return right;
470
471 return left + ( right - left ) * position;
472}
473
474bool QgsGradientStopEditor::selectedStopIsMovable() const
475{
476 // first and last stop can't be moved or deleted
477 return mSelectedStop > 0 && mSelectedStop < mGradient.count() - 1;
478}
479
480
482{
483 //is dragged data valid color data?
484 bool hasAlpha;
485 QColor mimeColor = QgsSymbolLayerUtils::colorFromMimeData( e->mimeData(), hasAlpha );
486
487 if ( mimeColor.isValid() )
488 {
489 //if so, we accept the drag
490 e->acceptProposedAction();
491 }
492}
493
495{
496 //is dropped data valid color data?
497 bool hasAlpha = false;
498 QColor mimeColor = QgsSymbolLayerUtils::colorFromMimeData( e->mimeData(), hasAlpha );
499
500 if ( mimeColor.isValid() )
501 {
502 //accept drop and set new color
503 e->acceptProposedAction();
504
505 // add a new stop here
506 double offset = pointToRelativePosition( e->pos().x() );
507 mStops << QgsGradientStop( offset, mimeColor );
508 mSelectedStop = mStops.length();
509 mGradient.setStops( mStops );
510 update();
511 emit changed();
512 }
513
514 //could not get color from mime data
515}
AngularDirection
Angular directions.
Definition qgis.h:3228
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