Skip to content

Commit 8b83a46

Browse files
committedJun 3, 2019
[needs-docs] Make label map tools follow click-click behavior
It's rather jarring that the move label and rotate label tools don't follow the rest of the application in using click-click to move and rotate and rotate labels, and instead use the older click-and-drag behaviour. This commit reworks the tools to use click to start, click to end behaviour instead. It also makes them respect the same conventions as the move and rotate features tools, where a right click cancels the move/rotate, and same with pressing "esc" mid-operation.
1 parent 942827b commit 8b83a46

File tree

4 files changed

+279
-223
lines changed

4 files changed

+279
-223
lines changed
 

‎src/app/qgsmaptoolmovelabel.cpp

Lines changed: 148 additions & 124 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,6 @@
2424

2525
QgsMapToolMoveLabel::QgsMapToolMoveLabel( QgsMapCanvas *canvas )
2626
: QgsMapToolLabel( canvas )
27-
, mClickOffsetX( 0 )
28-
, mClickOffsetY( 0 )
2927
{
3028
mToolName = tr( "Move label" );
3129

@@ -36,69 +34,6 @@ QgsMapToolMoveLabel::QgsMapToolMoveLabel( QgsMapCanvas *canvas )
3634
mDiagramProperties << QgsDiagramLayerSettings::PositionY;
3735
}
3836

