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