diff --git a/resources/etc/OpenBoard.config b/resources/etc/OpenBoard.config index 9eca661ee..ce77e232e 100644 --- a/resources/etc/OpenBoard.config +++ b/resources/etc/OpenBoard.config @@ -1,5 +1,4 @@ [App] -AngleTolerance=4 HideCheckForSoftwareUpdate=false HideSwapDisplayScreens=true EnableAutomaticSoftwareUpdates=true @@ -12,6 +11,7 @@ LastSessionPageIndex=0 PageCacheSize=20 PreferredLanguage=fr_CH ProductWebAddress=http://www.openboard.ch +RotationAngleStep=5. RunInWindow=false SoftwareUpdateURL=http://www.openboard.ch/update.json StartMode= diff --git a/src/board/UBBoardController.cpp b/src/board/UBBoardController.cpp index df52e1035..948258e04 100644 --- a/src/board/UBBoardController.cpp +++ b/src/board/UBBoardController.cpp @@ -299,21 +299,34 @@ void UBBoardController::setBoxing(QRect displayRect) } } -void UBBoardController::setCursorFromAngle(QString angle) +void UBBoardController::setCursorFromAngle(QString angle, const QPoint offset) { QWidget *controlViewport = controlView()->viewport(); QSize cursorSize(45,30); + QSize bitmapSize = cursorSize; + int hotX = -1; + int hotY = -1; - QImage mask_img(cursorSize, QImage::Format_Mono); + if (!offset.isNull()) + { + bitmapSize.setWidth(std::max(bitmapSize.width(), 2 * std::abs(offset.x()))); + bitmapSize.setHeight(std::max(bitmapSize.height(), 2 * std::abs(offset.y()))); + hotX = bitmapSize.width() / 2 - offset.x(); + hotY = bitmapSize.height() / 2 - offset.y(); + } + + QSize origin = (bitmapSize - cursorSize) / 2; + + QImage mask_img(bitmapSize, QImage::Format_Mono); mask_img.fill(0xff); QPainter mask_ptr(&mask_img); mask_ptr.setBrush( QBrush( QColor(0, 0, 0) ) ); - mask_ptr.drawRoundedRect(0,0, cursorSize.width()-1, cursorSize.height()-1, 6, 6); + mask_ptr.drawRoundedRect(origin.width(), origin.height(), cursorSize.width()-1, cursorSize.height()-1, 6, 6); QBitmap bmpMask = QBitmap::fromImage(mask_img); - QPixmap pixCursor(cursorSize); + QPixmap pixCursor(bitmapSize); pixCursor.fill(QColor(Qt::white)); QPainter painter(&pixCursor); @@ -321,13 +334,13 @@ void UBBoardController::setCursorFromAngle(QString angle) painter.setRenderHints(QPainter::Antialiasing | QPainter::SmoothPixmapTransform); painter.setBrush(QBrush(Qt::white)); painter.setPen(QPen(QColor(Qt::black))); - painter.drawRoundedRect(1,1,cursorSize.width()-2,cursorSize.height()-2,6,6); + painter.drawRoundedRect(origin.width() + 1, origin.height() + 1,cursorSize.width()-2,cursorSize.height()-2,6,6); painter.setFont(QFont("Arial", 10)); - painter.drawText(1,1,cursorSize.width(),cursorSize.height(), Qt::AlignCenter, angle.append(QChar(176))); + painter.drawText(origin.width() + 1, origin.height() + 1,cursorSize.width(),cursorSize.height(), Qt::AlignCenter, angle.append(QChar(176))); painter.end(); pixCursor.setMask(bmpMask); - controlViewport->setCursor(QCursor(pixCursor)); + controlViewport->setCursor(QCursor(pixCursor, hotX, hotY)); } diff --git a/src/board/UBBoardController.h b/src/board/UBBoardController.h index 55cc908fa..647cab509 100644 --- a/src/board/UBBoardController.h +++ b/src/board/UBBoardController.h @@ -165,7 +165,7 @@ class UBBoardController : public UBDocumentContainer void persistCurrentScene(bool isAnAutomaticBackup = false, bool forceImmediateSave = false); void showNewVersionAvailable(bool automatic, const UBVersion &installedVersion, const UBSoftwareUpdate &softwareUpdate); void setBoxing(QRect displayRect); - void setCursorFromAngle(QString angle); + void setCursorFromAngle(QString angle, const QPoint offset = {}); void setToolbarTexts(); static QUrl expandWidgetToTempDir(const QByteArray& pZipedData, const QString& pExtension = QString("wgt")); diff --git a/src/board/UBBoardView.cpp b/src/board/UBBoardView.cpp index db42cc31c..8931c16c3 100644 --- a/src/board/UBBoardView.cpp +++ b/src/board/UBBoardView.cpp @@ -53,6 +53,7 @@ #include "gui/UBToolWidget.h" #include "gui/UBResources.h" #include "gui/UBMainWindow.h" +#include "gui/UBSnapIndicator.h" #include "gui/UBThumbnailWidget.h" #include "board/UBBoardController.h" @@ -391,13 +392,13 @@ void UBBoardView::tabletEvent (QTabletEvent * event) switch (event->type ()) { case QEvent::TabletPress: { mTabletStylusIsPressed = true; - scene()->inputDevicePress (scenePos, pressure); + scene()->inputDevicePress (scenePos, pressure, event->modifiers()); break; } case QEvent::TabletMove: { if (mTabletStylusIsPressed) - scene ()->inputDeviceMove (scenePos, pressure); + scene ()->inputDeviceMove (scenePos, pressure, event->modifiers()); acceptEvent = false; // rerouted to mouse move @@ -409,7 +410,7 @@ void UBBoardView::tabletEvent (QTabletEvent * event) scene ()->setToolCursor (currentTool); setToolCursor (currentTool); - scene ()->inputDeviceRelease (); + scene ()->inputDeviceRelease (currentTool, event->modifiers()); mPendingStylusReleaseEvent = false; @@ -693,9 +694,9 @@ bool UBBoardView::itemShouldBeMoved(QGraphicsItem *item) case UBGraphicsMediaItem::Type: case UBGraphicsVideoItem::Type: case UBGraphicsAudioItem::Type: + case UBGraphicsStrokesGroup::Type: return true; - case UBGraphicsStrokesGroup::Type: case UBGraphicsTextItem::Type: if (currentTool == UBStylusTool::Play) return true; @@ -842,9 +843,32 @@ void UBBoardView::handleItemMouseMove(QMouseEvent *event) if (getMovingItem() && itemShouldBeMoved(getMovingItem()) && (mMouseButtonIsPressed || mTabletStylusIsPressed)) { QPointF scenePos = mapToScene(event->pos()); - QPointF newPos = getMovingItem()->pos() + scenePos - mLastPressedMousePos; - getMovingItem()->setPos(newPos); - mLastPressedMousePos = scenePos; + auto movingItem = getMovingItem(); + QPointF newPos = movingItem->pos() + scenePos - mLastPressedMousePos; + movingItem->setPos(newPos); + + // snap to grid + if (event->modifiers().testFlag(Qt::ShiftModifier)) + { + QRectF rect = UBGraphicsScene::itemRect(movingItem); + Qt::Corner corner; + auto offset = scene()->snap(rect, &corner); + newPos += offset; + movingItem->setPos(newPos); + + mLastPressedMousePos = scenePos + offset; + + // display snap indicator + if (!offset.isNull()) + { + updateSnapIndicator(corner); + } + } + else + { + mLastPressedMousePos = scenePos; + } + mWidgetMoved = true; event->accept(); } @@ -924,6 +948,17 @@ void UBBoardView::setMultiselection(bool enable) mMultipleSelectionIsEnabled = enable; } +void UBBoardView::updateSnapIndicator(Qt::Corner corner) +{ + if (!mSnapIndicator) + { + mSnapIndicator = new UBSnapIndicator(this); + mSnapIndicator->resize(60, 60); + } + + mSnapIndicator->appear(corner); +} + void UBBoardView::setBoxing(const QMargins& margins) { mMargins = margins; @@ -1052,7 +1087,7 @@ void UBBoardView::mousePressEvent (QMouseEvent *event) return; } - setMultiselection(event->modifiers() & (Qt::ControlModifier | Qt::ShiftModifier)); + setMultiselection(event->modifiers() & Qt::ControlModifier); #if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)) QPointF eventPosition = event->position(); @@ -1146,7 +1181,7 @@ void UBBoardView::mousePressEvent (QMouseEvent *event) connect(&mLongPressTimer, SIGNAL(timeout()), this, SLOT(longPressEvent())); mLongPressTimer.start(); } - scene()->inputDevicePress(mapToScene(UBGeometryUtils::pointConstrainedInRect(event->pos(), rect()))); + scene()->inputDevicePress(mapToScene(UBGeometryUtils::pointConstrainedInRect(event->pos(), rect())), 1., event->modifiers()); } event->accept (); } @@ -1311,7 +1346,7 @@ void UBBoardView::mouseMoveEvent (QMouseEvent *event) default: if (!mTabletStylusIsPressed && scene()) { - scene()->inputDeviceMove(mapToScene(UBGeometryUtils::pointConstrainedInRect(event->pos(), rect())) , mMouseButtonIsPressed); + scene()->inputDeviceMove(mapToScene(UBGeometryUtils::pointConstrainedInRect(event->pos(), rect())) , mMouseButtonIsPressed, event->modifiers()); } event->accept (); } @@ -1333,7 +1368,7 @@ void UBBoardView::mouseReleaseEvent (QMouseEvent *event) setToolCursor (currentTool); // first/ propagate device release to the scene if (scene()) - scene()->inputDeviceRelease(); + scene()->inputDeviceRelease(currentTool, event->modifiers()); #if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)) QPointF eventPosition = event->position(); diff --git a/src/board/UBBoardView.h b/src/board/UBBoardView.h index b74a823fb..d6b8aead6 100644 --- a/src/board/UBBoardView.h +++ b/src/board/UBBoardView.h @@ -42,6 +42,7 @@ class UBBoardController; class UBGraphicsScene; class UBGraphicsWidgetItem; class UBRubberBand; +class UBSnapIndicator; class UBBoardView : public QGraphicsView { @@ -66,6 +67,7 @@ class UBBoardView : public QGraphicsView bool isMultipleSelectionEnabled() { return mMultipleSelectionIsEnabled; } void setBoxing(const QMargins& margins); + void updateSnapIndicator(Qt::Corner corner); // work around for handling tablet events on MAC OS with Qt 4.8.0 and above #if defined(Q_OS_OSX) @@ -208,6 +210,7 @@ class UBBoardView : public QGraphicsView bool mRubberBandInPlayMode; QMargins mMargins{}; + UBSnapIndicator* mSnapIndicator{nullptr}; static bool hasSelectedParents(QGraphicsItem * item); diff --git a/src/core/UBSettings.cpp b/src/core/UBSettings.cpp index 40cbf52c9..645063e53 100644 --- a/src/core/UBSettings.cpp +++ b/src/core/UBSettings.cpp @@ -468,7 +468,7 @@ void UBSettings::init() KeyboardLocale = new UBSetting(this, "Board", "StartupKeyboardLocale", 0); swapControlAndDisplayScreens = new UBSetting(this, "App", "SwapControlAndDisplayScreens", false); - angleTolerance = new UBSetting(this, "App", "AngleTolerance", 4); + rotationAngleStep = new UBSetting(this, "App", "RotationAngleStep", 5.); historyLimit = new UBSetting(this, "Web", "HistoryLimit", 15); libIconSize = new UBSetting(this, "Library", "LibIconSize", defaultLibraryIconSize); diff --git a/src/core/UBSettings.h b/src/core/UBSettings.h index 5f6320cd3..53a608833 100644 --- a/src/core/UBSettings.h +++ b/src/core/UBSettings.h @@ -417,7 +417,7 @@ class UBSettings : public QObject UBSetting* KeyboardLocale; UBSetting* swapControlAndDisplayScreens; - UBSetting* angleTolerance; + UBSetting* rotationAngleStep; UBSetting* historyLimit; UBSetting* libIconSize; diff --git a/src/domain/UBGraphicsDelegateFrame.cpp b/src/domain/UBGraphicsDelegateFrame.cpp index b98bc8c89..f146fb35b 100644 --- a/src/domain/UBGraphicsDelegateFrame.cpp +++ b/src/domain/UBGraphicsDelegateFrame.cpp @@ -55,6 +55,7 @@ UBGraphicsDelegateFrame::UBGraphicsDelegateFrame(UBGraphicsItemDelegate* pDelega , mNominalFrameWidth(pFrameWidth) , mRespectRatio(respectRatio) , mAngle(0) + , mRotatedAngle(0) , mAngleOffset(0) , mTotalScaleX(-1) , mTotalScaleY(-1) @@ -86,7 +87,7 @@ UBGraphicsDelegateFrame::UBGraphicsDelegateFrame(UBGraphicsItemDelegate* pDelega , mTitleBarHeight(hasTitleBar ? 20 :0) , mNominalTitleBarHeight(hasTitleBar ? 20:0) { - mAngleTolerance = UBSettings::settings()->angleTolerance->get().toReal(); + mRotationAngleStep = UBSettings::settings()->rotationAngleStep->get().toReal(); setFlag(QGraphicsItem::ItemSendsGeometryChanges, true); @@ -280,6 +281,7 @@ void UBGraphicsDelegateFrame::mousePressEvent(QGraphicsSceneMouseEvent *event) mDelegate->startUndoStep(); mStartingPoint = event->scenePos(); + mStartingBounds = UBGraphicsScene::itemRect(delegated()); initializeTransform(); @@ -288,6 +290,7 @@ void UBGraphicsDelegateFrame::mousePressEvent(QGraphicsSceneMouseEvent *event) mTranslateX = 0; mTranslateY = 0; mAngleOffset = 0; + mRotatedAngle = mAngle; mInitialTransform = buildTransform(); mOriginalSize = delegated()->boundingRect().size(); @@ -426,6 +429,14 @@ void UBGraphicsDelegateFrame::mouseMoveEvent(QGraphicsSceneMouseEvent *event) { if(!rotating()) { + if (event->modifiers().testFlag(Qt::ShiftModifier)) + { + QPointF snap = snapVector(event->scenePos()); + moveX += snap.x(); + moveY += snap.y(); + move.setP2(move.p2() + snap); + } + mTranslateX = moveX; // Perform the resize if (resizingBottomRight()) @@ -536,27 +547,15 @@ void UBGraphicsDelegateFrame::mouseMoveEvent(QGraphicsSceneMouseEvent *event) QLineF startLine(sceneBoundingRect().center(), event->lastScenePos()); QLineF currentLine(sceneBoundingRect().center(), event->scenePos()); - mAngle += startLine.angleTo(currentLine); + mRotatedAngle += startLine.angleTo(currentLine); - if ((int)mAngle % 45 >= 45 - mAngleTolerance || (int)mAngle % 45 <= mAngleTolerance) + if (event->modifiers().testFlag(Qt::ShiftModifier)) { - mAngle = qRound(mAngle / 45) * 45; - mAngleOffset += startLine.angleTo(currentLine); - if ((int)mAngleOffset % 360 > mAngleTolerance && (int)mAngleOffset % 360 < 360 - mAngleTolerance) - { - mAngle += mAngleOffset; - mAngleOffset = 0; - } + mAngle = qRound(mRotatedAngle / mRotationAngleStep) * mRotationAngleStep; } - else if ((int)mAngle % 30 >= 30 - mAngleTolerance || (int)mAngle % 30 <= mAngleTolerance) + else { - mAngle = qRound(mAngle / 30) * 30; - mAngleOffset += startLine.angleTo(currentLine); - if ((int)mAngleOffset % 360 > mAngleTolerance && (int)mAngleOffset % 360 < 360 - mAngleTolerance) - { - mAngle += mAngleOffset; - mAngleOffset = 0; - } + mAngle = mRotatedAngle; } if (mCurrentTool == Rotate) @@ -567,6 +566,30 @@ void UBGraphicsDelegateFrame::mouseMoveEvent(QGraphicsSceneMouseEvent *event) } else if (moving()) { + if (event->modifiers().testFlag(Qt::ShiftModifier)) + { + // snap to grid + QPointF moved = event->scenePos() - mStartingPoint; + QRectF movedBounds = mStartingBounds.translated(moved); + + UBGraphicsScene* ubscene = dynamic_cast(scene()); + + if (ubscene) + { + Qt::Corner corner; + QPointF snapVector = ubscene->snap(movedBounds, &corner); + moveX += snapVector.x(); + moveY += snapVector.y(); + move.setP2(move.p2() + snapVector); + + // display snap indicator + if (!snapVector.isNull()) + { + UBApplication::boardController->controlView()->updateSnapIndicator(corner); + } + } + } + mTranslateX = move.dx(); mTranslateY = move.dy(); moveLinkedItems(move); @@ -655,13 +678,22 @@ void UBGraphicsDelegateFrame::mouseMoveEvent(QGraphicsSceneMouseEvent *event) mCurrentTool = ResizeBottomRight; } - else if ((resizingBottom() || resizingTop()) && mDelegate->testUBFlags(GF_SCALABLE_Y_AXIS)) - { - resizeDelegate(0., moveY); - } - else if ((resizingLeft() || resizingRight()) && mDelegate->testUBFlags(GF_SCALABLE_X_AXIS)) - { - resizeDelegate(moveX, 0.); + else { + if (event->modifiers().testFlag(Qt::ShiftModifier)) + { + QPointF snap = snapVector(event->scenePos()); + moveX += snap.x(); + moveY += snap.y(); + } + + if ((resizingBottom() || resizingTop()) && mDelegate->testUBFlags(GF_SCALABLE_Y_AXIS)) + { + resizeDelegate(0., moveY); + } + else if ((resizingLeft() || resizingRight()) && mDelegate->testUBFlags(GF_SCALABLE_X_AXIS)) + { + resizeDelegate(moveX, 0.); + } } } event->accept(); @@ -1061,3 +1093,45 @@ void UBGraphicsDelegateFrame::refreshGeometry() qreal height = leftLine.length(); setRect(topRight.x() - mFrameWidth, topLeft.y() - mFrameWidth, width + 2*mFrameWidth, height + 2*mFrameWidth); } + +QPointF UBGraphicsDelegateFrame::snapVector(QPointF scenePos) const +{ + QPointF moved = scenePos - mStartingPoint; + QRectF movedBounds = mStartingBounds.translated(moved); + std::vector corners; + + if (resizingLeft()) + { + corners.push_back(movedBounds.topLeft()); + corners.push_back(movedBounds.bottomLeft()); + } + else if (resizingRight()) + { + corners.push_back(movedBounds.topRight()); + corners.push_back(movedBounds.bottomRight()); + } + else if (resizingTop()) + { + corners.push_back(movedBounds.topLeft()); + corners.push_back(movedBounds.topRight()); + } + else if (resizingBottom()) + { + corners.push_back(movedBounds.bottomLeft()); + corners.push_back(movedBounds.bottomRight()); + } + else if (resizingBottomRight()) + { + corners.push_back(movedBounds.bottomRight()); + } + + UBGraphicsScene* ubscene = dynamic_cast(scene()); + + if (ubscene) + { + QPointF snapVector = ubscene->snap(corners); + return snapVector; + } + + return {}; +} diff --git a/src/domain/UBGraphicsDelegateFrame.h b/src/domain/UBGraphicsDelegateFrame.h index ab406746e..df7473b26 100644 --- a/src/domain/UBGraphicsDelegateFrame.h +++ b/src/domain/UBGraphicsDelegateFrame.h @@ -94,6 +94,7 @@ class UBGraphicsDelegateFrame: public QGraphicsRectItem, public QObject enum FrameTool {None, Move, Rotate, ResizeBottomRight, ResizeTop, ResizeRight, ResizeBottom, ResizeLeft}; FrameTool toolFromPos (QPointF pos); void refreshGeometry(); + QPointF snapVector(QPointF scenePos) const; FrameTool mCurrentTool; UBGraphicsItemDelegate* mDelegate; @@ -104,6 +105,7 @@ class UBGraphicsDelegateFrame: public QGraphicsRectItem, public QObject bool mRespectRatio; qreal mAngle; + qreal mRotatedAngle; qreal mAngleOffset; qreal mTotalScaleX; qreal mTotalScaleY; @@ -113,10 +115,11 @@ class UBGraphicsDelegateFrame: public QGraphicsRectItem, public QObject qreal mTranslateY; qreal mTotalTranslateX; qreal mTotalTranslateY; - qreal mAngleTolerance; + qreal mRotationAngleStep; QRect mAngleRect; QPointF mStartingPoint; + QRectF mStartingBounds; QTransform mInitialTransform; QSizeF mOriginalSize; QPointF mFixedPoint; diff --git a/src/domain/UBGraphicsScene.cpp b/src/domain/UBGraphicsScene.cpp index 333b0d5d8..c791e3ad0 100644 --- a/src/domain/UBGraphicsScene.cpp +++ b/src/domain/UBGraphicsScene.cpp @@ -407,13 +407,13 @@ QPointF UBGraphicsScene::lastCenter() return mViewState.lastSceneCenter(); } -bool UBGraphicsScene::inputDevicePress(const QPointF& scenePos, const qreal& pressure) +bool UBGraphicsScene::inputDevicePress(const QPointF& scenePos, const qreal& pressure, Qt::KeyboardModifiers modifiers) { bool accepted = false; if (mInputDeviceIsPressed) { qWarning() << "scene received input device pressed, without input device release, muting event as input device move"; - accepted = inputDeviceMove(scenePos, pressure); + accepted = inputDeviceMove(scenePos, pressure, modifiers); } else { mInputDeviceIsPressed = true; @@ -463,10 +463,18 @@ bool UBGraphicsScene::inputDevicePress(const QPointF& scenePos, const qreal& pre if (UBDrawingController::drawingController()->activeRuler()) UBDrawingController::drawingController()->activeRuler()->StartLine(scenePos, width); else { - moveTo(scenePos); - drawLineTo(scenePos, width, UBDrawingController::drawingController()->stylusTool() == UBStylusTool::Line); + bool isLine = UBDrawingController::drawingController()->stylusTool() == UBStylusTool::Line; + QPointF pos = scenePos; - mCurrentStroke->addPoint(scenePos, width); + if (isLine && modifiers.testFlag(Qt::ShiftModifier)) + { + pos += snap(scenePos); + } + + moveTo(pos); + drawLineTo(pos, width, isLine); + + mCurrentStroke->addPoint(pos, width); } accepted = true; } @@ -498,7 +506,7 @@ bool UBGraphicsScene::inputDevicePress(const QPointF& scenePos, const qreal& pre return accepted; } -bool UBGraphicsScene::inputDeviceMove(const QPointF& scenePos, const qreal& pressure) +bool UBGraphicsScene::inputDeviceMove(const QPointF& scenePos, const qreal& pressure, Qt::KeyboardModifiers modifiers) { bool accepted = false; @@ -549,6 +557,8 @@ bool UBGraphicsScene::inputDeviceMove(const QPointF& scenePos, const qreal& pres width /= UBApplication::boardController->systemScaleFactor(); width /= UBApplication::boardController->currentZoom(); + std::optional altPosition; + if (currentTool == UBStylusTool::Line || dc->activeRuler()) { if (UBDrawingController::drawingController()->stylusTool() != UBStylusTool::Marker) @@ -565,19 +575,24 @@ bool UBGraphicsScene::inputDeviceMove(const QPointF& scenePos, const qreal& pres } // ------------------------------------------------------------------------ - // Here we wanna make sure that the Line will 'grip' at i*45, i*90 degrees + // Here we wanna make sure that the Line will 'grip' at multiples of + // rotationAngleStep and propose a point as alternative snap point // ------------------------------------------------------------------------ - QLineF radius(mPreviousPoint, position); - qreal angle = radius.angle(); - angle = qRound(angle / 45) * 45; - qreal radiusLength = radius.length(); - QPointF newPosition( - mPreviousPoint.x() + radiusLength * cos((angle * PI) / 180), - mPreviousPoint.y() - radiusLength * sin((angle * PI) / 180)); - QLineF chord(position, newPosition); - if (chord.length() < qMin((int)16, (int)(radiusLength / 20))) - position = newPosition; + if (modifiers.testFlag(Qt::ShiftModifier)) + { + double step = UBSettings::settings()->rotationAngleStep->get().toDouble(); + QLineF radius(mPreviousPoint, position); + qreal angle = radius.angle(); + angle = qRound(angle / step) * step; + qreal radiusLength = radius.length(); + QPointF newPosition( + mPreviousPoint.x() + radiusLength * cos((angle * PI) / 180), + mPreviousPoint.y() - radiusLength * sin((angle * PI) / 180)); + QLineF chord(position, newPosition); + if (chord.length() < qMin((int)16, (int)(radiusLength / 20))) + altPosition = newPosition; + } } if (!mCurrentStroke) @@ -588,6 +603,21 @@ bool UBGraphicsScene::inputDeviceMove(const QPointF& scenePos, const qreal& pres } else if (currentTool == UBStylusTool::Line) { + if (modifiers.testFlag(Qt::ShiftModifier)) + { + position += snap(position, nullptr, altPosition); + } + + QLineF radius(mPreviousPoint, position); + auto angle = radius.angle(); + auto angleDecimals = angle - std::floor(angle); + QLineF viewRadius{UBApplication::boardController->controlView()->mapFromScene(radius.p1()), + UBApplication::boardController->controlView()->mapFromScene(radius.p2())}; + QPoint offset = - viewRadius.p2().toPoint(); + viewRadius.setLength(viewRadius.length() + 30); + offset += viewRadius.p2().toPoint(); + UBApplication::boardController->setCursorFromAngle(QString::number(((int)angle % 360) + angleDecimals, 'f', 1), offset); + drawLineTo(position, width, true); } @@ -658,7 +688,7 @@ bool UBGraphicsScene::inputDeviceMove(const QPointF& scenePos, const qreal& pres return accepted; } -bool UBGraphicsScene::inputDeviceRelease(int tool) +bool UBGraphicsScene::inputDeviceRelease(int tool, Qt::KeyboardModifiers modifiers) { bool accepted = false; @@ -2473,6 +2503,137 @@ UBGraphicsCache* UBGraphicsScene::graphicsCache() return mGraphicsCache; } +QPointF UBGraphicsScene::snap(const QPointF& point, double* force, std::optional proposedPoint) const +{ + QPointF snapPoint{point}; + double snapForce{0}; + double gridSize = backgroundGridSize(); + + if (mIntermediateLines) + { + gridSize /= 2.; + } + + // y axis + double floorY = std::floor(point.y () / gridSize) * gridSize; + snapPoint.setY(point.y() - floorY < gridSize / 2. ? floorY : floorY + gridSize); + + // for blank background, use same snapping as for grid + if (mPageBackground == UBPageBackground::crossed || mPageBackground == UBPageBackground::plain) + { + // x axis + double floorX = std::floor(point.x () / gridSize) * gridSize; + snapPoint.setX(point.x() - floorX < gridSize / 2. ? floorX : floorX + gridSize); + } + + // force is a number between 0 and 1 based on the manhattan distance of the snap point + // from the original point + double relativeDist = (snapPoint - point).manhattanLength() / gridSize; + snapForce = std::max(1. - relativeDist, 0.); + + if (proposedPoint) + { + // compute force for proposed point and take that if it has higher force + double relativeDist = (proposedPoint.value() - point).manhattanLength() / gridSize; + double proposedForce = std::max(1. - relativeDist, 0.); + + if (proposedForce > snapForce) + { + snapForce = proposedForce; + snapPoint = proposedPoint.value(); + } + } + + if (force) + { + *force = snapForce; + } + + return snapPoint - point; +} + +QPointF UBGraphicsScene::snap(const std::vector& corners, int* snapIndex) const +{ + if (corners.empty()) + { + if (snapIndex) + { + *snapIndex = -1; + } + + return QPointF{}; + } + + std::vector forces; + std::vector snapVectors; + + for (const auto& corner : corners) + { + double force = 0.; + snapVectors.emplace_back(snap(corner, &force)); + forces.emplace_back(force); + } + + const auto maxElement = std::max_element(forces.cbegin(), forces.cend()); + const auto index = std::distance(forces.cbegin(), maxElement); + + if (snapIndex) + { + *snapIndex = index; + } + + return snapVectors[index]; +} + +QPointF UBGraphicsScene::snap(const QRectF& rect, Qt::Corner* corner) const +{ + int snapIndex; + const auto offset = snap({rect.topLeft(), rect.topRight(), rect.bottomLeft(), rect.bottomRight()}, &snapIndex); + + if (corner) + { + *corner = Qt::Corner(snapIndex); + } + + return offset; +} + +QRectF UBGraphicsScene::itemRect(const QGraphicsItem* item) +{ + // compute an item's rectangle in scene coordinates + // taking into account the shape of the item and + // the nature of nominal lines + QRectF bounds = item->boundingRect(); + + const QAbstractGraphicsShapeItem* shapeItem = dynamic_cast(item); + + if (shapeItem && shapeItem->pen().style() != Qt::NoPen) + { + qreal margin = shapeItem->pen().widthF() / 2.f; + bounds -= QMarginsF(margin, margin, margin, margin); + } + + // Try to find out whether the item is a single line + // Note: this only works for lines drawn within the current session as the isNominalLine + // and originalLine attributes are lost when serializing the document. + const UBGraphicsStrokesGroup* strokesGroup = dynamic_cast(item); + + if (strokesGroup && strokesGroup->childItems().count() == 1) + { + UBGraphicsPolygonItem* polygonItem = dynamic_cast(strokesGroup->childItems().at(0)); + + if (polygonItem && polygonItem->isNominalLine()) + { + const auto line = polygonItem->originalLine(); + bounds = QRectF{line.p1(), line.p2()}; + } + } + + QRectF rect = item->mapRectToScene(bounds); + + return rect; +} + void UBGraphicsScene::addMask(const QPointF ¢er) { UBGraphicsCurtainItem* curtain = new UBGraphicsCurtainItem(); // mem : owned and destroyed by the scene diff --git a/src/domain/UBGraphicsScene.h b/src/domain/UBGraphicsScene.h index c1d20cf7b..aee05c1b5 100644 --- a/src/domain/UBGraphicsScene.h +++ b/src/domain/UBGraphicsScene.h @@ -138,9 +138,9 @@ class UBGraphicsScene: public UBCoreGraphicsScene, public UBItem, public std::en void clearContent(clearCase pCase = clearItemsAndAnnotations); void saveWidgetSnapshots(); - bool inputDevicePress(const QPointF& scenePos, const qreal& pressure = 1.0); - bool inputDeviceMove(const QPointF& scenePos, const qreal& pressure = 1.0); - bool inputDeviceRelease(int tool = -1); + bool inputDevicePress(const QPointF& scenePos, const qreal& pressure = 1.0, Qt::KeyboardModifiers modifiers = Qt::NoModifier); + bool inputDeviceMove(const QPointF& scenePos, const qreal& pressure = 1.0, Qt::KeyboardModifiers modifiers = Qt::NoModifier); + bool inputDeviceRelease(int tool = -1, Qt::KeyboardModifiers modifiers = Qt::NoModifier); void leaveEvent (QEvent* event); @@ -248,6 +248,11 @@ class UBGraphicsScene: public UBCoreGraphicsScene, public UBItem, public std::en void addCache(); UBGraphicsCache* graphicsCache(); + QPointF snap(const QPointF& point, double* force = nullptr, std::optional proposedPoint = {}) const; + QPointF snap(const std::vector& corners, int* snapIndex = nullptr) const; + QPointF snap(const QRectF& rect, Qt::Corner* corner = nullptr) const; + static QRectF itemRect(const QGraphicsItem* item); + class SceneViewState { public: diff --git a/src/domain/UBSelectionFrame.cpp b/src/domain/UBSelectionFrame.cpp index 72c067b6a..ca19a0cd8 100644 --- a/src/domain/UBSelectionFrame.cpp +++ b/src/domain/UBSelectionFrame.cpp @@ -32,6 +32,7 @@ #include "domain/UBItem.h" #include "domain/UBGraphicsItemZLevelUndoCommand.h" #include "domain/UBGraphicsGroupContainerItem.h" +#include "domain/UBGraphicsScene.h" #include "board/UBBoardController.h" #include "core/UBSettings.h" #include "core/UBApplication.h" @@ -44,7 +45,8 @@ UBSelectionFrame::UBSelectionFrame() : mThickness(UBSettings::settings()->objectFrameWidth) , mAntiscaleRatio(1.0) - , mRotationAngle(0) + , mCursorRotationAngle(0) + , mItemRotationAngle(0) , mDeleteButton(0) , mDuplicateButton(0) , mZOrderUpButton(0) @@ -106,7 +108,8 @@ void UBSelectionFrame::setEnclosedItems(const QList pGraphicsIte { mButtons.clear(); mButtons.append(mDeleteButton); - mRotationAngle = 0; + mCursorRotationAngle = 0; + mItemRotationAngle = 0; QRegion resultRegion; UBGraphicsFlags resultFlags; @@ -147,12 +150,12 @@ void UBSelectionFrame::setEnclosedItems(const QList pGraphicsIte void UBSelectionFrame::updateRect() { - QRegion resultRegion; + QRectF result; + foreach (UBGraphicsItemDelegate *curDelegateItem, mEnclosedtems) { - resultRegion |= curDelegateItem->delegated()->boundingRegion(curDelegateItem->delegated()->sceneTransform()); + result |= curDelegateItem->delegated()->sceneBoundingRect(); } - QRectF result = resultRegion.boundingRect(); setRect(result); placeButtons(); @@ -170,8 +173,10 @@ void UBSelectionFrame::updateScale() void UBSelectionFrame::mousePressEvent(QGraphicsSceneMouseEvent *event) { mPressedPos = mLastMovedPos = event->pos(); + mStartingBounds = rect(); mLastTranslateOffset = QPointF(); - mRotationAngle = 0; + mCursorRotationAngle = 0; + mItemRotationAngle = 0; if (scene()->itemAt(event->scenePos(), transform()) == mRotateButton) { mOperationMode = om_rotating; @@ -188,8 +193,53 @@ void UBSelectionFrame::mouseMoveEvent(QGraphicsSceneMouseEvent *event) QPointF dp = event->pos() - mPressedPos; QPointF rotCenter = mapToScene(rect().center()); - foreach (UBGraphicsItemDelegate *curDelegate, mEnclosedtems) { + QLineF startLine(sceneBoundingRect().center(), event->lastScenePos()); + QLineF currentLine(sceneBoundingRect().center(), event->scenePos()); + qreal dAngle = startLine.angleTo(currentLine); + mCursorRotationAngle = std::fmod(mCursorRotationAngle - dAngle + 360., 360.); + // snap to grid and angle + if (event->modifiers().testFlag(Qt::ShiftModifier)) + { + if (mOperationMode == om_moving) + { + const auto ubscene = dynamic_cast(scene()); + + if (ubscene) + { + Qt::Corner corner; + QRectF movedBounds = mStartingBounds.translated(dp); + QPointF snapVector = ubscene->snap(movedBounds, &corner); + dp += snapVector; + + // display snap indicator + if (mOperationMode == om_moving && !snapVector.isNull()) + { + UBApplication::boardController->controlView()->updateSnapIndicator(corner); + } + } + } + else if (mOperationMode == om_rotating) + { + qreal step = UBSettings::settings()->rotationAngleStep->get().toReal(); + qreal snappedAngle = qRound(mCursorRotationAngle / step) * step; + dAngle = mItemRotationAngle - snappedAngle; + mItemRotationAngle = std::fmod(snappedAngle + 360., 360.); + } + } + else + { + mItemRotationAngle = mCursorRotationAngle; + } + + if (mOperationMode == om_rotating) + { + auto angleDecimals = mItemRotationAngle - std::floor(mItemRotationAngle); + UBApplication::boardController->setCursorFromAngle(QString::number(((int)mItemRotationAngle % 360) + angleDecimals, 'f', 1)); + } + + foreach (UBGraphicsItemDelegate *curDelegate, mEnclosedtems) + { switch (static_cast(mOperationMode)) { case om_moving : { QGraphicsItem *item = curDelegate->delegated(); @@ -212,32 +262,24 @@ void UBSelectionFrame::mouseMoveEvent(QGraphicsSceneMouseEvent *event) } break; case om_rotating : { - QLineF startLine(sceneBoundingRect().center(), event->lastScenePos()); - QLineF currentLine(sceneBoundingRect().center(), event->scenePos()); - qreal dAngle = startLine.angleTo(currentLine); - QGraphicsItem *item = curDelegate->delegated(); QTransform ownTransform = item->transform(); - QPointF nextRotCenter = item->mapFromScene(rotCenter); qreal cntrX = nextRotCenter.x(); qreal cntrY = nextRotCenter.y(); ownTransform.translate(cntrX, cntrY); - mRotationAngle -= dAngle; ownTransform.rotate(-dAngle); ownTransform.translate(-cntrX, -cntrY); item->update(); item->setTransform(ownTransform, false); - qDebug() << "curAngle" << mRotationAngle; } break; } - } updateRect(); diff --git a/src/domain/UBSelectionFrame.h b/src/domain/UBSelectionFrame.h index 6e8c61875..116aa8cad 100644 --- a/src/domain/UBSelectionFrame.h +++ b/src/domain/UBSelectionFrame.h @@ -99,9 +99,11 @@ private slots: QBrush mLocalBrush; QPointF mPressedPos; + QRectF mStartingBounds; QPointF mLastMovedPos; QPointF mLastTranslateOffset; - qreal mRotationAngle; + qreal mCursorRotationAngle; + qreal mItemRotationAngle; bool mIsLocked; diff --git a/src/gui/CMakeLists.txt b/src/gui/CMakeLists.txt index f9e9bd244..843cad86a 100644 --- a/src/gui/CMakeLists.txt +++ b/src/gui/CMakeLists.txt @@ -61,6 +61,8 @@ target_sources(${PROJECT_NAME} PRIVATE UBRubberBand.h UBScreenMirror.cpp UBScreenMirror.h + UBSnapIndicator.cpp + UBSnapIndicator.h UBSpinningWheel.cpp UBSpinningWheel.h UBStartupHintsPalette.cpp diff --git a/src/gui/UBSnapIndicator.cpp b/src/gui/UBSnapIndicator.cpp new file mode 100644 index 000000000..4e084d258 --- /dev/null +++ b/src/gui/UBSnapIndicator.cpp @@ -0,0 +1,130 @@ +/* + * Copyright (C) 2015-2024 Département de l'Instruction Publique (DIP-SEM) + * + * This file is part of OpenBoard. + * + * OpenBoard is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3 of the License, + * with a specific linking exception for the OpenSSL project's + * "OpenSSL" library (or with modified versions of it that use the + * same license as the "OpenSSL" library). + * + * OpenBoard is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with OpenBoard. If not, see . + */ + + +#include "UBSnapIndicator.h" + +#include +#include +#include + +UBSnapIndicator::UBSnapIndicator(QWidget* parent) + : QLabel(parent) +{ + mAnimation = new QPropertyAnimation(this, "alpha", this); + mAnimation->setStartValue(0xff); + mAnimation->setEndValue(0); + mAnimation->setDuration(1000); + + connect(mAnimation, &QPropertyAnimation::finished, this, &QWidget::hide); +} + +void UBSnapIndicator::appear(Qt::Corner corner) +{ + if (corner != mCorner) + { + mAnimation->stop(); + mCorner = corner; + show(); + const auto cursorPos = parentWidget()->mapFromGlobal(QCursor::pos()); + move(cursorPos - QPoint(width() / 2, height() / 2)); + mAnimation->start(); + } +} + +int UBSnapIndicator::alpha() const +{ + return mAlpha; +} + +void UBSnapIndicator::setAlpha(int opacity) +{ + mAlpha = opacity; + update(); +} + +void UBSnapIndicator::setColor(const QColor& color) +{ + mColor = color; +} + +void UBSnapIndicator::paintEvent(QPaintEvent* event) +{ + QPainter painter(this); + QRect area = rect() - QMargins(2, 2, 2, 2); + + QPen pen; + QColor penColor{mColor}; + penColor.setAlpha(mAlpha); + pen.setColor(penColor); + pen.setWidth(3); + + painter.setPen(pen); + + QPoint p1; + QPoint p2; + QPoint p3; + int dist = rect().width() / 3; + + switch (mCorner) + { + case Qt::TopLeftCorner: + p2 = area.topLeft(); + p1 = p2 + QPoint{0, dist}; + p3 = p2 + QPoint(dist, 0); + break; + + case Qt::TopRightCorner: + p2 = area.topRight(); + p1 = p2 + QPoint{0, dist}; + p3 = p2 + QPoint(-dist, 0); + break; + + case Qt::BottomLeftCorner: + p2 = area.bottomLeft(); + p1 = p2 + QPoint{0, -dist}; + p3 = p2 + QPoint(dist, 0); + break; + + case Qt::BottomRightCorner: + p2 = area.bottomRight(); + p1 = p2 + QPoint{0, -dist}; + p3 = p2 + QPoint(-dist, 0); + break; + + default: + break; + } + + QPolygon polygon; + polygon << p1 << p2 << p3 << p1; + + QPainterPath path; + path.addPolygon(polygon); + + painter.drawPolygon(polygon); + painter.fillPath(path, penColor); + + const int radius = rect().width() / 3; + const QPoint center = rect().center(); + QRect circle = QRect(center, center) + QMargins(radius, radius, radius, radius); + painter.drawArc(circle, 0, 360 * 16); +} diff --git a/src/gui/UBSnapIndicator.h b/src/gui/UBSnapIndicator.h new file mode 100644 index 000000000..589db80b0 --- /dev/null +++ b/src/gui/UBSnapIndicator.h @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2015-2024 Département de l'Instruction Publique (DIP-SEM) + * + * This file is part of OpenBoard. + * + * OpenBoard is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3 of the License, + * with a specific linking exception for the OpenSSL project's + * "OpenSSL" library (or with modified versions of it that use the + * same license as the "OpenSSL" library). + * + * OpenBoard is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with OpenBoard. If not, see . + */ + + +#pragma once + +#include + +// forward +class QPropertyAnimation; + +class UBSnapIndicator : public QLabel +{ + Q_OBJECT + Q_PROPERTY(int alpha READ alpha WRITE setAlpha) + +public: + UBSnapIndicator(QWidget* parent); + + void appear(Qt::Corner corner); + + int alpha() const; + void setAlpha(int opacity); + + void setColor(const QColor& color); + +protected: + virtual void paintEvent(QPaintEvent* event) override; + +private: + Qt::Corner mCorner{Qt::TopLeftCorner}; + int mAlpha; + QColor mColor{0x62a7e0}; + QPropertyAnimation* mAnimation; +}; + diff --git a/src/gui/gui.pri b/src/gui/gui.pri index af4c06dde..7390d4afa 100644 --- a/src/gui/gui.pri +++ b/src/gui/gui.pri @@ -40,7 +40,8 @@ HEADERS += src/gui/UBThumbnailView.h \ src/gui/UBFeaturesActionBar.h \ src/gui/UBMessagesDialog.h \ src/gui/UBBackgroundPalette.h \ - src/gui/UBBoardThumbnailsView.h + src/gui/UBBoardThumbnailsView.h \ + src/gui/UBSnapIndicator.h SOURCES += src/gui/UBThumbnailView.cpp \ $$PWD/UBStartupHintsPalette.cpp \ src/gui/UBFloatingPalette.cpp \ @@ -83,7 +84,8 @@ SOURCES += src/gui/UBThumbnailView.cpp \ src/gui/UBFeaturesActionBar.cpp \ src/gui/UBMessagesDialog.cpp \ src/gui/UBBackgroundPalette.cpp \ - src/gui/UBBoardThumbnailsView.cpp + src/gui/UBBoardThumbnailsView.cpp \ + src/gui/UBSnapIndicator.cpp win32:SOURCES += src/gui/UBKeyboardPalette_win.cpp macx:OBJECTIVE_SOURCES += src/gui/UBKeyboardPalette_mac.mm linux-g++:SOURCES += src/gui/UBKeyboardPalette_linux.cpp diff --git a/src/tools/UBGraphicsAxes.cpp b/src/tools/UBGraphicsAxes.cpp index 82538d1f0..0e4d3b752 100644 --- a/src/tools/UBGraphicsAxes.cpp +++ b/src/tools/UBGraphicsAxes.cpp @@ -33,7 +33,6 @@ #include "domain/UBGraphicsScene.h" #include "frameworks/UBGeometryUtils.h" #include "core/UBApplication.h" -#include "gui/UBResources.h" #include "board/UBBoardController.h" // TODO UB 4.x clean that dependency #include "board/UBDrawingController.h" @@ -352,6 +351,12 @@ void UBGraphicsAxes::mouseMoveEvent(QGraphicsSceneMouseEvent *event) if (!mResizing) { QGraphicsItem::mouseMoveEvent(event); + + // snap to grid + if (event->modifiers().testFlag(Qt::ShiftModifier)) { + QPointF snapVector = scene()->snap(pos()); + setPos(pos() + snapVector); + } } else { @@ -411,15 +416,6 @@ void UBGraphicsAxes::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) } else { - // snap to grid - if (true) { - QPointF delta = pos(); - qreal gridSize = scene()->backgroundGridSize(); - qreal deltaX = delta.x() - round(delta.x() / gridSize) * gridSize; - qreal deltaY = delta.y() - round(delta.y() / gridSize) * gridSize; - setPos(pos() - QPointF(deltaX, deltaY)); - } - QGraphicsItem::mouseReleaseEvent(event); } diff --git a/src/tools/UBGraphicsCompass.cpp b/src/tools/UBGraphicsCompass.cpp index 5a6612279..e6946e548 100644 --- a/src/tools/UBGraphicsCompass.cpp +++ b/src/tools/UBGraphicsCompass.cpp @@ -64,6 +64,8 @@ UBGraphicsCompass::UBGraphicsCompass() , mMoveToolSvgItem(0) , mAntiScaleRatio(1.0) , mDrewCenterCross(false) + , mCursorRotationAngle(0) + , mItemRotationAngle(0) { setRect(sDefaultRect); @@ -262,6 +264,8 @@ void UBGraphicsCompass::mousePressEvent(QGraphicsSceneMouseEvent *event) { mRotating = true; mResizing = false; + mCursorRotationAngle = 0; + mItemRotationAngle = angleInDegrees(); event->accept(); qDebug() << "hinge"; } @@ -298,25 +302,74 @@ void UBGraphicsCompass::mouseMoveEvent(QGraphicsSceneMouseEvent *event) { QGraphicsRectItem::mouseMoveEvent(event); mDrewCenterCross = false; + + // snap to grid + if (event->modifiers().testFlag(Qt::ShiftModifier)) { + // snap needle position to grid + QPointF rotCenter = mapToScene(needlePosition()); + QPointF snapVector = scene()->snap(rotCenter); + setPos(pos() + snapVector); + } } else { if (mResizing) { - QPointF delta = event->pos() - event->lastPos(); - if (rect().width() + delta.x() < sMinRadius) - delta.setX(sMinRadius - rect().width()); - setRect(QRectF(rect().topLeft(), QSizeF(rect().width() + delta.x(), rect().height()))); + // snap to grid + if (event->modifiers().testFlag(Qt::ShiftModifier)) + { + // snap cursor position to grid + QPointF cursorPos = mapToScene(event->pos()); + QPointF snapPos = cursorPos + scene()->snap(cursorPos); + + // now resize so that the pencil goes through the snap point + QPointF needlePos = mapToScene(needlePosition()); + double length = QLineF(needlePos, snapPos).length(); + + if (length >= sMinRadius) + { + setRect(QRectF(rect().topLeft(), QSizeF(length, rect().height()))); + + // rotate so that pencil is on snap point + QLineF currentLine(needlePosition(), mapFromScene(snapPos)); + QLineF lastLine(needlePosition(), pencilPosition()); + qreal deltaAngle = currentLine.angleTo(lastLine); + rotateAroundNeedle(deltaAngle); + } + } + else + { + QPointF delta = event->pos() - event->lastPos(); + if (rect().width() + delta.x() < sMinRadius) + delta.setX(sMinRadius - rect().width()); + setRect(QRectF(rect().topLeft(), QSizeF(rect().width() + delta.x(), rect().height()))); + } } else { QLineF currentLine(needlePosition(), event->pos()); QLineF lastLine(needlePosition(), event->lastPos()); - qreal deltaAngle = currentLine.angleTo(lastLine); + mCursorRotationAngle = std::fmod(mCursorRotationAngle + lastLine.angleTo(currentLine), 360.); + qreal newAngle = mItemRotationAngle + mCursorRotationAngle; + + if (event->modifiers().testFlag(Qt::ShiftModifier)) + { + qreal step = UBSettings::settings()->rotationAngleStep->get().toReal(); + newAngle = qRound(newAngle / step) * step; + } + + newAngle = std::fmod(newAngle, 360.); + + QPointF topLeft = sceneTransform().map(boundingRect().topLeft()); + QPointF topRight = sceneTransform().map(boundingRect().topRight()); + qreal currentAngle = QLineF(topLeft, topRight).angle(); + qreal deltaAngle = currentAngle - newAngle; + if (deltaAngle > 180) deltaAngle -= 360; else if (deltaAngle < -180) deltaAngle += 360; + rotateAroundNeedle(deltaAngle); if (mDrawing) diff --git a/src/tools/UBGraphicsCompass.h b/src/tools/UBGraphicsCompass.h index 98aa5b843..32ff75f91 100644 --- a/src/tools/UBGraphicsCompass.h +++ b/src/tools/UBGraphicsCompass.h @@ -125,6 +125,8 @@ class UBGraphicsCompass: public QObject, public QGraphicsRectItem, public UBItem QGraphicsSvgItem* mMoveToolSvgItem; qreal mAntiScaleRatio; bool mDrewCenterCross; + qreal mCursorRotationAngle; + qreal mItemRotationAngle; // Constants static const int sNeedleLength = 12; diff --git a/src/tools/UBGraphicsProtractor.cpp b/src/tools/UBGraphicsProtractor.cpp index b130a2dd7..c615c7e04 100644 --- a/src/tools/UBGraphicsProtractor.cpp +++ b/src/tools/UBGraphicsProtractor.cpp @@ -52,6 +52,8 @@ UBGraphicsProtractor::UBGraphicsProtractor() , mSpan(180) , mStartAngle(0) , mScaleFactor(1) + , mCursorRotationAngle(0) + , mItemRotationAngle(0) , mResetSvgItem(0) , mResizeSvgItem(0) , mMarkerSvgItem(0) @@ -214,6 +216,8 @@ void UBGraphicsProtractor::mousePressEvent(QGraphicsSceneMouseEvent *event) mPreviousMousePos = event->pos(); mCurrentTool = toolFromPos(event->pos()); mShowButtons = mCurrentTool == Reset || mCurrentTool == Close; + mCursorRotationAngle = 0; + mItemRotationAngle = mStartAngle; if (mCurrentTool == None || mCurrentTool == Move) QGraphicsEllipseItem::mousePressEvent(event); @@ -234,7 +238,16 @@ void UBGraphicsProtractor::mouseMoveEvent(QGraphicsSceneMouseEvent *event) { case Rotate : prepareGeometryChange(); - mStartAngle = mStartAngle + angle; + mCursorRotationAngle = std::fmod(mCursorRotationAngle + angle, 360.); + mStartAngle = mItemRotationAngle + mCursorRotationAngle; + + if (event->modifiers().testFlag(Qt::ShiftModifier)) + { + qreal step = UBSettings::settings()->rotationAngleStep->get().toReal(); + mStartAngle = qRound(mStartAngle / step) * step; + } + + mStartAngle = std::fmod(mStartAngle, 360.); setStartAngle(mStartAngle * 16); mPreviousMousePos = currentPoint; angleDecimals = mStartAngle - std::floor(mStartAngle); @@ -267,6 +280,15 @@ void UBGraphicsProtractor::mouseMoveEvent(QGraphicsSceneMouseEvent *event) case Move : QGraphicsEllipseItem::mouseMoveEvent(event); + + // snap to grid + if (event->modifiers().testFlag(Qt::ShiftModifier)) { + // snap rotation center to grid + QPointF rotCenter = mapToScene(rotationCenter()); + QPointF snapVector = scene()->snap(rotCenter); + setPos(pos() + snapVector); + } + break; default : @@ -300,6 +322,9 @@ void UBGraphicsProtractor::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) update(); break; + case Move: + Q_FALLTHROUGH(); + default : QGraphicsEllipseItem::mouseReleaseEvent(event); break; @@ -701,6 +726,5 @@ void UBGraphicsProtractor::rotateAroundCenter(qreal angle) QPointF UBGraphicsProtractor::rotationCenter() const { - return QPointF(rect().x(), rect().y()); - + return QPointF{0., 0.}; } diff --git a/src/tools/UBGraphicsProtractor.h b/src/tools/UBGraphicsProtractor.h index d9cc6e8e8..576fb0464 100644 --- a/src/tools/UBGraphicsProtractor.h +++ b/src/tools/UBGraphicsProtractor.h @@ -114,6 +114,8 @@ class UBGraphicsProtractor : public UBAbstractDrawRuler, public QGraphicsEllipse qreal mSpan; qreal mStartAngle; qreal mScaleFactor; + qreal mCursorRotationAngle; + qreal mItemRotationAngle; QGraphicsSvgItem* mResetSvgItem; QGraphicsSvgItem* mResizeSvgItem; diff --git a/src/tools/UBGraphicsRuler.cpp b/src/tools/UBGraphicsRuler.cpp index c04ccc21f..14a9dab3c 100644 --- a/src/tools/UBGraphicsRuler.cpp +++ b/src/tools/UBGraphicsRuler.cpp @@ -47,6 +47,8 @@ UBGraphicsRuler::UBGraphicsRuler() : QGraphicsRectItem() , mResizing(false) , mRotating(false) + , mCursorRotationAngle(0) + , mItemRotationAngle(0) { setRect(sDefaultRect); @@ -402,6 +404,10 @@ void UBGraphicsRuler::mousePressEvent(QGraphicsSceneMouseEvent *event) else if (rotateButtonRect().contains(event->pos())) { mRotating = true; + mCursorRotationAngle = 0; + QPointF topLeft = sceneTransform().map(boundingRect().topLeft()); + QPointF topRight = sceneTransform().map(boundingRect().topRight()); + mItemRotationAngle = QLineF(topLeft, topRight).angle(); event->accept(); } else @@ -420,6 +426,14 @@ void UBGraphicsRuler::mouseMoveEvent(QGraphicsSceneMouseEvent *event) if (!mResizing && !mRotating) { QGraphicsItem::mouseMoveEvent(event); + + // snap to grid + if (event->modifiers().testFlag(Qt::ShiftModifier)) { + // snap rotation center to grid + QPointF rotCenter = mapToScene(rotationCenter()); + QPointF snapVector = scene()->snap(rotCenter); + setPos(pos() + snapVector); + } } else { @@ -438,13 +452,24 @@ void UBGraphicsRuler::mouseMoveEvent(QGraphicsSceneMouseEvent *event) { QLineF currentLine(rotationCenter(), event->pos()); QLineF lastLine(rotationCenter(), event->lastPos()); - rotateAroundCenter(currentLine.angleTo(lastLine)); + mCursorRotationAngle = std::fmod(mCursorRotationAngle + lastLine.angleTo(currentLine), 360.); + qreal newAngle = mItemRotationAngle + mCursorRotationAngle; + + if (event->modifiers().testFlag(Qt::ShiftModifier)) + { + qreal step = UBSettings::settings()->rotationAngleStep->get().toReal(); + newAngle = qRound(newAngle / step) * step; + } + + newAngle = std::fmod(newAngle, 360.); - //display current angle QPointF topLeft = sceneTransform().map(boundingRect().topLeft()); QPointF topRight = sceneTransform().map(boundingRect().topRight()); - QLineF topLine(topLeft, topRight); - UBApplication::boardController->setCursorFromAngle(QString::number(topLine.angle(), 'f', 1)); + qreal currentAngle = QLineF(topLeft, topRight).angle(); + rotateAroundCenter(currentAngle - newAngle); + + //display current angle + UBApplication::boardController->setCursorFromAngle(QString::number(newAngle, 'f', 1)); } event->accept(); diff --git a/src/tools/UBGraphicsRuler.h b/src/tools/UBGraphicsRuler.h index 3d84ce820..0f164f29f 100644 --- a/src/tools/UBGraphicsRuler.h +++ b/src/tools/UBGraphicsRuler.h @@ -80,6 +80,8 @@ class UBGraphicsRuler : public UBAbstractDrawRuler, public QGraphicsRectItem, pu bool mResizing; bool mRotating; + qreal mCursorRotationAngle; + qreal mItemRotationAngle; // Helpers diff --git a/src/tools/UBGraphicsTriangle.cpp b/src/tools/UBGraphicsTriangle.cpp index 928dd71fb..84982ddb3 100644 --- a/src/tools/UBGraphicsTriangle.cpp +++ b/src/tools/UBGraphicsTriangle.cpp @@ -49,6 +49,8 @@ UBGraphicsTriangle::UBGraphicsTriangle() , mResizing1(false) , mResizing2(false) , mRotating(false) + , mCursorRotationAngle(0) + , mItemRotationAngle(0) , mShouldPaintInnerTriangle(true) { @@ -555,7 +557,7 @@ void UBGraphicsTriangle::rotateAroundCenter(QTransform& transform, QPointF cente } -QPointF UBGraphicsTriangle::rotationCenter() const +QPointF UBGraphicsTriangle::rotationCenter() const { return B1; } @@ -787,6 +789,10 @@ void UBGraphicsTriangle::mousePressEvent(QGraphicsSceneMouseEvent *event) if(rotateRect().contains(event->pos())) { mRotating = true; + mCursorRotationAngle = 0; + QPointF topLeft = sceneTransform().map(boundingRect().topLeft()); + QPointF topRight = sceneTransform().map(boundingRect().topRight()); + mItemRotationAngle = QLineF(topLeft, topRight).angle(); event->accept(); } else @@ -807,6 +813,14 @@ void UBGraphicsTriangle::mouseMoveEvent(QGraphicsSceneMouseEvent *event) if (!mResizing1 && !mResizing2 && !mRotating) { QGraphicsItem::mouseMoveEvent(event); + + // snap to grid + if (event->modifiers().testFlag(Qt::ShiftModifier)) { + // snap rotation center to grid + QPointF rotCenter = mapToScene(rotationCenter()); + QPointF snapVector = scene()->snap(rotCenter); + setPos(pos() + snapVector); + } } else { @@ -865,13 +879,24 @@ void UBGraphicsTriangle::mouseMoveEvent(QGraphicsSceneMouseEvent *event) { QLineF currentLine(rotationCenter(), event->pos()); QLineF lastLine(rotationCenter(), event->lastPos()); - rotateAroundCenter(currentLine.angleTo(lastLine)); + mCursorRotationAngle = std::fmod(mCursorRotationAngle + lastLine.angleTo(currentLine), 360.); + qreal newAngle = mItemRotationAngle + mCursorRotationAngle; + + if (event->modifiers().testFlag(Qt::ShiftModifier)) + { + qreal step = UBSettings::settings()->rotationAngleStep->get().toReal(); + newAngle = qRound(newAngle / step) * step; + } + + newAngle = std::fmod(newAngle, 360.); - //display current angle QPointF topLeft = sceneTransform().map(boundingRect().topLeft()); QPointF topRight = sceneTransform().map(boundingRect().topRight()); - QLineF topLine(topLeft, topRight); - UBApplication::boardController->setCursorFromAngle(QString::number(topLine.angle(), 'f', 1)); + qreal currentAngle = QLineF(topLeft, topRight).angle(); + rotateAroundCenter(currentAngle - newAngle); + + //display current angle + UBApplication::boardController->setCursorFromAngle(QString::number(newAngle, 'f', 1)); } //-----------------------------------------------// diff --git a/src/tools/UBGraphicsTriangle.h b/src/tools/UBGraphicsTriangle.h index ce9a0089d..405391a29 100644 --- a/src/tools/UBGraphicsTriangle.h +++ b/src/tools/UBGraphicsTriangle.h @@ -166,6 +166,8 @@ class UBGraphicsTriangle : public UBAbstractDrawRuler, public QGraphicsPolygonIt bool mResizing1; bool mResizing2; bool mRotating; + qreal mCursorRotationAngle; + qreal mItemRotationAngle; QRect lastRect;