39-
void QgsMapToolMoveLabel::canvasPressEvent( QgsMapMouseEvent *e )
40-
{
41-
deleteRubberBands();
42-
43-
QgsLabelPosition labelPos;
44-
if ( !labelAtPosition( e, labelPos ) )
45-
{
46-
mCurrentLabel = LabelDetails();
47-
return;
48-
}
49-
50-
mCurrentLabel = LabelDetails( labelPos );
51-
52-
QgsVectorLayer *vlayer = mCurrentLabel.layer;
53-
if ( !vlayer )
54-
{
55-
return;
56-
}
57-
58-
int xCol = -1, yCol = -1;
59-
60-
if ( !mCurrentLabel.pos.isDiagram && !labelMoveable( vlayer, mCurrentLabel.settings, xCol, yCol ) )
61-
{
62-
QgsPalIndexes indexes;
63-
64-
if ( createAuxiliaryFields( indexes ) )
65-
return;
66-
67-
if ( !labelMoveable( vlayer, mCurrentLabel.settings, xCol, yCol ) )
68-
return;
69-
70-
xCol = indexes[ QgsPalLayerSettings::PositionX ];
71-
yCol = indexes[ QgsPalLayerSettings::PositionY ];
72-
}
73-
else if ( mCurrentLabel.pos.isDiagram && !diagramMoveable( vlayer, xCol, yCol ) )
74-
{
75-
QgsDiagramIndexes indexes;
76-
77-
if ( createAuxiliaryFields( indexes ) )
78-
return;
79-
80-
if ( !diagramMoveable( vlayer, xCol, yCol ) )
81-
return;
82-
83-
xCol = indexes[ QgsDiagramLayerSettings::PositionX ];
84-
yCol = indexes[ QgsDiagramLayerSettings::PositionY ];
85-
}
86-
87-
if ( xCol >= 0 && yCol >= 0 )
88-
{
89-
mStartPointMapCoords = toMapCoordinates( e->pos() );
90-
QgsPointXY referencePoint;
91-
if ( !currentLabelRotationPoint( referencePoint, !currentLabelPreserveRotation(), false ) )
92-
{
93-
referencePoint.setX( mCurrentLabel.pos.labelRect.xMinimum() );
94-
referencePoint.setY( mCurrentLabel.pos.labelRect.yMinimum() );
95-
}
96-
mClickOffsetX = mStartPointMapCoords.x() - referencePoint.x();
97-
mClickOffsetY = mStartPointMapCoords.y() - referencePoint.y();
98-
createRubberBands();
99-
}
100-
}
101-
10237
void QgsMapToolMoveLabel::canvasMoveEvent( QgsMapMouseEvent *e )
10338
{
10439
if ( mLabelRubberBand )
@@ -119,80 +54,169 @@ void QgsMapToolMoveLabel::canvasReleaseEvent( QgsMapMouseEvent *e )
11954
{
12055
if ( !mLabelRubberBand )
12156
{
122-
return;
123-
}
57+
if ( e->button() != Qt::LeftButton )
58+
return;
12459

125-
deleteRubberBands();
60+
// first click starts move
61+
deleteRubberBands();
12662

127-
QgsVectorLayer *vlayer = mCurrentLabel.layer;
128-
if ( !vlayer )
129-
{
130-
return;
131-
}
63+
QgsLabelPosition labelPos;
64+
if ( !labelAtPosition( e, labelPos ) )
65+
{
66+
mCurrentLabel = LabelDetails();
67+
return;
68+
}
13269

133-
QgsPointXY releaseCoords = toMapCoordinates( e->pos() );
134-
double xdiff = releaseCoords.x() - mStartPointMapCoords.x();
135-
double ydiff = releaseCoords.y() - mStartPointMapCoords.y();
70+
mCurrentLabel = LabelDetails( labelPos );
13671

137-
int xCol, yCol;
138-
double xPosOrig, yPosOrig;
139-
bool xSuccess, ySuccess;
72+
QgsVectorLayer *vlayer = mCurrentLabel.layer;
73+
if ( !vlayer )
74+
{
75+
return;
76+
}
14077

141-
if ( !currentLabelDataDefinedPosition( xPosOrig, xSuccess, yPosOrig, ySuccess, xCol, yCol ) )
142-
{
143-
return;
144-
}
78+
int xCol = -1, yCol = -1;
14579

146-
double xPosNew, yPosNew;
80+
if ( !mCurrentLabel.pos.isDiagram && !labelMoveable( vlayer, mCurrentLabel.settings, xCol, yCol ) )
81+
{
82+
QgsPalIndexes indexes;
14783

148-
if ( !xSuccess || !ySuccess )
149-
{
150-
xPosNew = releaseCoords.x() - mClickOffsetX;
151-
yPosNew = releaseCoords.y() - mClickOffsetY;
152-
}
153-
else
154-
{
155-
//transform to map crs first, because xdiff,ydiff are in map coordinates
156-
const QgsMapSettings &ms = mCanvas->mapSettings();
157-
QgsPointXY transformedPoint = ms.layerToMapCoordinates( vlayer, QgsPointXY( xPosOrig, yPosOrig ) );
158-
xPosOrig = transformedPoint.x();
159-
yPosOrig = transformedPoint.y();
160-
xPosNew = xPosOrig + xdiff;
161-
yPosNew = yPosOrig + ydiff;
162-
}
84+
if ( createAuxiliaryFields( indexes ) )
85+
return;
16386

164-
//transform back to layer crs
165-
if ( mCanvas )
166-
{
167-
const QgsMapSettings &s = mCanvas->mapSettings();
168-
QgsPointXY transformedPoint = s.mapToLayerCoordinates( vlayer, QgsPointXY( xPosNew, yPosNew ) );
169-
xPosNew = transformedPoint.x();
170-
yPosNew = transformedPoint.y();
171-
}
87+
if ( !labelMoveable( vlayer, mCurrentLabel.settings, xCol, yCol ) )
88+
return;
89+
90+
xCol = indexes[ QgsPalLayerSettings::PositionX ];
91+
yCol = indexes[ QgsPalLayerSettings::PositionY ];
92+
}
93+
else if ( mCurrentLabel.pos.isDiagram && !diagramMoveable( vlayer, xCol, yCol ) )
94+
{
95+
QgsDiagramIndexes indexes;
96+
97+
if ( createAuxiliaryFields( indexes ) )
98+
return;
17299

173-
vlayer->beginEditCommand( tr( "Moved label" ) + QStringLiteral( " '%1'" ).arg( currentLabelText( 24 ) ) );
174-
vlayer->changeAttributeValue( mCurrentLabel.pos.featureId, xCol, xPosNew );
175-
vlayer->changeAttributeValue( mCurrentLabel.pos.featureId, yCol, yPosNew );
100+
if ( !diagramMoveable( vlayer, xCol, yCol ) )
101+
return;
102+
103+
xCol = indexes[ QgsDiagramLayerSettings::PositionX ];
104+
yCol = indexes[ QgsDiagramLayerSettings::PositionY ];
105+
}
176106

177-
// set rotation to that of label, if data-defined and no rotation set yet
178-
// honor whether to preserve preexisting data on pin
179-
// must come after setting x and y positions
180-
if ( !mCurrentLabel.pos.isDiagram
181-
&& !mCurrentLabel.pos.isPinned
182-
&& !currentLabelPreserveRotation() )
107+
if ( xCol >= 0 && yCol >= 0 )
108+
{
109+
mStartPointMapCoords = toMapCoordinates( e->pos() );
110+
QgsPointXY referencePoint;
111+
if ( !currentLabelRotationPoint( referencePoint, !currentLabelPreserveRotation(), false ) )
112+
{
113+
referencePoint.setX( mCurrentLabel.pos.labelRect.xMinimum() );
114+
referencePoint.setY( mCurrentLabel.pos.labelRect.yMinimum() );
115+
}
116+
mClickOffsetX = mStartPointMapCoords.x() - referencePoint.x();
117+
mClickOffsetY = mStartPointMapCoords.y() - referencePoint.y();
118+
createRubberBands();
119+
}
120+
}
121+
else
183122
{
184-
double defRot;
185-
bool rSuccess;
186-
int rCol;
187-
if ( currentLabelDataDefinedRotation( defRot, rSuccess, rCol ) )
123+
switch ( e->button() )
188124
{
189-
double labelRot = mCurrentLabel.pos.rotation * 180 / M_PI;
190-
vlayer->changeAttributeValue( mCurrentLabel.pos.featureId, rCol, labelRot );
125+
case Qt::RightButton:
126+
{
127+
// right click is cancel
128+
deleteRubberBands();
129+
return;
130+
}
131+
132+
case Qt::LeftButton:
133+
{
134+
// second click drops label
135+
deleteRubberBands();
136+
QgsVectorLayer *vlayer = mCurrentLabel.layer;
137+
if ( !vlayer )
138+
{
139+
return;
140+
}
141+
142+
QgsPointXY releaseCoords = toMapCoordinates( e->pos() );
143+
double xdiff = releaseCoords.x() - mStartPointMapCoords.x();
144+
double ydiff = releaseCoords.y() - mStartPointMapCoords.y();
145+
146+
int xCol, yCol;
147+
double xPosOrig, yPosOrig;
148+
bool xSuccess, ySuccess;
149+
150+
if ( !currentLabelDataDefinedPosition( xPosOrig, xSuccess, yPosOrig, ySuccess, xCol, yCol ) )
151+
{
152+
return;
153+
}
154+
155+
double xPosNew, yPosNew;
156+
157+
if ( !xSuccess || !ySuccess )
158+
{
159+
xPosNew = releaseCoords.x() - mClickOffsetX;
160+
yPosNew = releaseCoords.y() - mClickOffsetY;
161+
}
162+
else
163+
{
164+
//transform to map crs first, because xdiff,ydiff are in map coordinates
165+
const QgsMapSettings &ms = mCanvas->mapSettings();
166+
QgsPointXY transformedPoint = ms.layerToMapCoordinates( vlayer, QgsPointXY( xPosOrig, yPosOrig ) );
167+
xPosOrig = transformedPoint.x();
168+
yPosOrig = transformedPoint.y();
169+
xPosNew = xPosOrig + xdiff;
170+
yPosNew = yPosOrig + ydiff;
171+
}
172+
173+
//transform back to layer crs
174+
if ( mCanvas )
175+
{
176+
const QgsMapSettings &s = mCanvas->mapSettings();
177+
QgsPointXY transformedPoint = s.mapToLayerCoordinates( vlayer, QgsPointXY( xPosNew, yPosNew ) );
178+
xPosNew = transformedPoint.x();
179+
yPosNew = transformedPoint.y();
180+
}
181+
182+
vlayer->beginEditCommand( tr( "Moved label" ) + QStringLiteral( " '%1'" ).arg( currentLabelText( 24 ) ) );
183+
vlayer->changeAttributeValue( mCurrentLabel.pos.featureId, xCol, xPosNew );
184+
vlayer->changeAttributeValue( mCurrentLabel.pos.featureId, yCol, yPosNew );
185+
186+
// set rotation to that of label, if data-defined and no rotation set yet
187+
// honor whether to preserve preexisting data on pin
188+
// must come after setting x and y positions
189+
if ( !mCurrentLabel.pos.isDiagram
190+
&& !mCurrentLabel.pos.isPinned
191+
&& !currentLabelPreserveRotation() )
192+
{
193+
double defRot;
194+
bool rSuccess;
195+
int rCol;
196+
if ( currentLabelDataDefinedRotation( defRot, rSuccess, rCol ) )
197+
{
198+
double labelRot = mCurrentLabel.pos.rotation * 180 / M_PI;
199+
vlayer->changeAttributeValue( mCurrentLabel.pos.featureId, rCol, labelRot );
200+
}
201+
}
202+
vlayer->endEditCommand();
203+
204+
vlayer->triggerRepaint();
205+
break;
206+
}
207+
default:
208+
break;
191209
}
192210
}
193-
vlayer->endEditCommand();
211+
}
194212

