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