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