195-
vlayer->triggerRepaint();
213+
void QgsMapToolMoveLabel::keyReleaseEvent( QKeyEvent *e )
214+
{
215+
if ( mLabelRubberBand && e->key() == Qt::Key_Escape )
216+
{
217+
// escape is cancel
218+
deleteRubberBands();
219+
}
196220
}
197221

198222

‎src/app/qgsmaptoolmovelabel.h

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,19 +29,17 @@ class APP_EXPORT QgsMapToolMoveLabel: public QgsMapToolLabel
2929
public:
3030
QgsMapToolMoveLabel( QgsMapCanvas *canvas );
3131

32-
void canvasPressEvent( QgsMapMouseEvent *e ) override;
33-
3432
void canvasMoveEvent( QgsMapMouseEvent *e ) override;
35-
3633
void canvasReleaseEvent( QgsMapMouseEvent *e ) override;
34+
void keyReleaseEvent( QKeyEvent *e ) override;
3735

3836
protected:
3937

4038
//! Start point of the move in map coordinates
4139
QgsPointXY mStartPointMapCoords;
4240

43-
double mClickOffsetX;
44-
double mClickOffsetY;
41+
double mClickOffsetX = 0;
42+
double mClickOffsetY = 0;
4543
};
4644

4745
#endif // QGSMAPTOOLMOVELABEL_H

