17 #include <QGraphicsView> 18 #include <QGraphicsSceneHoverEvent> 36 , mComposition( composition )
37 , mGraphicsView( nullptr )
38 , mCurrentMouseMoveAction( NoAction )
39 , mBeginHandleWidth( 0 )
40 , mBeginHandleHeight( 0 )
43 , mIsDragging( false )
44 , mIsResizing( false )
45 , mHAlignSnapItem( nullptr )
46 , mVAlignSnapItem( nullptr )
74 mGraphicsView = viewList.
at( 0 );
85 Q_UNUSED( itemStyle );
97 double rectHandlerSize = rectHandlerBorderTolerance();
98 drawHandles( painter, rectHandlerSize );
104 drawSelectedItemBounds( painter );
108 void QgsComposerMouseHandles::drawHandles(
QPainter* painter,
double rectHandlerSize )
113 painter->
setPen( handlePen );
122 painter->
drawRect(
QRectF( 0, 0, rectHandlerSize, rectHandlerSize ) );
124 painter->
drawRect(
QRectF((
rect().width() - rectHandlerSize ) / 2, 0, rectHandlerSize, rectHandlerSize ) );
126 painter->
drawRect(
QRectF(
rect().width() - rectHandlerSize, 0, rectHandlerSize, rectHandlerSize ) );
128 painter->
drawRect(
QRectF( 0, (
rect().height() - rectHandlerSize ) / 2, rectHandlerSize, rectHandlerSize ) );
130 painter->
drawRect(
QRectF(
rect().width() - rectHandlerSize, (
rect().height() - rectHandlerSize ) / 2, rectHandlerSize, rectHandlerSize ) );
132 painter->
drawRect(
QRectF( 0,
rect().height() - rectHandlerSize, rectHandlerSize, rectHandlerSize ) );
134 painter->
drawRect(
QRectF((
rect().width() - rectHandlerSize ) / 2,
rect().height() - rectHandlerSize, rectHandlerSize, rectHandlerSize ) );
136 painter->
drawRect(
QRectF(
rect().width() - rectHandlerSize,
rect().height() - rectHandlerSize, rectHandlerSize, rectHandlerSize ) );
139 void QgsComposerMouseHandles::drawSelectedItemBounds(
QPainter* painter )
154 selectedItemPen.
setStyle( Qt::DashLine );
156 painter->
setPen( selectedItemPen );
160 for ( ; itemIter != selectedItems.
end(); ++itemIter )
164 if ( mIsDragging && !( *itemIter )->positionLock() )
168 QPolygonF itemSceneBounds = ( *itemIter )->mapToScene(( *itemIter )->rectWithFrame() );
175 else if ( mIsResizing && !( *itemIter )->positionLock() )
178 if ( selectedItems.
size() > 1 )
195 itemBounds =
mapRectFromItem(( *itemIter ), ( *itemIter )->rectWithFrame() );
212 for ( ; itemIt != itemList.
end(); ++itemIt )
240 if ( !mIsDragging && !mIsResizing )
249 if ( !mIsDragging && !mIsResizing )
256 void QgsComposerMouseHandles::updateHandles()
262 if ( !selectedItems.
isEmpty() )
268 if ( selectionRotation( rotation ) )
280 QRectF newHandleBounds = selectionBounds();
297 QRectF QgsComposerMouseHandles::selectionBounds()
const 307 for ( ++itemIter; itemIter != selectedItems.
end(); ++itemIter )
315 bool QgsComposerMouseHandles::selectionRotation(
double &
rotation )
const 322 double firstItemRotation = ( *itemIter )->itemRotation();
325 for ( ++itemIter; itemIter != selectedItems.
end(); ++itemIter )
327 if ( !
qgsDoubleNear(( *itemIter )->itemRotation(), firstItemRotation ) )
335 rotation = firstItemRotation;
339 double QgsComposerMouseHandles::rectHandlerBorderTolerance()
343 double viewScaleFactor = graphicsView()->
transform().
m11();
346 double rectHandlerSize = 10.0 / viewScaleFactor;
349 if ( rectHandlerSize > (
rect().width() / 3 ) )
353 if ( rectHandlerSize > (
rect().height() / 3 ) )
357 return rectHandlerSize;
360 Qt::CursorShape QgsComposerMouseHandles::cursorForPosition(
QPointF itemCoordPos )
363 switch ( mouseAction )
366 return Qt::ForbiddenCursor;
368 return Qt::SizeAllCursor;
374 return Qt::SizeVerCursor;
378 return Qt::SizeBDiagCursor;
382 return Qt::SizeHorCursor;
386 return Qt::SizeFDiagCursor;
393 return Qt::SizeHorCursor;
397 return Qt::SizeFDiagCursor;
401 return Qt::SizeVerCursor;
405 return Qt::SizeBDiagCursor;
413 return Qt::SizeFDiagCursor;
417 return Qt::SizeVerCursor;
421 return Qt::SizeBDiagCursor;
425 return Qt::SizeHorCursor;
432 return Qt::SizeBDiagCursor;
436 return Qt::SizeHorCursor;
440 return Qt::SizeFDiagCursor;
444 return Qt::SizeVerCursor;
448 return Qt::ArrowCursor;
454 bool nearLeftBorder =
false;
455 bool nearRightBorder =
false;
456 bool nearLowerBorder =
false;
457 bool nearUpperBorder =
false;
459 bool withinWidth =
false;
460 bool withinHeight =
false;
461 if ( itemCoordPos.
x() >= 0 && itemCoordPos.
x() <=
rect().
width() )
465 if ( itemCoordPos.
y() >= 0 && itemCoordPos.
y() <=
rect().
height() )
470 double borderTolerance = rectHandlerBorderTolerance();
472 if ( itemCoordPos.
x() >= 0 && itemCoordPos.
x() < borderTolerance )
474 nearLeftBorder =
true;
476 if ( itemCoordPos.
y() >= 0 && itemCoordPos.
y() < borderTolerance )
478 nearUpperBorder =
true;
480 if ( itemCoordPos.
x() <=
rect().
width() && itemCoordPos.
x() > (
rect().
width() - borderTolerance ) )
482 nearRightBorder =
true;
486 nearLowerBorder =
true;
489 if ( nearLeftBorder && nearUpperBorder )
493 else if ( nearLeftBorder && nearLowerBorder )
497 else if ( nearRightBorder && nearUpperBorder )
501 else if ( nearRightBorder && nearLowerBorder )
505 else if ( nearLeftBorder && withinHeight )
509 else if ( nearRightBorder && withinHeight )
513 else if ( nearUpperBorder && withinWidth )
517 else if ( nearLowerBorder && withinWidth )
525 if ( itemsAtCursorPos.
isEmpty() )
531 for ( ; itemIter != itemsAtCursorPos.
end(); ++itemIter )
549 return mouseActionForPosition( itemPos );
554 setViewportCursor( cursorForPosition( event->
pos() ) );
560 setViewportCursor( Qt::ArrowCursor );
563 void QgsComposerMouseHandles::setViewportCursor( Qt::CursorShape
cursor )
581 dragMouseMove( event->
lastScenePos(),
event->modifiers() & Qt::ShiftModifier,
event->modifiers() & Qt::ControlModifier );
583 else if ( mIsResizing )
588 resizeMouseMove( event->
lastScenePos(),
event->modifiers() & Qt::ShiftModifier,
event->modifiers() & Qt::AltModifier );
591 mLastMouseEventPos =
event->lastScenePos();
596 QPointF mouseMoveStopPoint =
event->lastScenePos();
597 double diffX = mouseMoveStopPoint.
x() - mMouseMoveStartPos.
x();
598 double diffY = mouseMoveStopPoint.
y() - mMouseMoveStartPos.
y();
619 for ( ; itemIter != selectedItems.
end(); ++itemIter )
621 if (( *itemIter )->positionLock() || (( *itemIter )->flags() & QGraphicsItem::ItemIsSelectable ) == 0 )
628 ( *itemIter )->move( mEndHandleMovePos.
x() - mBeginHandlePos.
x(), mEndHandleMovePos.
y() - mBeginHandlePos.
y() );
642 for ( ; itemIter != selectedItems.
end(); ++itemIter )
644 if (( *itemIter )->positionLock() || (( *itemIter )->flags() & QGraphicsItem::ItemIsSelectable ) == 0 )
653 if ( selectedItems.
size() == 1 )
656 itemRect = mResizeRect;
661 itemRect =
mapRectFromItem(( *itemIter ), ( *itemIter )->rectWithFrame() );
688 setViewportCursor( Qt::ArrowCursor );
696 void QgsComposerMouseHandles::resetStatusBar()
699 int selectedCount = selectedItems.
size();
700 if ( selectedCount > 1 )
705 else if ( selectedCount == 1 )
720 mMouseMoveStartPos =
event->lastScenePos();
721 mLastMouseEventPos =
event->lastScenePos();
723 mBeginMouseEventPos =
event->lastScenePos();
728 mCurrentMouseMoveAction = mouseActionForPosition( event->
pos() );
742 mResizeRect =
QRectF( 0, 0, mBeginHandleWidth, mBeginHandleHeight );
745 mCursorOffset = calcCursorEdgeOffset( mMouseMoveStartPos );
756 QSizeF QgsComposerMouseHandles::calcCursorEdgeOffset(
QPointF cursorPos )
761 switch ( mCurrentMouseMoveAction )
765 return QSizeF( 0, sceneMousePos.
y() );
772 return QSizeF( sceneMousePos.
x(), 0 );
779 return QSizeF( sceneMousePos.
x(), sceneMousePos.
y() );
796 void QgsComposerMouseHandles::dragMouseMove(
QPointF currentPosition,
bool lockMovement,
bool preventSnap )
804 double moveX = currentPosition.
x() - mBeginMouseEventPos.
x();
805 double moveY = currentPosition.
y() - mBeginMouseEventPos.
y();
808 QPointF upperLeftPoint( mBeginHandlePos.
x() + moveX, mBeginHandlePos.
y() + moveY );
820 snappedLeftPoint = upperLeftPoint;
825 double moveRectX = snappedLeftPoint.x() - mBeginHandlePos.
x();
826 double moveRectY = snappedLeftPoint.y() - mBeginHandlePos.
y();
832 if ( qAbs( moveRectX ) <= qAbs( moveRectY ) )
844 moveTransform.
translate( moveRectX, moveRectY );
850 void QgsComposerMouseHandles::resizeMouseMove(
QPointF currentPosition,
bool lockRatio,
bool fromCenter )
858 double mx = 0.0, my = 0.0, rx = 0.0, ry = 0.0;
879 double diffX = finalPosition.
x() - beginMousePos.
x();
880 double diffY = finalPosition.
y() - beginMousePos.
y();
883 if ( lockRatio && !
qgsDoubleNear( mBeginHandleHeight, 0.0 ) )
885 ratio = mBeginHandleWidth / mBeginHandleHeight;
888 switch ( mCurrentMouseMoveAction )
895 diffX = (( mBeginHandleHeight - diffY ) * ratio ) - mBeginHandleWidth;
915 diffX = (( mBeginHandleHeight + diffY ) * ratio ) - mBeginHandleWidth;
936 diffY = (( mBeginHandleWidth - diffX ) / ratio ) - mBeginHandleHeight;
955 diffY = (( mBeginHandleWidth + diffX ) / ratio ) - mBeginHandleHeight;
976 if (( mBeginHandleWidth - diffX ) / ( mBeginHandleHeight - diffY ) > ratio )
978 diffX = mBeginHandleWidth - (( mBeginHandleHeight - diffY ) * ratio );
982 diffY = mBeginHandleHeight - (( mBeginHandleWidth - diffX ) / ratio );
985 mx = diffX, my = diffY;
996 if (( mBeginHandleWidth + diffX ) / ( mBeginHandleHeight + diffY ) > ratio )
998 diffX = (( mBeginHandleHeight + diffY ) * ratio ) - mBeginHandleWidth;
1002 diffY = (( mBeginHandleWidth + diffX ) / ratio ) - mBeginHandleHeight;
1007 rx = diffX, ry = diffY;
1016 if (( mBeginHandleWidth + diffX ) / ( mBeginHandleHeight - diffY ) > ratio )
1018 diffX = (( mBeginHandleHeight - diffY ) * ratio ) - mBeginHandleWidth;
1022 diffY = mBeginHandleHeight - (( mBeginHandleWidth + diffX ) / ratio );
1026 my = diffY, rx = diffX, ry = -diffY;
1035 if (( mBeginHandleWidth - diffX ) / ( mBeginHandleHeight + diffY ) > ratio )
1037 diffX = mBeginHandleWidth - (( mBeginHandleHeight + diffY ) * ratio );
1041 diffY = (( mBeginHandleWidth - diffX ) / ratio ) - mBeginHandleHeight;
1068 mResizeMoveX = mBeginHandleWidth + rx > 0 ? mx : mx + mBeginHandleWidth + rx;
1069 mResizeMoveY = mBeginHandleHeight + ry > 0 ? my : my + mBeginHandleHeight + ry;
1072 QLineF translateLine =
QLineF( 0, 0, mResizeMoveX, mResizeMoveY );
1074 QPointF sceneTranslate = translateLine.
p2();
1078 itemTransform.
translate( sceneTranslate.
x(), sceneTranslate.
y() );
1082 if ( mBeginHandleWidth + rx >= 0 && mBeginHandleHeight + ry >= 0 )
1084 mResizeRect =
QRectF( 0, 0, mBeginHandleWidth + rx, mBeginHandleHeight + ry );
1086 else if ( mBeginHandleHeight + ry >= 0 )
1088 mResizeRect =
QRectF(
QPointF( -( mBeginHandleWidth + rx ), 0 ),
QPointF( 0, mBeginHandleHeight + ry ) );
1090 else if ( mBeginHandleWidth + rx >= 0 )
1092 mResizeRect =
QRectF(
QPointF( 0, -( mBeginHandleHeight + ry ) ),
QPointF( mBeginHandleWidth + rx, 0 ) );
1096 mResizeRect =
QRectF(
QPointF( -( mBeginHandleWidth + rx ), -( mBeginHandleHeight + ry ) ),
QPointF( 0, 0 ) );
1099 setRect( 0, 0, fabs( mBeginHandleWidth + rx ), fabs( mBeginHandleHeight + ry ) );
1110 if ( snappedPoint != point )
1113 return snappedPoint;
1129 snappedPoint = alignItem( alignX, alignY, point.
x(), point.
y() );
1132 snappedPoint = alignPos( point, alignX, alignY );
1139 int numPages = mComposition->
numPages();
1140 double yLineCoord = 300;
1150 deleteHAlignSnapItem();
1160 deleteVAlignSnapItem();
1162 return snappedPoint;
1167 if ( !mHAlignSnapItem )
1174 return mHAlignSnapItem;
1179 if ( !mVAlignSnapItem )
1186 return mVAlignSnapItem;
1189 void QgsComposerMouseHandles::deleteHAlignSnapItem()
1191 if ( mHAlignSnapItem )
1194 delete mHAlignSnapItem;
1195 mHAlignSnapItem =
nullptr;
1199 void QgsComposerMouseHandles::deleteVAlignSnapItem()
1201 if ( mVAlignSnapItem )
1204 delete mVAlignSnapItem;
1205 mVAlignSnapItem =
nullptr;
1209 void QgsComposerMouseHandles::deleteAlignItems()
1211 deleteHAlignSnapItem();
1212 deleteVAlignSnapItem();
1215 QPointF QgsComposerMouseHandles::alignItem(
double& alignX,
double& alignY,
double unalignedX,
double unalignedY )
1217 double left = unalignedX;
1219 double midH = ( left + right ) / 2.0;
1220 double top = unalignedY;
1222 double midV = ( top + bottom ) / 2.0;
1226 collectAlignCoordinates( xAlignCoordinates, yAlignCoordinates );
1229 double xItemLeft = left;
1230 double xAlignCoord = 0;
1231 double smallestDiffX = DBL_MAX;
1233 checkNearestItem( left, xAlignCoordinates, smallestDiffX, 0, xItemLeft, xAlignCoord );
1234 checkNearestItem( midH, xAlignCoordinates, smallestDiffX, ( left - right ) / 2.0, xItemLeft, xAlignCoord );
1235 checkNearestItem( right, xAlignCoordinates, smallestDiffX, left - right, xItemLeft, xAlignCoord );
1238 double yItemTop = top;
1239 double yAlignCoord = 0;
1240 double smallestDiffY = DBL_MAX;
1242 checkNearestItem( top, yAlignCoordinates, smallestDiffY, 0, yItemTop, yAlignCoord );
1243 checkNearestItem( midV, yAlignCoordinates, smallestDiffY, ( top - bottom ) / 2.0, yItemTop, yAlignCoord );
1244 checkNearestItem( bottom, yAlignCoordinates, smallestDiffY, top - bottom, yItemTop, yAlignCoord );
1246 double xCoord = ( smallestDiffX < 5 ) ? xItemLeft : unalignedX;
1247 alignX = ( smallestDiffX < 5 ) ? xAlignCoord : -1;
1248 double yCoord = ( smallestDiffY < 5 ) ? yItemTop : unalignedY;
1249 alignY = ( smallestDiffY < 5 ) ? yAlignCoord : -1;
1250 return QPointF( xCoord, yCoord );
1253 QPointF QgsComposerMouseHandles::alignPos(
QPointF pos,
double& alignX,
double& alignY )
1257 collectAlignCoordinates( xAlignCoordinates, yAlignCoordinates );
1259 double nearestX = pos.
x();
1260 double nearestY = pos.
y();
1261 if ( !nearestItem( xAlignCoordinates, pos.
x(), nearestX )
1262 || !nearestItem( yAlignCoordinates, pos.
y(), nearestY ) )
1270 double viewScaleFactor = graphicsView()->
transform().
m11();
1271 double alignThreshold = mComposition->
snapTolerance() / viewScaleFactor;
1274 if ( fabs( nearestX - pos.
x() ) < alignThreshold )
1276 result.
setX( nearestX );
1284 if ( fabs( nearestY - pos.
y() ) < alignThreshold )
1286 result.setY( nearestY );
1298 alignCoordsX.
clear();
1299 alignCoordsY.
clear();
1305 for ( ; itemIt != itemList.
end(); ++itemIt )
1316 if ( dynamic_cast<const QgsPaperItem *>( *itemIt ) )
1326 alignCoordsX.
insert( itemRect.
left(), currentItem );
1327 alignCoordsX.
insert( itemRect.
right(), currentItem );
1329 alignCoordsY.
insert( itemRect.
top(), currentItem );
1341 double x = ( *sIt )->line().x1();
1342 double y = ( *sIt )->line().y1();
1345 alignCoordsX.
insert( x,
nullptr );
1349 alignCoordsY.
insert( y,
nullptr );
1355 void QgsComposerMouseHandles::checkNearestItem(
double checkCoord,
const QMap< double, const QgsComposerItem* >& alignCoords,
double& smallestDiff,
double itemCoordOffset,
double& itemCoord,
double& alignCoord )
1357 double currentCoord = 0;
1358 if ( !nearestItem( alignCoords, checkCoord, currentCoord ) )
1363 double currentDiff = fabs( checkCoord - currentCoord );
1365 double viewScaleFactor = graphicsView()->
transform().
m11();
1366 double alignThreshold = mComposition->
snapTolerance() / viewScaleFactor;
1368 if ( currentDiff < alignThreshold && currentDiff < smallestDiff )
1370 itemCoord = currentCoord + itemCoordOffset;
1371 alignCoord = currentCoord;
1372 smallestDiff = currentDiff;
1378 if ( coords.
size() < 1 )
1386 nearestValue = it.
key();
1389 else if ( it == coords.
constEnd() )
1392 nearestValue = it.
key();
1398 double upperVal = it.
key();
1400 double lowerVal = it.
key();
1402 double lowerDiff = value - lowerVal;
1403 double upperDiff = upperVal - value;
1404 if ( lowerDiff < upperDiff )
1406 nearestValue = lowerVal;
1411 nearestValue = upperVal;
QgsComposerMouseHandles::MouseAction mouseActionForScenePos(QPointF sceneCoordPos)
Finds out which mouse move action to choose depending on the scene cursor position.
QRectF mapRectFromItem(const QGraphicsItem *item, const QRectF &rect) const
static void relativeResizeRect(QRectF &rectToResize, const QRectF &boundsBefore, const QRectF &boundsAfter)
Resizes a QRectF relative to a resized bounding rectangle.
void setDirty(bool b=true)
Flag the project as dirty (modified).
QUndoStack * undoStack()
Returns pointer to undo/redo command storage.
void hoverLeaveEvent(QGraphicsSceneHoverEvent *event) override
void setStyle(Qt::PenStyle style)
bool isGroupMember() const
Returns whether this item is part of a group.
void setCompositionMode(CompositionMode mode)
void paint(QPainter *painter, const QStyleOptionGraphicsItem *itemStyle, QWidget *pWidget) override
void selectedItemRotationChanged()
Redraws handles when selected item rotation changes.
QList< QGraphicsItem * > items() const
void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) override
bool alignmentSnap() const
void setAcceptHoverEvents(bool enabled)
const_iterator constBegin() const
const T & at(int i) const
A item that forms part of a map composition.
int numPages() const
Returns the number of pages in the composition.
void mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event) override
void savePreviousState()
Saves current item state as previous state.
MouseAction
Describes the action (move or resize in different directon) to be done during mouse move...
void selectedItemSizeChanged()
Redraws handles when selected item size changes.
QRectF mapRectToScene(const QRectF &rect) const
virtual ~QgsComposerMouseHandles()
QGraphicsScene * scene() const
bool disconnect(const QObject *sender, const char *signal, const QObject *receiver, const char *method)
void setStatusMessage(const QString &message)
Sets the status bar message for the composer window.
QString tr(const char *sourceText, const char *disambiguation, int n)
bool qgsDoubleNear(double a, double b, double epsilon=4 *DBL_EPSILON)
Compare two doubles (but allow some difference)
void update(const QRectF &rect)
bool preventCursorChange() const
void setRect(const QRectF &rectangle)
QgsComposition::PlotStyle plotStyle() const
virtual bool event(QEvent *e)
void drawRect(const QRectF &rectangle)
virtual QRectF boundingRect() const
void addPolygon(const QPolygonF &polygon)
void removeItem(QGraphicsItem *item)
void setPen(const QColor &color)
virtual bool selected() const
Is selected.
void setPos(const QPointF &pos)
QList< QGraphicsView * > views() const
QRectF normalized() const
void setAngle(qreal angle)
bool boundingBoxesVisible() const
Returns whether selection bounding boxes should be shown in the composition.
const_iterator constEnd() const
void setLine(const QLineF &line)
QPointF mapFromScene(const QPointF &point) const
void translate(qreal dx, qreal dy)
void setBrush(const QBrush &brush)
void mouseMoveEvent(QGraphicsSceneMouseEvent *event) override
QRectF united(const QRectF &rectangle) const
iterator lowerBound(const Key &key)
void selectionChanged()
Sets up listeners to sizeChanged signal for all selected items.
Graphics scene for map printing.
void setPen(const QPen &pen)
bool smartGuidesEnabled() const
void hoverMoveEvent(QGraphicsSceneHoverEvent *event) override
void saveAfterState()
Saves current item state as after state.
QPointF mapToScene(const QPointF &point) const
QgsComposition * composition()
QPointF mapFromItem(const QGraphicsItem *item, const QPointF &point) const
Undo command to undo/redo all composer item related changes.
void drawPath(const QPainterPath &path)
QPointF snapPointToGrid(QPointF scenePoint) const
Snaps a scene coordinate point to grid.
int snapTolerance() const
Returns the snap tolerance to use when automatically snapping items during movement and resizing to g...
static QgsProject * instance()
Returns the QgsProject singleton instance.
double paperHeight() const
Height of paper item.
void setRotation(qreal angle)
double paperWidth() const
Width of paper item.
iterator insert(const Key &key, const T &value)
double ANALYSIS_EXPORT min(double x, double y)
Returns the minimum of two doubles or the first argument if both are equal.
const_iterator constEnd() const
const_iterator constBegin() const
QgsComposerMouseHandles(QgsComposition *composition)
void addItem(QGraphicsItem *item)
bool connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
void mousePressEvent(QGraphicsSceneMouseEvent *event) override
double spaceBetweenPages() const
Returns the vertical space between pages in a composer view.
void push(QUndoCommand *cmd)
virtual QRectF rectWithFrame() const
Returns the item's rectangular bounds, including any bleed caused by the item's frame.
QPointF lastScenePos() const
QList< QgsComposerItem * > selectedComposerItems(const bool includeLockedItems=true)
Returns list of selected composer items.
QList< QGraphicsLineItem *> * snapLines()
Returns pointer to snap lines collection.