1 /***************************************************************************
2 qgsmaptooloffsetcurve.cpp
3 ------------------------------------------------------------
4 begin : February 2012
5 copyright : (C) 2012 by Marco Hugentobler
6 email : marco dot hugentobler at sourcepole dot ch
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
16 #include <QGraphicsProxyWidget>
17 #include <QGridLayout>
18 #include <QLabel>
19
20 #include "qgsdoublespinbox.h"
21 #include "qgsfeatureiterator.h"
22 #include "qgsmaptooloffsetcurve.h"
23 #include "qgsmapcanvas.h"
24 #include "qgsproject.h"
25 #include "qgsrubberband.h"
26 #include "qgssnappingutils.h"
27 #include "qgsvectorlayer.h"
28 #include "qgssnapindicator.h"
29 #include "qgssnappingconfig.h"
30 #include "qgssettingsregistrycore.h"
31 #include "qgisapp.h"
32 #include "qgsmapmouseevent.h"
33 #include "qgslogger.h"
34 #include "qgsvectorlayerutils.h"
35
QgsMapToolOffsetCurve(QgsMapCanvas * canvas)36 QgsMapToolOffsetCurve::QgsMapToolOffsetCurve( QgsMapCanvas *canvas )
37 : QgsMapToolEdit( canvas )
38 , mSnapIndicator( std::make_unique< QgsSnapIndicator >( canvas ) )
39 {
40 mToolName = tr( "Map tool offset curve" );
41 }
42
~QgsMapToolOffsetCurve()43 QgsMapToolOffsetCurve::~QgsMapToolOffsetCurve()
44 {
45 cancel();
46 }
47
keyPressEvent(QKeyEvent * e)48 void QgsMapToolOffsetCurve::keyPressEvent( QKeyEvent *e )
49 {
50 if ( e && e->key() == Qt::Key_Escape && !e->isAutoRepeat() )
51 {
52 cancel();
53 }
54 else
55 {
56 QgsMapToolEdit::keyPressEvent( e );
57 }
58 }
59
60
canvasReleaseEvent(QgsMapMouseEvent * e)61 void QgsMapToolOffsetCurve::canvasReleaseEvent( QgsMapMouseEvent *e )
62 {
63 mCtrlHeldOnFirstClick = false;
64
65 if ( e->button() == Qt::RightButton )
66 {
67 cancel();
68 return;
69 }
70
71 if ( mOriginalGeometry.isNull() )
72 {
73 // first click, get feature to modify
74 deleteRubberBandAndGeometry();
75 mGeometryModified = false;
76
77 QgsPointLocator::Match match;
78
79 if ( e->modifiers() & Qt::ControlModifier )
80 {
81 match = mCanvas->snappingUtils()->snapToMap( e->pos(), nullptr );
82 }
83 else
84 {
85 match = mCanvas->snappingUtils()->snapToCurrentLayer( e->pos(),
86 QgsPointLocator::Types( QgsPointLocator::Edge | QgsPointLocator::Area ) );
87 }
88
89 if ( auto *lLayer = match.layer() )
90 {
91 mSourceLayer = lLayer;
92 QgsFeature fet;
93 if ( lLayer->getFeatures( QgsFeatureRequest( match.featureId() ) ).nextFeature( fet ) )
94 {
95 mSourceFeature = fet;
96 mCtrlHeldOnFirstClick = ( e->modifiers() & Qt::ControlModifier ); //no geometry modification if ctrl is pressed
97 prepareGeometry( match, fet );
98 mRubberBand = createRubberBand();
99 if ( mRubberBand )
100 {
101 mRubberBand->setToGeometry( mManipulatedGeometry, lLayer );
102 }
103 mModifiedFeature = fet.id();
104 createUserInputWidget();
105
106 const bool hasZ = QgsWkbTypes::hasZ( mSourceLayer->wkbType() );
107 const bool hasM = QgsWkbTypes::hasZ( mSourceLayer->wkbType() );
108 if ( hasZ || hasM )
109 {
110 emit messageEmitted( QStringLiteral( "layer %1 has %2%3%4 geometry. %2%3%4 values be set to 0 when using offset tool." )
111 .arg( mSourceLayer->name(),
112 hasZ ? QStringLiteral( "Z" ) : QString(),
113 hasZ && hasM ? QStringLiteral( "/" ) : QString(),
114 hasM ? QStringLiteral( "M" ) : QString() )
115 , Qgis::MessageLevel::Warning );
116 }
117 }
118 }
119
120 if ( mOriginalGeometry.isNull() )
121 {
122 emit messageEmitted( tr( "Could not find a nearby feature in any vector layer." ) );
123 cancel();
124 }
125 }
126 else
127 {
128 // second click - apply changes
129 const double offset = calculateOffset( e->snapPoint() );
130 applyOffset( offset, e->modifiers() );
131 }
132 }
133
applyOffsetFromWidget(double offset,Qt::KeyboardModifiers modifiers)134 void QgsMapToolOffsetCurve::applyOffsetFromWidget( double offset, Qt::KeyboardModifiers modifiers )
135 {
136 if ( mSourceLayer && !mOriginalGeometry.isNull() && !qgsDoubleNear( offset, 0 ) )
137 {
138 mGeometryModified = true;
139 applyOffset( offset, modifiers );
140 }
141 }
142
applyOffset(double offset,Qt::KeyboardModifiers modifiers)143 void QgsMapToolOffsetCurve::applyOffset( double offset, Qt::KeyboardModifiers modifiers )
144 {
145 if ( !mSourceLayer || offset == 0.0 )
146 {
147 cancel();
148 return;
149 }
150
151 updateGeometryAndRubberBand( offset );
152
153 // no modification
154 if ( !mGeometryModified )
155 {
156 cancel();
157 return;
158 }
159
160 if ( mModifiedPart >= 0 )
161 {
162 QgsGeometry geometry;
163 int partIndex = 0;
164 const QgsWkbTypes::Type geomType = mOriginalGeometry.wkbType();
165 if ( QgsWkbTypes::geometryType( geomType ) == QgsWkbTypes::LineGeometry )
166 {
167 QgsMultiPolylineXY newMultiLine;
168 const QgsMultiPolylineXY multiLine = mOriginalGeometry.asMultiPolyline();
169 QgsMultiPolylineXY::const_iterator it = multiLine.constBegin();
170 for ( ; it != multiLine.constEnd(); ++it )
171 {
172 if ( partIndex == mModifiedPart )
173 {
174 newMultiLine.append( mModifiedGeometry.asPolyline() );
175 }
176 else
177 {
178 newMultiLine.append( *it );
179 }
180 partIndex++;
181 }
182 geometry = QgsGeometry::fromMultiPolylineXY( newMultiLine );
183 }
184 else
185 {
186 QgsMultiPolygonXY newMultiPoly;
187 const QgsMultiPolygonXY multiPoly = mOriginalGeometry.asMultiPolygon();
188 QgsMultiPolygonXY::const_iterator multiPolyIt = multiPoly.constBegin();
189 for ( ; multiPolyIt != multiPoly.constEnd(); ++multiPolyIt )
190 {
191 if ( partIndex == mModifiedPart )
192 {
193 if ( mModifiedGeometry.isMultipart() )
194 {
195 // not a ring
196 if ( mModifiedRing <= 0 )
197 {
198 // part became mulitpolygon, that means discard original rings from the part
199 newMultiPoly += mModifiedGeometry.asMultiPolygon();
200 }
201 else
202 {
203 // ring became multipolygon, oh boy!
204 QgsPolygonXY newPoly;
205 int ringIndex = 0;
206 QgsPolygonXY::const_iterator polyIt = multiPolyIt->constBegin();
207 for ( ; polyIt != multiPolyIt->constEnd(); ++polyIt )
208 {
209 if ( ringIndex == mModifiedRing )
210 {
211 const QgsMultiPolygonXY ringParts = mModifiedGeometry.asMultiPolygon();
212 QgsPolygonXY newRings;
213 QgsMultiPolygonXY::const_iterator ringIt = ringParts.constBegin();
214 for ( ; ringIt != ringParts.constEnd(); ++ringIt )
215 {
216 // the different parts of the new rings cannot have rings themselves
217 newRings.append( ringIt->at( 0 ) );
218 }
219 newPoly += newRings;
220 }
221 else
222 {
223 newPoly.append( *polyIt );
224 }
225 ringIndex++;
226 }
227 newMultiPoly.append( newPoly );
228 }
229 }
230 else
231 {
232 // original part had no ring
233 if ( mModifiedRing == -1 )
234 {
235 newMultiPoly.append( mModifiedGeometry.asPolygon() );
236 }
237 else
238 {
239 QgsPolygonXY newPoly;
240 int ringIndex = 0;
241 QgsPolygonXY::const_iterator polyIt = multiPolyIt->constBegin();
242 for ( ; polyIt != multiPolyIt->constEnd(); ++polyIt )
243 {
244 if ( ringIndex == mModifiedRing )
245 {
246 newPoly.append( mModifiedGeometry.asPolygon().at( 0 ) );
247 }
248 else
249 {
250 newPoly.append( *polyIt );
251 }
252 ringIndex++;
253 }
254 newMultiPoly.append( newPoly );
255 }
256 }
257 }
258 else
259 {
260 newMultiPoly.append( *multiPolyIt );
261 }
262 partIndex++;
263 }
264 geometry = QgsGeometry::fromMultiPolygonXY( newMultiPoly );
265 }
266 geometry.convertToMultiType();
267 mModifiedGeometry = geometry;
268 }
269 else if ( mModifiedRing >= 0 )
270 {
271 // original geometry had some rings
272 if ( mModifiedGeometry.isMultipart() )
273 {
274 // not a ring
275 if ( mModifiedRing == 0 )
276 {
277 // polygon became mulitpolygon, that means discard original rings from the part
278 // keep the modified geometry as is
279 }
280 else
281 {
282 QgsPolygonXY newPoly;
283 const QgsPolygonXY poly = mOriginalGeometry.asPolygon();
284
285 // ring became multipolygon, oh boy!
286 int ringIndex = 0;
287 QgsPolygonXY::const_iterator polyIt = poly.constBegin();
288 for ( ; polyIt != poly.constEnd(); ++polyIt )
289 {
290 if ( ringIndex == mModifiedRing )
291 {
292 const QgsMultiPolygonXY ringParts = mModifiedGeometry.asMultiPolygon();
293 QgsPolygonXY newRings;
294 QgsMultiPolygonXY::const_iterator ringIt = ringParts.constBegin();
295 for ( ; ringIt != ringParts.constEnd(); ++ringIt )
296 {
297 // the different parts of the new rings cannot have rings themselves
298 newRings.append( ringIt->at( 0 ) );
299 }
300 newPoly += newRings;
301 }
302 else
303 {
304 newPoly.append( *polyIt );
305 }
306 ringIndex++;
307 }
308 mModifiedGeometry = QgsGeometry::fromPolygonXY( newPoly );
309 }
310 }
311 else
312 {
313 // simple case where modified geom is a polygon (not multi)
314 QgsPolygonXY newPoly;
315 const QgsPolygonXY poly = mOriginalGeometry.asPolygon();
316
317 int ringIndex = 0;
318 QgsPolygonXY::const_iterator polyIt = poly.constBegin();
319 for ( ; polyIt != poly.constEnd(); ++polyIt )
320 {
321 if ( ringIndex == mModifiedRing )
322 {
323 newPoly.append( mModifiedGeometry.asPolygon().at( 0 ) );
324 }
325 else
326 {
327 newPoly.append( *polyIt );
328 }
329 ringIndex++;
330 }
331 mModifiedGeometry = QgsGeometry::fromPolygonXY( newPoly );
332 }
333 }
334
335 if ( !mModifiedGeometry.isGeosValid() )
336 {
337 emit messageEmitted( tr( "Generated geometry is not valid." ), Qgis::MessageLevel::Critical );
338 // no cancel, continue editing.
339 return;
340 }
341
342 QgsVectorLayer *destLayer = qobject_cast< QgsVectorLayer * >( canvas()->currentLayer() );
343 if ( !destLayer )
344 return;
345
346 destLayer->beginEditCommand( tr( "Offset curve" ) );
347
348 bool editOk = true;
349 if ( !mCtrlHeldOnFirstClick && !( modifiers & Qt::ControlModifier ) )
350 {
351 editOk = destLayer->changeGeometry( mModifiedFeature, mModifiedGeometry );
352 }
353 else
354 {
355 const QgsCoordinateTransform ct( mSourceLayer->crs(), destLayer->crs(), QgsProject::instance() );
356 try
357 {
358 QgsGeometry g = mModifiedGeometry;
359 g.transform( ct );
360
361 QgsFeature f = mSourceFeature;
362 f.setGeometry( g );
363
364 // auto convert source feature attributes to destination attributes, make geometry compatible
365 // note that this may result in multiple features, e.g. if inserting multipart feature into single-part layer
366 const QgsFeatureList features = QgsVectorLayerUtils::makeFeatureCompatible( f, destLayer );
367 for ( const QgsFeature &feature : features )
368 {
369 QgsAttributeMap attrs;
370 for ( int idx = 0; idx < destLayer->fields().count(); ++idx )
371 {
372 if ( !feature.attribute( idx ).isNull() )
373 attrs[idx] = feature.attribute( idx );
374 }
375
376 QgsExpressionContext context = destLayer->createExpressionContext();
377 // use createFeature to ensure default values and provider side constraints are respected
378 f = QgsVectorLayerUtils::createFeature( destLayer, feature.geometry(), attrs, &context );
379
380 editOk = editOk && destLayer->addFeature( f );
381 }
382 }
383 catch ( QgsCsException & )
384 {
385 editOk = false;
386 }
387 }
388
389 if ( editOk )
390 {
391 destLayer->endEditCommand();
392 }
393 else
394 {
395 destLayer->destroyEditCommand();
396 emit messageEmitted( QStringLiteral( "Could not apply offset" ), Qgis::MessageLevel::Critical );
397 }
398
399 deleteRubberBandAndGeometry();
400 deleteUserInputWidget();
401 destLayer->triggerRepaint();
402 mSourceLayer = nullptr;
403 }
404
cancel()405 void QgsMapToolOffsetCurve::cancel()
406 {
407 deleteUserInputWidget();
408 deleteRubberBandAndGeometry();
409 mSourceLayer = nullptr;
410 }
411
calculateOffset(const QgsPointXY & mapPoint)412 double QgsMapToolOffsetCurve::calculateOffset( const QgsPointXY &mapPoint )
413 {
414 double offset = 0.0;
415 if ( mSourceLayer )
416 {
417 //get offset from current position rectangular to feature
418 const QgsPointXY layerCoords = toLayerCoordinates( mSourceLayer, mapPoint );
419
420 QgsPointXY minDistPoint;
421 int beforeVertex;
422 int leftOf = 0;
423
424 offset = std::sqrt( mManipulatedGeometry.closestSegmentWithContext( layerCoords, minDistPoint, beforeVertex, &leftOf ) );
425 if ( QgsWkbTypes::geometryType( mManipulatedGeometry.wkbType() ) == QgsWkbTypes::LineGeometry )
426 {
427 offset = leftOf < 0 ? offset : -offset;
428 }
429 else
430 {
431 offset = mManipulatedGeometry.contains( &layerCoords ) ? -offset : offset;
432 }
433 }
434 return offset;
435 }
436
canvasMoveEvent(QgsMapMouseEvent * e)437 void QgsMapToolOffsetCurve::canvasMoveEvent( QgsMapMouseEvent *e )
438 {
439 if ( mOriginalGeometry.isNull() || !mRubberBand )
440 {
441 QgsPointLocator::Match match;
442 if ( e->modifiers() & Qt::ControlModifier )
443 {
444 match = mCanvas->snappingUtils()->snapToMap( e->pos(), nullptr );
445 }
446 else
447 {
448 match = mCanvas->snappingUtils()->snapToCurrentLayer( e->pos(),
449 QgsPointLocator::Types( QgsPointLocator::Edge | QgsPointLocator::Area ) );
450 }
451 mSnapIndicator->setMatch( match );
452 return;
453 }
454
455 mGeometryModified = true;
456
457 const QgsPointXY mapPoint = e->snapPoint();
458 mSnapIndicator->setMatch( e->mapPointMatch() );
459
460 const double offset = calculateOffset( mapPoint );
461
462 if ( mUserInputWidget )
463 {
464 disconnect( mUserInputWidget, &QgsOffsetUserWidget::offsetChanged, this, &QgsMapToolOffsetCurve::updateGeometryAndRubberBand );
465 mUserInputWidget->setOffset( offset );
466 connect( mUserInputWidget, &QgsOffsetUserWidget::offsetChanged, this, &QgsMapToolOffsetCurve::updateGeometryAndRubberBand );
467 mUserInputWidget->setFocus( Qt::TabFocusReason );
468 mUserInputWidget->editor()->selectAll();
469 }
470
471 //create offset geometry using geos
472 updateGeometryAndRubberBand( offset );
473 }
474
prepareGeometry(const QgsPointLocator::Match & match,QgsFeature & snappedFeature)475 void QgsMapToolOffsetCurve::prepareGeometry( const QgsPointLocator::Match &match, QgsFeature &snappedFeature )
476 {
477 QgsVectorLayer *vl = match.layer();
478 if ( !vl )
479 {
480 return;
481 }
482
483 mOriginalGeometry = QgsGeometry();
484 mManipulatedGeometry = QgsGeometry();
485 mModifiedPart = -1;
486 mModifiedRing = -1;
487
488 //assign feature part by vertex number (snap to vertex) or by before vertex number (snap to segment)
489 const QgsGeometry geom = snappedFeature.geometry();
490 if ( geom.isNull() )
491 {
492 return;
493 }
494 mOriginalGeometry = geom;
495
496 const QgsWkbTypes::Type geomType = geom.wkbType();
497 if ( QgsWkbTypes::geometryType( geomType ) == QgsWkbTypes::LineGeometry )
498 {
499 if ( !geom.isMultipart() )
500 {
501 mManipulatedGeometry = geom;
502 }
503 else
504 {
505 const int vertex = match.vertexIndex();
506 QgsVertexId vertexId;
507 geom.vertexIdFromVertexNr( vertex, vertexId );
508 mModifiedPart = vertexId.part;
509
510 const QgsMultiPolylineXY multiLine = geom.asMultiPolyline();
511 mManipulatedGeometry = QgsGeometry::fromPolylineXY( multiLine.at( mModifiedPart ) );
512 }
513 }
514 else if ( QgsWkbTypes::geometryType( geomType ) == QgsWkbTypes::PolygonGeometry )
515 {
516 if ( !match.hasEdge() && !match.hasVertex() && match.hasArea() )
517 {
518 if ( !geom.isMultipart() )
519 {
520 mManipulatedGeometry = geom;
521 }
522 else
523 {
524 // get the correct part
525 QgsMultiPolygonXY mpolygon = geom.asMultiPolygon();
526 for ( int part = 0; part < mpolygon.count(); part++ ) // go through the polygons
527 {
528 const QgsPolygonXY &polygon = mpolygon[part];
529 const QgsGeometry partGeo = QgsGeometry::fromPolygonXY( polygon );
530 const QgsPointXY layerCoords = match.point();
531 if ( partGeo.contains( &layerCoords ) )
532 {
533 mModifiedPart = part;
534 mManipulatedGeometry = partGeo;
535 }
536 }
537 }
538 }
539 else if ( match.hasEdge() || match.hasVertex() )
540 {
541 const int vertex = match.vertexIndex();
542 QgsVertexId vertexId;
543 geom.vertexIdFromVertexNr( vertex, vertexId );
544 QgsDebugMsgLevel( QString::number( vertexId.ring ), 2 );
545
546 if ( !geom.isMultipart() )
547 {
548 const QgsPolygonXY poly = geom.asPolygon();
549 // if has rings
550 if ( poly.count() > 0 )
551 {
552 mModifiedRing = vertexId.ring;
553 mManipulatedGeometry = QgsGeometry::fromPolygonXY( QgsPolygonXY() << poly.at( mModifiedRing ) );
554 }
555 else
556 {
557 mManipulatedGeometry = QgsGeometry::fromPolygonXY( poly );
558 }
559
560 }
561 else
562 {
563 mModifiedPart = vertexId.part;
564 // get part, get ring
565 const QgsMultiPolygonXY multiPoly = geom.asMultiPolygon();
566 // if has rings
567 if ( multiPoly.at( mModifiedPart ).count() > 0 )
568 {
569 mModifiedRing = vertexId.ring;
570 mManipulatedGeometry = QgsGeometry::fromPolygonXY( QgsPolygonXY() << multiPoly.at( mModifiedPart ).at( mModifiedRing ) );
571 }
572 else
573 {
574 mManipulatedGeometry = QgsGeometry::fromPolygonXY( multiPoly.at( mModifiedPart ) );
575 }
576 }
577 }
578 }
579 }
580
createUserInputWidget()581 void QgsMapToolOffsetCurve::createUserInputWidget()
582 {
583 deleteUserInputWidget();
584
585 mUserInputWidget = new QgsOffsetUserWidget();
586 mUserInputWidget->setPolygonMode( QgsWkbTypes::geometryType( mOriginalGeometry.wkbType() ) != QgsWkbTypes::LineGeometry );
587 QgisApp::instance()->addUserInputWidget( mUserInputWidget );
588 mUserInputWidget->setFocus( Qt::TabFocusReason );
589
590 connect( mUserInputWidget, &QgsOffsetUserWidget::offsetChanged, this, &QgsMapToolOffsetCurve::updateGeometryAndRubberBand );
591 connect( mUserInputWidget, &QgsOffsetUserWidget::offsetEditingFinished, this, &QgsMapToolOffsetCurve::applyOffsetFromWidget );
592 connect( mUserInputWidget, &QgsOffsetUserWidget::offsetEditingCanceled, this, &QgsMapToolOffsetCurve::cancel );
593
594 connect( mUserInputWidget, &QgsOffsetUserWidget::offsetConfigChanged, this, [ = ] {updateGeometryAndRubberBand( mUserInputWidget->offset() );} );
595 }
596
deleteUserInputWidget()597 void QgsMapToolOffsetCurve::deleteUserInputWidget()
598 {
599 if ( mUserInputWidget )
600 {
601 disconnect( mUserInputWidget, &QgsOffsetUserWidget::offsetChanged, this, &QgsMapToolOffsetCurve::updateGeometryAndRubberBand );
602 disconnect( mUserInputWidget, &QgsOffsetUserWidget::offsetEditingFinished, this, &QgsMapToolOffsetCurve::applyOffsetFromWidget );
603 disconnect( mUserInputWidget, &QgsOffsetUserWidget::offsetEditingCanceled, this, &QgsMapToolOffsetCurve::cancel );
604 mUserInputWidget->releaseKeyboard();
605 mUserInputWidget->deleteLater();
606 }
607 mUserInputWidget = nullptr;
608 }
609
deleteRubberBandAndGeometry()610 void QgsMapToolOffsetCurve::deleteRubberBandAndGeometry()
611 {
612 mOriginalGeometry.set( nullptr );
613 mManipulatedGeometry.set( nullptr );
614 delete mRubberBand;
615 mRubberBand = nullptr;
616 }
617
updateGeometryAndRubberBand(double offset)618 void QgsMapToolOffsetCurve::updateGeometryAndRubberBand( double offset )
619 {
620 if ( !mRubberBand || mOriginalGeometry.isNull() )
621 {
622 return;
623 }
624
625 if ( !mSourceLayer )
626 {
627 return;
628 }
629
630 QgsGeometry offsetGeom;
631 const Qgis::JoinStyle joinStyle = QgsSettingsRegistryCore::settingsDigitizingOffsetJoinStyle.value();
632 const int quadSegments = QgsSettingsRegistryCore::settingsDigitizingOffsetQuadSeg.value();
633 const double miterLimit = QgsSettingsRegistryCore::settingsDigitizingOffsetMiterLimit.value();
634 const Qgis::EndCapStyle capStyle = QgsSettingsRegistryCore::settingsDigitizingOffsetCapStyle.value();
635
636
637 if ( QgsWkbTypes::geometryType( mOriginalGeometry.wkbType() ) == QgsWkbTypes::LineGeometry )
638 {
639 offsetGeom = mManipulatedGeometry.offsetCurve( offset, quadSegments, joinStyle, miterLimit );
640 }
641 else
642 {
643 offsetGeom = mManipulatedGeometry.buffer( offset, quadSegments, capStyle, joinStyle, miterLimit );
644 }
645
646 if ( offsetGeom.isNull() )
647 {
648 deleteRubberBandAndGeometry();
649 deleteUserInputWidget();
650 mSourceLayer = nullptr;
651 mGeometryModified = false;
652 emit messageDiscarded();
653 emit messageEmitted( tr( "Creating offset geometry failed: %1" ).arg( offsetGeom.lastError() ), Qgis::MessageLevel::Critical );
654 }
655 else
656 {
657 mModifiedGeometry = offsetGeom;
658 mRubberBand->setToGeometry( mModifiedGeometry, mSourceLayer );
659 }
660 }
661
662
663 // ******************
664 // Offset User Widget
665
QgsOffsetUserWidget(QWidget * parent)666 QgsOffsetUserWidget::QgsOffsetUserWidget( QWidget *parent )
667 : QWidget( parent )
668 {
669 setupUi( this );
670
671 mOffsetSpinBox->setDecimals( 6 );
672 mOffsetSpinBox->setClearValue( 0.0 );
673
674 // fill comboboxes
675 mJoinStyleComboBox->addItem( tr( "Round" ), static_cast< int >( Qgis::JoinStyle::Round ) );
676 mJoinStyleComboBox->addItem( tr( "Miter" ), static_cast< int >( Qgis::JoinStyle::Miter ) );
677 mJoinStyleComboBox->addItem( tr( "Bevel" ), static_cast< int >( Qgis::JoinStyle::Bevel ) );
678 mCapStyleComboBox->addItem( tr( "Round" ), static_cast< int >( Qgis::EndCapStyle::Round ) );
679 mCapStyleComboBox->addItem( tr( "Flat" ), static_cast< int >( Qgis::EndCapStyle::Flat ) );
680 mCapStyleComboBox->addItem( tr( "Square" ), static_cast< int >( Qgis::EndCapStyle::Square ) );
681
682 const Qgis::JoinStyle joinStyle = QgsSettingsRegistryCore::settingsDigitizingOffsetJoinStyle.value();
683 const int quadSegments = QgsSettingsRegistryCore::settingsDigitizingOffsetQuadSeg.value();
684 const double miterLimit = QgsSettingsRegistryCore::settingsDigitizingOffsetMiterLimit.value();
685 const Qgis::EndCapStyle capStyle = QgsSettingsRegistryCore::settingsDigitizingOffsetCapStyle.value();
686
687 mJoinStyleComboBox->setCurrentIndex( mJoinStyleComboBox->findData( static_cast< int >( joinStyle ) ) );
688 mQuadrantSpinBox->setValue( quadSegments );
689 mQuadrantSpinBox->setClearValue( 8 );
690 mMiterLimitSpinBox->setValue( miterLimit );
691 mMiterLimitSpinBox->setClearValue( 5.0 );
692 mCapStyleComboBox->setCurrentIndex( mCapStyleComboBox->findData( static_cast< int >( capStyle ) ) );
693
694 // connect signals
695 mOffsetSpinBox->installEventFilter( this );
696 connect( mOffsetSpinBox, static_cast < void ( QDoubleSpinBox::* )( double ) > ( &QDoubleSpinBox::valueChanged ), this, &QgsOffsetUserWidget::offsetChanged );
697
698 connect( mJoinStyleComboBox, static_cast < void ( QComboBox::* )( int ) > ( &QComboBox::currentIndexChanged ), this, [ = ] { QgsSettingsRegistryCore::settingsDigitizingOffsetJoinStyle.setValue( static_cast< Qgis::JoinStyle >( mJoinStyleComboBox->currentData().toInt() ) ); emit offsetConfigChanged(); } );
699 connect( mQuadrantSpinBox, static_cast < void ( QSpinBox::* )( int ) > ( &QSpinBox::valueChanged ), this, [ = ]( const int quadSegments ) { QgsSettingsRegistryCore::settingsDigitizingOffsetQuadSeg.setValue( quadSegments ); emit offsetConfigChanged(); } );
700 connect( mMiterLimitSpinBox, static_cast < void ( QDoubleSpinBox::* )( double ) > ( &QDoubleSpinBox::valueChanged ), this, [ = ]( double miterLimit ) { QgsSettingsRegistryCore::settingsDigitizingOffsetMiterLimit.setValue( miterLimit ); emit offsetConfigChanged(); } );
701 connect( mCapStyleComboBox, static_cast < void ( QComboBox::* )( int ) > ( &QComboBox::currentIndexChanged ), this, [ = ] { QgsSettingsRegistryCore::settingsDigitizingOffsetCapStyle.setValue( static_cast< Qgis::EndCapStyle >( mCapStyleComboBox->currentData().toInt() ) ); emit offsetConfigChanged(); } );
702
703 const bool showAdvanced = QgsSettingsRegistryCore::settingsDigitizingOffsetShowAdvanced.value();
704 mShowAdvancedButton->setChecked( showAdvanced );
705 mAdvancedConfigWidget->setVisible( showAdvanced );
706 connect( mShowAdvancedButton, &QToolButton::clicked, mAdvancedConfigWidget, &QWidget::setVisible );
707 connect( mShowAdvancedButton, &QToolButton::clicked, this, [ = ]( const bool clicked ) {QgsSettingsRegistryCore::settingsDigitizingConvertToCurveDistanceTolerance.setValue( clicked );} );
708
709 // config focus
710 setFocusProxy( mOffsetSpinBox );
711 }
712
setOffset(double offset)713 void QgsOffsetUserWidget::setOffset( double offset )
714 {
715 mOffsetSpinBox->setValue( offset );
716 }
717
offset()718 double QgsOffsetUserWidget::offset()
719 {
720 return mOffsetSpinBox->value();
721 }
722
setPolygonMode(bool polygon)723 void QgsOffsetUserWidget::setPolygonMode( bool polygon )
724 {
725 mCapStyleLabel->setEnabled( polygon );
726 mCapStyleComboBox->setEnabled( polygon );
727 }
728
eventFilter(QObject * obj,QEvent * ev)729 bool QgsOffsetUserWidget::eventFilter( QObject *obj, QEvent *ev )
730 {
731 if ( obj == mOffsetSpinBox && ev->type() == QEvent::KeyPress )
732 {
733 QKeyEvent *event = static_cast<QKeyEvent *>( ev );
734 if ( event->key() == Qt::Key_Escape )
735 {
736 emit offsetEditingCanceled();
737 return true;
738 }
739 if ( event->key() == Qt::Key_Enter || event->key() == Qt::Key_Return )
740 {
741 emit offsetEditingFinished( offset(), event->modifiers() );
742 return true;
743 }
744 }
745
746 return false;
747 }
748