‎src/app/qgsmaptoolrotatelabel.cpp

Lines changed: 127 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -42,71 +42,6 @@ QgsMapToolRotateLabel::~QgsMapToolRotateLabel()
4242
delete mRotationPreviewBox;
4343
}
4444

45-
void QgsMapToolRotateLabel::canvasPressEvent( QgsMapMouseEvent *e )
46-
{
47-
deleteRubberBands();
48-
49-
QgsLabelPosition labelPos;
50-
if ( !labelAtPosition( e, labelPos ) )
51-
{
52-
mCurrentLabel = LabelDetails();
53-
return;
54-
}
55-
56-
mCurrentLabel = LabelDetails( labelPos );
57-
58-
if ( !mCurrentLabel.valid )
59-
return;
60-
61-
// only rotate non-pinned OverPoint placements until other placements are supported in pal::Feature
62-
63-
if ( !mCurrentLabel.pos.isPinned
64-
&& mCurrentLabel.settings.placement != QgsPalLayerSettings::OverPoint )
65-
{
66-
return;
67-
}
68-
69-
// rotate unpinned labels (i.e. no hali/vali settings) as if hali/vali was Center/Half
70-
if ( !currentLabelRotationPoint( mRotationPoint, false, !mCurrentLabel.pos.isPinned ) )
71-
{
72-
return;
73-
}
74-
75-
{
76-
mCurrentMouseAzimuth = convertAzimuth( mRotationPoint.azimuth( toMapCoordinates( e->pos() ) ) );
77-
78-
bool hasRotationValue;
79-
int rotationCol;
80-
81-
if ( !labelIsRotatable( mCurrentLabel.layer, mCurrentLabel.settings, rotationCol ) )
82-
{
83-
QgsPalIndexes indexes;
84-
if ( createAuxiliaryFields( indexes ) )
85-
return;
86-
87-
if ( !labelIsRotatable( mCurrentLabel.layer, mCurrentLabel.settings, rotationCol ) )
88-
return;
89-
}
90-
91-
if ( currentLabelDataDefinedRotation( mCurrentRotation, hasRotationValue, rotationCol, true ) )
92-
{
93-
if ( !hasRotationValue )
94-
{
95-
mCurrentRotation = 0;
96-
}
97-
mStartRotation = mCurrentRotation;
98-
createRubberBands();
99-
100-
mRotationPreviewBox = createRotationPreviewBox();
101-
102-
mRotationItem = new QgsPointRotationItem( mCanvas );
103-
mRotationItem->setOrientation( QgsPointRotationItem::Clockwise );
104-
mRotationItem->setPointLocation( mRotationPoint );
105-
mRotationItem->setSymbolRotation( mCurrentRotation );
106-
}
107-
}
108-
}
109-
11045
void QgsMapToolRotateLabel::canvasMoveEvent( QgsMapMouseEvent *e )
11146
{
11247
if ( mLabelRubberBand )
@@ -133,7 +68,7 @@ void QgsMapToolRotateLabel::canvasMoveEvent( QgsMapMouseEvent *e )
13368
}
13469
else
13570
{
136-
displayValue = ( int )( mCurrentRotation );
71+
displayValue = static_cast< int >( mCurrentRotation );
13772
mCtrlPressed = false;
13873
}
13974

