diff --git a/src/app/puzzle/layout/vplayout.cpp b/src/app/puzzle/layout/vplayout.cpp index 6c4dcf352..b76edd1a7 100644 --- a/src/app/puzzle/layout/vplayout.cpp +++ b/src/app/puzzle/layout/vplayout.cpp @@ -147,6 +147,7 @@ auto VPLayout::AddSheet(const VPSheetPtr &sheet) -> VPSheetPtr if (not sheet.isNull() && GetSheet(sheet->Uuid()).isNull()) { m_sheets.append(sheet); + connect(this, &VPLayout::PieceTransformationChanged, sheet.get(), &VPSheet::CheckPiecePositionValidity); } return sheet; } @@ -269,6 +270,19 @@ void VPLayout::Clear() m_layoutSettings = VPLayoutSettings(); } +//--------------------------------------------------------------------------------------------------------------------- +void VPLayout::CheckPiecesPositionValidity() const +{ + for (const auto &sheet : m_sheets) + { + if (not sheet.isNull()) + { + sheet->ValidateSuperpositionOfPieces(); + sheet->ValidatePiecesOutOfBound(); + } + } +} + //--------------------------------------------------------------------------------------------------------------------- void VPLayout::AddTrashSheet(const VPSheetPtr &sheet) { diff --git a/src/app/puzzle/layout/vplayout.h b/src/app/puzzle/layout/vplayout.h index 5ae365376..c35b136cf 100644 --- a/src/app/puzzle/layout/vplayout.h +++ b/src/app/puzzle/layout/vplayout.h @@ -85,6 +85,8 @@ public: void Clear(); + void CheckPiecesPositionValidity() const; + signals: void PieceSheetChanged(const VPPiecePtr &piece); void ActiveSheetChanged(const VPSheetPtr &focusedSheet); @@ -92,6 +94,7 @@ signals: void TransformationOriginChanged(); void SheetListChanged(); void PieceSelectionChanged(const VPPiecePtr &piece); + void PiecePositionValidityChanged(const VPPiecePtr &piece); void LayoutChanged(); protected: diff --git a/src/app/puzzle/layout/vppiece.cpp b/src/app/puzzle/layout/vppiece.cpp index 8ce7f581e..219966ce9 100644 --- a/src/app/puzzle/layout/vppiece.cpp +++ b/src/app/puzzle/layout/vppiece.cpp @@ -152,43 +152,6 @@ void VPPiece::RotateToGrainline(const VPTransformationOrigon &origin) } } -//--------------------------------------------------------------------------------------------------------------------- -void VPPiece::SetSelected(bool value) -{ - m_isSelected = value; -} - -//--------------------------------------------------------------------------------------------------------------------- -auto VPPiece::IsSelected() const -> bool -{ - return m_isSelected; -} - -//--------------------------------------------------------------------------------------------------------------------- -auto VPPiece::Sheet() const -> VPSheetPtr -{ - return m_sheet; -} - -//--------------------------------------------------------------------------------------------------------------------- -void VPPiece::SetSheet(const VPSheetPtr &newSheet) -{ - m_sheet = newSheet; -} - -//--------------------------------------------------------------------------------------------------------------------- -auto VPPiece::Layout() const -> VPLayoutPtr -{ - return m_layout; -} - -//--------------------------------------------------------------------------------------------------------------------- -void VPPiece::SetLayout(const VPLayoutPtr &layout) -{ - SCASSERT(layout != nullptr) - m_layout = layout; -} - //--------------------------------------------------------------------------------------------------------------------- void VPPiece::SetGrainlineEnabled(bool enabled) { diff --git a/src/app/puzzle/layout/vppiece.h b/src/app/puzzle/layout/vppiece.h index e2bf5985d..91254e26d 100644 --- a/src/app/puzzle/layout/vppiece.h +++ b/src/app/puzzle/layout/vppiece.h @@ -106,6 +106,12 @@ public: */ void Flip(); + auto OutOfBound() const -> bool; + void SetOutOfBound(bool newOutOfBound); + + auto HasSuperpositionWithPieces() const -> bool; + void SetHasSuperpositionWithPieces(bool newHasSuperpositionWithPieces); + private: Q_DISABLE_COPY(VPPiece) @@ -114,8 +120,71 @@ private: VPSheetWeakPtr m_sheet{}; bool m_isSelected{false}; + bool m_outOfBound{false}; + bool m_hasSuperpositionWithPieces{false}; }; +//--------------------------------------------------------------------------------------------------------------------- +inline void VPPiece::SetSelected(bool value) +{ + m_isSelected = value; +} + +//--------------------------------------------------------------------------------------------------------------------- +inline auto VPPiece::IsSelected() const -> bool +{ + return m_isSelected; +} + +//--------------------------------------------------------------------------------------------------------------------- +inline auto VPPiece::Sheet() const -> VPSheetPtr +{ + return m_sheet; +} + +//--------------------------------------------------------------------------------------------------------------------- +inline void VPPiece::SetSheet(const VPSheetPtr &newSheet) +{ + m_sheet = newSheet; +} + +//--------------------------------------------------------------------------------------------------------------------- +inline auto VPPiece::Layout() const -> VPLayoutPtr +{ + return m_layout; +} + +//--------------------------------------------------------------------------------------------------------------------- +inline void VPPiece::SetLayout(const VPLayoutPtr &layout) +{ + SCASSERT(layout != nullptr) + m_layout = layout; +} + +//--------------------------------------------------------------------------------------------------------------------- +inline auto VPPiece::OutOfBound() const -> bool +{ + return m_outOfBound; +} + +//--------------------------------------------------------------------------------------------------------------------- +inline void VPPiece::SetOutOfBound(bool newOutOfBound) +{ + m_outOfBound = newOutOfBound; +} + +//--------------------------------------------------------------------------------------------------------------------- +inline auto VPPiece::HasSuperpositionWithPieces() const -> bool +{ + return m_hasSuperpositionWithPieces; +} + +//--------------------------------------------------------------------------------------------------------------------- +inline void VPPiece::SetHasSuperpositionWithPieces(bool newHasSuperpositionWithPieces) +{ + m_hasSuperpositionWithPieces = newHasSuperpositionWithPieces; +} + Q_DECLARE_METATYPE(VPPiecePtr) #endif // VPPIECE_H diff --git a/src/app/puzzle/layout/vpsheet.cpp b/src/app/puzzle/layout/vpsheet.cpp index 0538215a5..4d01ee4d3 100644 --- a/src/app/puzzle/layout/vpsheet.cpp +++ b/src/app/puzzle/layout/vpsheet.cpp @@ -158,3 +158,178 @@ void VPSheet::SetTrashSheet(bool newTrashSheet) { m_trashSheet = newTrashSheet; } + +//--------------------------------------------------------------------------------------------------------------------- +void VPSheet::ValidateSuperpositionOfPieces() const +{ + QList pieces = GetPieces(); + + for (const auto &piece : pieces) + { + if (piece.isNull()) + { + continue; + } + + const bool oldSuperpositionOfPieces = piece->HasSuperpositionWithPieces(); + QVector path1 = piece->GetMappedExternalContourPoints(); + bool hasSuperposition = false; + + for (const auto &p : pieces) + { + if (p.isNull() || piece == p) + { + continue; + } + + QVector path2 = p->GetMappedExternalContourPoints(); + + bool superposition = PathsSuperposition(path1, path2); + if (superposition) + { + hasSuperposition = superposition; + break; + } + } + + piece->SetHasSuperpositionWithPieces(hasSuperposition); + + if (oldSuperpositionOfPieces != piece->HasSuperpositionWithPieces()) + { + VPLayoutPtr layout = GetLayout(); + if (not layout.isNull()) + { + emit layout->PiecePositionValidityChanged(piece); + } + } + } +} + +//--------------------------------------------------------------------------------------------------------------------- +void VPSheet::ValidatePieceOutOfBound(const VPPiecePtr &piece) const +{ + if (piece.isNull()) + { + return; + } + + const bool oldOutOfBound = piece->OutOfBound(); + + QRectF pieceRect = piece->MappedDetailBoundingRect(); + QRectF sheetRect = GetMarginsRect(); + + piece->SetOutOfBound(not sheetRect.contains(pieceRect)); + + if (oldOutOfBound != piece->OutOfBound()) + { + VPLayoutPtr layout = GetLayout(); + if (not layout.isNull()) + { + emit layout->PiecePositionValidityChanged(piece); + } + } + +} + +//--------------------------------------------------------------------------------------------------------------------- +void VPSheet::ValidatePiecesOutOfBound() const +{ + QList pieces = GetPieces(); + for (const auto &piece : pieces) + { + ValidatePieceOutOfBound(piece); + } +} + +//--------------------------------------------------------------------------------------------------------------------- +auto VPSheet::GetSheetRect() const -> QRectF +{ + return GetSheetRect(GetLayout()); +} + +//--------------------------------------------------------------------------------------------------------------------- +auto VPSheet::GetMarginsRect() const -> QRectF +{ + return GetMarginsRect(GetLayout()); +} + +//--------------------------------------------------------------------------------------------------------------------- +auto VPSheet::GetSheetRect(const VPLayoutPtr &layout) -> QRectF +{ + if (layout.isNull()) + { + return {}; + } + + QPoint topLeft = QPoint(0,0); + QSizeF size = layout->LayoutSettings().GetSheetSize(); + QRectF rect = QRectF(topLeft, size); + return rect; +} + +//--------------------------------------------------------------------------------------------------------------------- +auto VPSheet::GetMarginsRect(const VPLayoutPtr &layout) -> QRectF +{ + if (layout.isNull()) + { + return {}; + } + + QSizeF size = layout->LayoutSettings().GetSheetSize(); + + if (not layout->LayoutSettings().IgnoreMargins()) + { + QMarginsF margins = layout->LayoutSettings().GetSheetMargins(); + QRectF rect = QRectF(QPointF(margins.left(), margins.top()), + QPointF(size.width()-margins.right(), size.height()-margins.bottom())); + return rect; + } + + return QRectF(0, 0, size.width(), size.height()); +} + +//--------------------------------------------------------------------------------------------------------------------- +void VPSheet::CheckPiecePositionValidity(const VPPiecePtr &piece) const +{ + VPLayoutPtr layout = GetLayout(); + if (layout.isNull()) + { + return; + } + + QList pieces = GetPieces(); + if (piece.isNull() || not pieces.contains(piece)) + { + return; + } + + if (layout->LayoutSettings().GetWarningPiecesOutOfBound()) + { + ValidatePieceOutOfBound(piece); + } + + if (layout->LayoutSettings().GetWarningSuperpositionOfPieces()) + { + ValidateSuperpositionOfPieces(); + } +} + +//--------------------------------------------------------------------------------------------------------------------- +auto VPSheet::PathsSuperposition(const QVector &path1, const QVector &path2) const -> bool +{ + const QRectF path1Rect = VLayoutPiece::BoundingRect(path1); + const QPainterPath path1Path = VAbstractPiece::PainterPath(path1); + + const QRectF path2Rect = VLayoutPiece::BoundingRect(path2); + const QPainterPath path2Path = VAbstractPiece::PainterPath(path2); + + if (path1Rect.intersects(path2Rect) || path2Rect.contains(path1Rect) || path1Rect.contains(path2Rect)) + { + if (path1Path.contains(path2Path) || path2Path.contains(path1Path) || path1Path.intersects(path2Path)) + { + return true; + } + } + + return false; +} diff --git a/src/app/puzzle/layout/vpsheet.h b/src/app/puzzle/layout/vpsheet.h index 46bfe0243..f92fead22 100644 --- a/src/app/puzzle/layout/vpsheet.h +++ b/src/app/puzzle/layout/vpsheet.h @@ -40,8 +40,9 @@ class VPLayout; class VPPiece; -class VPSheet +class VPSheet : public QObject { + Q_OBJECT public: explicit VPSheet(const VPLayoutPtr &layout); @@ -84,6 +85,19 @@ public: auto TrashSheet() const -> bool; void SetTrashSheet(bool newTrashSheet); + void ValidateSuperpositionOfPieces() const; + void ValidatePieceOutOfBound(const VPPiecePtr &piece) const; + void ValidatePiecesOutOfBound() const; + + auto GetSheetRect() const -> QRectF; + auto GetMarginsRect() const -> QRectF; + + static auto GetSheetRect(const VPLayoutPtr &layout) -> QRectF; + static auto GetMarginsRect(const VPLayoutPtr &layout) -> QRectF; + +public slots: + void CheckPiecePositionValidity(const VPPiecePtr &piece) const; + private: Q_DISABLE_COPY(VPSheet) @@ -97,6 +111,8 @@ private: bool m_trashSheet{false}; VPTransformationOrigon m_transformationOrigin{}; + + auto PathsSuperposition(const QVector &path1, const QVector &path2) const -> bool; }; Q_DECLARE_METATYPE(VPSheetPtr) diff --git a/src/app/puzzle/scene/vpgraphicspiece.cpp b/src/app/puzzle/scene/vpgraphicspiece.cpp index c2168b8da..3a8eeb0c9 100644 --- a/src/app/puzzle/scene/vpgraphicspiece.cpp +++ b/src/app/puzzle/scene/vpgraphicspiece.cpp @@ -55,6 +55,9 @@ Q_LOGGING_CATEGORY(pGraphicsPiece, "p.graphicsPiece") namespace { constexpr qreal penWidth = 1; + +QColor mainColor = Qt::black; +QColor errorColor = Qt::red; } //--------------------------------------------------------------------------------------------------------------------- @@ -112,7 +115,7 @@ void VPGraphicsPiece::paint(QPainter *painter, const QStyleOptionGraphicsItem *o Q_UNUSED(widget); Q_UNUSED(option); - QPen pen(Qt::black, penWidth, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin); + QPen pen(PieceColor(), penWidth, Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin); painter->setPen(pen); PaintPiece(painter); @@ -407,6 +410,41 @@ void VPGraphicsPiece::GroupMove(const QPointF &pos) } } +//--------------------------------------------------------------------------------------------------------------------- +QColor VPGraphicsPiece::PieceColor() const +{ + VPPiecePtr piece = m_piece.toStrongRef(); + if (piece.isNull()) + { + return mainColor; + } + + VPLayoutPtr layout = piece->Layout(); + if (layout.isNull()) + { + return mainColor; + } + + bool outOfBound = false; + if (layout->LayoutSettings().GetWarningPiecesOutOfBound()) + { + outOfBound = piece->OutOfBound(); + } + + bool superposition = false; + if (layout->LayoutSettings().GetWarningSuperpositionOfPieces()) + { + superposition = piece->HasSuperpositionWithPieces(); + } + + if (outOfBound || superposition) + { + return errorColor; + } + + return mainColor; +} + //--------------------------------------------------------------------------------------------------------------------- void VPGraphicsPiece::on_RefreshPiece(const VPPiecePtr &piece) { diff --git a/src/app/puzzle/scene/vpgraphicspiece.h b/src/app/puzzle/scene/vpgraphicspiece.h index da7ea0b51..6893317c3 100644 --- a/src/app/puzzle/scene/vpgraphicspiece.h +++ b/src/app/puzzle/scene/vpgraphicspiece.h @@ -92,6 +92,8 @@ private: void PaintPiece(QPainter *painter=nullptr); void GroupMove(const QPointF &pos); + + QColor PieceColor() const; }; #endif // VPGRAPHICSPIECE_H diff --git a/src/app/puzzle/scene/vpgraphicssheet.cpp b/src/app/puzzle/scene/vpgraphicssheet.cpp index af5257948..12810f888 100644 --- a/src/app/puzzle/scene/vpgraphicssheet.cpp +++ b/src/app/puzzle/scene/vpgraphicssheet.cpp @@ -105,38 +105,13 @@ void VPGraphicsSheet::paint(QPainter *painter, const QStyleOptionGraphicsItem *o //--------------------------------------------------------------------------------------------------------------------- auto VPGraphicsSheet::GetSheetRect() const -> QRectF { - VPLayoutPtr layout = m_layout.toStrongRef(); - if (layout.isNull()) - { - return {}; - } - - QPoint topLeft = QPoint(0,0); - QSizeF size = layout->LayoutSettings().GetSheetSize(); - QRectF rect = QRectF(topLeft, size); - return rect; + return VPSheet::GetSheetRect(m_layout.toStrongRef()); } //--------------------------------------------------------------------------------------------------------------------- auto VPGraphicsSheet::GetMarginsRect() const -> QRectF { - VPLayoutPtr layout = m_layout.toStrongRef(); - if (layout.isNull()) - { - return {}; - } - - QSizeF size = layout->LayoutSettings().GetSheetSize(); - - if (not layout->LayoutSettings().IgnoreMargins()) - { - QMarginsF margins = layout->LayoutSettings().GetSheetMargins(); - QRectF rect = QRectF(QPointF(margins.left(), margins.top()), - QPointF(size.width()-margins.right(), size.height()-margins.bottom())); - return rect; - } - - return QRectF(0, 0, size.width(), size.height()); + return VPSheet::GetMarginsRect(m_layout.toStrongRef()); } //--------------------------------------------------------------------------------------------------------------------- diff --git a/src/app/puzzle/scene/vpmaingraphicsview.cpp b/src/app/puzzle/scene/vpmaingraphicsview.cpp index b91d1cd8c..074f3b5ba 100644 --- a/src/app/puzzle/scene/vpmaingraphicsview.cpp +++ b/src/app/puzzle/scene/vpmaingraphicsview.cpp @@ -515,6 +515,8 @@ void VPMainGraphicsView::ConnectPiece(VPGraphicsPiece *piece) &VPGraphicsPiece::on_RefreshPiece); connect(layout.get(), &VPLayout::PieceSelectionChanged, m_rotationControls, &VPGraphicsPieceControls::on_UpdateControls); + connect(layout.get(), &VPLayout::PiecePositionValidityChanged, + piece, &VPGraphicsPiece::on_RefreshPiece); connect(piece, &VPGraphicsPiece::PieceTransformationChanged, m_rotationControls, &VPGraphicsPieceControls::on_UpdateControls); connect(piece, &VPGraphicsPiece::HideTransformationHandles, diff --git a/src/app/puzzle/undocommands/vpundomovepieceonsheet.cpp b/src/app/puzzle/undocommands/vpundomovepieceonsheet.cpp index f2f356694..8fca0cac0 100644 --- a/src/app/puzzle/undocommands/vpundomovepieceonsheet.cpp +++ b/src/app/puzzle/undocommands/vpundomovepieceonsheet.cpp @@ -122,6 +122,7 @@ void VPUndoMovePieceOnSheet::redo() if (not layout.isNull()) { emit layout->PieceSheetChanged(piece); + emit layout->PieceTransformationChanged(piece); } } } diff --git a/src/app/puzzle/vpmainwindow.cpp b/src/app/puzzle/vpmainwindow.cpp index e2ebe001b..b992e99fc 100644 --- a/src/app/puzzle/vpmainwindow.cpp +++ b/src/app/puzzle/vpmainwindow.cpp @@ -284,6 +284,7 @@ auto VPMainWindow::LoadFile(QString path) -> bool m_graphicsView->RefreshLayout(); m_graphicsView->RefreshPieces(); m_tileFactory->refreshTileInfos(); + m_layout->CheckPiecesPositionValidity(); VMainGraphicsView::NewSceneRect(m_graphicsView->scene(), m_graphicsView); return true; @@ -732,6 +733,12 @@ void VPMainWindow::InitPropertyTabCurrentSheet() LayoutWasSaved(false); m_tileFactory->refreshTileInfos(); m_graphicsView->RefreshLayout(); + + VPSheetPtr sheet = m_layout->GetFocusedSheet(); + if (not sheet.isNull()) + { + sheet->ValidatePiecesOutOfBound(); + } } }); @@ -834,7 +841,15 @@ void VPMainWindow::InitPropertyTabLayout() { m_layout->LayoutSettings().SetWarningSuperpositionOfPieces(checked); LayoutWasSaved(false); - // TODO update the QGraphicView + if (checked) + { + VPSheetPtr sheet = m_layout->GetFocusedSheet(); + if (not sheet.isNull()) + { + sheet->ValidateSuperpositionOfPieces(); + } + } + m_graphicsView->RefreshPieces(); } }); @@ -844,7 +859,16 @@ void VPMainWindow::InitPropertyTabLayout() { m_layout->LayoutSettings().SetWarningPiecesOutOfBound(checked); LayoutWasSaved(false); - // TODO update the QGraphicView + + if (checked) + { + VPSheetPtr sheet = m_layout->GetFocusedSheet(); + if (not sheet.isNull()) + { + sheet->ValidatePiecesOutOfBound(); + } + } + m_graphicsView->RefreshPieces(); } }); @@ -1748,9 +1772,18 @@ void VPMainWindow::SheetPaperSizeChanged() ui->toolButtonSheetLandscapeOrientation->setChecked(not portrait); ui->toolButtonSheetLandscapeOrientation->blockSignals(false); - if (not m_layout.isNull() && m_layout->LayoutSettings().GetFollowGrainline()) + if (not m_layout.isNull()) { - RotatePiecesToGrainline(); + if (m_layout->LayoutSettings().GetFollowGrainline()) + { + RotatePiecesToGrainline(); + } + + VPSheetPtr sheet = m_layout->GetFocusedSheet(); + if (not sheet.isNull()) + { + sheet->ValidatePiecesOutOfBound(); + } } } @@ -2224,6 +2257,13 @@ void VPMainWindow::on_SheetMarginChanged() ui->doubleSpinBoxSheetMarginBottom->value()); LayoutWasSaved(false); + + VPSheetPtr sheet = m_layout->GetFocusedSheet(); + if (not sheet.isNull()) + { + sheet->ValidatePiecesOutOfBound(); + } + m_graphicsView->RefreshLayout(); } } diff --git a/src/libs/vlayout/vlayoutpiece.cpp b/src/libs/vlayout/vlayoutpiece.cpp index 2a09a91cb..33e3c0b43 100644 --- a/src/libs/vlayout/vlayoutpiece.cpp +++ b/src/libs/vlayout/vlayoutpiece.cpp @@ -1048,15 +1048,13 @@ int VLayoutPiece::LayoutEdgeByPoint(const QPointF &p1) const //--------------------------------------------------------------------------------------------------------------------- QRectF VLayoutPiece::MappedDetailBoundingRect() const { - return IsSeamAllowance() && not IsSeamAllowanceBuiltIn() ? BoundingRect(GetMappedSeamAllowancePoints()) : - BoundingRect(GetMappedContourPoints()); + return BoundingRect(GetMappedExternalContourPoints()); } //--------------------------------------------------------------------------------------------------------------------- QRectF VLayoutPiece::DetailBoundingRect() const { - return IsSeamAllowance() && not IsSeamAllowanceBuiltIn() ? BoundingRect(GetSeamAllowancePoints()) : - BoundingRect(GetContourPoints()); + return BoundingRect(GetExternalContourPoints()); } //--------------------------------------------------------------------------------------------------------------------- @@ -1133,6 +1131,20 @@ void VLayoutPiece::SetLayoutAllowancePoints() } } +//--------------------------------------------------------------------------------------------------------------------- +QVector VLayoutPiece::GetMappedExternalContourPoints() const +{ + return IsSeamAllowance() && not IsSeamAllowanceBuiltIn() ? GetMappedSeamAllowancePoints() : + GetMappedContourPoints(); +} + +//--------------------------------------------------------------------------------------------------------------------- +QVector VLayoutPiece::GetExternalContourPoints() const +{ + return IsSeamAllowance() && not IsSeamAllowanceBuiltIn() ? GetSeamAllowancePoints() : + GetContourPoints(); +} + //--------------------------------------------------------------------------------------------------------------------- QVector VLayoutPiece::GetMappedPassmarks() const { diff --git a/src/libs/vlayout/vlayoutpiece.h b/src/libs/vlayout/vlayoutpiece.h index ce70f8f7d..568ba4308 100644 --- a/src/libs/vlayout/vlayoutpiece.h +++ b/src/libs/vlayout/vlayoutpiece.h @@ -86,6 +86,9 @@ public: QVector GetLayoutAllowancePoints() const; void SetLayoutAllowancePoints(); + QVector GetMappedExternalContourPoints() const; + QVector GetExternalContourPoints() const; + QVector GetMappedPassmarks() const; QVector GetPassmarks() const; void SetPassmarks(const QVector &passmarks);