@@ -148,46 +83,145 @@ void QgsMapToolRotateLabel::canvasMoveEvent( QgsMapMouseEvent *e )
14883

14984
void QgsMapToolRotateLabel::canvasReleaseEvent( QgsMapMouseEvent *e )
15085
{
151-
Q_UNUSED( e )
152-
153-
if ( !mLabelRubberBand ) //no rubber band created (most likely because the current label cannot be rotated )
86+
if ( !mLabelRubberBand )
15487
{
155-
return;
156-
}
88+
if ( e->button() != Qt::LeftButton )
89+
return;
15790

158-
deleteRubberBands();
159-
delete mRotationItem;
160-
mRotationItem = nullptr;
161-
delete mRotationPreviewBox;
162-
mRotationPreviewBox = nullptr;
91+
// first click starts rotation tool
92+
deleteRubberBands();
16393

164-
QgsVectorLayer *vlayer = mCurrentLabel.layer;
165-
if ( !vlayer )
166-
{
167-
return;
168-
}
94+
QgsLabelPosition labelPos;
95+
if ( !labelAtPosition( e, labelPos ) )
96+
{
97+
mCurrentLabel = LabelDetails();
98+
return;
99+
}
100+
101+
mCurrentLabel = LabelDetails( labelPos );
102+
103+
if ( !mCurrentLabel.valid )
104+
return;
105+
106+
// only rotate non-pinned OverPoint placements until other placements are supported in pal::Feature
107+
108+
if ( !mCurrentLabel.pos.isPinned
109+
&& mCurrentLabel.settings.placement != QgsPalLayerSettings::OverPoint )
110+
{
111+
return;
112+
}
113+
114+
// rotate unpinned labels (i.e. no hali/vali settings) as if hali/vali was Center/Half
115+
if ( !currentLabelRotationPoint( mRotationPoint, false, !mCurrentLabel.pos.isPinned ) )
116+
{
117+
return;
118+
}
119+
120+
{
121+
mCurrentMouseAzimuth = convertAzimuth( mRotationPoint.azimuth( toMapCoordinates( e->pos() ) ) );
122+
123+
bool hasRotationValue;
124+
int rotationCol;
125+
126+
if ( !labelIsRotatable( mCurrentLabel.layer, mCurrentLabel.settings, rotationCol ) )
127+
{
128+
QgsPalIndexes indexes;
129+
if ( createAuxiliaryFields( indexes ) )
130+
return;
169131

170-
int rotationCol;
171-
if ( !labelIsRotatable( vlayer, mCurrentLabel.settings, rotationCol ) )
132+
if ( !labelIsRotatable( mCurrentLabel.layer, mCurrentLabel.settings, rotationCol ) )
133+
return;
134+
}
135+
136+
if ( currentLabelDataDefinedRotation( mCurrentRotation, hasRotationValue, rotationCol, true ) )
137+
{
138+
if ( !hasRotationValue )
139+
{
140+
mCurrentRotation = 0;
141+
}
142+
mStartRotation = mCurrentRotation;
143+
createRubberBands();
144+
145+
mRotationPreviewBox = createRotationPreviewBox();
146+
147+
mRotationItem = new QgsPointRotationItem( mCanvas );
148+
mRotationItem->setOrientation( QgsPointRotationItem::Clockwise );
149+
mRotationItem->setPointLocation( mRotationPoint );
150+
mRotationItem->setSymbolRotation( static_cast< int >( mCurrentRotation ) );
151+
}
152+
}
153+
}
154+
else
172155
{
173-
return;
156+
switch ( e->button() )
157+
{
158+
case Qt::RightButton:
159+
{
160+
// right click is cancel
161+
deleteRubberBands();
162+
delete mRotationItem;
163+
mRotationItem = nullptr;
164+
delete mRotationPreviewBox;
165+
mRotationPreviewBox = nullptr;
166+
return;
167+
}
168+
169+
case Qt::LeftButton:
170+
{
171+
// second click locks in rotation
172+
deleteRubberBands();
173+
delete mRotationItem;
174+
mRotationItem = nullptr;
175+
delete mRotationPreviewBox;
176+
mRotationPreviewBox = nullptr;
177+
178+
QgsVectorLayer *vlayer = mCurrentLabel.layer;
179+
if ( !vlayer )
180+
{
181+
return;
182+
}
183+
184+
int rotationCol;
185+
if ( !labelIsRotatable( vlayer, mCurrentLabel.settings, rotationCol ) )
186+
{
187+
return;
188+
}
189+
190+
double rotation = mCtrlPressed ? roundTo15Degrees( mCurrentRotation ) : mCurrentRotation;
191+
if ( qgsDoubleNear( rotation, mStartRotation ) ) //mouse button pressed / released, but no rotation
192+
{
193+
return;
194+
}
195+
196+
vlayer->beginEditCommand( tr( "Rotated label" ) + QStringLiteral( " '%1'" ).arg( currentLabelText( 24 ) ) );
197+
vlayer->changeAttributeValue( mCurrentLabel.pos.featureId, rotationCol, rotation );
198+
vlayer->endEditCommand();
199+
vlayer->triggerRepaint();
200+
break;
201+
}
202+
203+
default:
204+
break;
205+
}
174206
}
207+
}
175208

176-
double rotation = mCtrlPressed ? roundTo15Degrees( mCurrentRotation ) : mCurrentRotation;
177-
if ( rotation == mStartRotation ) //mouse button pressed / released, but no rotation
209+
void QgsMapToolRotateLabel::keyReleaseEvent( QKeyEvent *e )
210+
{
211+
if ( mLabelRubberBand && e->key() == Qt::Key_Escape )
178212
{
179-
return;
213+
// escape is cancel
214+
deleteRubberBands();
215+
delete mRotationItem;
216+
mRotationItem = nullptr;
217+
delete mRotationPreviewBox;
218+
mRotationPreviewBox = nullptr;
180219
}
181-
182-
vlayer->beginEditCommand( tr( "Rotated label" ) + QStringLiteral( " '%1'" ).arg( currentLabelText( 24 ) ) );
183-
vlayer->changeAttributeValue( mCurrentLabel.pos.featureId, rotationCol, rotation );
184-
vlayer->endEditCommand();
185-
vlayer->triggerRepaint();
186220
}
187221

188222
int QgsMapToolRotateLabel::roundTo15Degrees( double n )
189223
{
190-
int m = ( int )( n / 15.0 + 0.5 );
224+
int m = static_cast< int >( n / 15.0 + 0.5 );
191225
return ( m * 15 );
192226
}
193227

‎src/app/qgsmaptoolrotatelabel.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,9 @@ class APP_EXPORT QgsMapToolRotateLabel: public QgsMapToolLabel
3030
QgsMapToolRotateLabel( QgsMapCanvas *canvas );
3131
~QgsMapToolRotateLabel() override;
3232

33-
void canvasPressEvent( QgsMapMouseEvent *e ) override;
3433
void canvasMoveEvent( QgsMapMouseEvent *e ) override;
3534
void canvasReleaseEvent( QgsMapMouseEvent *e ) override;
35+
void keyReleaseEvent( QKeyEvent *e ) override;
3636

3737
protected:
3838

0 commit comments

Comments
 (0)
Please sign in to comment.