From 357fd3a0ac64b1c2a24a85ab7d45cf18b253527b Mon Sep 17 00:00:00 2001 From: Roman Telezhynskyi Date: Fri, 28 Jan 2022 16:54:20 +0200 Subject: [PATCH] New feature Background image. Closes #43 --- ChangeLog.txt | 1 + src/app/puzzle/scene/vpmaingraphicsview.cpp | 2 +- .../core/vtooloptionspropertybrowser.cpp | 194 +- .../core/vtooloptionspropertybrowser.h | 16 + .../dialogs/dialogaddbackgroundimage.cpp | 55 + .../dialogs/dialogaddbackgroundimage.h | 58 + .../dialogs/dialogaddbackgroundimage.ui | 94 + src/app/valentina/dialogs/dialoghistory.cpp | 6 +- .../dialogs/dialogpatternproperties.cpp | 31 +- src/app/valentina/dialogs/dialogs.pri | 6 + .../dialogs/vwidgetbackgroundimages.cpp | 1013 +++++++++ .../dialogs/vwidgetbackgroundimages.h | 112 + .../dialogs/vwidgetbackgroundimages.ui | 418 ++++ src/app/valentina/mainwindow.cpp | 351 ++- src/app/valentina/mainwindow.h | 30 +- src/app/valentina/mainwindow.ui | 21 +- src/app/valentina/mainwindowsnogui.h | 1 + src/app/valentina/xml/vpattern.cpp | 6 +- src/libs/ifc/schema/pattern/v0.9.0.xsd | 31 + src/libs/ifc/xml/utils.cpp | 110 + src/libs/ifc/xml/utils.h | 42 + src/libs/ifc/xml/vabstractpattern.cpp | 296 ++- src/libs/ifc/xml/vabstractpattern.h | 27 + src/libs/ifc/xml/vbackgroundpatternimage.cpp | 310 +++ src/libs/ifc/xml/vbackgroundpatternimage.h | 109 + src/libs/ifc/xml/vpatternimage.cpp | 44 +- src/libs/ifc/xml/vpatternimage.h | 1 - src/libs/ifc/xml/xml.pri | 4 + src/libs/vformat/vpatternrecipe.cpp | 6 +- src/libs/vmisc/def.h | 4 + src/libs/vmisc/defglobal.h | 9 + src/libs/vmisc/share/resources/icon.qrc | 53 + .../share/resources/icon/16x16/hold_image.png | Bin 0 -> 1681 bytes .../resources/icon/16x16/hold_image@2x.png | Bin 0 -> 1990 bytes .../resources/icon/16x16/not_hold_image.png | Bin 0 -> 1682 bytes .../icon/16x16/not_hold_image@2x.png | Bin 0 -> 1981 bytes .../double-arrow-horizontal-disabled.png | Bin 0 -> 2930 bytes .../double-arrow-horizontal-disabled@2x.png | Bin 0 -> 5115 bytes .../32x32/double-arrow-horizontal-hover.png | Bin 0 -> 634 bytes .../double-arrow-horizontal-hover@2x.png | Bin 0 -> 1054 bytes .../icon/32x32/double-arrow-horizontal.png | Bin 0 -> 310 bytes .../icon/32x32/double-arrow-horizontal@2x.png | Bin 0 -> 501 bytes .../32x32/double-arrow-vertical-disabled.png | Bin 0 -> 3103 bytes .../double-arrow-vertical-disabled@2x.png | Bin 0 -> 5605 bytes .../32x32/double-arrow-vertical-hover.png | Bin 0 -> 758 bytes .../32x32/double-arrow-vertical-hover@2x.png | Bin 0 -> 1504 bytes .../icon/32x32/double-arrow-vertical.png | Bin 0 -> 316 bytes .../icon/32x32/double-arrow-vertical@2x.png | Bin 0 -> 575 bytes .../resources/icon/32x32/expand1-disabled.png | Bin 0 -> 3681 bytes .../icon/32x32/expand1-disabled@2x.png | Bin 0 -> 7292 bytes .../resources/icon/32x32/expand1-hover.png | Bin 0 -> 3846 bytes .../resources/icon/32x32/expand1-hover@2x.png | Bin 0 -> 7417 bytes .../share/resources/icon/32x32/expand1.png | Bin 0 -> 1680 bytes .../share/resources/icon/32x32/expand1@2x.png | Bin 0 -> 1979 bytes .../resources/icon/32x32/expand2-disabled.png | Bin 0 -> 3743 bytes .../icon/32x32/expand2-disabled@2x.png | Bin 0 -> 7417 bytes .../resources/icon/32x32/expand2-hover.png | Bin 0 -> 1495 bytes .../resources/icon/32x32/expand2-hover@2x.png | Bin 0 -> 3241 bytes .../share/resources/icon/32x32/expand2.png | Bin 0 -> 484 bytes .../share/resources/icon/32x32/expand2@2x.png | Bin 0 -> 899 bytes .../32x32/rotate-bottom-left-disabled.png | Bin 0 -> 3542 bytes .../32x32/rotate-bottom-left-disabled@2x.png | Bin 0 -> 6319 bytes .../icon/32x32/rotate-bottom-left-hover.png | Bin 0 -> 1233 bytes .../32x32/rotate-bottom-left-hover@2x.png | Bin 0 -> 2555 bytes .../icon/32x32/rotate-bottom-left.png | Bin 0 -> 609 bytes .../icon/32x32/rotate-bottom-left@2x.png | Bin 0 -> 1265 bytes .../32x32/rotate-bottom-right-disabled.png | Bin 0 -> 3485 bytes .../32x32/rotate-bottom-right-disabled@2x.png | Bin 0 -> 6357 bytes .../icon/32x32/rotate-bottom-right-hover.png | Bin 0 -> 1206 bytes .../32x32/rotate-bottom-right-hover@2x.png | Bin 0 -> 2440 bytes .../icon/32x32/rotate-bottom-right.png | Bin 0 -> 599 bytes .../icon/32x32/rotate-bottom-right@2x.png | Bin 0 -> 1243 bytes .../icon/32x32/rotate-top-left-disabled.png | Bin 0 -> 3565 bytes .../32x32/rotate-top-left-disabled@2x.png | Bin 0 -> 6195 bytes .../icon/32x32/rotate-top-left-hover.png | Bin 0 -> 1245 bytes .../icon/32x32/rotate-top-left-hover@2x.png | Bin 0 -> 2402 bytes .../resources/icon/32x32/rotate-top-left.png | Bin 0 -> 612 bytes .../icon/32x32/rotate-top-left@2x.png | Bin 0 -> 1192 bytes .../icon/32x32/rotate-top-right-disabled.png | Bin 0 -> 3571 bytes .../32x32/rotate-top-right-disabled@2x.png | Bin 0 -> 6309 bytes .../icon/32x32/rotate-top-right-hover.png | Bin 0 -> 1283 bytes .../icon/32x32/rotate-top-right-hover@2x.png | Bin 0 -> 2605 bytes .../resources/icon/32x32/rotate-top-right.png | Bin 0 -> 606 bytes .../icon/32x32/rotate-top-right@2x.png | Bin 0 -> 1252 bytes .../share/resources/icon/svg/broken_path.svg | 1 + src/libs/vmisc/vvalentinasettings.cpp | 20 + src/libs/vmisc/vvalentinasettings.h | 4 + .../plugins/vboolproperty.cpp | 94 +- .../vpropertyexplorer/plugins/vboolproperty.h | 41 +- .../vbackgroundimagecontrols.cpp | 1895 +++++++++++++++++ .../vbackgroundimagecontrols.h | 185 ++ .../backgroundimage/vbackgroundimageitem.cpp | 734 +++++++ .../backgroundimage/vbackgroundimageitem.h | 133 ++ .../backgroundimage/vbackgroundpixmapitem.cpp | 245 +++ .../backgroundimage/vbackgroundpixmapitem.h | 88 + .../backgroundimage/vbackgroundsvgitem.cpp | 123 ++ .../backgroundimage/vbackgroundsvgitem.h | 63 + src/libs/vtools/tools/tools.pri | 8 + src/libs/vtools/tools/toolsdef.cpp | 30 + src/libs/vtools/tools/toolsdef.h | 2 + src/libs/vtools/tools/vabstracttool.cpp | 33 +- src/libs/vtools/tools/vabstracttool.h | 1 - src/libs/vtools/tools/vtoolseamallowance.cpp | 3 +- .../undocommands/image/addbackgroundimage.cpp | 58 + .../undocommands/image/addbackgroundimage.h | 54 + .../image/deletebackgroundimage.cpp | 78 + .../image/deletebackgroundimage.h | 56 + .../image/hideallbackgroundimages.cpp | 79 + .../image/hideallbackgroundimages.h | 51 + .../image/hidebackgroundimage.cpp | 75 + .../undocommands/image/hidebackgroundimage.h | 52 + .../image/holdallbackgroundimages.cpp | 79 + .../image/holdallbackgroundimages.h | 50 + .../image/holdbackgroundimage.cpp | 75 + .../undocommands/image/holdbackgroundimage.h | 52 + .../image/movebackgroundimage.cpp | 126 ++ .../undocommands/image/movebackgroundimage.h | 69 + .../image/renamebackgroundimage.cpp | 68 + .../image/renamebackgroundimage.h | 52 + .../image/resetbackgroundimage.cpp | 66 + .../undocommands/image/resetbackgroundimage.h | 56 + .../image/rotatebackgroundimage.cpp | 114 + .../image/rotatebackgroundimage.h | 67 + .../image/scalebackgroundimage.cpp | 114 + .../undocommands/image/scalebackgroundimage.h | 68 + .../image/zvaluemovebackgroundimage.cpp | 158 ++ .../image/zvaluemovebackgroundimage.h | 59 + src/libs/vtools/undocommands/undocommands.pri | 24 + src/libs/vtools/undocommands/vundocommand.h | 5 +- src/libs/vwidgets/vmaingraphicsscene.cpp | 50 +- src/libs/vwidgets/vmaingraphicsscene.h | 9 +- src/libs/vwidgets/vmaingraphicsview.cpp | 66 + src/libs/vwidgets/vmaingraphicsview.h | 11 +- 133 files changed, 8878 insertions(+), 227 deletions(-) create mode 100644 src/app/valentina/dialogs/dialogaddbackgroundimage.cpp create mode 100644 src/app/valentina/dialogs/dialogaddbackgroundimage.h create mode 100644 src/app/valentina/dialogs/dialogaddbackgroundimage.ui create mode 100644 src/app/valentina/dialogs/vwidgetbackgroundimages.cpp create mode 100644 src/app/valentina/dialogs/vwidgetbackgroundimages.h create mode 100644 src/app/valentina/dialogs/vwidgetbackgroundimages.ui create mode 100644 src/libs/ifc/xml/utils.cpp create mode 100644 src/libs/ifc/xml/utils.h create mode 100644 src/libs/ifc/xml/vbackgroundpatternimage.cpp create mode 100644 src/libs/ifc/xml/vbackgroundpatternimage.h create mode 100644 src/libs/vmisc/share/resources/icon/16x16/hold_image.png create mode 100644 src/libs/vmisc/share/resources/icon/16x16/hold_image@2x.png create mode 100644 src/libs/vmisc/share/resources/icon/16x16/not_hold_image.png create mode 100644 src/libs/vmisc/share/resources/icon/16x16/not_hold_image@2x.png create mode 100644 src/libs/vmisc/share/resources/icon/32x32/double-arrow-horizontal-disabled.png create mode 100644 src/libs/vmisc/share/resources/icon/32x32/double-arrow-horizontal-disabled@2x.png create mode 100644 src/libs/vmisc/share/resources/icon/32x32/double-arrow-horizontal-hover.png create mode 100644 src/libs/vmisc/share/resources/icon/32x32/double-arrow-horizontal-hover@2x.png create mode 100644 src/libs/vmisc/share/resources/icon/32x32/double-arrow-horizontal.png create mode 100644 src/libs/vmisc/share/resources/icon/32x32/double-arrow-horizontal@2x.png create mode 100644 src/libs/vmisc/share/resources/icon/32x32/double-arrow-vertical-disabled.png create mode 100644 src/libs/vmisc/share/resources/icon/32x32/double-arrow-vertical-disabled@2x.png create mode 100644 src/libs/vmisc/share/resources/icon/32x32/double-arrow-vertical-hover.png create mode 100644 src/libs/vmisc/share/resources/icon/32x32/double-arrow-vertical-hover@2x.png create mode 100644 src/libs/vmisc/share/resources/icon/32x32/double-arrow-vertical.png create mode 100644 src/libs/vmisc/share/resources/icon/32x32/double-arrow-vertical@2x.png create mode 100644 src/libs/vmisc/share/resources/icon/32x32/expand1-disabled.png create mode 100644 src/libs/vmisc/share/resources/icon/32x32/expand1-disabled@2x.png create mode 100644 src/libs/vmisc/share/resources/icon/32x32/expand1-hover.png create mode 100644 src/libs/vmisc/share/resources/icon/32x32/expand1-hover@2x.png create mode 100644 src/libs/vmisc/share/resources/icon/32x32/expand1.png create mode 100644 src/libs/vmisc/share/resources/icon/32x32/expand1@2x.png create mode 100644 src/libs/vmisc/share/resources/icon/32x32/expand2-disabled.png create mode 100644 src/libs/vmisc/share/resources/icon/32x32/expand2-disabled@2x.png create mode 100644 src/libs/vmisc/share/resources/icon/32x32/expand2-hover.png create mode 100644 src/libs/vmisc/share/resources/icon/32x32/expand2-hover@2x.png create mode 100644 src/libs/vmisc/share/resources/icon/32x32/expand2.png create mode 100644 src/libs/vmisc/share/resources/icon/32x32/expand2@2x.png create mode 100644 src/libs/vmisc/share/resources/icon/32x32/rotate-bottom-left-disabled.png create mode 100644 src/libs/vmisc/share/resources/icon/32x32/rotate-bottom-left-disabled@2x.png create mode 100644 src/libs/vmisc/share/resources/icon/32x32/rotate-bottom-left-hover.png create mode 100644 src/libs/vmisc/share/resources/icon/32x32/rotate-bottom-left-hover@2x.png create mode 100644 src/libs/vmisc/share/resources/icon/32x32/rotate-bottom-left.png create mode 100644 src/libs/vmisc/share/resources/icon/32x32/rotate-bottom-left@2x.png create mode 100644 src/libs/vmisc/share/resources/icon/32x32/rotate-bottom-right-disabled.png create mode 100644 src/libs/vmisc/share/resources/icon/32x32/rotate-bottom-right-disabled@2x.png create mode 100644 src/libs/vmisc/share/resources/icon/32x32/rotate-bottom-right-hover.png create mode 100644 src/libs/vmisc/share/resources/icon/32x32/rotate-bottom-right-hover@2x.png create mode 100644 src/libs/vmisc/share/resources/icon/32x32/rotate-bottom-right.png create mode 100644 src/libs/vmisc/share/resources/icon/32x32/rotate-bottom-right@2x.png create mode 100644 src/libs/vmisc/share/resources/icon/32x32/rotate-top-left-disabled.png create mode 100644 src/libs/vmisc/share/resources/icon/32x32/rotate-top-left-disabled@2x.png create mode 100644 src/libs/vmisc/share/resources/icon/32x32/rotate-top-left-hover.png create mode 100644 src/libs/vmisc/share/resources/icon/32x32/rotate-top-left-hover@2x.png create mode 100644 src/libs/vmisc/share/resources/icon/32x32/rotate-top-left.png create mode 100644 src/libs/vmisc/share/resources/icon/32x32/rotate-top-left@2x.png create mode 100644 src/libs/vmisc/share/resources/icon/32x32/rotate-top-right-disabled.png create mode 100644 src/libs/vmisc/share/resources/icon/32x32/rotate-top-right-disabled@2x.png create mode 100644 src/libs/vmisc/share/resources/icon/32x32/rotate-top-right-hover.png create mode 100644 src/libs/vmisc/share/resources/icon/32x32/rotate-top-right-hover@2x.png create mode 100644 src/libs/vmisc/share/resources/icon/32x32/rotate-top-right.png create mode 100644 src/libs/vmisc/share/resources/icon/32x32/rotate-top-right@2x.png create mode 100644 src/libs/vmisc/share/resources/icon/svg/broken_path.svg create mode 100644 src/libs/vtools/tools/backgroundimage/vbackgroundimagecontrols.cpp create mode 100644 src/libs/vtools/tools/backgroundimage/vbackgroundimagecontrols.h create mode 100644 src/libs/vtools/tools/backgroundimage/vbackgroundimageitem.cpp create mode 100644 src/libs/vtools/tools/backgroundimage/vbackgroundimageitem.h create mode 100644 src/libs/vtools/tools/backgroundimage/vbackgroundpixmapitem.cpp create mode 100644 src/libs/vtools/tools/backgroundimage/vbackgroundpixmapitem.h create mode 100644 src/libs/vtools/tools/backgroundimage/vbackgroundsvgitem.cpp create mode 100644 src/libs/vtools/tools/backgroundimage/vbackgroundsvgitem.h create mode 100644 src/libs/vtools/undocommands/image/addbackgroundimage.cpp create mode 100644 src/libs/vtools/undocommands/image/addbackgroundimage.h create mode 100644 src/libs/vtools/undocommands/image/deletebackgroundimage.cpp create mode 100644 src/libs/vtools/undocommands/image/deletebackgroundimage.h create mode 100644 src/libs/vtools/undocommands/image/hideallbackgroundimages.cpp create mode 100644 src/libs/vtools/undocommands/image/hideallbackgroundimages.h create mode 100644 src/libs/vtools/undocommands/image/hidebackgroundimage.cpp create mode 100644 src/libs/vtools/undocommands/image/hidebackgroundimage.h create mode 100644 src/libs/vtools/undocommands/image/holdallbackgroundimages.cpp create mode 100644 src/libs/vtools/undocommands/image/holdallbackgroundimages.h create mode 100644 src/libs/vtools/undocommands/image/holdbackgroundimage.cpp create mode 100644 src/libs/vtools/undocommands/image/holdbackgroundimage.h create mode 100644 src/libs/vtools/undocommands/image/movebackgroundimage.cpp create mode 100644 src/libs/vtools/undocommands/image/movebackgroundimage.h create mode 100644 src/libs/vtools/undocommands/image/renamebackgroundimage.cpp create mode 100644 src/libs/vtools/undocommands/image/renamebackgroundimage.h create mode 100644 src/libs/vtools/undocommands/image/resetbackgroundimage.cpp create mode 100644 src/libs/vtools/undocommands/image/resetbackgroundimage.h create mode 100644 src/libs/vtools/undocommands/image/rotatebackgroundimage.cpp create mode 100644 src/libs/vtools/undocommands/image/rotatebackgroundimage.h create mode 100644 src/libs/vtools/undocommands/image/scalebackgroundimage.cpp create mode 100644 src/libs/vtools/undocommands/image/scalebackgroundimage.h create mode 100644 src/libs/vtools/undocommands/image/zvaluemovebackgroundimage.cpp create mode 100644 src/libs/vtools/undocommands/image/zvaluemovebackgroundimage.h diff --git a/ChangeLog.txt b/ChangeLog.txt index a17a41f5a..bab27baa5 100644 --- a/ChangeLog.txt +++ b/ChangeLog.txt @@ -27,6 +27,7 @@ - Backport fix vulnerability CVE-2021-21900. - Improved main path validations. - New path validation Invalid segment. +- [smart-pattern/valentina#43] Background image. # Valentina 0.7.49 July 1, 2021 - Fix crash. diff --git a/src/app/puzzle/scene/vpmaingraphicsview.cpp b/src/app/puzzle/scene/vpmaingraphicsview.cpp index aa9953346..9b1656320 100644 --- a/src/app/puzzle/scene/vpmaingraphicsview.cpp +++ b/src/app/puzzle/scene/vpmaingraphicsview.cpp @@ -657,7 +657,7 @@ void VPMainGraphicsView::SwitchScene(const VPSheetPtr &sheet) { VMainGraphicsScene *scene = sheet->SceneData()->Scene(); setScene(scene); - connect(scene, &VMainGraphicsScene::ItemClicked, this, &VPMainGraphicsView::on_ItemClicked, + connect(scene, &VMainGraphicsScene::ItemByMousePress, this, &VPMainGraphicsView::on_ItemClicked, Qt::UniqueConnection); connect(scene, &VMainGraphicsScene::mouseMove, this, &VPMainGraphicsView::on_SceneMouseMove, Qt::UniqueConnection); diff --git a/src/app/valentina/core/vtooloptionspropertybrowser.cpp b/src/app/valentina/core/vtooloptionspropertybrowser.cpp index 2bd34452e..9faee2563 100644 --- a/src/app/valentina/core/vtooloptionspropertybrowser.cpp +++ b/src/app/valentina/core/vtooloptionspropertybrowser.cpp @@ -28,6 +28,8 @@ #include "vtooloptionspropertybrowser.h" #include "../vtools/tools/drawTools/drawtools.h" +#include "../vtools/tools/backgroundimage/vbackgroundpixmapitem.h" +#include "../vtools/tools/backgroundimage/vbackgroundsvgitem.h" #include "../core/vapplication.h" #include "../vwidgets/vmaingraphicsview.h" #include "../vwidgets/vgraphicssimpletextitem.h" @@ -46,6 +48,12 @@ #include #include +namespace +{ +Q_GLOBAL_STATIC_WITH_ARGS(const QString, AttrHold, (QLatin1String("hold"))) +Q_GLOBAL_STATIC_WITH_ARGS(const QString, AttrVisible, (QLatin1String("visible"))) +} + //--------------------------------------------------------------------------------------------------------------------- VToolOptionsPropertyBrowser::VToolOptionsPropertyBrowser(QDockWidget *parent) :QObject(parent), PropertyModel(nullptr), formView(nullptr), currentItem(nullptr), @@ -79,7 +87,7 @@ void VToolOptionsPropertyBrowser::ClearPropertyBrowser() void VToolOptionsPropertyBrowser::ShowItemOptions(QGraphicsItem *item) { // This check helps to find missed tools in the switch - Q_STATIC_ASSERT_X(static_cast(Tool::LAST_ONE_DO_NOT_USE) == 55, "Not all tools were used in switch."); + Q_STATIC_ASSERT_X(static_cast(Tool::LAST_ONE_DO_NOT_USE) == 59, "Not all tools were used in switch."); switch (item->type()) { @@ -192,6 +200,12 @@ void VToolOptionsPropertyBrowser::ShowItemOptions(QGraphicsItem *item) case VToolEllipticalArc::Type: ShowOptionsToolEllipticalArc(item); break; + case VBackgroundPixmapItem::Type: + ShowOptionsBackgroundPixmapItem(item); + break; + case VBackgroundSVGItem::Type: + ShowOptionsBackgroundSVGItem(item); + break; default: break; } @@ -206,7 +220,7 @@ void VToolOptionsPropertyBrowser::UpdateOptions() } // This check helps to find missed tools in the switch - Q_STATIC_ASSERT_X(static_cast(Tool::LAST_ONE_DO_NOT_USE) == 55, "Not all tools were used in switch."); + Q_STATIC_ASSERT_X(static_cast(Tool::LAST_ONE_DO_NOT_USE) == 59, "Not all tools were used in switch."); switch (currentItem->type()) { @@ -316,6 +330,12 @@ void VToolOptionsPropertyBrowser::UpdateOptions() case VToolEllipticalArc::Type: UpdateOptionsToolEllipticalArc(); break; + case VBackgroundPixmapItem::Type: + UpdateOptionsBackgroundPixmapItem(); + break; + case VBackgroundSVGItem::Type: + UpdateOptionsBackgroundSVGItem(); + break; default: break; } @@ -351,7 +371,7 @@ void VToolOptionsPropertyBrowser::userChangedData(VPE::VProperty *property) } // This check helps to find missed tools in the switch - Q_STATIC_ASSERT_X(static_cast(Tool::LAST_ONE_DO_NOT_USE) == 55, "Not all tools were used in switch."); + Q_STATIC_ASSERT_X(static_cast(Tool::LAST_ONE_DO_NOT_USE) == 59, "Not all tools were used in switch."); switch (currentItem->type()) { @@ -457,6 +477,12 @@ void VToolOptionsPropertyBrowser::userChangedData(VPE::VProperty *property) case VToolEllipticalArc::Type: ChangeDataToolEllipticalArc(prop); break; + case VBackgroundPixmapItem::Type: + ChangeDataBackgroundPixmapItem(prop); + break; + case VBackgroundSVGItem::Type: + ChangeDataBackgroundSVGItem(prop); + break; default: break; } @@ -613,6 +639,14 @@ void VToolOptionsPropertyBrowser::AddPropertyText(const QString &propertyName, c AddProperty(itemText, attrName); } +//--------------------------------------------------------------------------------------------------------------------- +void VToolOptionsPropertyBrowser::AddPropertyBool(const QString &propertyName, bool value, const QString &attrName) +{ + auto *itemBool = new VPE::VBoolProperty(propertyName); + itemBool->setValue(value); + AddProperty(itemBool, attrName); +} + //--------------------------------------------------------------------------------------------------------------------- template void VToolOptionsPropertyBrowser::AddPropertyCrossPoint(Tool *i, const QString &propertyName) @@ -719,6 +753,66 @@ void VToolOptionsPropertyBrowser::AddPropertyApproximationScale(const QString &p AddProperty(aScaleProperty, AttrAScale); } +//--------------------------------------------------------------------------------------------------------------------- +template +void VToolOptionsPropertyBrowser::SetName(VPE::VProperty *property) +{ + if (auto *i = qgraphicsitem_cast(currentItem)) + { + QString name = property->data(VPE::VProperty::DPC_Data, Qt::DisplayRole).toString(); + if (name == i->name()) + { + return; + } + + i->setName(name); + } + else + { + qWarning()<<"Can't cast item"; + } +} + +//--------------------------------------------------------------------------------------------------------------------- +template +void VToolOptionsPropertyBrowser::SetHold(VPE::VProperty *property) +{ + if (auto *i = qgraphicsitem_cast(currentItem)) + { + bool hold = property->data(VPE::VProperty::DPC_Data, Qt::DisplayRole).toBool(); + if (hold == i->IsHold()) + { + return; + } + + i->SetHold(hold); + } + else + { + qWarning()<<"Can't cast item"; + } +} + +//--------------------------------------------------------------------------------------------------------------------- +template +void VToolOptionsPropertyBrowser::SetVisible(VPE::VProperty *property) +{ + if (auto *i = qgraphicsitem_cast(currentItem)) + { + bool visible = property->data(VPE::VProperty::DPC_Data, Qt::DisplayRole).toBool(); + if (visible == i->IsVisible()) + { + return; + } + + i->SetVisible(visible); + } + else + { + qWarning()<<"Can't cast item"; + } +} + //--------------------------------------------------------------------------------------------------------------------- template void VToolOptionsPropertyBrowser::SetPointName(VPE::VProperty *property) @@ -2513,6 +2607,54 @@ void VToolOptionsPropertyBrowser::ChangeDataToolEllipticalArc(VPE::VProperty *pr } } +//--------------------------------------------------------------------------------------------------------------------- +void VToolOptionsPropertyBrowser::ChangeDataBackgroundPixmapItem(VPE::VProperty *property) +{ + SCASSERT(property != nullptr) + + const QString id = propertyToId[property]; + + switch (PropertiesList().indexOf(id)) + { + case 0: // AttrName + SetName(property); + break; + case 65: // AttrHold + SetHold(property); + break; + case 66: // AttrVisible + SetVisible(property); + break; + default: + qWarning()<<"Unknown property type. id = "<(property); + break; + case 65: // AttrHold + SetHold(property); + break; + case 66: // AttrVisible + SetVisible(property); + break; + default: + qWarning()<<"Unknown property type. id = "<GetNotes(), AttrNotes); } +//--------------------------------------------------------------------------------------------------------------------- +void VToolOptionsPropertyBrowser::ShowOptionsBackgroundPixmapItem(QGraphicsItem *item) +{ + auto *i = qgraphicsitem_cast(item); + formView->setTitle(tr("Background image")); + + AddPropertyObjectName(i, tr("Name:"), false); + AddPropertyBool(tr("Hold:"), i->IsHold(), *AttrHold); + AddPropertyBool(tr("Visible:"), i->IsVisible(), *AttrVisible); +} + +//--------------------------------------------------------------------------------------------------------------------- +void VToolOptionsPropertyBrowser::ShowOptionsBackgroundSVGItem(QGraphicsItem *item) +{ + auto *i = qgraphicsitem_cast(item); + formView->setTitle(tr("Background image")); + + AddPropertyObjectName(i, tr("Name:"), false); + AddPropertyBool(tr("Hold:"), i->IsHold(), *AttrHold); + AddPropertyBool(tr("Visible:"), i->IsVisible(), *AttrVisible); +} + //--------------------------------------------------------------------------------------------------------------------- void VToolOptionsPropertyBrowser::UpdateOptionsToolSinglePoint() { @@ -4028,6 +4192,26 @@ void VToolOptionsPropertyBrowser::UpdateOptionsToolEllipticalArc() idToProperty[AttrAlias]->setValue(i->GetAliasSuffix()); } +//--------------------------------------------------------------------------------------------------------------------- +void VToolOptionsPropertyBrowser::UpdateOptionsBackgroundPixmapItem() +{ + auto *i = qgraphicsitem_cast(currentItem); + + idToProperty.value(AttrName)->setValue(i->name()); + idToProperty.value(*AttrHold)->setValue(i->IsHold()); + idToProperty.value(*AttrVisible)->setValue(i->IsVisible()); +} + +//--------------------------------------------------------------------------------------------------------------------- +void VToolOptionsPropertyBrowser::UpdateOptionsBackgroundSVGItem() +{ + auto *i = qgraphicsitem_cast(currentItem); + + idToProperty.value(AttrName)->setValue(i->name()); + idToProperty.value(*AttrHold)->setValue(i->IsHold()); + idToProperty.value(*AttrVisible)->setValue(i->IsVisible()); +} + //--------------------------------------------------------------------------------------------------------------------- QStringList VToolOptionsPropertyBrowser::PropertiesList() const { @@ -4096,7 +4280,9 @@ QStringList VToolOptionsPropertyBrowser::PropertiesList() const AttrNotes, /* 61 */ AttrAlias, /* 62 */ AttrAlias1, /* 63 */ - AttrAlias2 /* 64 */ + AttrAlias2, /* 64 */ + *AttrHold, /* 65 */ + *AttrVisible /* 66 */ }; return attr; } diff --git a/src/app/valentina/core/vtooloptionspropertybrowser.h b/src/app/valentina/core/vtooloptionspropertybrowser.h index cee994701..489800011 100644 --- a/src/app/valentina/core/vtooloptionspropertybrowser.h +++ b/src/app/valentina/core/vtooloptionspropertybrowser.h @@ -66,6 +66,15 @@ private: void AddProperty(VPE::VProperty *property, const QString &id); void ShowItemOptions(QGraphicsItem *item); + template + void SetName(VPE::VProperty *property); + + template + void SetHold(VPE::VProperty *property); + + template + void SetVisible(VPE::VProperty *property); + template void SetPointName(VPE::VProperty *property); @@ -183,6 +192,7 @@ private: void AddPropertyParentPointName(const QString &pointName, const QString &propertyName, const QString &propertyAttribure); void AddPropertyText(const QString &propertyName, const QString &text, const QString &attrName); + void AddPropertyBool(const QString &propertyName, bool value, const QString &attrName); QStringList PropertiesList() const; @@ -220,6 +230,8 @@ private: void ChangeDataToolFlippingByLine(VPE::VProperty *property); void ChangeDataToolFlippingByAxis(VPE::VProperty *property); void ChangeDataToolEllipticalArc(VPE::VProperty *property); + void ChangeDataBackgroundPixmapItem(VPE::VProperty *property); + void ChangeDataBackgroundSVGItem(VPE::VProperty *property); void ShowOptionsToolSinglePoint(QGraphicsItem *item); void ShowOptionsToolEndLine(QGraphicsItem *item); @@ -255,6 +267,8 @@ private: void ShowOptionsToolFlippingByLine(QGraphicsItem *item); void ShowOptionsToolFlippingByAxis(QGraphicsItem *item); void ShowOptionsToolEllipticalArc(QGraphicsItem *item); + void ShowOptionsBackgroundPixmapItem(QGraphicsItem *item); + void ShowOptionsBackgroundSVGItem(QGraphicsItem *item); void UpdateOptionsToolSinglePoint(); void UpdateOptionsToolEndLine(); @@ -290,6 +304,8 @@ private: void UpdateOptionsToolFlippingByLine(); void UpdateOptionsToolFlippingByAxis(); void UpdateOptionsToolEllipticalArc(); + void UpdateOptionsBackgroundPixmapItem(); + void UpdateOptionsBackgroundSVGItem(); }; #endif // VTOOLOPTIONSPROPERTYBROWSER_H diff --git a/src/app/valentina/dialogs/dialogaddbackgroundimage.cpp b/src/app/valentina/dialogs/dialogaddbackgroundimage.cpp new file mode 100644 index 000000000..406349c75 --- /dev/null +++ b/src/app/valentina/dialogs/dialogaddbackgroundimage.cpp @@ -0,0 +1,55 @@ +/************************************************************************ + ** + ** @file dialogaddbackgroundimage.cpp + ** @author Roman Telezhynskyi + ** @date 21 1, 2022 + ** + ** @brief + ** @copyright + ** This source code is part of the Valentina project, a pattern making + ** program, whose allow create and modeling patterns of clothing. + ** Copyright (C) 2022 Valentina project + ** All Rights Reserved. + ** + ** Valentina 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, either version 3 of the License, or + ** (at your option) any later version. + ** + ** Valentina 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 Valentina. If not, see . + ** + *************************************************************************/ +#include "dialogaddbackgroundimage.h" +#include "ui_dialogaddbackgroundimage.h" + +//--------------------------------------------------------------------------------------------------------------------- +DialogAddBackgroundImage::DialogAddBackgroundImage(QWidget *parent) : + QDialog(parent), + ui(new Ui::DialogAddBackgroundImage) +{ + ui->setupUi(this); +} + +//--------------------------------------------------------------------------------------------------------------------- +DialogAddBackgroundImage::~DialogAddBackgroundImage() +{ + delete ui; +} + +//--------------------------------------------------------------------------------------------------------------------- +auto DialogAddBackgroundImage::Name() const -> QString +{ + return ui->lineEditName->text(); +} + +//--------------------------------------------------------------------------------------------------------------------- +auto DialogAddBackgroundImage::BuiltIn() const -> bool +{ + return ui->checkBoxBuiltIn->isChecked(); +} diff --git a/src/app/valentina/dialogs/dialogaddbackgroundimage.h b/src/app/valentina/dialogs/dialogaddbackgroundimage.h new file mode 100644 index 000000000..2fc3914e4 --- /dev/null +++ b/src/app/valentina/dialogs/dialogaddbackgroundimage.h @@ -0,0 +1,58 @@ +/************************************************************************ + ** + ** @file dialogaddbackgroundimage.h + ** @author Roman Telezhynskyi + ** @date 21 1, 2022 + ** + ** @brief + ** @copyright + ** This source code is part of the Valentina project, a pattern making + ** program, whose allow create and modeling patterns of clothing. + ** Copyright (C) 2022 Valentina project + ** All Rights Reserved. + ** + ** Valentina 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, either version 3 of the License, or + ** (at your option) any later version. + ** + ** Valentina 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 Valentina. If not, see . + ** + *************************************************************************/ +#ifndef DIALOGADDBACKGROUNDIMAGE_H +#define DIALOGADDBACKGROUNDIMAGE_H + +#include + +#if QT_VERSION < QT_VERSION_CHECK(5, 13, 0) +#include "../vmisc/defglobal.h" +#endif // QT_VERSION < QT_VERSION_CHECK(5, 13, 0) + +namespace Ui +{ + class DialogAddBackgroundImage; +} + +class DialogAddBackgroundImage : public QDialog +{ + Q_OBJECT + +public: + explicit DialogAddBackgroundImage(QWidget *parent = nullptr); + ~DialogAddBackgroundImage() override; + + auto Name() const -> QString; + auto BuiltIn() const -> bool; + +private: + Q_DISABLE_COPY_MOVE(DialogAddBackgroundImage) + Ui::DialogAddBackgroundImage *ui; +}; + +#endif // DIALOGADDBACKGROUNDIMAGE_H diff --git a/src/app/valentina/dialogs/dialogaddbackgroundimage.ui b/src/app/valentina/dialogs/dialogaddbackgroundimage.ui new file mode 100644 index 000000000..da0fe874a --- /dev/null +++ b/src/app/valentina/dialogs/dialogaddbackgroundimage.ui @@ -0,0 +1,94 @@ + + + DialogAddBackgroundImage + + + + 0 + 0 + 204 + 105 + + + + Background image + + + + :/icon/64x64/icon64x64.png:/icon/64x64/icon64x64.png + + + + + + + + Name: + + + + + + + + + + + + Determine should an image built in or added as path to the file. + + + Built in + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + + + buttonBox + accepted() + DialogAddBackgroundImage + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + DialogAddBackgroundImage + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/src/app/valentina/dialogs/dialoghistory.cpp b/src/app/valentina/dialogs/dialoghistory.cpp index 20fc03cf7..06fe143bd 100644 --- a/src/app/valentina/dialogs/dialoghistory.cpp +++ b/src/app/valentina/dialogs/dialoghistory.cpp @@ -233,7 +233,7 @@ QT_WARNING_DISABLE_GCC("-Wswitch-default") HistoryRecord DialogHistory::Record(const VToolRecord &tool) const { // This check helps to find missed tools in the switch - Q_STATIC_ASSERT_X(static_cast(Tool::LAST_ONE_DO_NOT_USE) == 55, "Not all tools were used in history."); + Q_STATIC_ASSERT_X(static_cast(Tool::LAST_ONE_DO_NOT_USE) == 59, "Not all tools were used in history."); HistoryRecord record; record.id = tool.getId(); @@ -257,6 +257,10 @@ HistoryRecord DialogHistory::Record(const VToolRecord &tool) const case Tool::Cut: case Tool::Midpoint:// Same as Tool::AlongLine, but tool will never has such type case Tool::ArcIntersectAxis:// Same as Tool::CurveIntersectAxis, but tool will never has such type + case Tool::BackgroundImage: + case Tool::BackgroundImageControls: + case Tool::BackgroundPixmapImage: + case Tool::BackgroundSVGImage: case Tool::LAST_ONE_DO_NOT_USE: Q_UNREACHABLE(); //-V501 break; diff --git a/src/app/valentina/dialogs/dialogpatternproperties.cpp b/src/app/valentina/dialogs/dialogpatternproperties.cpp index 47f540e4f..3973c8f18 100644 --- a/src/app/valentina/dialogs/dialogpatternproperties.cpp +++ b/src/app/valentina/dialogs/dialogpatternproperties.cpp @@ -50,6 +50,7 @@ #include "../vmisc/vvalentinasettings.h" #include "../qmuparser/qmudef.h" #include "../ifc/xml/vpatternimage.h" +#include "../ifc/xml/utils.h" //--------------------------------------------------------------------------------------------------------------------- DialogPatternProperties::DialogPatternProperties(VPattern *doc, VContainer *pattern, QWidget *parent) @@ -327,33 +328,9 @@ void DialogPatternProperties::InitImage() //--------------------------------------------------------------------------------------------------------------------- void DialogPatternProperties::ChangeImage() { - auto PrepareFilter = []() - { - const QList supportedFormats = QImageReader::supportedImageFormats(); - const QSet filterFormats{"bmp", "jpeg", "jpg", "png", "svg", "svgz", "tif", "tiff", "webp"}; - QStringList sufixes; - for (const auto& format : supportedFormats) - { - if (filterFormats.contains(format)) - { - sufixes.append(QStringLiteral("*.%1").arg(QString(format))); - } - } - - QStringList filters; - - if (not sufixes.isEmpty()) - { - filters.append(tr("Images") + QStringLiteral(" (%1)").arg(sufixes.join(' '))); - } - - filters.append(tr("All files") + QStringLiteral(" (*.*)")); - - return filters.join(QStringLiteral(";;")); - }; - - const QString fileName = QFileDialog::getOpenFileName(this, tr("Image for pattern"), QString(), PrepareFilter(), - nullptr, VAbstractApplication::VApp()->NativeFileDialog()); + const QString fileName = QFileDialog::getOpenFileName(this, tr("Image for pattern"), QString(), + PrepareImageFilters(), nullptr, + VAbstractApplication::VApp()->NativeFileDialog()); if (not fileName.isEmpty()) { VPatternImage image = VPatternImage::FromFile(fileName); diff --git a/src/app/valentina/dialogs/dialogs.pri b/src/app/valentina/dialogs/dialogs.pri index 192266995..cfeb9151c 100644 --- a/src/app/valentina/dialogs/dialogs.pri +++ b/src/app/valentina/dialogs/dialogs.pri @@ -2,6 +2,7 @@ # This need for corect working file translations.pro HEADERS += \ + $$PWD/dialogaddbackgroundimage.h \ $$PWD/dialogs.h \ $$PWD/dialogincrements.h \ $$PWD/dialoghistory.h \ @@ -11,6 +12,7 @@ HEADERS += \ $$PWD/dialoglayoutsettings.h \ $$PWD/dialoglayoutprogress.h \ $$PWD/dialogsavelayout.h \ + $$PWD/vwidgetbackgroundimages.h \ $$PWD/vwidgetgroups.h \ $$PWD/vwidgetdetails.h \ $$PWD/dialogpreferences.h \ @@ -22,6 +24,7 @@ HEADERS += \ $$PWD/dialogfinalmeasurements.h SOURCES += \ + $$PWD/dialogaddbackgroundimage.cpp \ $$PWD/dialogincrements.cpp \ $$PWD/dialoghistory.cpp \ $$PWD/dialogpatternproperties.cpp \ @@ -30,6 +33,7 @@ SOURCES += \ $$PWD/dialoglayoutsettings.cpp \ $$PWD/dialoglayoutprogress.cpp \ $$PWD/dialogsavelayout.cpp \ + $$PWD/vwidgetbackgroundimages.cpp \ $$PWD/vwidgetgroups.cpp \ $$PWD/vwidgetdetails.cpp \ $$PWD/dialogpreferences.cpp \ @@ -41,6 +45,7 @@ SOURCES += \ $$PWD/dialogfinalmeasurements.cpp FORMS += \ + $$PWD/dialogaddbackgroundimage.ui \ $$PWD/dialogincrements.ui \ $$PWD/dialoghistory.ui \ $$PWD/dialogpatternproperties.ui \ @@ -49,6 +54,7 @@ FORMS += \ $$PWD/dialoglayoutsettings.ui \ $$PWD/dialoglayoutprogress.ui \ $$PWD/dialogsavelayout.ui \ + $$PWD/vwidgetbackgroundimages.ui \ $$PWD/vwidgetgroups.ui \ $$PWD/vwidgetdetails.ui \ $$PWD/dialogpreferences.ui \ diff --git a/src/app/valentina/dialogs/vwidgetbackgroundimages.cpp b/src/app/valentina/dialogs/vwidgetbackgroundimages.cpp new file mode 100644 index 000000000..c9182f036 --- /dev/null +++ b/src/app/valentina/dialogs/vwidgetbackgroundimages.cpp @@ -0,0 +1,1013 @@ +/************************************************************************ + ** + ** @file vwidgetbackgroundimages.cpp + ** @author Roman Telezhynskyi + ** @date 26 1, 2022 + ** + ** @brief + ** @copyright + ** This source code is part of the Valentina project, a pattern making + ** program, whose allow create and modeling patterns of clothing. + ** Copyright (C) 2022 Valentina project + ** All Rights Reserved. + ** + ** Valentina 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, either version 3 of the License, or + ** (at your option) any later version. + ** + ** Valentina 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 Valentina. If not, see . + ** + *************************************************************************/ +#include "vwidgetbackgroundimages.h" +#include "ui_vwidgetbackgroundimages.h" + +#include "../vmisc/def.h" +#include "../ifc/xml/vabstractpattern.h" +#include "../ifc/xml/vbackgroundpatternimage.h" +#include "../vtools/undocommands/image/holdbackgroundimage.h" +#include "../vtools/undocommands/image/renamebackgroundimage.h" +#include "../vtools/undocommands/image/hidebackgroundimage.h" +#include "../vtools/undocommands/image/hideallbackgroundimages.h" +#include "../vtools/undocommands/image/holdallbackgroundimages.h" +#include "../vtools/undocommands/image/zvaluemovebackgroundimage.h" +#include "../vtools/undocommands/image/movebackgroundimage.h" +#include "../vtools/undocommands/image/rotatebackgroundimage.h" +#include "../vtools/undocommands/image/scalebackgroundimage.h" +#include "../vtools/undocommands/image/resetbackgroundimage.h" +#include "../vmisc/vabstractapplication.h" + +#include +#include + +namespace +{ +enum ImageData +{ + Hold = 0, + Visibility = 1, + Name = 2 +}; + +//--------------------------------------------------------------------------------------------------------------------- +void SetImageHold(QTableWidgetItem *item, const VBackgroundPatternImage &image) +{ + if (item) + { + (image.Hold()) ? item->setIcon(QIcon(QStringLiteral("://icon/16x16/hold_image.png"))) + : item->setIcon(QIcon(QStringLiteral("://icon/16x16/not_hold_image.png"))); + } +} + +//--------------------------------------------------------------------------------------------------------------------- +void SetImageVisibility(QTableWidgetItem *item, const VBackgroundPatternImage &image) +{ + if (item) + { + (image.Visible()) ? item->setIcon(QIcon(QStringLiteral("://icon/16x16/open_eye.png"))) + : item->setIcon(QIcon(QStringLiteral("://icon/16x16/closed_eye.png"))); + } +} + +//--------------------------------------------------------------------------------------------------------------------- +void SetImageName(QTableWidgetItem *item, const VBackgroundPatternImage &image, const QString &def) +{ + QString imageName = def; + if (not image.Name().isEmpty()) + { + imageName = image.Name(); + } + item->setText(imageName); + item->setToolTip(imageName); +} + +//--------------------------------------------------------------------------------------------------------------------- +void SetCheckBoxValue(QCheckBox *checkbox, bool value) +{ + checkbox->blockSignals(true); + checkbox->setChecked(value); + checkbox->blockSignals(false); +} + +//--------------------------------------------------------------------------------------------------------------------- +auto ScaleUnitConvertor(qreal base, qreal value, ScaleUnit from, ScaleUnit to) -> qreal +{ + auto FromPxTo = [base, to](qreal value) + { + switch (to) + { + case ScaleUnit::Mm: + return (value / PrintDPI) * 25.4; + case ScaleUnit::Cm: + return ((value / PrintDPI) * 25.4) / 10.0; + case ScaleUnit::Inch: + return value / PrintDPI; + case ScaleUnit::Px: + return value; + case ScaleUnit::Percent: + if (qFuzzyIsNull(base)) + { + return 0.0; + } + return value / base * 100.; + default: + break; + } + + return 0.0; + }; + + switch (from) + { + case ScaleUnit::Percent: + return FromPxTo(base * (value / 100.)); + case ScaleUnit::Px: + return FromPxTo(value); + case ScaleUnit::Mm: + return FromPxTo((value / 25.4) * PrintDPI); + case ScaleUnit::Cm: + return FromPxTo(((value * 10.0) / 25.4) * PrintDPI); + case ScaleUnit::Inch: + return FromPxTo(value * PrintDPI); + default: + break; + } + + return 0; +} +} // namespace + +//--------------------------------------------------------------------------------------------------------------------- +VWidgetBackgroundImages::VWidgetBackgroundImages(VAbstractPattern *doc, QWidget *parent) + : QWidget(parent), + ui(new Ui::VWidgetBackgroundImages), + m_doc(doc) +{ + ui->setupUi(this); + + SCASSERT(doc != nullptr) + + UpdateImages(); + + ui->tableWidget->setContextMenuPolicy(Qt::CustomContextMenu); + + connect(doc, &VAbstractPattern::BackgroundImageHoldChanged, this, &VWidgetBackgroundImages::UpdateImage); + connect(doc, &VAbstractPattern::BackgroundImageVisibilityChanged, this, &VWidgetBackgroundImages::UpdateImage); + connect(doc, &VAbstractPattern::BackgroundImageNameChanged, this, &VWidgetBackgroundImages::UpdateImage); + connect(doc, &VAbstractPattern::BackgroundImagesHoldChanged, this, &VWidgetBackgroundImages::UpdateImages); + connect(doc, &VAbstractPattern::BackgroundImagesVisibilityChanged, this, &VWidgetBackgroundImages::UpdateImages); + connect(doc, &VAbstractPattern::BackgroundImagePositionChanged, this, + &VWidgetBackgroundImages::ImagePositionChanged); + connect(doc, &VAbstractPattern::BackgroundImageTransformationChanged, this, + &VWidgetBackgroundImages::ImagePositionChanged); + + connect(ui->tableWidget, &QTableWidget::cellClicked, this, &VWidgetBackgroundImages::ImageHoldChanged); + connect(ui->tableWidget, &QTableWidget::cellClicked, this, &VWidgetBackgroundImages::ImageVisibilityChanged); + connect(ui->tableWidget, &QTableWidget::cellChanged, this, &VWidgetBackgroundImages::ImageNameChanged); + connect(ui->tableWidget, &QTableWidget::customContextMenuRequested, this, &VWidgetBackgroundImages::ContextMenu); + connect(ui->tableWidget, &QTableWidget::currentCellChanged, this, &VWidgetBackgroundImages::CurrentImageChanged); + + connect(ui->toolButtonTop, &QToolButton::clicked, this, &VWidgetBackgroundImages::MoveImageOnTop); + connect(ui->toolButtonUp, &QToolButton::clicked, this, &VWidgetBackgroundImages::MoveImageUp); + connect(ui->toolButtonDown, &QToolButton::clicked, this, &VWidgetBackgroundImages::MoveImageDown); + connect(ui->toolButtonBottom, &QToolButton::clicked, this, &VWidgetBackgroundImages::MoveImageBottom); + + InitImageTranslation(); +} + +//--------------------------------------------------------------------------------------------------------------------- +VWidgetBackgroundImages::~VWidgetBackgroundImages() +{ + delete ui; +} + +//--------------------------------------------------------------------------------------------------------------------- +void VWidgetBackgroundImages::UpdateImages() +{ + FillTable(m_doc->GetBackgroundImages()); +} + +//--------------------------------------------------------------------------------------------------------------------- +void VWidgetBackgroundImages::UpdateImage(const QUuid &id) +{ + int row = ImageRow(id); + if (row == -1) + { + return; + } + + VBackgroundPatternImage image = m_doc->GetBackgroundImage(id); + if (image.IsNull()) + { + return; + } + + ui->tableWidget->blockSignals(true); + + QTableWidgetItem *item = ui->tableWidget->item(row, ImageData::Hold); + SetImageHold(item, image); + + item = ui->tableWidget->item(row, ImageData::Visibility); + SetImageVisibility(item, image); + + item = ui->tableWidget->item(row, ImageData::Name); + SetImageName(item, image, tr("Background image")); + + ui->tableWidget->blockSignals(false); +} + +//--------------------------------------------------------------------------------------------------------------------- +void VWidgetBackgroundImages::ImageSelected(const QUuid &id) +{ + int row = ImageRow(id); + + ui->tableWidget->blockSignals(true); + ui->tableWidget->setCurrentCell(row, ImageData::Name); + ui->tableWidget->blockSignals(false); + + if (row != -1 && not ui->checkBoxRelativeTranslation->isChecked()) + { + SetAbsolutePisition(id); + } +} + +//--------------------------------------------------------------------------------------------------------------------- +void VWidgetBackgroundImages::ImageHoldChanged(int row, int column) +{ + if (column != ImageData::Hold) + { + return; + } + + QTableWidgetItem *item = ui->tableWidget->item(row, column); + if (item != nullptr) + { + ToggleImageHold(item->data(Qt::UserRole).toUuid()); + } +} + +//--------------------------------------------------------------------------------------------------------------------- +void VWidgetBackgroundImages::ImageVisibilityChanged(int row, int column) +{ + if (column != ImageData::Visibility) + { + return; + } + + QTableWidgetItem *item = ui->tableWidget->item(row, column); + if (item != nullptr) + { + ToggleImageVisibility(item->data(Qt::UserRole).toUuid()); + } +} + +//--------------------------------------------------------------------------------------------------------------------- +void VWidgetBackgroundImages::ImageNameChanged(int row, int column) +{ + if (column != ImageData::Name) + { + return; + } + + QTableWidgetItem *item = ui->tableWidget->item(row, column); + if (item != nullptr) + { + auto *command = new RenameBackgroundImage(item->data(Qt::UserRole).toUuid(), item->text(), m_doc); + VAbstractApplication::VApp()->getUndoStack()->push(command); + } +} + +//--------------------------------------------------------------------------------------------------------------------- +void VWidgetBackgroundImages::ContextMenu(const QPoint &pos) +{ + QTableWidgetItem *item = ui->tableWidget->itemAt(pos); + if(item == nullptr) + { + return; + } + + const int row = item->row(); + item = ui->tableWidget->item(row, 0); + const QUuid id = item->data(Qt::UserRole).toUuid(); + VBackgroundPatternImage image = m_doc->GetBackgroundImage(id); + if (image.IsNull()) + { + return; + } + + auto MultipleChangeHoldTo = [this](bool visibility) + { + for (int r = 0; r < ui->tableWidget->rowCount(); ++r) + { + QTableWidgetItem *rowItem = ui->tableWidget->item(r, ImageData::Visibility); + if (rowItem && visibility != m_doc->GetBackgroundImage(rowItem->data(Qt::UserRole).toUuid()).Hold()) + { + return true; + } + } + + return false; + }; + + auto MultipleChangeVisibilityTo = [this](bool visibility) + { + for (int r = 0; r < ui->tableWidget->rowCount(); ++r) + { + QTableWidgetItem *rowItem = ui->tableWidget->item(r, ImageData::Visibility); + if (rowItem && visibility != m_doc->GetBackgroundImage(rowItem->data(Qt::UserRole).toUuid()).Visible()) + { + return true; + } + } + + return false; + }; + + QMenu menu; + + QAction *holdOption = menu.addAction(tr("Hold")); + holdOption->setCheckable(true); + holdOption->setChecked(image.Hold()); + + QAction *actionVisible = menu.addAction(tr("Visible")); + actionVisible->setCheckable(true); + actionVisible->setChecked(image.Visible()); + + QAction *actionReset = menu.addAction(tr("Reset transformation")); + actionReset->setEnabled(not image.Hold()); + + QAction *actionDelete = menu.addAction(QIcon::fromTheme(editDeleteIcon), tr("Delete")); + + menu.addSeparator(); + QAction *actionHoldAll = menu.addAction(tr("Hold All")); + actionHoldAll->setEnabled(MultipleChangeHoldTo(true)); + QAction *actionUnholdAll = menu.addAction(tr("Unhold All")); + actionUnholdAll->setEnabled(MultipleChangeHoldTo(false)); + + menu.addSeparator(); + QAction *actionHideAll = menu.addAction(tr("Hide All")); + actionHideAll->setEnabled(MultipleChangeVisibilityTo(false)); + QAction *actionShowAll = menu.addAction(tr("Show All")); + actionShowAll->setEnabled(MultipleChangeVisibilityTo(true)); + + QAction *selectedAction = menu.exec(ui->tableWidget->viewport()->mapToGlobal(pos)); + + if (selectedAction == holdOption) + { + ToggleImageHold(id); + } + else if (selectedAction == actionVisible) + { + ToggleImageVisibility(id); + } + else if (selectedAction == actionReset) + { + VAbstractApplication::VApp()->getUndoStack()->push(new ResetBackgroundImage(image.Id(), m_doc)); + } + else if (selectedAction == actionDelete) + { + emit DeleteImage(id); + } + else if (selectedAction == actionHoldAll) + { + auto *command = new HoldAllBackgroundImages(true, m_doc); + VAbstractApplication::VApp()->getUndoStack()->push(command); + } + else if (selectedAction == actionUnholdAll) + { + auto *command = new HoldAllBackgroundImages(false, m_doc); + VAbstractApplication::VApp()->getUndoStack()->push(command); + } + else if (selectedAction == actionHideAll) + { + auto *command = new HideAllBackgroundImages(true, m_doc); + VAbstractApplication::VApp()->getUndoStack()->push(command); + } + else if (selectedAction == actionShowAll) + { + auto *command = new HideAllBackgroundImages(false, m_doc); + VAbstractApplication::VApp()->getUndoStack()->push(command); + } +} + +//--------------------------------------------------------------------------------------------------------------------- +void VWidgetBackgroundImages::CurrentImageChanged(int currentRow, int currentColumn, int previousRow, int previousColumn) +{ + Q_UNUSED(currentColumn) + Q_UNUSED(previousColumn) + if (previousRow != currentRow) + { + QTableWidgetItem *item = ui->tableWidget->item(currentRow, 0); + if (item != nullptr) + { + emit SelectImage(item->data(Qt::UserRole).toUuid()); + } + } +} + +//--------------------------------------------------------------------------------------------------------------------- +void VWidgetBackgroundImages::MoveImageOnTop() +{ + int row = ui->tableWidget->currentRow(); + if (row == -1) + { + return; + } + + QTableWidgetItem *item = ui->tableWidget->item(row, 0); + if (item != nullptr) + { + QUuid id = item->data(Qt::UserRole).toUuid(); + auto *command = new ZValueMoveBackgroundImage(id, ZValueMove::Top, m_doc); + VAbstractApplication::VApp()->getUndoStack()->push(command); + } +} + +//--------------------------------------------------------------------------------------------------------------------- +void VWidgetBackgroundImages::MoveImageUp() +{ + int row = ui->tableWidget->currentRow(); + if (row == -1) + { + return; + } + + QTableWidgetItem *item = ui->tableWidget->item(row, 0); + if (item != nullptr) + { + QUuid id = item->data(Qt::UserRole).toUuid(); + auto *command = new ZValueMoveBackgroundImage(id, ZValueMove::Up, m_doc); + VAbstractApplication::VApp()->getUndoStack()->push(command); + } +} + +//--------------------------------------------------------------------------------------------------------------------- +void VWidgetBackgroundImages::MoveImageDown() +{ + int row = ui->tableWidget->currentRow(); + if (row == -1) + { + return; + } + + QTableWidgetItem *item = ui->tableWidget->item(row, 0); + if (item != nullptr) + { + QUuid id = item->data(Qt::UserRole).toUuid(); + auto *command = new ZValueMoveBackgroundImage(id, ZValueMove::Down, m_doc); + VAbstractApplication::VApp()->getUndoStack()->push(command); + } +} + +//--------------------------------------------------------------------------------------------------------------------- +void VWidgetBackgroundImages::MoveImageBottom() +{ + int row = ui->tableWidget->currentRow(); + if (row == -1) + { + return; + } + + QTableWidgetItem *item = ui->tableWidget->item(row, 0); + if (item != nullptr) + { + QUuid id = item->data(Qt::UserRole).toUuid(); + auto *command = new ZValueMoveBackgroundImage(id, ZValueMove::Bottom, m_doc); + VAbstractApplication::VApp()->getUndoStack()->push(command); + } +} + +//--------------------------------------------------------------------------------------------------------------------- +void VWidgetBackgroundImages::ApplyImageTransformation() +{ + int row = ui->tableWidget->currentRow(); + if (row == -1) + { + return; + } + + QTableWidgetItem *item = ui->tableWidget->item(row, 0); + if (item == nullptr) + { + return; + } + + QUuid id = item->data(Qt::UserRole).toUuid(); + VBackgroundPatternImage image = m_doc->GetBackgroundImage(id); + + const int index = ui->tabWidgetImageTransformation->currentIndex(); + if (ui->tabWidgetImageTransformation->indexOf(ui->tabTranslate) == index) + { // translate + qreal dx = UnitConvertor(ui->doubleSpinBoxImageHorizontalTranslate->value(), CurrentTranslateUnit(), Unit::Px); + qreal dy = UnitConvertor(ui->doubleSpinBoxImageVerticalTranslate->value(), CurrentTranslateUnit(), Unit::Px); + + if (not ui->checkBoxRelativeTranslation->isChecked()) + { + QRectF rect = image.BoundingRect(); + dx = dx - rect.topLeft().x(); + dy = dy - rect.topLeft().y(); + } + + auto *command = new MoveBackgroundImage(id, dx, dy, m_doc); + VAbstractApplication::VApp()->getUndoStack()->push(command); + } + else if (ui->tabWidgetImageTransformation->indexOf(ui->tabScale) == index) + { // scale + qreal sx = WidthScaleUnitConvertor(ui->doubleSpinBoxScaleWidth->value(), CurrentScaleUnit(), + ScaleUnit::Percent) / 100; + qreal sy = HeightScaleUnitConvertor(ui->doubleSpinBoxScaleHeight->value(), CurrentScaleUnit(), + ScaleUnit::Percent) / 100; + + QTransform imageMatrix = image.Matrix(); + QPointF originPos = image.BoundingRect().center(); + + QTransform m; + m.translate(originPos.x(), originPos.y()); + m.scale(sx, sy); + m.translate(-originPos.x(), -originPos.y()); + imageMatrix *= m; + + auto *command = new ScaleBackgroundImage(id, imageMatrix, m_doc); + VAbstractApplication::VApp()->getUndoStack()->push(command); + + } + else if (ui->tabWidgetImageTransformation->indexOf(ui->tabRotate) == index) + { // rotate + qreal angle = ui->doubleSpinBoxRotationAngle->value(); + + if (ui->toolButtonImageRotationClockwise->isChecked()) + { + angle *= -1; + } + + QTransform imageMatrix = image.Matrix(); + + QPointF originPos = image.BoundingRect().center(); + + QTransform m; + m.translate(originPos.x(), originPos.y()); + m.rotate(-angle); + m.translate(-originPos.x(), -originPos.y()); + imageMatrix *= m; + + auto *command = new RotateBackgroundImage(id, imageMatrix, m_doc); + VAbstractApplication::VApp()->getUndoStack()->push(command); + } +} + +//--------------------------------------------------------------------------------------------------------------------- +void VWidgetBackgroundImages::ResetImageTransformationSettings() +{ + const int index = ui->tabWidgetImageTransformation->currentIndex(); + if (ui->tabWidgetImageTransformation->indexOf(ui->tabTranslate) == index) + { // translate + if (ui->checkBoxRelativeTranslation->isChecked()) + { + ui->doubleSpinBoxImageHorizontalTranslate->setValue(0); + ui->doubleSpinBoxImageVerticalTranslate->setValue(0); + } + else + { + if (ui->tableWidget->currentRow() == -1) + { + ui->doubleSpinBoxImageHorizontalTranslate->setValue(0); + ui->doubleSpinBoxImageVerticalTranslate->setValue(0); + } + } + + int unitIndex = ui->comboBoxTranslateUnit->findData(QVariant(static_cast(Unit::Px))); + if (unitIndex != -1) + { + ui->comboBoxTranslateUnit->setCurrentIndex(unitIndex); + } + } + else if (ui->tabWidgetImageTransformation->indexOf(ui->tabScale) == index) + { // scale + int unitIndex = ui->comboBoxScaleUnit->findData(QVariant(static_cast(ScaleUnit::Percent))); + if (unitIndex != -1) + { + ui->comboBoxScaleUnit->setCurrentIndex(unitIndex); + } + + ui->doubleSpinBoxScaleHeight->setValue(100); + ui->doubleSpinBoxScaleWidth->setValue(100); + } + else if (ui->tabWidgetImageTransformation->indexOf(ui->tabRotate) == index) + { // rotate + ui->doubleSpinBoxRotationAngle->setValue(0); + } +} + +//--------------------------------------------------------------------------------------------------------------------- +void VWidgetBackgroundImages::RelativeTranslationChanged(bool checked) +{ + if (checked) + { + ui->doubleSpinBoxImageHorizontalTranslate->setValue(0); + ui->doubleSpinBoxImageVerticalTranslate->setValue(0); + } + else + { + int row = ui->tableWidget->currentRow(); + if (row == -1) + { + return; + } + + QTableWidgetItem *item = ui->tableWidget->item(row, 0); + if (item != nullptr) + { + QUuid id = item->data(Qt::UserRole).toUuid(); + SetAbsolutePisition(id); + } + } +} + +//--------------------------------------------------------------------------------------------------------------------- +void VWidgetBackgroundImages::ScaleProportionallyChanged(bool checked) +{ + if (checked) + { + qreal value = ui->doubleSpinBoxScaleWidth->value(); + ui->doubleSpinBoxScaleHeight->setValue(value); + } +} + +//--------------------------------------------------------------------------------------------------------------------- +void VWidgetBackgroundImages::ScaleWidthChanged(double value) +{ + if (ui->checkBoxScaleProportionally->isChecked()) + { + ScaleUnit unit = CurrentScaleUnit(); + if (unit == ScaleUnit::Percent) + { + ui->doubleSpinBoxScaleHeight->blockSignals(true); + ui->doubleSpinBoxScaleHeight->setValue(value); + ui->doubleSpinBoxScaleHeight->blockSignals(false); + } + else + { + qreal factor = WidthScaleUnitConvertor(value, unit, ScaleUnit::Percent) / 100.; + qreal heightPx = ImageHeight() * factor; + qreal height = HeightScaleUnitConvertor(heightPx, ScaleUnit::Px, unit); + + ui->doubleSpinBoxScaleHeight->blockSignals(true); + ui->doubleSpinBoxScaleHeight->setValue(height); + ui->doubleSpinBoxScaleHeight->blockSignals(false); + } + } +} + +//--------------------------------------------------------------------------------------------------------------------- +void VWidgetBackgroundImages::ScaleHeightChanged(double value) +{ + if (ui->checkBoxScaleProportionally->isChecked()) + { + ScaleUnit unit = CurrentScaleUnit(); + if (unit == ScaleUnit::Percent) + { + ui->doubleSpinBoxScaleWidth->blockSignals(true); + ui->doubleSpinBoxScaleWidth->setValue(value); + ui->doubleSpinBoxScaleWidth->blockSignals(false); + } + else + { + qreal factor = HeightScaleUnitConvertor(value, unit, ScaleUnit::Percent) / 100.; + qreal widthPx = ImageWidth() * factor; + qreal width = WidthScaleUnitConvertor(widthPx, ScaleUnit::Px, unit); + + ui->doubleSpinBoxScaleHeight->blockSignals(true); + ui->doubleSpinBoxScaleHeight->setValue(width); + ui->doubleSpinBoxScaleHeight->blockSignals(false); + } + } +} + +//--------------------------------------------------------------------------------------------------------------------- +void VWidgetBackgroundImages::ImagePositionChanged(const QUuid &id) +{ + if (ui->checkBoxRelativeTranslation->isChecked()) + { + return; + } + + int row = ui->tableWidget->currentRow(); + if (row == -1) + { + return; + } + + QTableWidgetItem *item = ui->tableWidget->item(row, 0); + if (item != nullptr) + { + QUuid curentId = item->data(Qt::UserRole).toUuid(); + if (curentId != id) + { + return; + } + + SetAbsolutePisition(id); + } +} + +//--------------------------------------------------------------------------------------------------------------------- +void VWidgetBackgroundImages::FillTable(const QVector &images) +{ + ui->tableWidget->blockSignals(true); + ui->tableWidget->clear(); + + ui->tableWidget->setColumnCount(3); + ui->tableWidget->setRowCount(images.size()); + qint32 currentRow = -1; + + auto ReadOnly = [](QTableWidgetItem *item) + { + // set the item non-editable (view only), and non-selectable + Qt::ItemFlags flags = item->flags(); + flags &= ~(Qt::ItemIsEditable); // reset/clear the flag + item->setFlags(flags); + }; + + for (const auto &image : images) + { + ++currentRow; + + // Hold + auto *item = new QTableWidgetItem(); + item->setTextAlignment(Qt::AlignHCenter); + SetImageHold(item, image); + item->setData(Qt::UserRole, image.Id()); + ReadOnly(item); + ui->tableWidget->setItem(currentRow, 0, item); + + // Visibility + item = new QTableWidgetItem(); + item->setTextAlignment(Qt::AlignHCenter); + SetImageVisibility(item, image); + item->setData(Qt::UserRole, image.Id()); + ReadOnly(item); + ui->tableWidget->setItem(currentRow, 1, item); + + item = new QTableWidgetItem(); + item->setTextAlignment(Qt::AlignLeft | Qt::AlignVCenter); + item->setData(Qt::UserRole, image.Id()); + SetImageName(item, image, tr("Background image")); + ui->tableWidget->setItem(currentRow, 2, item); + } + + ui->tableWidget->resizeColumnsToContents(); + ui->tableWidget->resizeRowsToContents(); + ui->tableWidget->blockSignals(false); +} + +//--------------------------------------------------------------------------------------------------------------------- +void VWidgetBackgroundImages::ToggleImageHold(const QUuid &id) const +{ + VBackgroundPatternImage image = m_doc->GetBackgroundImage(id); + if (not image.IsNull()) + { + auto *command = new HoldBackgroundImage(image.Id(), not image.Hold(), m_doc); + VAbstractApplication::VApp()->getUndoStack()->push(command); + } +} + +//--------------------------------------------------------------------------------------------------------------------- +void VWidgetBackgroundImages::ToggleImageVisibility(const QUuid &id) const +{ + VBackgroundPatternImage image = m_doc->GetBackgroundImage(id); + if (not image.IsNull()) + { + auto *command = new HideBackgroundImage(image.Id(), image.Visible(), m_doc); + VAbstractApplication::VApp()->getUndoStack()->push(command); + } +} + +//--------------------------------------------------------------------------------------------------------------------- +auto VWidgetBackgroundImages::ImageRow(const QUuid &id) const -> int +{ + for (int r = 0; r < ui->tableWidget->rowCount(); ++r) + { + QTableWidgetItem *item = ui->tableWidget->item(r, 0); + if (item != nullptr && id == item->data(Qt::UserRole).toUuid()) + { + return r; + } + } + + return -1; +} + +//--------------------------------------------------------------------------------------------------------------------- +auto VWidgetBackgroundImages::CurrentTranslateUnit() const -> Unit +{ + return static_cast(ui->comboBoxTranslateUnit->currentData().toInt()); +} + +//--------------------------------------------------------------------------------------------------------------------- +auto VWidgetBackgroundImages::CurrentScaleUnit() const -> enum ScaleUnit +{ + return static_cast(ui->comboBoxScaleUnit->currentData().toInt()); +} + +//--------------------------------------------------------------------------------------------------------------------- +void VWidgetBackgroundImages::InitImageTranslation() +{ + // Translate + ui->comboBoxTranslateUnit->addItem(tr("Pixels"), QVariant(static_cast(Unit::Px))); + ui->comboBoxTranslateUnit->addItem(tr("Millimiters"), QVariant(static_cast(Unit::Mm))); + ui->comboBoxTranslateUnit->addItem(tr("Centimeters"), QVariant(static_cast(Unit::Cm))); + ui->comboBoxTranslateUnit->addItem(tr("Inches"), QVariant(static_cast(Unit::Inch))); + + ui->comboBoxTranslateUnit->blockSignals(true); + ui->comboBoxTranslateUnit->setCurrentIndex(0); + ui->comboBoxTranslateUnit->blockSignals(false); + + const int minTranslate = -10000; + const int maxTranslate = 10000; + + ui->doubleSpinBoxImageHorizontalTranslate->setMinimum( + UnitConvertor(minTranslate, Unit::Cm, m_oldImageTranslationUnit)); + ui->doubleSpinBoxImageHorizontalTranslate->setMaximum( + UnitConvertor(maxTranslate, Unit::Cm, m_oldImageTranslationUnit)); + ui->doubleSpinBoxImageHorizontalTranslate->setValue(0); + + ui->doubleSpinBoxImageVerticalTranslate->setMinimum( + UnitConvertor(minTranslate, Unit::Cm, m_oldImageTranslationUnit)); + ui->doubleSpinBoxImageVerticalTranslate->setMaximum( + UnitConvertor(maxTranslate, Unit::Cm, m_oldImageTranslationUnit)); + ui->doubleSpinBoxImageVerticalTranslate->setValue(0); + + connect(ui->comboBoxTranslateUnit, QOverload::of(&QComboBox::currentIndexChanged), this, + [this, minTranslate, maxTranslate]() + { + const Unit newUnit = CurrentTranslateUnit(); + const qreal oldTranslateX = ui->doubleSpinBoxImageHorizontalTranslate->value(); + const qreal oldTranslateY = ui->doubleSpinBoxImageVerticalTranslate->value(); + + ui->doubleSpinBoxImageHorizontalTranslate->setMinimum(UnitConvertor(minTranslate, Unit::Cm, newUnit)); + ui->doubleSpinBoxImageHorizontalTranslate->setMaximum(UnitConvertor(maxTranslate, Unit::Cm, newUnit)); + + ui->doubleSpinBoxImageVerticalTranslate->setMinimum(UnitConvertor(minTranslate, Unit::Cm, newUnit)); + ui->doubleSpinBoxImageVerticalTranslate->setMaximum(UnitConvertor(maxTranslate, Unit::Cm, newUnit)); + + ui->doubleSpinBoxImageHorizontalTranslate->setValue( + UnitConvertor(oldTranslateX, m_oldImageTranslationUnit, newUnit)); + ui->doubleSpinBoxImageVerticalTranslate->setValue( + UnitConvertor(oldTranslateY, m_oldImageTranslationUnit, newUnit)); + + m_oldImageTranslationUnit = newUnit; + }); + + SetCheckBoxValue(ui->checkBoxRelativeTranslation, true); + connect(ui->checkBoxRelativeTranslation, &QCheckBox::toggled, this, + &VWidgetBackgroundImages::RelativeTranslationChanged); + + // Scale + ui->comboBoxScaleUnit->addItem(QChar('%'), QVariant(static_cast(ScaleUnit::Percent))); + ui->comboBoxScaleUnit->addItem(tr("Millimiters"), QVariant(static_cast(ScaleUnit::Mm))); + ui->comboBoxScaleUnit->addItem(tr("Centimeters"), QVariant(static_cast(ScaleUnit::Cm))); + ui->comboBoxScaleUnit->addItem(tr("Inches"), QVariant(static_cast(ScaleUnit::Inch))); + ui->comboBoxScaleUnit->addItem(tr("Pixels"), QVariant(static_cast(ScaleUnit::Px))); + + ui->comboBoxScaleUnit->blockSignals(true); + ui->comboBoxScaleUnit->setCurrentIndex(0); + ui->comboBoxScaleUnit->blockSignals(false); + + const int minScale = -100000; + const int maxScale = 100000; + + ui->doubleSpinBoxScaleWidth->setMinimum(minScale); + ui->doubleSpinBoxScaleWidth->setMaximum(maxScale); + ui->doubleSpinBoxScaleWidth->setValue(100); + + ui->doubleSpinBoxScaleHeight->setMinimum(minScale); + ui->doubleSpinBoxScaleHeight->setMaximum(maxScale); + ui->doubleSpinBoxScaleHeight->setValue(100); + + connect(ui->comboBoxScaleUnit, QOverload::of(&QComboBox::currentIndexChanged), this, + [this, minScale, maxScale]() + { + const enum ScaleUnit newUnit = CurrentScaleUnit(); + const qreal oldScaleWidth = ui->doubleSpinBoxScaleWidth->value(); + const qreal oldScaleHeight = ui->doubleSpinBoxScaleHeight->value(); + + ui->doubleSpinBoxScaleWidth->blockSignals(true); + + ui->doubleSpinBoxScaleWidth->setMinimum(WidthScaleUnitConvertor(minScale, ScaleUnit::Percent, newUnit)); + ui->doubleSpinBoxScaleWidth->setMinimum(WidthScaleUnitConvertor(minScale, ScaleUnit::Percent, newUnit)); + + ui->doubleSpinBoxScaleWidth->setValue( + WidthScaleUnitConvertor(oldScaleWidth, m_oldImageScaleUnit, newUnit)); + ui->doubleSpinBoxScaleWidth->blockSignals(false); + + ui->doubleSpinBoxScaleHeight->blockSignals(true); + + ui->doubleSpinBoxScaleHeight->setMaximum(HeightScaleUnitConvertor(maxScale, ScaleUnit::Percent, newUnit)); + ui->doubleSpinBoxScaleHeight->setMaximum(HeightScaleUnitConvertor(maxScale, ScaleUnit::Percent, newUnit)); + + if (ui->checkBoxScaleProportionally->isChecked() && newUnit == ScaleUnit::Percent) + { + ui->doubleSpinBoxScaleHeight->setValue(ui->doubleSpinBoxScaleWidth->value()); + } + else + { + ui->doubleSpinBoxScaleHeight->setValue( + HeightScaleUnitConvertor(oldScaleHeight, m_oldImageScaleUnit, newUnit)); + } + ui->doubleSpinBoxScaleHeight->blockSignals(false); + + m_oldImageScaleUnit = newUnit; + }); + + connect(ui->doubleSpinBoxScaleHeight, QOverload::of(&QDoubleSpinBox::valueChanged), this, + &VWidgetBackgroundImages::ScaleHeightChanged); + connect(ui->doubleSpinBoxScaleWidth, QOverload::of(&QDoubleSpinBox::valueChanged), this, + &VWidgetBackgroundImages::ScaleWidthChanged); + + SetCheckBoxValue(ui->checkBoxScaleProportionally, true); + connect(ui->checkBoxScaleProportionally, &QCheckBox::toggled, this, + &VWidgetBackgroundImages::ScaleProportionallyChanged); + + // Rotate + ui->doubleSpinBoxRotationAngle->setValue(0); + + ui->toolButtonImageRotationAnticlockwise->setChecked(true); + + QPushButton *bApply = ui->buttonBox->button(QDialogButtonBox::Apply); + SCASSERT(bApply != nullptr) + connect(bApply, &QPushButton::clicked, this, &VWidgetBackgroundImages::ApplyImageTransformation); + + QPushButton *bReset = ui->buttonBox->button(QDialogButtonBox::Reset); + SCASSERT(bReset != nullptr) + connect(bReset, &QPushButton::clicked, this, &VWidgetBackgroundImages::ResetImageTransformationSettings); +} + +//--------------------------------------------------------------------------------------------------------------------- +auto VWidgetBackgroundImages::ImageWidth() const -> qreal +{ + qreal width = 0; + + int row = ui->tableWidget->currentRow(); + if (row != -1) + { + QTableWidgetItem *item = ui->tableWidget->item(row, 0); + if (item != nullptr) + { + QUuid id = item->data(Qt::UserRole).toUuid(); + VBackgroundPatternImage image = m_doc->GetBackgroundImage(id); + width = image.Size().width(); + } + } + + return width; +} + +//--------------------------------------------------------------------------------------------------------------------- +auto VWidgetBackgroundImages::ImageHeight() const -> qreal +{ + qreal height = 0; + + int row = ui->tableWidget->currentRow(); + if (row != -1) + { + QTableWidgetItem *item = ui->tableWidget->item(row, 0); + if (item != nullptr) + { + QUuid id = item->data(Qt::UserRole).toUuid(); + VBackgroundPatternImage image = m_doc->GetBackgroundImage(id); + height = image.Size().height(); + } + } + + return height; +} + +//--------------------------------------------------------------------------------------------------------------------- +auto VWidgetBackgroundImages::WidthScaleUnitConvertor(qreal value, ScaleUnit from, ScaleUnit to) const -> qreal +{ + return ScaleUnitConvertor(ImageWidth(), value, from, to); +} + +//--------------------------------------------------------------------------------------------------------------------- +auto VWidgetBackgroundImages::HeightScaleUnitConvertor(qreal value, ScaleUnit from, ScaleUnit to) const -> qreal +{ + return ScaleUnitConvertor(ImageHeight(), value, from, to); +} + +//--------------------------------------------------------------------------------------------------------------------- +void VWidgetBackgroundImages::SetAbsolutePisition(const QUuid &id) +{ + VBackgroundPatternImage image = m_doc->GetBackgroundImage(id); + QRectF rect = image.BoundingRect(); + + ui->doubleSpinBoxImageHorizontalTranslate->setValue( + UnitConvertor(rect.topLeft().x(), Unit::Px, CurrentTranslateUnit())); + ui->doubleSpinBoxImageVerticalTranslate->setValue( + UnitConvertor(rect.topLeft().y(), Unit::Px, CurrentTranslateUnit())); +} diff --git a/src/app/valentina/dialogs/vwidgetbackgroundimages.h b/src/app/valentina/dialogs/vwidgetbackgroundimages.h new file mode 100644 index 000000000..c89955495 --- /dev/null +++ b/src/app/valentina/dialogs/vwidgetbackgroundimages.h @@ -0,0 +1,112 @@ +/************************************************************************ + ** + ** @file vwidgetbackgroundimages.h + ** @author Roman Telezhynskyi + ** @date 26 1, 2022 + ** + ** @brief + ** @copyright + ** This source code is part of the Valentina project, a pattern making + ** program, whose allow create and modeling patterns of clothing. + ** Copyright (C) 2022 Valentina project + ** All Rights Reserved. + ** + ** Valentina 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, either version 3 of the License, or + ** (at your option) any later version. + ** + ** Valentina 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 Valentina. If not, see . + ** + *************************************************************************/ +#ifndef VWIDGETBACKGROUNDIMAGES_H +#define VWIDGETBACKGROUNDIMAGES_H + +#include + +#if QT_VERSION < QT_VERSION_CHECK(5, 13, 0) +#include "../vmisc/defglobal.h" +#endif // QT_VERSION < QT_VERSION_CHECK(5, 13, 0) + +#include "../vmisc/def.h" + +class VAbstractPattern; +class VBackgroundPatternImage; + +namespace Ui +{ + class VWidgetBackgroundImages; +} + +enum class ScaleUnit {Percent, Mm, Cm, Inch, Px}; + +class VWidgetBackgroundImages : public QWidget +{ + Q_OBJECT + +public: + explicit VWidgetBackgroundImages(VAbstractPattern *doc, QWidget *parent = nullptr); + ~VWidgetBackgroundImages() override; + +signals: + void DeleteImage(const QUuid &id); + void SelectImage(const QUuid &id); + +public slots: + void UpdateImages(); + void UpdateImage(const QUuid &id); + void ImageSelected(const QUuid &id); + +private slots: + void ImageHoldChanged(int row, int column); + void ImageVisibilityChanged(int row, int column); + void ImageNameChanged(int row, int column); + void ContextMenu(const QPoint &pos); + void CurrentImageChanged(int currentRow, int currentColumn, int previousRow, int previousColumn); + void MoveImageOnTop(); + void MoveImageUp(); + void MoveImageDown(); + void MoveImageBottom(); + void ApplyImageTransformation(); + void ResetImageTransformationSettings(); + void RelativeTranslationChanged(bool checked); + void ScaleProportionallyChanged(bool checked); + void ScaleWidthChanged(double value); + void ScaleHeightChanged(double value); + void ImagePositionChanged(const QUuid &id); + +private: + Q_DISABLE_COPY_MOVE(VWidgetBackgroundImages) + Ui::VWidgetBackgroundImages *ui; + VAbstractPattern *m_doc; + + Unit m_oldImageTranslationUnit{Unit::Mm}; + ScaleUnit m_oldImageScaleUnit{ScaleUnit::Percent}; + + void FillTable(const QVector &images); + + void ToggleImageHold(const QUuid &id) const; + void ToggleImageVisibility(const QUuid &id) const; + + Q_REQUIRED_RESULT auto ImageRow(const QUuid &id) const -> int; + + Q_REQUIRED_RESULT auto CurrentTranslateUnit() const -> Unit; + Q_REQUIRED_RESULT auto CurrentScaleUnit() const -> ScaleUnit; + void InitImageTranslation(); + + Q_REQUIRED_RESULT auto ImageWidth() const -> qreal; + Q_REQUIRED_RESULT auto ImageHeight() const -> qreal; + + Q_REQUIRED_RESULT auto WidthScaleUnitConvertor(qreal value, enum ScaleUnit from, enum ScaleUnit to) const -> qreal; + Q_REQUIRED_RESULT auto HeightScaleUnitConvertor(qreal value, enum ScaleUnit from, enum ScaleUnit to) const -> qreal; + + void SetAbsolutePisition(const QUuid &id); +}; + +#endif // VWIDGETBACKGROUNDIMAGES_H diff --git a/src/app/valentina/dialogs/vwidgetbackgroundimages.ui b/src/app/valentina/dialogs/vwidgetbackgroundimages.ui new file mode 100644 index 000000000..bf531036a --- /dev/null +++ b/src/app/valentina/dialogs/vwidgetbackgroundimages.ui @@ -0,0 +1,418 @@ + + + VWidgetBackgroundImages + + + + 0 + 0 + 401 + 640 + + + + Form + + + + + + true + + + + + 0 + 0 + 381 + 620 + + + + + + + + 0 + 0 + + + + Transformation + + + + + + 0 + + + + Translate + + + + + + + + Horizontal: + + + + + + + -10000.000000000000000 + + + 10000.000000000000000 + + + 0.100000000000000 + + + + + + + + + + Vertical: + + + + + + + -10000.000000000000000 + + + 10000.000000000000000 + + + 0.100000000000000 + + + + + + + + + Relative translation + + + true + + + + + + + + Scale + + + + + + + + Width: + + + + + + + -10000.000000000000000 + + + 10000.000000000000000 + + + 0.100000000000000 + + + + + + + + + + Height: + + + + + + + -10000.000000000000000 + + + 10000.000000000000000 + + + 0.100000000000000 + + + + + + + + + Scale proportionally + + + true + + + + + + + + Rotate + + + + + + Rotation + + + + + + + + + + ../../puzzle../../puzzle + + + true + + + buttonGroup + + + + + + + ° + + + 3 + + + -360.000000000000000 + + + 360.000000000000000 + + + 0.100000000000000 + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + ../../puzzle../../puzzle + + + true + + + true + + + buttonGroup + + + + + + + + 0 + 0 + + + + Angle: + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + + + + QDialogButtonBox::Apply|QDialogButtonBox::Reset + + + + + + + + + + Z Value + + + + + + ... + + + + + + + 24 + 24 + + + + + + + + ... + + + + + + + 24 + 24 + + + + + + + + ... + + + + + + + 24 + 24 + + + + + + + + ... + + + + + + + 24 + 24 + + + + + + + + + + + + 1 + 1 + + + + true + + + QAbstractItemView::SingleSelection + + + QAbstractItemView::SelectRows + + + false + + + 16 + + + true + + + false + + + 10 + + + + + + + + + + + + + + + diff --git a/src/app/valentina/mainwindow.cpp b/src/app/valentina/mainwindow.cpp index 8631e090f..52551e5d0 100644 --- a/src/app/valentina/mainwindow.cpp +++ b/src/app/valentina/mainwindow.cpp @@ -61,6 +61,7 @@ #include "dialogs/vwidgetgroups.h" #include "../vtools/undocommands/undogroup.h" #include "dialogs/vwidgetdetails.h" +#include "dialogs/dialogaddbackgroundimage.h" #include "../vpatterndb/vpiecepath.h" #include "../qmuparser/qmuparsererror.h" #include "../vtools/dialogs/support/dialogeditlabel.h" @@ -71,6 +72,15 @@ #include "../vwidgets/vgraphicssimpletextitem.h" #include "../vlayout/dialogs/dialoglayoutscale.h" #include "../vmisc/dialogs/dialogselectlanguage.h" +#include "../ifc/xml/vbackgroundpatternimage.h" +#include "../vtools/tools/backgroundimage/vbackgroundimageitem.h" +#include "../vtools/tools/backgroundimage/vbackgroundpixmapitem.h" +#include "../vtools/tools/backgroundimage/vbackgroundsvgitem.h" +#include "../vtools/tools/backgroundimage/vbackgroundimagecontrols.h" +#include "../vtools/undocommands/image/addbackgroundimage.h" +#include "../vtools/undocommands/image/deletebackgroundimage.h" +#include "../ifc/xml/utils.h" +#include "dialogs/vwidgetbackgroundimages.h" #if QT_VERSION < QT_VERSION_CHECK(5, 12, 0) #include "../vmisc/backport/qscopeguard.h" @@ -104,6 +114,8 @@ #include #include #include +#include +#include #if defined(Q_OS_WIN32) && QT_VERSION >= QT_VERSION_CHECK(5, 7, 0) #include @@ -250,6 +262,8 @@ MainWindow::MainWindow(QWidget *parent) ToolBarStages(); InitToolButtons(); + connect(ui->actionAddBackgroundImage, &QAction::triggered, this, &MainWindow::ActionAddBackgroundImage); + m_progressBar->setVisible(false); #if defined(Q_OS_WIN32) && QT_VERSION >= QT_VERSION_CHECK(5, 7, 0) m_taskbarProgress->setVisible(false); @@ -495,6 +509,7 @@ void MainWindow::InitScenes() connect(this, &MainWindow::EnableSplinePathHover, sceneDraw, &VMainGraphicsScene::ToggleSplinePathHover); connect(sceneDraw, &VMainGraphicsScene::mouseMove, this, &MainWindow::MouseMove); + connect(sceneDraw, &VMainGraphicsScene::AddBackgroundImage, this, &MainWindow::PlaceBackgroundImage); sceneDetails = new VMainGraphicsScene(this); connect(this, &MainWindow::EnableItemMove, sceneDetails, &VMainGraphicsScene::EnableItemMove); @@ -662,7 +677,7 @@ void MainWindow::SetToolButton(bool checked, Tool t, const QString &cursor, cons dialogTool = new Dialog(pattern, 0, this); // This check helps to find missed tools in the switch - Q_STATIC_ASSERT_X(static_cast(Tool::LAST_ONE_DO_NOT_USE) == 55, "Check if need to extend."); + Q_STATIC_ASSERT_X(static_cast(Tool::LAST_ONE_DO_NOT_USE) == 59, "Check if need to extend."); switch(t) { @@ -1364,6 +1379,50 @@ void MainWindow::ZoomFitBestCurrent() } } +//--------------------------------------------------------------------------------------------------------------------- +void MainWindow::PlaceBackgroundImage(const QPointF &pos, const QString &fileName) +{ + DialogAddBackgroundImage dialog(this); + if (dialog.exec() == QDialog::Rejected) + { + qCritical() << tr("Unable to add background image"); + return; + } + + VBackgroundPatternImage image = VBackgroundPatternImage::FromFile(fileName, dialog.BuiltIn()); + image.SetName(dialog.Name()); + + QTransform m; + m.translate(pos.x(), pos.y()); + + QTransform imageMatrix = image.Matrix(); + imageMatrix *= m; + + image.SetMatrix(m); + + if (not image.IsValid()) + { + qCritical() << tr("Invalid image. Error: %1").arg(image.ErrorString()); + return; + } + + auto* addBackgroundImage = new AddBackgroundImage(image, doc); + connect(addBackgroundImage, &AddBackgroundImage::AddItem, this, &MainWindow::AddBackgroundImageItem); + connect(addBackgroundImage, &AddBackgroundImage::DeleteItem, this, &MainWindow::DeleteBackgroundImageItem); + VApplication::VApp()->getUndoStack()->push(addBackgroundImage); +} + +//--------------------------------------------------------------------------------------------------------------------- +void MainWindow::RemoveBackgroundImage(const QUuid &id) +{ + VBackgroundPatternImage image = doc->GetBackgroundImage(id); + + auto* deleteBackgroundImage = new DeleteBackgroundImage(image, doc); + connect(deleteBackgroundImage, &DeleteBackgroundImage::AddItem, this, &MainWindow::AddBackgroundImageItem); + connect(deleteBackgroundImage, &DeleteBackgroundImage::DeleteItem, this, &MainWindow::DeleteBackgroundImageItem); + VApplication::VApp()->getUndoStack()->push(deleteBackgroundImage); +} + //--------------------------------------------------------------------------------------------------------------------- /** * @brief ToolCutArc handler tool cutArc. @@ -2302,7 +2361,7 @@ void MainWindow::ExportDraw(const QString &fileName) // Restore scale, scrollbars and current active pattern piece ui->view->setTransform(viewTransform); VMainGraphicsView::NewSceneRect(ui->view->scene(), ui->view); - emit ScaleChanged(ui->view->transform().m11()); + ScaleChanged(ui->view->transform().m11()); ui->view->verticalScrollBar()->setValue(verticalScrollBarValue); ui->view->horizontalScrollBar()->setValue(horizontalScrollBarValue); @@ -2310,6 +2369,70 @@ void MainWindow::ExportDraw(const QString &fileName) doc->ChangeActivPP(doc->GetNameActivPP(), Document::FullParse); } +//--------------------------------------------------------------------------------------------------------------------- +void MainWindow::NewBackgroundImageItem(const VBackgroundPatternImage &image) +{ + if (m_backgroudcontrols == nullptr) + { + m_backgroudcontrols = new VBackgroundImageControls(doc); + connect(sceneDraw, &VMainGraphicsScene::ItemByMouseRelease, m_backgroudcontrols, + &VBackgroundImageControls::DeactivateControls); + sceneDraw->addItem(m_backgroudcontrols); + } + + if (m_backgroundImages.contains(image.Id())) + { + VBackgroundImageItem *item = m_backgroundImages.value(image.Id()); + if (item != nullptr) + { + item->SetImage(image); + } + } + else if (m_deletedBackgroundImageItems.contains(image.Id())) + { + VBackgroundImageItem *item = m_deletedBackgroundImageItems.value(image.Id()); + if (item != nullptr) + { + item->SetImage(image); + sceneDraw->addItem(item); + m_backgroundImages.insert(image.Id(), item); + } + m_deletedBackgroundImageItems.remove(image.Id()); + } + else + { + VBackgroundImageItem *item = nullptr; + if (image.Type() == PatternImage::Raster) + { + item = new VBackgroundPixmapItem(image, doc); + } + else if (image.Type() == PatternImage::Vector || image.Type() == PatternImage::Unknown) + { + item = new VBackgroundSVGItem(image, doc); + } + + if (item != nullptr) + { + connect(item, &VBackgroundImageItem::UpdateControls, m_backgroudcontrols, + &VBackgroundImageControls::UpdateControls); + connect(item, &VBackgroundImageItem::ActivateControls, m_backgroudcontrols, + &VBackgroundImageControls::ActivateControls); + connect(item, &VBackgroundImageItem::DeleteImage, this, &MainWindow::RemoveBackgroundImage); + connect(this, &MainWindow::EnableBackgroundImageSelection, item, &VBackgroundImageItem::EnableSelection); + connect(item, &VBackgroundImageItem::ShowImageInExplorer, this, &MainWindow::ShowBackgroundImageInExplorer); + connect(item, &VBackgroundImageItem::SaveImage, this, &MainWindow::SaveBackgroundImage); + connect(m_backgroudcontrols, &VBackgroundImageControls::ActiveImageChanged, backgroundImagesWidget, + &VWidgetBackgroundImages::ImageSelected); + connect(backgroundImagesWidget, &VWidgetBackgroundImages::SelectImage, m_backgroudcontrols, + &VBackgroundImageControls::ActivateControls); + sceneDraw->addItem(item); + m_backgroundImages.insert(image.Id(), item); + } + } + + VMainGraphicsView::NewSceneRect(sceneDraw, ui->view); +} + //--------------------------------------------------------------------------------------------------------------------- #if defined(Q_OS_MAC) void MainWindow::OpenAt(QAction *where) @@ -2543,7 +2666,7 @@ void MainWindow::InitToolButtons() } // This check helps to find missed tools - Q_STATIC_ASSERT_X(static_cast(Tool::LAST_ONE_DO_NOT_USE) == 55, "Check if all tools were connected."); + Q_STATIC_ASSERT_X(static_cast(Tool::LAST_ONE_DO_NOT_USE) == 59, "Check if all tools were connected."); connect(ui->toolButtonEndLine, &QToolButton::clicked, this, &MainWindow::ToolEndLine); connect(ui->toolButtonLine, &QToolButton::clicked, this, &MainWindow::ToolLine); @@ -2622,7 +2745,7 @@ QT_WARNING_DISABLE_GCC("-Wswitch-default") void MainWindow::CancelTool() { // This check helps to find missed tools in the switch - Q_STATIC_ASSERT_X(static_cast(Tool::LAST_ONE_DO_NOT_USE) == 55, "Not all tools were handled."); + Q_STATIC_ASSERT_X(static_cast(Tool::LAST_ONE_DO_NOT_USE) == 59, "Not all tools were handled."); qCDebug(vMainWindow, "Canceling tool."); if(not dialogTool.isNull()) @@ -2664,6 +2787,10 @@ void MainWindow::CancelTool() case Tool::NodeElArc: case Tool::NodeSpline: case Tool::NodeSplinePath: + case Tool::BackgroundImage: + case Tool::BackgroundImageControls: + case Tool::BackgroundPixmapImage: + case Tool::BackgroundSVGImage: Q_UNREACHABLE(); //-V501 //Nothing to do here because we can't create this tool from main window. break; @@ -2837,6 +2964,7 @@ void MainWindow::ArrowTool(bool checked) emit EnableNodeLabelSelection(true); emit EnableNodePointSelection(true); emit EnableDetailSelection(true);// Disable when done visualization details + emit EnableBackgroundImageSelection(true); // Hovering emit EnableLabelHover(true); @@ -2849,6 +2977,7 @@ void MainWindow::ArrowTool(bool checked) emit EnableNodeLabelHover(true); emit EnableNodePointHover(true); emit EnableDetailHover(true); + emit EnableImageBackgroundHover(true); ui->view->AllowRubberBand(true); ui->view->viewport()->unsetCursor(); @@ -2967,12 +3096,14 @@ void MainWindow::ActionDraw(bool checked) } ui->dockWidgetLayoutPages->setVisible(false); - ui->dockWidgetToolOptions->setVisible(isDockToolOptionsVisible); + ui->dockWidgetToolOptions->setVisible(m_toolOptionsActive); ui->dockWidgetGroups->setWidget(groupsWidget); ui->dockWidgetGroups->setWindowTitle(tr("Groups of visibility")); - ui->dockWidgetGroups->setVisible(isDockGroupsVisible); + ui->dockWidgetGroups->setVisible(m_groupsActive); ui->dockWidgetGroups->setToolTip(tr("Contains all visibility groups")); + + ui->dockWidgetBackgroundImages->setVisible(m_backgroundImagesActive); } else { @@ -3045,10 +3176,11 @@ void MainWindow::ActionDetails(bool checked) ui->dockWidgetGroups->setWidget(detailsWidget); ui->dockWidgetGroups->setWindowTitle(tr("Details")); - ui->dockWidgetGroups->setVisible(isDockGroupsVisible); + ui->dockWidgetGroups->setVisible(m_groupsActive); ui->dockWidgetGroups->setToolTip(tr("Show which details will go in layout")); - ui->dockWidgetToolOptions->setVisible(isDockToolOptionsVisible); + ui->dockWidgetToolOptions->setVisible(m_toolOptionsActive); + ui->dockWidgetBackgroundImages->setVisible(false); m_statusLabel->setText(QString()); @@ -3159,14 +3291,9 @@ void MainWindow::ActionLayout(bool checked) } ui->dockWidgetLayoutPages->setVisible(true); - - ui->dockWidgetToolOptions->blockSignals(true); ui->dockWidgetToolOptions->setVisible(false); - ui->dockWidgetToolOptions->blockSignals(false); - - ui->dockWidgetGroups->blockSignals(true); ui->dockWidgetGroups->setVisible(false); - ui->dockWidgetGroups->blockSignals(false); + ui->dockWidgetBackgroundImages->setVisible(false); ShowPaper(ui->listWidget->currentRow()); @@ -3548,6 +3675,18 @@ void MainWindow::on_actionUpdateManualLayout_triggered() } } +//--------------------------------------------------------------------------------------------------------------------- +void MainWindow::ActionAddBackgroundImage() +{ + const QString fileName = QFileDialog::getOpenFileName(this, tr("Select background image"), QString(), + PrepareImageFilters(), nullptr, + VAbstractApplication::VApp()->NativeFileDialog()); + if (not fileName.isEmpty()) + { + PlaceBackgroundImage(QPointF(), fileName); + } +} + //--------------------------------------------------------------------------------------------------------------------- /** * @brief Clear reset to default window. @@ -3570,8 +3709,10 @@ void MainWindow::Clear() UpdateWindowTitle(); UpdateVisibilityGroups(); detailsWidget->UpdateList(); + backgroundImagesWidget->UpdateImages(); qCDebug(vMainWindow, "Clearing scenes."); sceneDraw->clear(); + sceneDraw->SetAcceptDrop(false); sceneDetails->clear(); ArrowTool(true); comboBoxDraws->clear(); @@ -3603,6 +3744,7 @@ void MainWindow::Clear() ui->actionEditCurrent->setEnabled(false); ui->actionPreviousPatternPiece->setEnabled(false); ui->actionNextPatternPiece->setEnabled(false); + ui->actionAddBackgroundImage->setEnabled(false); SetEnableTool(false); VAbstractValApplication::VApp()->SetPatternUnits(Unit::Cm); VAbstractValApplication::VApp()->SetMeasurementsType(MeasurementsType::Unknown); @@ -3985,6 +4127,7 @@ void MainWindow::SetEnableWidgets(bool enable) ui->actionUnloadMeasurements->setEnabled(enable && designStage); ui->actionPreviousPatternPiece->setEnabled(enable && drawStage); ui->actionNextPatternPiece->setEnabled(enable && drawStage); + ui->actionAddBackgroundImage->setEnabled(enable && drawStage); ui->actionIncreaseLabelFont->setEnabled(enable); ui->actionDecreaseLabelFont->setEnabled(enable); ui->actionOriginalLabelFont->setEnabled(enable); @@ -3998,6 +4141,7 @@ void MainWindow::SetEnableWidgets(bool enable) actionDockWidgetToolOptions->setEnabled(enable && designStage); actionDockWidgetGroups->setEnabled(enable && designStage); + actionDockWidgetBackgroundImages->setEnabled(enable && drawStage); undoAction->setEnabled(enable && designStage && VAbstractApplication::VApp()->getUndoStack()->canUndo()); redoAction->setEnabled(enable && designStage && VAbstractApplication::VApp()->getUndoStack()->canRedo()); @@ -4189,6 +4333,98 @@ void MainWindow::SetDefaultGUILanguage() } } +//--------------------------------------------------------------------------------------------------------------------- +void MainWindow::AddBackgroundImageItem(const QUuid &id) +{ + NewBackgroundImageItem(doc->GetBackgroundImage(id)); + + if (backgroundImagesWidget != nullptr) + { + backgroundImagesWidget->UpdateImages(); + } +} + +//--------------------------------------------------------------------------------------------------------------------- +void MainWindow::DeleteBackgroundImageItem(const QUuid &id) +{ + if (m_backgroundImages.contains(id)) + { + VBackgroundImageItem *item = m_backgroundImages.value(id); + emit ui->view->itemClicked(nullptr); // Hide visualization to avoid a crash + sceneDraw->removeItem(item); + if (m_backgroudcontrols != nullptr && m_backgroudcontrols->Id() == id) + { + m_backgroudcontrols->ActivateControls(QUuid()); + } + m_backgroundImages.remove(id); + m_deletedBackgroundImageItems.insert(id, item); + + if (backgroundImagesWidget != nullptr) + { + backgroundImagesWidget->UpdateImages(); + } + } +} + +//--------------------------------------------------------------------------------------------------------------------- +void MainWindow::ShowBackgroundImageInExplorer(const QUuid &id) +{ + ShowInGraphicalShell(doc->GetBackgroundImage(id).FilePath()); +} + +//--------------------------------------------------------------------------------------------------------------------- +void MainWindow::SaveBackgroundImage(const QUuid &id) +{ + VBackgroundPatternImage image = doc->GetBackgroundImage(id); + + if (not image.IsValid()) + { + qCritical() << tr("Unable to save image. Error: %1").arg(image.ErrorString()); + return; + } + + if (image.ContentData().isEmpty()) + { + qCritical() << tr("Unable to save image. No data."); + return; + } + + const QByteArray imageData = QByteArray::fromBase64(image.ContentData()); + QMimeType mime = MimeTypeFromByteArray(imageData); + QString path = QDir::homePath() + QDir::separator() + tr("untitled"); + QStringList filters; + + if (mime.isValid()) + { + QStringList suffixes = mime.suffixes(); + if (not suffixes.isEmpty()) + { + path += '.' + suffixes.at(0); + } + + filters.append(mime.filterString()); + } + + filters.append(tr("All files") + QStringLiteral(" (*.*)")); + + QString filter = filters.join(QStringLiteral(";;")); + + QString filename = QFileDialog::getSaveFileName(this, tr("Save Image"), path, filter, nullptr, + VAbstractApplication::VApp()->NativeFileDialog()); + if (not filename.isEmpty()) + { + QFile file(filename); + if (file.open(QIODevice::WriteOnly)) + { + file.write(imageData); + } + else + { + qCritical() << tr("Unable to save image. Error: %1").arg(file.errorString()); + } + } +} + //--------------------------------------------------------------------------------------------------------------------- void MainWindow::InitDimensionControls() { @@ -4396,7 +4632,7 @@ QT_WARNING_DISABLE_GCC("-Wswitch-default") QT_WARNING_POP // This check helps to find missed tools - Q_STATIC_ASSERT_X(static_cast(Tool::LAST_ONE_DO_NOT_USE) == 55, "Not all tools were handled."); + Q_STATIC_ASSERT_X(static_cast(Tool::LAST_ONE_DO_NOT_USE) == 59, "Not all tools were handled."); //Drawing Tools ui->toolButtonEndLine->setEnabled(drawTools); @@ -4593,9 +4829,15 @@ void MainWindow::ReadSettings() restoreState(settings->GetWindowState()); restoreState(settings->GetToolbarsState(), AppVersion()); - ui->dockWidgetGroups->setVisible(settings->IsDockWidgetGroupsActive()); - ui->dockWidgetToolOptions->setVisible(settings->IsDockWidgetToolOptionsActive()); - ui->dockWidgetMessages->setVisible(settings->IsDockWidgetPatternMessagesActive()); + m_groupsActive = settings->IsDockWidgetGroupsActive(); + m_toolOptionsActive = settings->IsDockWidgetToolOptionsActive(); + m_patternMessagesActive = settings->IsDockWidgetPatternMessagesActive(); + m_backgroundImagesActive = settings->IsDockWidgetBackgroundImagesActive(); + + ui->dockWidgetGroups->setVisible(m_groupsActive); + ui->dockWidgetToolOptions->setVisible(m_toolOptionsActive); + ui->dockWidgetMessages->setVisible(m_patternMessagesActive); + ui->dockWidgetBackgroundImages->setVisible(m_backgroundImagesActive); // Scene antialiasing ui->view->SetAntialiasing(settings->GetGraphicalOutput()); @@ -4609,9 +4851,6 @@ void MainWindow::ReadSettings() // Tool box scaling ToolBoxSizePolicy(); - isDockToolOptionsVisible = ui->dockWidgetToolOptions->isEnabled(); - isDockGroupsVisible = ui->dockWidgetGroups->isEnabled(); - QFont f = ui->plainTextEditPatternMessages->font(); f.setPointSize(settings->GetPatternMessageFontSize(f.pointSize())); ui->plainTextEditPatternMessages->setFont(f); @@ -4635,9 +4874,10 @@ void MainWindow::WriteSettings() settings->SetWindowState(saveState()); settings->SetToolbarsState(saveState(AppVersion())); - settings->SetDockWidgetGroupsActive(ui->dockWidgetGroups->isEnabled()); - settings->SetDockWidgetToolOptionsActive(ui->dockWidgetToolOptions->isEnabled()); - settings->SetDockWidgetPatternMessagesActive(ui->dockWidgetMessages->isEnabled()); + settings->SetDockWidgetGroupsActive(ui->dockWidgetGroups->isVisible()); + settings->SetDockWidgetToolOptionsActive(ui->dockWidgetToolOptions->isVisible()); + settings->SetDockWidgetPatternMessagesActive(ui->dockWidgetMessages->isVisible()); + settings->SetDockWidgetBackgroundImagesActive(actionDockWidgetBackgroundImages->isChecked()); settings->sync(); if (settings->status() == QSettings::AccessError) @@ -4734,7 +4974,7 @@ QT_WARNING_DISABLE_GCC("-Wswitch-default") void MainWindow::LastUsedTool() { // This check helps to find missed tools in the switch - Q_STATIC_ASSERT_X(static_cast(Tool::LAST_ONE_DO_NOT_USE) == 55, "Not all tools were handled."); + Q_STATIC_ASSERT_X(static_cast(Tool::LAST_ONE_DO_NOT_USE) == 59, "Not all tools were handled."); if (currentTool == lastUsedTool) { @@ -4762,6 +5002,10 @@ void MainWindow::LastUsedTool() case Tool::NodeElArc: case Tool::NodeSpline: case Tool::NodeSplinePath: + case Tool::BackgroundImage: + case Tool::BackgroundImageControls: + case Tool::BackgroundPixmapImage: + case Tool::BackgroundSVGImage: Q_UNREACHABLE(); //-V501 //Nothing to do here because we can't create this tool from main window. break; @@ -4949,20 +5193,32 @@ void MainWindow::AddDocks() //Add dock actionDockWidgetToolOptions = ui->dockWidgetToolOptions->toggleViewAction(); - ui->menuWindow->addAction(actionDockWidgetToolOptions); connect(actionDockWidgetToolOptions, &QAction::triggered, this, [this](bool checked) { - isDockToolOptionsVisible = checked; + m_toolOptionsActive = checked; }); + ui->menuWindow->addAction(actionDockWidgetToolOptions); actionDockWidgetGroups = ui->dockWidgetGroups->toggleViewAction(); - ui->menuWindow->addAction(actionDockWidgetGroups); connect(actionDockWidgetGroups, &QAction::triggered, this, [this](bool checked) { - isDockGroupsVisible = checked; + m_groupsActive = checked; }); + ui->menuWindow->addAction(actionDockWidgetGroups); - ui->menuWindow->addAction(ui->dockWidgetMessages->toggleViewAction()); + QAction *action = ui->dockWidgetMessages->toggleViewAction(); + connect(action, &QAction::triggered, this, [this](bool checked) + { + m_patternMessagesActive = checked; + }); + ui->menuWindow->addAction(action); + + actionDockWidgetBackgroundImages = ui->dockWidgetBackgroundImages->toggleViewAction(); + connect(actionDockWidgetBackgroundImages, &QAction::triggered, this, [this](bool checked) + { + m_backgroundImagesActive = checked; + }); + ui->menuWindow->addAction(actionDockWidgetBackgroundImages); } //--------------------------------------------------------------------------------------------------------------------- @@ -4977,7 +5233,7 @@ void MainWindow::InitDocksContain() qCDebug(vMainWindow, "Initialization groups dock."); groupsWidget = new VWidgetGroups(doc, this); ui->dockWidgetGroups->setWidget(groupsWidget); - connect(doc,&VAbstractPattern::UpdateGroups , this, &MainWindow::UpdateVisibilityGroups); + connect(doc, &VAbstractPattern::UpdateGroups, this, &MainWindow::UpdateVisibilityGroups); detailsWidget = new VWidgetDetails(pattern, doc, this); connect(doc, &VPattern::FullUpdateFromFile, detailsWidget, &VWidgetDetails::UpdateList); @@ -4985,6 +5241,10 @@ void MainWindow::InitDocksContain() connect(doc, &VPattern::ShowDetail, detailsWidget, &VWidgetDetails::SelectDetail); connect(detailsWidget, &VWidgetDetails::Highlight, sceneDetails, &VMainGraphicsScene::HighlightItem); detailsWidget->setVisible(false); + + backgroundImagesWidget = new VWidgetBackgroundImages(doc, this); + ui->dockWidgetBackgroundImages->setWidget(backgroundImagesWidget); + connect(backgroundImagesWidget, &VWidgetBackgroundImages::DeleteImage, this, &MainWindow::RemoveBackgroundImage); } //--------------------------------------------------------------------------------------------------------------------- @@ -5316,6 +5576,8 @@ MainWindow::~MainWindow() delete doc; delete ui; + + qDeleteAll(m_deletedBackgroundImageItems); } //--------------------------------------------------------------------------------------------------------------------- @@ -5576,9 +5838,20 @@ bool MainWindow::LoadPattern(QString fileName, const QString& customMeasureFile) /* Collect garbage only after successfully parse. This way wrongly accused items have one more time to restore * a reference. */ QTimer::singleShot(100, Qt::CoarseTimer, this, [this](){doc->GarbageCollector(true);}); + + QTimer::singleShot(500, Qt::CoarseTimer, this, [this]() + { + QVector allImages = doc->GetBackgroundImages(); + for (const auto &image : allImages) + { + NewBackgroundImageItem(image); + } + backgroundImagesWidget->UpdateImages(); + }); } patternReadOnly = doc->IsReadOnly(); + sceneDraw->SetAcceptDrop(true); SetEnableWidgets(true); setCurrentFile(fileName); qCDebug(vMainWindow, "File loaded."); @@ -6562,6 +6835,7 @@ void MainWindow::ToolSelectPoint() emit EnableElArcSelection(false); emit EnableSplineSelection(false); emit EnableSplinePathSelection(false); + emit EnableBackgroundImageSelection(false); // Hovering emit EnableLabelHover(true); @@ -6571,6 +6845,7 @@ void MainWindow::ToolSelectPoint() emit EnableElArcHover(false); emit EnableSplineHover(false); emit EnableSplinePathHover(false); + emit EnableImageBackgroundHover(false); ui->view->AllowRubberBand(false); } @@ -6600,6 +6875,7 @@ void MainWindow::ToolSelectSpline() emit EnableElArcSelection(false); emit EnableSplineSelection(false); emit EnableSplinePathSelection(false); + emit EnableBackgroundImageSelection(false); // Hovering emit EnableLabelHover(false); @@ -6609,6 +6885,7 @@ void MainWindow::ToolSelectSpline() emit EnableElArcHover(false); emit EnableSplineHover(true); emit EnableSplinePathHover(false); + emit EnableImageBackgroundHover(false); emit ItemsSelection(SelectionType::ByMouseRelease); @@ -6626,6 +6903,7 @@ void MainWindow::ToolSelectSplinePath() emit EnableElArcSelection(false); emit EnableSplineSelection(false); emit EnableSplinePathSelection(false); + emit EnableBackgroundImageSelection(false); // Hovering emit EnableLabelHover(false); @@ -6635,6 +6913,7 @@ void MainWindow::ToolSelectSplinePath() emit EnableElArcHover(false); emit EnableSplineHover(false); emit EnableSplinePathHover(true); + emit EnableImageBackgroundHover(false); emit ItemsSelection(SelectionType::ByMouseRelease); @@ -6652,6 +6931,7 @@ void MainWindow::ToolSelectArc() emit EnableElArcSelection(false); emit EnableSplineSelection(false); emit EnableSplinePathSelection(false); + emit EnableBackgroundImageSelection(false); // Hovering emit EnableLabelHover(false); @@ -6661,6 +6941,7 @@ void MainWindow::ToolSelectArc() emit EnableElArcHover(false); emit EnableSplineHover(false); emit EnableSplinePathHover(false); + emit EnableImageBackgroundHover(false); emit ItemsSelection(SelectionType::ByMouseRelease); @@ -6678,6 +6959,7 @@ void MainWindow::ToolSelectPointArc() emit EnableElArcSelection(false); emit EnableSplineSelection(false); emit EnableSplinePathSelection(false); + emit EnableBackgroundImageSelection(false); // Hovering emit EnableLabelHover(true); @@ -6687,6 +6969,7 @@ void MainWindow::ToolSelectPointArc() emit EnableElArcHover(false); emit EnableSplineHover(false); emit EnableSplinePathHover(false); + emit EnableImageBackgroundHover(false); emit ItemsSelection(SelectionType::ByMouseRelease); @@ -6704,6 +6987,7 @@ void MainWindow::ToolSelectCurve() emit EnableElArcSelection(false); emit EnableSplineSelection(false); emit EnableSplinePathSelection(false); + emit EnableBackgroundImageSelection(false); // Hovering emit EnableLabelHover(false); @@ -6713,6 +6997,7 @@ void MainWindow::ToolSelectCurve() emit EnableElArcHover(true); emit EnableSplineHover(true); emit EnableSplinePathHover(true); + emit EnableImageBackgroundHover(false); emit ItemsSelection(SelectionType::ByMouseRelease); @@ -6730,6 +7015,7 @@ void MainWindow::ToolSelectAllDrawObjects() emit EnableElArcSelection(false); emit EnableSplineSelection(false); emit EnableSplinePathSelection(false); + emit EnableBackgroundImageSelection(false); // Hovering emit EnableLabelHover(true); @@ -6739,6 +7025,7 @@ void MainWindow::ToolSelectAllDrawObjects() emit EnableElArcHover(true); emit EnableSplineHover(true); emit EnableSplinePathHover(true); + emit EnableImageBackgroundHover(false); emit ItemsSelection(SelectionType::ByMouseRelease); @@ -6756,6 +7043,7 @@ void MainWindow::ToolSelectOperationObjects() emit EnableElArcSelection(true); emit EnableSplineSelection(true); emit EnableSplinePathSelection(true); + emit EnableBackgroundImageSelection(false); // Hovering emit EnableLabelHover(true); @@ -6765,6 +7053,7 @@ void MainWindow::ToolSelectOperationObjects() emit EnableElArcHover(true); emit EnableSplineHover(true); emit EnableSplinePathHover(true); + emit EnableImageBackgroundHover(false); emit ItemsSelection(SelectionType::ByMouseRelease); diff --git a/src/app/valentina/mainwindow.h b/src/app/valentina/mainwindow.h index 17637bcec..d4d8bd533 100644 --- a/src/app/valentina/mainwindow.h +++ b/src/app/valentina/mainwindow.h @@ -55,6 +55,10 @@ class VWidgetDetails; class QToolButton; class QProgressBar; class WatermarkWindow; +class Quuid; +class VBackgroundImageItem; +class VBackgroundImageControls; +class VWidgetBackgroundImages; /** * @brief The MainWindow class main windows. @@ -74,6 +78,8 @@ public slots: virtual void UpdateVisibilityGroups() override; virtual void UpdateDetailsList() override; virtual void ZoomFitBestCurrent() override; + void PlaceBackgroundImage(const QPointF &pos, const QString &fileName); + void RemoveBackgroundImage(const QUuid &id); signals: void RefreshHistory(); @@ -90,6 +96,7 @@ signals: void EnableNodeLabelSelection(bool enable); void EnableNodePointSelection(bool enable); void EnableDetailSelection(bool enable); + void EnableBackgroundImageSelection(bool enable); void EnableLabelHover(bool enable); void EnablePointHover(bool enable); @@ -101,6 +108,7 @@ signals: void EnableNodeLabelHover(bool enable); void EnableNodePointHover(bool enable); void EnableDetailHover(bool enable); + void EnableImageBackgroundHover(bool enable); protected: virtual void keyPressEvent(QKeyEvent *event) override; virtual void showEvent(QShowEvent *event) override; @@ -192,6 +200,8 @@ private slots: void on_actionCreateManualLayout_triggered(); void on_actionUpdateManualLayout_triggered(); + void ActionAddBackgroundImage(); + void ClosedDialogUnionDetails(int result); void ClosedDialogDuplicateDetail(int result); void ClosedDialogGroup(int result); @@ -226,6 +236,11 @@ private slots: void SetDefaultGUILanguage(); + void AddBackgroundImageItem(const QUuid &id); + void DeleteBackgroundImageItem(const QUuid &id); + void ShowBackgroundImageInExplorer(const QUuid &id); + void SaveBackgroundImage(const QUuid &id); + private: Q_DISABLE_COPY(MainWindow) /** @brief ui keeps information about user interface */ @@ -269,9 +284,6 @@ private: /** @brief currentToolBoxIndex save current set of tools. */ qint32 currentToolBoxIndex; - bool isDockToolOptionsVisible{false}; - bool isDockGroupsVisible{false}; - /** @brief drawMode true if we current draw scene. */ bool drawMode; @@ -291,6 +303,7 @@ private: VToolOptionsPropertyBrowser *toolOptions; VWidgetGroups *groupsWidget; VWidgetDetails *detailsWidget; + VWidgetBackgroundImages *backgroundImagesWidget{nullptr}; QSharedPointer> lock; QList toolButtonPointerList; @@ -308,6 +321,15 @@ private: QTimer *m_gradation; + QMap m_backgroundImages{}; + QMap m_deletedBackgroundImageItems{}; + VBackgroundImageControls *m_backgroudcontrols{nullptr}; + + bool m_groupsActive{false}; + bool m_toolOptionsActive{false}; + bool m_patternMessagesActive{false}; + bool m_backgroundImagesActive{false}; + void InitDimensionControls(); void InitDimensionGradation(int index, const MeasurementDimension_p &dimension, const QPointer &control); @@ -422,6 +444,8 @@ private: void StoreDimensions(); void ExportDraw(const QString &fileName); + + void NewBackgroundImageItem(const VBackgroundPatternImage &image); }; #endif // MAINWINDOW_H diff --git a/src/app/valentina/mainwindow.ui b/src/app/valentina/mainwindow.ui index 21deed217..c21a81b6f 100644 --- a/src/app/valentina/mainwindow.ui +++ b/src/app/valentina/mainwindow.ui @@ -1613,7 +1613,7 @@ 0 0 140 - 168 + 169 @@ -1778,6 +1778,8 @@ + + @@ -2185,6 +2187,15 @@ + + + Background images + + + 2 + + + @@ -3094,6 +3105,14 @@ Shop + + + false + + + Add background image + + diff --git a/src/app/valentina/mainwindowsnogui.h b/src/app/valentina/mainwindowsnogui.h index 0e43a8ba0..c793df64d 100644 --- a/src/app/valentina/mainwindowsnogui.h +++ b/src/app/valentina/mainwindowsnogui.h @@ -107,6 +107,7 @@ protected: QAction *redoAction{nullptr}; QAction *actionDockWidgetToolOptions{nullptr}; QAction *actionDockWidgetGroups{nullptr}; + QAction *actionDockWidgetBackgroundImages{nullptr}; bool isNoScaling{false}; bool isNeedAutosave{false}; diff --git a/src/app/valentina/xml/vpattern.cpp b/src/app/valentina/xml/vpattern.cpp index 2c299651d..d21d64849 100644 --- a/src/app/valentina/xml/vpattern.cpp +++ b/src/app/valentina/xml/vpattern.cpp @@ -4408,7 +4408,7 @@ QT_WARNING_DISABLE_GCC("-Wswitch-default") QRectF VPattern::ActiveDrawBoundingRect() const { // This check helps to find missed tools in the switch - Q_STATIC_ASSERT_X(static_cast(Tool::LAST_ONE_DO_NOT_USE) == 55, "Not all tools were used."); + Q_STATIC_ASSERT_X(static_cast(Tool::LAST_ONE_DO_NOT_USE) == 59, "Not all tools were used."); QRectF rec; @@ -4427,6 +4427,10 @@ QRectF VPattern::ActiveDrawBoundingRect() const case Tool::Cut: case Tool::Midpoint:// Same as Tool::AlongLine, but tool will never has such type case Tool::ArcIntersectAxis:// Same as Tool::CurveIntersectAxis, but tool will never has such type + case Tool::BackgroundImage:// Not part of active draw + case Tool::BackgroundImageControls:// Not part of active draw + case Tool::BackgroundPixmapImage:// Not part of active draw + case Tool::BackgroundSVGImage:// Not part of active draw case Tool::LAST_ONE_DO_NOT_USE: Q_UNREACHABLE(); break; diff --git a/src/libs/ifc/schema/pattern/v0.9.0.xsd b/src/libs/ifc/schema/pattern/v0.9.0.xsd index 2bc8afac8..db88fb7a9 100644 --- a/src/libs/ifc/schema/pattern/v0.9.0.xsd +++ b/src/libs/ifc/schema/pattern/v0.9.0.xsd @@ -59,6 +59,28 @@ + + + + + + + + + + + + + + + + + + + + + + @@ -778,6 +800,10 @@ + + + + @@ -1129,4 +1155,9 @@ + + + + + diff --git a/src/libs/ifc/xml/utils.cpp b/src/libs/ifc/xml/utils.cpp new file mode 100644 index 000000000..9ec56c910 --- /dev/null +++ b/src/libs/ifc/xml/utils.cpp @@ -0,0 +1,110 @@ +/************************************************************************ + ** + ** @file utils.cpp + ** @author Roman Telezhynskyi + ** @date 12 1, 2022 + ** + ** @brief + ** @copyright + ** This source code is part of the Valentina project, a pattern making + ** program, whose allow create and modeling patterns of clothing. + ** Copyright (C) 2022 Valentina project + ** All Rights Reserved. + ** + ** Valentina 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, either version 3 of the License, or + ** (at your option) any later version. + ** + ** Valentina 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 Valentina. If not, see . + ** + *************************************************************************/ +#include "utils.h" + +#include +#include +#include +#include +#include +#include +#include + +//--------------------------------------------------------------------------------------------------------------------- +auto IsMimeTypeImage(const QMimeType &mime) -> bool +{ + QStringList aliases = mime.aliases(); + aliases.prepend(mime.name()); + + QRegularExpression rx(QStringLiteral("^image\\/[-\\w]+(\\.[-\\w]+)*([+][-\\w]+)?$")); + + return std::any_of(aliases.begin(), aliases.end(), [rx](const QString &name) { return rx.match(name).hasMatch(); }); +} + +//--------------------------------------------------------------------------------------------------------------------- +auto SplitString(QString str) -> QStringList +{ + QStringList list; + + const int n = 80; + while (not str.isEmpty()) + { + list.append(str.left(n)); + str.remove(0, n); + } + + return list; +} + +//--------------------------------------------------------------------------------------------------------------------- +auto MimeTypeFromByteArray(const QByteArray &data) -> QMimeType +{ + QMimeType mime = QMimeDatabase().mimeTypeForData(data); + + QSet aliases = mime.aliases().toSet(); + aliases.insert(mime.name()); + + QSet gzipMime {"application/gzip", "application/x-gzip"}; + + if (gzipMime.contains(aliases)) + { + QSvgRenderer render(data); + if (render.isValid()) + { + mime = QMimeDatabase().mimeTypeForName(QStringLiteral("image/svg+xml-compressed")); + } + } + + return mime; +} + +//--------------------------------------------------------------------------------------------------------------------- +auto PrepareImageFilters() -> QString +{ + const QList supportedFormats = QImageReader::supportedImageFormats(); + const QSet filterFormats{"bmp", "jpeg", "jpg", "png", "svg", "svgz", "tif", "tiff", "webp"}; + QStringList sufixes; + for (const auto& format : supportedFormats) + { + if (filterFormats.contains(format)) + { + sufixes.append(QStringLiteral("*.%1").arg(QString(format))); + } + } + + QStringList filters; + + if (not sufixes.isEmpty()) + { + filters.append(QObject::tr("Images") + QStringLiteral(" (%1)").arg(sufixes.join(' '))); + } + + filters.append(QObject::tr("All files") + QStringLiteral(" (*.*)")); + + return filters.join(QStringLiteral(";;")); +} diff --git a/src/libs/ifc/xml/utils.h b/src/libs/ifc/xml/utils.h new file mode 100644 index 000000000..6c9487158 --- /dev/null +++ b/src/libs/ifc/xml/utils.h @@ -0,0 +1,42 @@ +/************************************************************************ + ** + ** @file utils.h + ** @author Roman Telezhynskyi + ** @date 12 1, 2022 + ** + ** @brief + ** @copyright + ** This source code is part of the Valentina project, a pattern making + ** program, whose allow create and modeling patterns of clothing. + ** Copyright (C) 2022 Valentina project + ** All Rights Reserved. + ** + ** Valentina 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, either version 3 of the License, or + ** (at your option) any later version. + ** + ** Valentina 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 Valentina. If not, see . + ** + *************************************************************************/ +#ifndef UTILS_H +#define UTILS_H + +class QMimeType; +class QString; +class QStringList; +class QMimeType; +class QByteArray; + +auto IsMimeTypeImage(const QMimeType &mime) -> bool; +auto SplitString(QString str) -> QStringList; +auto MimeTypeFromByteArray(const QByteArray &data) -> QMimeType; +auto PrepareImageFilters() -> QString; + +#endif // UTILS_H diff --git a/src/libs/ifc/xml/vabstractpattern.cpp b/src/libs/ifc/xml/vabstractpattern.cpp index c4ce5b1fd..903449644 100644 --- a/src/libs/ifc/xml/vabstractpattern.cpp +++ b/src/libs/ifc/xml/vabstractpattern.cpp @@ -59,6 +59,7 @@ #include "../vmisc/compatibility.h" #include "../vlayout/vtextmanager.h" #include "vpatternimage.h" +#include "vbackgroundpatternimage.h" class QDomElement; @@ -102,6 +103,8 @@ const QString VAbstractPattern::TagGrainline = QStringLiteral("grainline" const QString VAbstractPattern::TagPath = QStringLiteral("path"); const QString VAbstractPattern::TagNodes = QStringLiteral("nodes"); const QString VAbstractPattern::TagNode = QStringLiteral("node"); +const QString VAbstractPattern::TagBackgroundImages = QStringLiteral("backgroudImages"); +const QString VAbstractPattern::TagBackgroundImage = QStringLiteral("backgroudImage"); const QString VAbstractPattern::AttrName = QStringLiteral("name"); const QString VAbstractPattern::AttrVisible = QStringLiteral("visible"); @@ -138,6 +141,10 @@ const QString VAbstractPattern::AttrManualPassmarkLength = QStringLiteral("manua const QString VAbstractPattern::AttrPassmarkLength = QStringLiteral("passmarkLength"); const QString VAbstractPattern::AttrOpacity = QStringLiteral("opacity"); const QString VAbstractPattern::AttrTags = QStringLiteral("tags"); +const QString VAbstractPattern::AttrTransform = QStringLiteral("transform"); +const QString VAbstractPattern::AttrHold = QStringLiteral("hold"); +const QString VAbstractPattern::AttrZValue = QStringLiteral("zValue"); +const QString VAbstractPattern::AttrImageId = QStringLiteral("imageId"); const QString VAbstractPattern::AttrContentType = QStringLiteral("contentType"); @@ -235,8 +242,55 @@ QString PrepareGroupTags(QStringList tags) return ConvertToList(ConvertToSet(tags)).join(','); } + +//--------------------------------------------------------------------------------------------------------------------- +auto StringToTransfrom(const QString &matrix) -> QTransform +{ + QStringList elements = matrix.split(QChar(';')); + if (elements.count() == 9) + { + qreal m11 = elements.at(0).toDouble(); + qreal m12 = elements.at(1).toDouble(); + qreal m13 = elements.at(2).toDouble(); + qreal m21 = elements.at(3).toDouble(); + qreal m22 = elements.at(4).toDouble(); + qreal m23 = elements.at(5).toDouble(); + qreal m31 = elements.at(6).toDouble(); + qreal m32 = elements.at(7).toDouble(); + qreal m33 = elements.at(8).toDouble(); + return {m11, m12, m13, m21, m22, m23, m31, m32, m33}; + } + + return {}; } +//--------------------------------------------------------------------------------------------------------------------- +template +auto NumberToString(T number) -> QString +{ + const QLocale locale = QLocale::c(); + return locale.toString(number, 'g', 12).remove(locale.groupSeparator()); +} + +//--------------------------------------------------------------------------------------------------------------------- +auto TransformToString(const QTransform &m) -> QString +{ + QStringList matrix + { + NumberToString(m.m11()), + NumberToString(m.m12()), + NumberToString(m.m13()), + NumberToString(m.m21()), + NumberToString(m.m22()), + NumberToString(m.m23()), + NumberToString(m.m31()), + NumberToString(m.m32()), + NumberToString(m.m33()) + }; + return matrix.join(QChar(';')); +} +} // namespace + //--------------------------------------------------------------------------------------------------------------------- VAbstractPattern::VAbstractPattern(QObject *parent) : VDomDocument(parent), @@ -848,7 +902,7 @@ void VAbstractPattern::SetMPath(const QString &path) quint32 VAbstractPattern::SiblingNodeId(const quint32 &nodeId) const { // This check helps to find missed tools in the switch - Q_STATIC_ASSERT_X(static_cast(Tool::LAST_ONE_DO_NOT_USE) == 55, "Check if need to ignore modeling tools."); + Q_STATIC_ASSERT_X(static_cast(Tool::LAST_ONE_DO_NOT_USE) == 59, "Check if need to ignore modeling tools."); quint32 siblingId = NULL_ID; @@ -882,6 +936,10 @@ quint32 VAbstractPattern::SiblingNodeId(const quint32 &nodeId) const case Tool::PiecePath: case Tool::InsertNode: case Tool::DuplicateDetail: + case Tool::BackgroundImage: + case Tool::BackgroundImageControls: + case Tool::BackgroundPixmapImage: + case Tool::BackgroundSVGImage: continue; default: siblingId = tool.getId(); @@ -1267,6 +1325,126 @@ void VAbstractPattern::DeleteImage() emit patternChanged(false); } +//--------------------------------------------------------------------------------------------------------------------- +auto VAbstractPattern::GetBackgroundImages() const -> QVector +{ + QVector images; + const QDomNodeList list = elementsByTagName(TagBackgroundImages); + if (list.isEmpty()) + { + return images; + } + + QDomElement imagesTag = list.at(0).toElement(); + if (not imagesTag.isNull()) + { + QDomNode imageNode = imagesTag.firstChild(); + while (not imageNode.isNull()) + { + const QDomElement imageElement = imageNode.toElement(); + if (not imageElement.isNull()) + { + images.append(GetBackgroundPatternImage(imageElement)); + } + imageNode = imageNode.nextSibling(); + } + } + + return images; +} + +//--------------------------------------------------------------------------------------------------------------------- +void VAbstractPattern::SaveBackgroundImages(const QVector &images) +{ + QDomElement imagesElement = CheckTagExists(TagBackgroundImages); + RemoveAllChildren(imagesElement); + + for (const auto& image : images) + { + if (not image.Id().isNull()) + { + QDomElement imageElement = createElement(TagBackgroundImage); + WriteBackgroundImage(imageElement, image); + imagesElement.appendChild(imageElement); + } + } +} + +//--------------------------------------------------------------------------------------------------------------------- +auto VAbstractPattern::GetBackgroundImage(const QUuid &id) const -> VBackgroundPatternImage +{ + const QDomElement imageElement = GetBackgroundImageElement(id); + if (not imageElement.isNull()) + { + return GetBackgroundPatternImage(imageElement); + } + + return {}; +} + +//--------------------------------------------------------------------------------------------------------------------- +void VAbstractPattern::SaveBackgroundImage(const VBackgroundPatternImage &image) +{ + QDomElement imageElement = GetBackgroundImageElement(image.Id()); + if (imageElement.isNull()) + { + QDomElement imageElement = createElement(TagBackgroundImage); + WriteBackgroundImage(imageElement, image); + QDomElement imagesElement = CheckTagExists(TagBackgroundImages); + imagesElement.appendChild(imageElement); + } + else + { + WriteBackgroundImage(imageElement, image); + } + + modified = true; + emit patternChanged(false); +} + +//--------------------------------------------------------------------------------------------------------------------- +void VAbstractPattern::DeleteBackgroundImage(const QUuid &id) +{ + const QDomNodeList list = elementsByTagName(TagBackgroundImages); + if (list.isEmpty()) + { + return; + } + + QDomElement imagesTag = list.at(0).toElement(); + if (not imagesTag.isNull()) + { + QDomNode imageNode = imagesTag.firstChild(); + while (not imageNode.isNull()) + { + const QDomElement imageElement = imageNode.toElement(); + if (not imageElement.isNull()) + { + QUuid imageId = QUuid(GetParametrEmptyString(imageElement, AttrImageId)); + if (imageId == id) + { + imagesTag.removeChild(imageElement); + + if (imagesTag.childNodes().size() == 0) + { + QDomNode parent = imagesTag.parentNode(); + if (not parent.isNull()) + { + parent.removeChild(imagesTag); + } + } + + modified = true; + emit patternChanged(false); + + return; + } + } + imageNode = imageNode.nextSibling(); + } + } +} + //--------------------------------------------------------------------------------------------------------------------- QString VAbstractPattern::GetVersion() const { @@ -1357,7 +1535,7 @@ void VAbstractPattern::SetActivPP(const QString &name) } //--------------------------------------------------------------------------------------------------------------------- -QDomElement VAbstractPattern::CheckTagExists(const QString &tag) +auto VAbstractPattern::CheckTagExists(const QString &tag) -> QDomElement { const QDomNodeList list = elementsByTagName(tag); QDomElement element; @@ -1378,7 +1556,8 @@ QDomElement VAbstractPattern::CheckTagExists(const QString &tag) TagPatternLabel, // 10 TagWatermark, // 11 TagPatternMaterials, // 12 - TagFinalMeasurements // 13 + TagFinalMeasurements, // 13 + TagBackgroundImages // 14 }; switch (tags.indexOf(tag)) @@ -1422,9 +1601,12 @@ QDomElement VAbstractPattern::CheckTagExists(const QString &tag) case 13: // TagFinalMeasurements element = createElement(TagFinalMeasurements); break; + case 14: // TagBackgroundImages + element = createElement(TagBackgroundImages); + break; case 0: //TagUnit (Mandatory tag) default: - return QDomElement(); + return {}; } InsertTag(tags, element); return element; @@ -1531,7 +1713,7 @@ QVector VAbstractPattern::ListPointExpressions() const // Check if new tool doesn't bring new attribute with a formula. // If no just increment a number. // If new tool bring absolutely new type and has formula(s) create new method to cover it. - Q_STATIC_ASSERT(static_cast(Tool::LAST_ONE_DO_NOT_USE) == 55); + Q_STATIC_ASSERT(static_cast(Tool::LAST_ONE_DO_NOT_USE) == 59); QVector expressions; const QDomNodeList list = elementsByTagName(TagPoint); @@ -1559,7 +1741,7 @@ QVector VAbstractPattern::ListArcExpressions() const // Check if new tool doesn't bring new attribute with a formula. // If no just increment number. // If new tool bring absolutely new type and has formula(s) create new method to cover it. - Q_STATIC_ASSERT(static_cast(Tool::LAST_ONE_DO_NOT_USE) == 55); + Q_STATIC_ASSERT(static_cast(Tool::LAST_ONE_DO_NOT_USE) == 59); QVector expressions; const QDomNodeList list = elementsByTagName(TagArc); @@ -1583,7 +1765,7 @@ QVector VAbstractPattern::ListElArcExpressions() const // Check if new tool doesn't bring new attribute with a formula. // If no just increment number. // If new tool bring absolutely new type and has formula(s) create new method to cover it. - Q_STATIC_ASSERT(static_cast(Tool::LAST_ONE_DO_NOT_USE) == 55); + Q_STATIC_ASSERT(static_cast(Tool::LAST_ONE_DO_NOT_USE) == 59); QVector expressions; const QDomNodeList list = elementsByTagName(TagElArc); @@ -1616,7 +1798,7 @@ QVector VAbstractPattern::ListPathPointExpressions() const // Check if new tool doesn't bring new attribute with a formula. // If no just increment number. // If new tool bring absolutely new type and has formula(s) create new method to cover it. - Q_STATIC_ASSERT(static_cast(Tool::LAST_ONE_DO_NOT_USE) == 55); + Q_STATIC_ASSERT(static_cast(Tool::LAST_ONE_DO_NOT_USE) == 59); QVector expressions; const QDomNodeList list = elementsByTagName(AttrPathPoint); @@ -1654,7 +1836,7 @@ QVector VAbstractPattern::ListOperationExpressions() const // Check if new tool doesn't bring new attribute with a formula. // If no just increment number. // If new tool bring absolutely new type and has formula(s) create new method to cover it. - Q_STATIC_ASSERT(static_cast(Tool::LAST_ONE_DO_NOT_USE) == 55); + Q_STATIC_ASSERT(static_cast(Tool::LAST_ONE_DO_NOT_USE) == 59); QVector expressions; const QDomNodeList list = elementsByTagName(TagOperation); @@ -1676,7 +1858,7 @@ QVector VAbstractPattern::ListNodesExpressions(const QDomElement // Check if new tool doesn't bring new attribute with a formula. // If no just increment number. // If new tool bring absolutely new type and has formula(s) create new method to cover it. - Q_STATIC_ASSERT(static_cast(Tool::LAST_ONE_DO_NOT_USE) == 55); + Q_STATIC_ASSERT(static_cast(Tool::LAST_ONE_DO_NOT_USE) == 59); QVector expressions; @@ -1700,7 +1882,7 @@ QVector VAbstractPattern::ListPathExpressions() const // Check if new tool doesn't bring new attribute with a formula. // If no just increment number. // If new tool bring absolutely new type and has formula(s) create new method to cover it. - Q_STATIC_ASSERT(static_cast(Tool::LAST_ONE_DO_NOT_USE) == 55); + Q_STATIC_ASSERT(static_cast(Tool::LAST_ONE_DO_NOT_USE) == 59); QVector expressions; const QDomNodeList list = elementsByTagName(TagPath); @@ -1738,7 +1920,7 @@ QVector VAbstractPattern::ListPieceExpressions() const // Check if new tool doesn't bring new attribute with a formula. // If no just increment number. // If new tool bring absolutely new type and has formula(s) create new method to cover it. - Q_STATIC_ASSERT(static_cast(Tool::LAST_ONE_DO_NOT_USE) == 55); + Q_STATIC_ASSERT(static_cast(Tool::LAST_ONE_DO_NOT_USE) == 59); QVector expressions; const QDomNodeList list = elementsByTagName(TagDetail); @@ -1951,6 +2133,96 @@ void VAbstractPattern::SetFMeasurements(QDomElement &element, const QVector VBackgroundPatternImage +{ + VBackgroundPatternImage image; + image.SetId(QUuid(GetParametrEmptyString(element, AttrImageId))); + QString path = GetParametrEmptyString(element, AttrPath); + + if (not path.isEmpty()) + { + image.SetFilePath(path); + } + else + { + QString contentType = GetParametrEmptyString(element, AttrContentType); + QByteArray contentData = element.text().toLatin1(); + image.SetContentData(contentData, contentType); + } + + image.SetName(GetParametrEmptyString(element, AttrName)); + image.SetHold(GetParametrBool(element, AttrHold, falseStr)); + image.SetZValue(GetParametrUInt(element, AttrZValue, QChar('0'))); + image.SetVisible(GetParametrBool(element, AttrVisible, trueStr)); + + QString matrix = GetParametrEmptyString(element, AttrTransform); + image.SetMatrix(StringToTransfrom(matrix)); + + return image; +} + +//--------------------------------------------------------------------------------------------------------------------- +auto VAbstractPattern::GetBackgroundImageElement(const QUuid &id) const -> QDomElement +{ + const QDomNodeList list = elementsByTagName(TagBackgroundImages); + if (not list.isEmpty()) + { + QDomElement imagesTag = list.at(0).toElement(); + if (not imagesTag.isNull()) + { + QDomNode imageNode = imagesTag.firstChild(); + while (not imageNode.isNull()) + { + if (imageNode.isElement()) + { + const QDomElement imageElement = imageNode.toElement(); + if (not imageElement.isNull()) + { + QUuid imageId = QUuid(GetParametrEmptyString(imageElement, AttrImageId)); + if (imageId == id) + { + return imageElement; + } + } + } + imageNode = imageNode.nextSibling(); + } + } + } + + return {}; +} + +//--------------------------------------------------------------------------------------------------------------------- +void VAbstractPattern::WriteBackgroundImage(QDomElement &element, const VBackgroundPatternImage &image) +{ + SetAttribute(element, AttrImageId, image.Id().toString()); + + if (not image.FilePath().isEmpty()) + { + SetAttribute(element, AttrPath, image.FilePath()); + element.removeAttribute(AttrContentType); + setTagText(element, QString()); + } + else + { + SetAttributeOrRemoveIf(element, AttrContentType, image.ContentType(), + [](const QString &contentType) noexcept {return contentType.isEmpty();}); + setTagText(element, image.ContentData()); + SetAttributeOrRemoveIf(element, AttrPath, image.FilePath(), + [](const QString &path) noexcept {return path.isEmpty();}); + } + + SetAttributeOrRemoveIf(element, AttrName, image.Name(), + [](const QString &name) noexcept {return name.isEmpty();}); + SetAttribute(element, AttrTransform, TransformToString(image.Matrix())); + + SetAttributeOrRemoveIf(element, AttrHold, image.Hold(), [](bool hold) noexcept {return not hold;}); + SetAttributeOrRemoveIf(element, AttrZValue, image.ZValue(), [](qreal z) noexcept {return qFuzzyIsNull(z);}); + SetAttributeOrRemoveIf(element, AttrVisible, image.Visible(), [](bool visible) noexcept {return visible;}); +} + //--------------------------------------------------------------------------------------------------------------------- /** * @brief IsModified state of the document for cases that do not cover QUndoStack. diff --git a/src/libs/ifc/xml/vabstractpattern.h b/src/libs/ifc/xml/vabstractpattern.h index 3b28de601..d4a2a9c5d 100644 --- a/src/libs/ifc/xml/vabstractpattern.h +++ b/src/libs/ifc/xml/vabstractpattern.h @@ -39,6 +39,7 @@ #include #include #include +#include #include "../vmisc/def.h" #include "vdomdocument.h" @@ -49,6 +50,7 @@ class QDomElement; class VPiecePath; class VPieceNode; class VPatternImage; +class VBackgroundPatternImage; enum class Document : qint8 { FullLiteParse, LiteParse, LitePPParse, FullParse }; enum class LabelType : qint8 {NewPatternPiece, NewLabel}; @@ -205,6 +207,12 @@ public: bool SetImage(const VPatternImage &image); void DeleteImage(); + auto GetBackgroundImages() const -> QVector; + void SaveBackgroundImages(const QVector &images); + auto GetBackgroundImage(const QUuid &id) const -> VBackgroundPatternImage; + void SaveBackgroundImage(const VBackgroundPatternImage &image); + void DeleteBackgroundImage(const QUuid &id); + QString GetVersion() const; void SetVersion(); @@ -282,6 +290,8 @@ public: static const QString TagPath; static const QString TagNodes; static const QString TagNode; + static const QString TagBackgroundImages; + static const QString TagBackgroundImage; static const QString AttrName; static const QString AttrVisible; @@ -318,6 +328,10 @@ public: static const QString AttrPassmarkLength; static const QString AttrOpacity; static const QString AttrTags; + static const QString AttrTransform; + static const QString AttrHold; + static const QString AttrZValue; + static const QString AttrImageId; static const QString AttrContentType; @@ -378,6 +392,15 @@ signals: void UpdateGroups(); void UpdateToolTip(); + void BackgroundImageTransformationChanged(QUuid id); + void BackgroundImagesHoldChanged(); + void BackgroundImageHoldChanged(const QUuid &id); + void BackgroundImageVisibilityChanged(const QUuid &id); + void BackgroundImagesVisibilityChanged(); + void BackgroundImageNameChanged(const QUuid &id); + void BackgroundImagesZValueChanged(); + void BackgroundImagePositionChanged(const QUuid &id); + public slots: virtual void LiteParseTree(const Document &parse)=0; void haveLiteChange(); @@ -473,6 +496,10 @@ private: QVector GetFMeasurements(const QDomElement &element) const; void SetFMeasurements(QDomElement &element, const QVector &measurements); + + auto GetBackgroundPatternImage(const QDomElement &element) const -> VBackgroundPatternImage; + auto GetBackgroundImageElement(const QUuid &id) const -> QDomElement; + void WriteBackgroundImage(QDomElement &element, const VBackgroundPatternImage &image); }; QT_WARNING_POP diff --git a/src/libs/ifc/xml/vbackgroundpatternimage.cpp b/src/libs/ifc/xml/vbackgroundpatternimage.cpp new file mode 100644 index 000000000..c0230363d --- /dev/null +++ b/src/libs/ifc/xml/vbackgroundpatternimage.cpp @@ -0,0 +1,310 @@ +/************************************************************************ + ** + ** @file vbackgroundpatternimage.cpp + ** @author Roman Telezhynskyi + ** @date 11 1, 2022 + ** + ** @brief + ** @copyright + ** This source code is part of the Valentina project, a pattern making + ** program, whose allow create and modeling patterns of clothing. + ** Copyright (C) 2022 Valentina project + ** All Rights Reserved. + ** + ** Valentina 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, either version 3 of the License, or + ** (at your option) any later version. + ** + ** Valentina 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 Valentina. If not, see . + ** + *************************************************************************/ +#include "vbackgroundpatternimage.h" + +#include "utils.h" + +#include +#include +#include +#include +#include +#include +#include + +//--------------------------------------------------------------------------------------------------------------------- +auto VBackgroundPatternImage::FromFile(const QString &fileName, bool builtIn) -> VBackgroundPatternImage +{ + VBackgroundPatternImage image; + QMimeType mime = QMimeDatabase().mimeTypeForFile(fileName); + + if (not IsMimeTypeImage(mime)) + { + qCritical() << tr("Unexpected mime type: %1").arg(mime.name()); + return {}; + } + + if (builtIn) + { + QFile file(fileName); + if (not file.open(QIODevice::ReadOnly)) + { + qCritical() << tr("Couldn't read the image. Error: %1").arg(file.errorString()); + return {}; + } + + QString base64 = SplitString(QString::fromLatin1(file.readAll().toBase64().data())).join('\n'); + image.SetContentData(base64.toLatin1(), mime.name()); + } + else + { + image.SetFilePath(fileName); + } + + return image; +} + +//--------------------------------------------------------------------------------------------------------------------- +auto VBackgroundPatternImage::ContentType() const -> const QString & +{ + return m_contentType; +} + +//--------------------------------------------------------------------------------------------------------------------- +auto VBackgroundPatternImage::ContentData() const -> const QByteArray & +{ + return m_contentData; +} + +//--------------------------------------------------------------------------------------------------------------------- +void VBackgroundPatternImage::SetContentData(const QByteArray &newContentData, const QString &newContentType) +{ + m_contentData = newContentData; + m_contentType = newContentType; + m_filePath.clear(); +} + +//--------------------------------------------------------------------------------------------------------------------- +auto VBackgroundPatternImage::IsNull() const -> bool +{ + return m_filePath.isEmpty() && m_contentData.isEmpty(); +} + +//--------------------------------------------------------------------------------------------------------------------- +auto VBackgroundPatternImage::IsValid() const -> bool +{ + m_errorString.clear(); + + if (IsNull()) + { + m_errorString = tr("No data."); + return false; + } + + if (m_id.isNull()) + { + m_errorString = tr("Invalid id."); + return false; + } + + if (not m_filePath.isEmpty()) + { + QMimeType mime = MimeTypeFromData(); + + if (not IsMimeTypeImage(mime)) + { + qCritical() << tr("Unexpected mime type: %1").arg(mime.name()); + return false; + } + } + else + { + if (m_contentType.isEmpty()) + { + m_errorString = tr("Content type is empty."); + return false; + } + + QMimeType mime = MimeTypeFromData(); + QSet aliases = mime.aliases().toSet(); + aliases.insert(mime.name()); + + if (not aliases.contains(m_contentType)) + { + m_errorString = tr("Content type mistmatch."); + return false; + } + + if (not IsMimeTypeImage(mime)) + { + m_errorString = tr("Not image."); + return false; + } + } + + return true; +} + +//--------------------------------------------------------------------------------------------------------------------- +auto VBackgroundPatternImage::MimeTypeFromData() const -> QMimeType +{ + if (not m_filePath.isEmpty()) + { + return QMimeDatabase().mimeTypeForFile(m_filePath); + } + + if (not m_contentData.isEmpty()) + { + return MimeTypeFromByteArray(QByteArray::fromBase64(m_contentData)); + } + + return {}; +} + +//--------------------------------------------------------------------------------------------------------------------- +auto VBackgroundPatternImage::FilePath() const -> const QString & +{ + return m_filePath; +} + +//--------------------------------------------------------------------------------------------------------------------- +void VBackgroundPatternImage::SetFilePath(const QString &newFilePath) +{ + m_filePath = newFilePath; + m_contentData.clear(); + m_contentType.clear(); +} + +//--------------------------------------------------------------------------------------------------------------------- +auto VBackgroundPatternImage::Name() const -> const QString & +{ + return m_name; +} + +//--------------------------------------------------------------------------------------------------------------------- +void VBackgroundPatternImage::SetName(const QString &newName) +{ + m_name = newName; +} + +//--------------------------------------------------------------------------------------------------------------------- +auto VBackgroundPatternImage::ZValue() const -> qreal +{ + return m_zValue; +} + +//--------------------------------------------------------------------------------------------------------------------- +void VBackgroundPatternImage::SetZValue(qreal newZValue) +{ + m_zValue = newZValue; +} + +//--------------------------------------------------------------------------------------------------------------------- +auto VBackgroundPatternImage::Hold() const -> bool +{ + return m_hold; +} + +//--------------------------------------------------------------------------------------------------------------------- +void VBackgroundPatternImage::SetHold(bool newHold) +{ + m_hold = newHold; +} + +//--------------------------------------------------------------------------------------------------------------------- +auto VBackgroundPatternImage::Id() const -> QUuid +{ + return m_id; +} + +//--------------------------------------------------------------------------------------------------------------------- +void VBackgroundPatternImage::SetId(const QUuid &newId) +{ + m_id = newId; +} + +//--------------------------------------------------------------------------------------------------------------------- +auto VBackgroundPatternImage::Size() const -> QSize +{ + if (not IsValid()) + { + return {}; + } + + if (not m_filePath.isEmpty()) + { + return QImageReader(m_filePath).size(); + } + + if (not m_contentData.isEmpty()) + { + QByteArray array = QByteArray::fromBase64(m_contentData); + QBuffer buffer(&array); + buffer.open(QIODevice::ReadOnly); + + return QImageReader(&buffer).size(); + } + + return {}; +} + +//--------------------------------------------------------------------------------------------------------------------- +auto VBackgroundPatternImage::ErrorString() const -> const QString & +{ + return m_errorString; +} + +//--------------------------------------------------------------------------------------------------------------------- +auto VBackgroundPatternImage::Matrix() const -> const QTransform & +{ + return m_matrix; +} + +//--------------------------------------------------------------------------------------------------------------------- +void VBackgroundPatternImage::SetMatrix(const QTransform &newMatrix) +{ + m_matrix = newMatrix; +} + +//--------------------------------------------------------------------------------------------------------------------- +auto VBackgroundPatternImage::Type() const -> PatternImage +{ + if (not IsValid()) + { + return PatternImage::Unknown; + } + + QMimeType mime = MimeTypeFromData(); + + if (mime.name().startsWith(u"image/svg+xml")) + { + return PatternImage::Vector; + } + + return PatternImage::Raster; +} + +//--------------------------------------------------------------------------------------------------------------------- +auto VBackgroundPatternImage::BoundingRect() const -> QRectF +{ + QSize imageSize = Size(); + QRectF imageRect({0, 0}, QSizeF(imageSize.width(), imageSize.height())); + return m_matrix.mapRect(imageRect); +} + +//--------------------------------------------------------------------------------------------------------------------- +auto VBackgroundPatternImage::Visible() const -> bool +{ + return m_visible; +} + +//--------------------------------------------------------------------------------------------------------------------- +void VBackgroundPatternImage::SetVisible(bool newVisible) +{ + m_visible = newVisible; +} diff --git a/src/libs/ifc/xml/vbackgroundpatternimage.h b/src/libs/ifc/xml/vbackgroundpatternimage.h new file mode 100644 index 000000000..7f7897965 --- /dev/null +++ b/src/libs/ifc/xml/vbackgroundpatternimage.h @@ -0,0 +1,109 @@ +/************************************************************************ + ** + ** @file vbackgroundpatternimage.h + ** @author Roman Telezhynskyi + ** @date 11 1, 2022 + ** + ** @brief + ** @copyright + ** This source code is part of the Valentina project, a pattern making + ** program, whose allow create and modeling patterns of clothing. + ** Copyright (C) 2022 Valentina project + ** All Rights Reserved. + ** + ** Valentina 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, either version 3 of the License, or + ** (at your option) any later version. + ** + ** Valentina 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 Valentina. If not, see . + ** + *************************************************************************/ +#ifndef VBACKGROUNDPATTERNIMAGE_H +#define VBACKGROUNDPATTERNIMAGE_H + +#include +#include +#include +#include + +#include "../vmisc/typedef.h" + +class QPixmap; +class QMimeType; + +enum class PatternImage +{ + Raster, + Vector, + Unknown +}; + +class VBackgroundPatternImage +{ + Q_DECLARE_TR_FUNCTIONS(VBackgroundPatternImage) +public: + VBackgroundPatternImage() = default; + + static auto FromFile(const QString &fileName, bool builtIn) -> VBackgroundPatternImage; + + auto ContentType() const -> const QString &; + + auto ContentData() const -> const QByteArray &; + void SetContentData(const QByteArray &newContentData, const QString & newContentType); + + auto IsNull() const -> bool; + auto IsValid() const -> bool; + + auto MimeTypeFromData() const -> QMimeType; + + auto FilePath() const -> const QString &; + void SetFilePath(const QString &newFilePath); + + auto Name() const -> const QString &; + void SetName(const QString &newName); + + auto ZValue() const -> qreal; + void SetZValue(qreal newZValue); + + auto Hold() const -> bool; + void SetHold(bool newHold); + + auto Id() const -> QUuid; + void SetId(const QUuid &newId); + + auto Size() const -> QSize; + void SetSize(const QSize &newSize); + + auto ErrorString() const -> const QString &; + + auto Matrix() const -> const QTransform &; + void SetMatrix(const QTransform &newMatrix); + + auto Type() const -> PatternImage; + + auto BoundingRect() const -> QRectF; + + auto Visible() const -> bool; + void SetVisible(bool newVisible); + +private: + QUuid m_id{QUuid::createUuid()}; + QString m_contentType{}; + QByteArray m_contentData{}; + mutable QString m_errorString{}; + QString m_filePath{}; + QString m_name{}; + qreal m_zValue{0}; + QTransform m_matrix{}; + bool m_hold{false}; + bool m_visible{true}; +}; + +#endif // VBACKGROUNDPATTERNIMAGE_H diff --git a/src/libs/ifc/xml/vpatternimage.cpp b/src/libs/ifc/xml/vpatternimage.cpp index d37489af1..6bc21c0ae 100644 --- a/src/libs/ifc/xml/vpatternimage.cpp +++ b/src/libs/ifc/xml/vpatternimage.cpp @@ -39,34 +39,7 @@ #include #include -namespace -{ -//--------------------------------------------------------------------------------------------------------------------- -auto IsMimeTypeImage(const QMimeType &mime) -> bool -{ - QStringList aliases = mime.aliases(); - aliases.prepend(mime.name()); - - QRegularExpression rx(QStringLiteral("^image\\/[-\\w]+(\\.[-\\w]+)*([+][-\\w]+)?$")); - - return std::any_of(aliases.begin(), aliases.end(), [rx](const QString &name) { return rx.match(name).hasMatch(); }); -} - -//--------------------------------------------------------------------------------------------------------------------- -auto SplitString(QString str) -> QStringList -{ - QStringList list; - - const int n = 80; - while (not str.isEmpty()) - { - list.append(str.left(n)); - str.remove(0, n); - } - - return list; -} -} // namespace +#include "utils.h" //--------------------------------------------------------------------------------------------------------------------- @@ -140,19 +113,6 @@ auto VPatternImage::IsValid() const -> bool QSet aliases = mime.aliases().toSet(); aliases.insert(mime.name()); - QSet gzipMime {"application/gzip", "application/x-gzip"}; - - if (gzipMime.contains(aliases)) - { - QSvgRenderer render(QByteArray::fromBase64(m_contentData)); - if (render.isValid()) - { - mime = QMimeDatabase().mimeTypeForName(QStringLiteral("image/svg+xml-compressed")); - aliases = mime.aliases().toSet(); - aliases.insert(mime.name()); - } - } - if (not aliases.contains(m_contentType)) { m_errorString = tr("Content type mistmatch."); @@ -202,7 +162,7 @@ auto VPatternImage::ErrorString() const -> const QString & //--------------------------------------------------------------------------------------------------------------------- auto VPatternImage::MimeTypeFromData() const -> QMimeType { - return QMimeDatabase().mimeTypeForData(QByteArray::fromBase64(m_contentData)); + return MimeTypeFromByteArray(QByteArray::fromBase64(m_contentData)); } //--------------------------------------------------------------------------------------------------------------------- diff --git a/src/libs/ifc/xml/vpatternimage.h b/src/libs/ifc/xml/vpatternimage.h index 615b0bb79..81dced06a 100644 --- a/src/libs/ifc/xml/vpatternimage.h +++ b/src/libs/ifc/xml/vpatternimage.h @@ -31,7 +31,6 @@ #include #include - class QPixmap; class QMimeType; diff --git a/src/libs/ifc/xml/xml.pri b/src/libs/ifc/xml/xml.pri index fa8c2ce4a..c142d3cd4 100644 --- a/src/libs/ifc/xml/xml.pri +++ b/src/libs/ifc/xml/xml.pri @@ -2,7 +2,9 @@ # This need for corect working file translations.pro HEADERS += \ + $$PWD/utils.h \ $$PWD/vabstractconverter.h \ + $$PWD/vbackgroundpatternimage.h \ $$PWD/vdomdocument.h \ $$PWD/vlayoutconverter.h \ $$PWD/vpatternconverter.h \ @@ -16,7 +18,9 @@ HEADERS += \ $$PWD/vwatermarkconverter.h SOURCES += \ + $$PWD/utils.cpp \ $$PWD/vabstractconverter.cpp \ + $$PWD/vbackgroundpatternimage.cpp \ $$PWD/vdomdocument.cpp \ $$PWD/vlayoutconverter.cpp \ $$PWD/vpatternconverter.cpp \ diff --git a/src/libs/vformat/vpatternrecipe.cpp b/src/libs/vformat/vpatternrecipe.cpp index b6b3adaa3..c38e3f89a 100644 --- a/src/libs/vformat/vpatternrecipe.cpp +++ b/src/libs/vformat/vpatternrecipe.cpp @@ -299,7 +299,7 @@ QDomElement VPatternRecipe::Draft(const QDomElement &draft) QDomElement VPatternRecipe::Step(const VToolRecord &tool, const VContainer &data) { // This check helps to find missed tools in the switch - Q_STATIC_ASSERT_X(static_cast(Tool::LAST_ONE_DO_NOT_USE) == 55, "Not all tools were used in history."); + Q_STATIC_ASSERT_X(static_cast(Tool::LAST_ONE_DO_NOT_USE) == 59, "Not all tools were used in history."); const QDomElement domElem = m_pattern->elementById(tool.getId()); if (not domElem.isElement() && tool.IsMandatory()) @@ -320,6 +320,10 @@ QT_WARNING_DISABLE_GCC("-Wswitch-default") case Tool::Cut: case Tool::Midpoint:// Same as Tool::AlongLine, but tool will never has such type case Tool::ArcIntersectAxis:// Same as Tool::CurveIntersectAxis, but tool will never has such type + case Tool::BackgroundImage: + case Tool::BackgroundImageControls: + case Tool::BackgroundPixmapImage: + case Tool::BackgroundSVGImage: case Tool::LAST_ONE_DO_NOT_USE: Q_UNREACHABLE(); //-V501 break; diff --git a/src/libs/vmisc/def.h b/src/libs/vmisc/def.h index f15aa1402..89a98fe4a 100644 --- a/src/libs/vmisc/def.h +++ b/src/libs/vmisc/def.h @@ -205,6 +205,10 @@ enum class Tool : ToolVisHolderType InsertNode, PlaceLabel, DuplicateDetail, + BackgroundImage, + BackgroundImageControls, + BackgroundPixmapImage, + BackgroundSVGImage, LAST_ONE_DO_NOT_USE //add new stuffs above this, this constant must be last and never used }; diff --git a/src/libs/vmisc/defglobal.h b/src/libs/vmisc/defglobal.h index 5ccdbc213..7479967a1 100644 --- a/src/libs/vmisc/defglobal.h +++ b/src/libs/vmisc/defglobal.h @@ -48,4 +48,13 @@ void qAsConst(const T &&) Q_DECL_EQ_DELETE; Class &operator=(const Class &) Q_DECL_EQ_DELETE; #endif +#if QT_VERSION < QT_VERSION_CHECK(5, 13, 0) +#define Q_DISABLE_MOVE(Class) \ + Class(Class &&) = delete; \ + Class &operator=(Class &&) = delete; +#define Q_DISABLE_COPY_MOVE(Class) \ + Q_DISABLE_COPY(Class) \ + Q_DISABLE_MOVE(Class) +#endif + #endif // DEFGLOBAL_H diff --git a/src/libs/vmisc/share/resources/icon.qrc b/src/libs/vmisc/share/resources/icon.qrc index 7e62554aa..da1d88ca5 100644 --- a/src/libs/vmisc/share/resources/icon.qrc +++ b/src/libs/vmisc/share/resources/icon.qrc @@ -94,5 +94,58 @@ icon/24x24/close.png icon/24x24/close@2x.png icon/layout.png + icon/32x32/rotate-top-left-hover@2x.png + icon/32x32/rotate-top-left-hover.png + icon/32x32/rotate-top-left@2x.png + icon/32x32/rotate-top-left.png + icon/32x32/rotate-top-right-hover@2x.png + icon/32x32/rotate-top-right-hover.png + icon/32x32/rotate-top-right@2x.png + icon/32x32/rotate-top-right.png + icon/32x32/rotate-bottom-right-hover@2x.png + icon/32x32/rotate-bottom-right-hover.png + icon/32x32/rotate-bottom-right@2x.png + icon/32x32/rotate-bottom-right.png + icon/32x32/rotate-bottom-left-hover@2x.png + icon/32x32/rotate-bottom-left-hover.png + icon/32x32/rotate-bottom-left@2x.png + icon/32x32/rotate-bottom-left.png + icon/32x32/expand2-hover@2x.png + icon/32x32/expand2-hover.png + icon/32x32/expand2@2x.png + icon/32x32/expand2.png + icon/32x32/expand1-hover@2x.png + icon/32x32/expand1-hover.png + icon/32x32/expand1@2x.png + icon/32x32/expand1.png + icon/32x32/double-arrow-vertical-hover@2x.png + icon/32x32/double-arrow-vertical-hover.png + icon/32x32/double-arrow-vertical@2x.png + icon/32x32/double-arrow-vertical.png + icon/32x32/double-arrow-horizontal-hover@2x.png + icon/32x32/double-arrow-horizontal@2x.png + icon/32x32/double-arrow-horizontal-hover.png + icon/32x32/double-arrow-horizontal.png + icon/svg/broken_path.svg + icon/16x16/not_hold_image@2x.png + icon/16x16/not_hold_image.png + icon/16x16/hold_image@2x.png + icon/16x16/hold_image.png + icon/32x32/rotate-top-right-disabled@2x.png + icon/32x32/rotate-top-right-disabled.png + icon/32x32/rotate-top-left-disabled@2x.png + icon/32x32/rotate-top-left-disabled.png + icon/32x32/rotate-bottom-right-disabled@2x.png + icon/32x32/rotate-bottom-right-disabled.png + icon/32x32/rotate-bottom-left-disabled@2x.png + icon/32x32/rotate-bottom-left-disabled.png + icon/32x32/expand2-disabled@2x.png + icon/32x32/expand2-disabled.png + icon/32x32/expand1-disabled@2x.png + icon/32x32/expand1-disabled.png + icon/32x32/double-arrow-vertical-disabled@2x.png + icon/32x32/double-arrow-vertical-disabled.png + icon/32x32/double-arrow-horizontal-disabled@2x.png + icon/32x32/double-arrow-horizontal-disabled.png diff --git a/src/libs/vmisc/share/resources/icon/16x16/hold_image.png b/src/libs/vmisc/share/resources/icon/16x16/hold_image.png new file mode 100644 index 0000000000000000000000000000000000000000..5b90c5fb9be234846824ea9c090d111dd6a8d455 GIT binary patch literal 1681 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 YJ0{2gWx*Ws0000007*qoM6N<$g6llBTL1t6 literal 0 HcmV?d00001 diff --git a/src/libs/vmisc/share/resources/icon/16x16/not_hold_image.png b/src/libs/vmisc/share/resources/icon/16x16/not_hold_image.png new file mode 100644 index 0000000000000000000000000000000000000000..3782e02cd153110852aadaa99ad4d97233add8eb GIT binary patch literal 1682 zcmV;D25tF?P) zaB^>EX>4U6ba`-PAZ2)IW&i+q+Pzn8vYRjr{I65=2uQO0aU5gtOmEQRcWn$wUeYFc zq{Ra=!bs~~*(9|7`dR4*4w*#AS%^M)KjCo6CCTW-X**KC^5pt;+)R7t#!trsflFa^ zyA64a&+yyjg*^w)w|? zy=^X=$GfHkyTlzevKOfZKr64Wn4wQw4S1mkp$y(pDPHtnchL*bWP9= z$$O8!vVZixkS}@o^$VANaD>jo2W*eY?-XAp)&r5;KFDW|y4@qkdFW zC!6;$7zo;(_SZaGz-j1*-V$1z@>C%CGL>mYE1X!sbtQJZaOTQuw}lHJI$0&O!j~f2 zeNlrXDP1ww8dnLLbkPX?imU|roXUZPd*0OZ6^_WfFop^v7+Z9Z$tj+s(w(aP|!Nf5fjYQyoH#JFSVszXT>;!-Ifqj zSVjO)gxm%&tWaR`tj3YABnUGc#|&B3ZKR)jMzA`{3IqmxBfuEcg&Y3@PNO(M5|s zh8SatIZ0CNftGvYOQK@)vB+d#+qtAR2!<^wKr7bp_+81+PHA2 z1}}xMhv2rISknxOiDM`pngS?jX_`%uos*_q(`-tDMPUMv*0iC8n_`gAtt?jg(C(t# zOS+N8mvob7%2AW<6Uq^!yVvc3YD@dhm9E&f3LB@BV)bE6mPk0UElRw$y56~-S*5ks zH{W?|>09|9SQ>MA8`szLbb3w$2A#ukv2+f{#q~`7tKoka*eZUu;O{g22Dc)`yJFH^o&W#=g=s@W zP)S2WAaHVTW@&6?004NLeUUv#!%!53Pg|u@D;?}0#UVp=vLGtrs8uLJg-|QB>R|HH zKWNgBq_{W=t_25w7OM^}&bm6d3WDGdh@+E}qKlMxUs7lhYcSI&0sPzc5_TR+hO=YZxgkVhJJy$f%=&Dl8;u*GMsuq4T7NKj!#F za>?YXfRSSXHK>prKlmT~o~>D&oN$xENuc+|wm(LI;4aW=*!K6aZMRNYT&7<$qrLvo}5Eq}2Hyr0oG<$=LlAhhPrt$mKu2Ovwc zO5Xqnhrnowve!M{-Pb+0e|y^V`vDBpa(?{*JBI)O00v@9M??Vs0RI60puMM)00009 za7bBm0001Q0001Q0r8^TLjV8(2XskIMF-{q8Vd?7y=a$e0003MNklvgc z@BLnvJoVP;d7t-r&-#SFC%k4f~5p@QftGnq)-9L{hAgPGvGq{~j0UrgK@mSc>n2)+?@ zVlUgvMdEHy4Bk$4GdrjGwl(kOFF)%(TXG1 zFqQbj5W=aFE+0K^QBRAkVzyG!{lza3V{FD4FUt&S{(y>qqJq~hxz8?q$B5gB?Dd2# c>=%FP7qG1ACg_-E3jhEB07*qoM6N<$g10avcK`qY literal 0 HcmV?d00001 diff --git a/src/libs/vmisc/share/resources/icon/16x16/not_hold_image@2x.png b/src/libs/vmisc/share/resources/icon/16x16/not_hold_image@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..8ac0d649f8fb0dbfa32757935fdf6ae3b8aa034d GIT binary patch literal 1981 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±TJ5#8uvQBT4-iE>8^yu{Vqpv- zNmd~mBpy2!T8RgXM^fwu4<7Hm8HSmE<^dWS8X9Sa|LW4yXRpr6=C(?k3vhXOyQ{{r z+h+~v1%9T&E3IDaC!(q&bM+Scb41+}0IKwg>uT-aY{-zG_3ZK$jehoyy+3FUf{X2n zD@RLm3n>Hu$aiF?OojTGng9}4Dd{GBuLl6eF;upjS0#76Z7m4p%amJd1Q`G^4kS7a z1mH(YbQ*|puX}5aAba}k)gNY{zNaW~yd0lM%8$I!tV7pW851me#6n5OPedk_8pC(7 z2h0whP*Q$?(X3NyJ1ryI(~)i& zv&|cT0p+dc9loKkf!XRz(a1qUVjR%eUxKCq2V*?@la`}r!un*GHm1%&3Sg@5i10R=!raj5ha$|JHq3}Yl#Wr1?>-}^CokmcE%vLmb^ELVrzbD;KIIx3 c8X6hL3wYS|X>jFXhyVZp07*qoM6N<$f@kJ~<^TWy literal 0 HcmV?d00001 diff --git a/src/libs/vmisc/share/resources/icon/32x32/double-arrow-horizontal-disabled@2x.png b/src/libs/vmisc/share/resources/icon/32x32/double-arrow-horizontal-disabled@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..3195fbcf0c0ecf7663dd8c0d739f79a518fca7d1 GIT binary patch literal 5115 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 Utng|4l>h($07*qoM6N<$g0E5?ga7~l literal 0 HcmV?d00001 diff --git a/src/libs/vmisc/share/resources/icon/32x32/double-arrow-horizontal-hover@2x.png b/src/libs/vmisc/share/resources/icon/32x32/double-arrow-horizontal-hover@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..c19c800318aec902bf8fc0ffed25f9568747a264 GIT binary patch literal 1054 zcmV+(1mXLMP)0Rrln`~*TEX`FQyOIEj_N=_Rlt2_gs~0xhD4g5K1~u%7y$F9IJlBP5-pvSeCIi`v$7J3W+f z_o1@fSML9V^K$<8-v2p=bI#k$kJ?uFHw2_<(*>oUV75Cir1~xRWQBV(WJ&>)5cWwXxu!y=H8l%!3+6YME0002V z%w@e6QC}VH^QdSpHrx2N*Y`dh^tt^J?=#o(mnfj7qisYq2oR>X-FYNZOsFsbHj%m`_rhKXvqo5uZ4&`ZM()rQ`os}~vJ<0A=w1qP$J*5pg>w-FOw()Hm;MJY+uo!1h{#;X`se5*Hq)S>K zGyt=$@{EFp@<0$9F!N!%MAOP?KXXg@Xp<3m5fVn#vjSW(1To?jjwr!1mw$KZX} zE!W%dDPL5z9IU7EQ+CSF$LIFL-DW%Vca4R`ID^t_D(7}nY8~sF{NT-y1~XV1b~fch zaSfla#>P`$rxWg-wZoJ*5LB<$vpzaWZAuw3^B%s(Gci@`nVe>3Dg)C5-aK%@#E2JG z3Yj3w_&>+V=k~*$lSA;h?L7phNl=V4D7|YcG}X8WN(1TmGZ?hII|sgZZ{C~J?s2|t11rxWnyy$AA2Q}>ndx&6@9GzgDdBTo6B#WlRG=jP~nX?@y%3eRcQ%@9kI z%4So_Y=$bEO@xWC8Ir4HWMdtFEvin3O6%?LjKzE)f=6P}O7@}XpB!Pi|MgJxpCt2G zJkkIp^jKUfZT}dN5=4DjVU@PiBIcGSDyK!vEibInb_$}tEKxZTLI@#*5JCtcgwSH~ Y3tuLt{A3vF6951J07*qoM6N<$f~CjtJ^%m! literal 0 HcmV?d00001 diff --git a/src/libs/vmisc/share/resources/icon/32x32/double-arrow-horizontal.png b/src/libs/vmisc/share/resources/icon/32x32/double-arrow-horizontal.png new file mode 100644 index 0000000000000000000000000000000000000000..579f38c221c9f92f5312b761af66d84a389e7c39 GIT binary patch literal 310 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdzmSQK*5Dp-y;YjHK@;M7UB8!3a z8xUr!)F|8wWJs2{MwA5SrERJ7UC#W5tJ z_3hQOoQE6)+8(M$xl~@7Sox8Tp_MoqQE zT5jIlfGeFrPvquq7u+f3y~_Pw+MdWs37y&ON9PA7Xh+n%*zh6yjeFunaaHCG4?Ody yZ}574zM!Cah1b4nJ zFx~)R#$PeZihzQWC9V-A!TD(=<%vb94CUqJdYO6I#mR{Use1WE>9gP2NC8zBdAc}; zRLpsM)6wg&gUGRu^;er#Oy>w$^dL88C9B|_9iMd8-4Q>yc*j*SmZQ7M7j}r22Z{R? zwD6ofV&Q9apZ}g#>OJon<}ZC5T%h35rkKMLoCPd4%=)H1;m6bpIBXc#Hx@m!Vn{n6 zGlT8S?~I~zKD-ClGNmu$u>QqZ^u72~U&B_0os1rhujeR<{bYD{KzGAq>lMl&C6vDKfqZKSRQnJN`6*P!`EGTb=(r>50=#==Vbk#tZ2WwM*156V!IE7q iUC3a02NaYX5`VX)zoX)~(sp1JF?hQAxvX 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 literal 0 HcmV?d00001 diff --git a/src/libs/vmisc/share/resources/icon/32x32/double-arrow-vertical-disabled@2x.png b/src/libs/vmisc/share/resources/icon/32x32/double-arrow-vertical-disabled@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..fc75c19e2432b7447542e0cbbfbca0f883e598c0 GIT binary patch literal 5605 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 literal 0 HcmV?d00001 diff --git a/src/libs/vmisc/share/resources/icon/32x32/double-arrow-vertical-hover.png b/src/libs/vmisc/share/resources/icon/32x32/double-arrow-vertical-hover.png new file mode 100644 index 0000000000000000000000000000000000000000..903e8179c5d4a1b0c94ce6c49c9091cac1e23645 GIT binary patch literal 758 zcmVj#3cSNn8bhuQ#E4hP$G89R@x#6 zc5)CEGIUTfxVTAib?hn*MX*p%DHg{PHH(TUmY6@Ln3@nZo(@KrK)neyQ$Bcb;r;l| z$K{^)?g5IFBj6A%UHUWYbA8wmaEQhF6s?X|@(%z6fO6~7sc+Qc2LmzT?g0b!javLA zDefM#c1p6C1wfwhCi09o5de~8G3y?)c0OtG!2l}EPF)-Twe=flCOAMd!2xRPHxK|a zJ9Y8F04fUc6bgZ(+sfK$Fj*(bB7E~5U>XLdVerj&K$1nA29tHsZDp+xPYo~VdT;vp zb0zwLG2qRj1NG0Ohj(JN21c8N;Fk!Z2k92qJJZ##;Gge2zFa%t9X_<#&hj}(Wdj&C}I`>lvXX&b}5fh+a)NiS^z+e zB4&*?osY+xE5O#$$&PJUnPvh*{3wNFB>!5FjARf$N`VOswqa#@Yw2W%$GadohLvx) zyMoiydN0lHd1!XeLsRR$B*`KHpd7=kg1SKMi4B>Zy7SBU6_))WP?kVh!m>Yv%lH+{ zPTjfM6B{bTvtZo~CY3kH8%!#1o_sL5SNB`fBoo-fXvPFa-EU2kB`JO{$7^*Jx#2Xo oF0ld-EM5ADrAvQ?K=EJHzb*dg+`b{dZ~y=R07*qoM6N<$f;t&k?f?J) literal 0 HcmV?d00001 diff --git a/src/libs/vmisc/share/resources/icon/32x32/double-arrow-vertical-hover@2x.png b/src/libs/vmisc/share/resources/icon/32x32/double-arrow-vertical-hover@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..17755c2c36784b19febfff06d099ccb57f38b5c0 GIT binary patch literal 1504 zcmV<61t0o}P)UCg)<3ywvNT!azfhHGZO3dPik7xh!E|k; z>}8vfRfHl8*~13Imz6zq;M|I9CpbFBpbyGmqZ6v4>w`s#v#!&mS&L3xs%A-})%-Kb z-Gll6XVvyx&nEdmUVb_EcfaSG^EgcUS5Yw2NpD~`8l*w z+a%x)hb8ze0HF7l>k0H?c;42W@`y4$d{T5<6AAAMg+q+a=RA_kuZoGL8Wsj&AXwH4!w!ZDA-2gV_5Kikg-td0O4$^2%1upTv`X+Kdxt~xWWEtScK zbg2N0!`4vDY4`GYB%`x0xV~gdBWoj|TqnIYJ7&*|j2&srfzq>*q}P<5mB5k4=ns5$ z%$`@SlU`#-B}*eftyjD`^~3UKzDBpsBM6c*3wWkHg0?4q9&3 zw8*kh>qR~SI0E9qli%a(KCd#q?vt^PCSgBKf@vf?AGlV%JdS{9LOmZKHK1H4{mO9r z?=!J+(=Rr#Ja$0wne9E`^?zW1v2PZE@kht)3$Dy^om8@9SigltFd!@ejb2ebJz!}~ z^q$|#0OQ+!F2=WYO(LcTEUg;7qWXnICIFcM&sN;+k#)}&9*2+f%;J6@RX$_}P_(c+ zT_)Ike#@65T_)JvpDpZ0m5+xi(fEz|K>#3Ynw3HTfYo)^mC3%PHWazbo1|B{V(y0k z024iC$KBp}6lnnfp#ecgZ_K#>0FE`i;zgB{103%}kJ*7LUnH#l7WhAqG61&#+@uV^ zEdVzu18@t#P09e=0&tTu0Ji|#qzu3<05>TEa0|dq$^hH~aFa3sw*cIv48Sb_Hz@;f z3&0KXb%En8G712|Z}TI|%>cs-=0&|efJ|gX^Q)(gx0i2C^qBdmas~;kFtq_}EsYh| zOX~0T&JUx=jjRM5R@bFfvm1HmV_7x3`Rf+fB~= z%7@H=vEGF{CH2|Y6W)Z!0nQG#VsMD9YZ6gXpM8C-cj3+piA;1aREdJ9YQ^_WBet^0 z*c%_^L&e(%ca&$cZwY?tFvaGcGL6`dzP!pX$iD_V5_yca@VA!5ltC>)5FKT_>>Fc}E8(*xVqEg}f6P)3D7WGl8~y3$V!IBOzrt+)m5vz@)6){dn_KsiCYL}5UU zk&k5Yx5X_#9ZKO3#&U4igUY(Qc&`W5b$5~U3?#nAq1$jSbVu~X+$1l$9hVW2AVd8C O0000J%=P)Tp2nVu} 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 literal 0 HcmV?d00001 diff --git a/src/libs/vmisc/share/resources/icon/32x32/expand1-disabled@2x.png b/src/libs/vmisc/share/resources/icon/32x32/expand1-disabled@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..fdf3a1babc831b1ae468d1ab8a1bdf33620f30ec GIT binary patch literal 7292 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 literal 0 HcmV?d00001 diff --git a/src/libs/vmisc/share/resources/icon/32x32/expand1.png b/src/libs/vmisc/share/resources/icon/32x32/expand1.png new file mode 100644 index 0000000000000000000000000000000000000000..3af13762975cc7177c9644880f36cfe641354a0e GIT binary patch literal 1680 zcmV;B25 zaB^>EX>4U6ba`-PAZ2)IW&i+q+U=HGlH@21MgLhvFTs)!63f9nB4!7({Jvl^E3>Nl z6;mG_F=-+V7Fd$+k#UCp?>{4a!$CQ5h-#j4N(YXZW9EW^AJ>uBnU1*5>)<_w>nD4- zPcTe^Uam_kdwqdj-)^|Yp!3-te9y3NM|X={YRrxT=~i@zj(2rT!E_<6$O&DBSd$7nuAzn-qd z1Vz)Xs3kVt++ZQ}kFst{XojC+Y^!B7Kgv;oydCv8(JEzPP#J+DH7Ycyuf9@6ijg~m z2{TugWpgmfTowL)Gz>l$fHa`TK;077GP(HYD*Sp!8%2M zy+RbMZ3mDDu@%Xn>?0kCL@Ci>%tizq9e@flXNi1*0I70Ak{m^_9~nE(*to}Nt!3rJ z(~P|dAXJhV*kmby6{DhlEIMkasAyExq^2G;Ye|w~(v*^=xycn1i>8*$%q?4S@#yNw z&E2z?%vrDpYEId*=bTI7P!OoVSHb!M%gLvl@zgV&cKVsmQc*rNR;{U8^_pvG+@xWf zXsKEAmRso*I(CX4yY|$rd(XWL)Y^~{hmJIC_{gK&scots+8!3TkH*&wlEvWw% zH~tm5FwuPlxd3z@xqU*d&F@@Y#ICn+YI+FXKFoJ7fxXO+TKTX2%hAix|J_ke`11XD zK`398(%YciDr?@8zc7^3R+hO=YX}J}VhJJy$f%-(GAzVs*GMsuqVu?if6(!Zxx8^xcAAmH?DtQAO90DT+ z%3k+)cUSw|{_Sbb?*~-}a+`&U$ov2R00v@9M??Vs0RI60puMM)00009a7bBm0002& z0002&0eL8Ky#N3J2XskIMF-{q5eY91x^Ofq0004lNklEhzx(xsD=5y44J1_x6Ik#?zrh;_L_2-Wu9yOM(= z3E|!M-Fx5nxHHUutN<}kw=*t06XOXaDuIjvVqoc?xPcGwGhif~N#KFNf+9BcL7)ZP zND257S_pInC1?ho2&`G*Rg!@^@G2BxxHn~I%}fI9%G2KIqlU{zy5DS=(! z3RqTVFa@YN2JP6LFM1Gf<{GdLJWQ&mF;nZEPv{d3SGD(mZ-Lly8y^B4i3K%Q%S#O^ z1lqvqB%pKqGn@hZX9n<|OLvS4fgMZY&z&1%rh$I~+b%V3loseJ(mn)^Tov9_bhKhQ z2Eefk@VF7h3O^g*3wQ)hfNRMj7m5swfHz=6)0Gk>_?EUU_C5Jrd~UC7=Ku#)f(Jqe zJOMAh2uNypXzdpIAaSX1K)b38jK@$*z*q}TyFh042$ccPh3^k7ff{fR?3F;$!u$I? a@9w{ZhC@;g6W)^m0000 zaB^>EX>4U6ba`-PAZ2)IW&i+q+U=KJlI$i7h2L34mVhLL#Bwk{RoOw7p98jgx@RWI zy>;(Xs!bWNz(Sl)rf0(V>(3efz(plwwAa>Z{0ecAs;cyQ?Wf z(R^jp7MpH2n0P>P*{@|3!>#DsE(OJ7jSa|qZ1F&8RER-k21?Ya(xkE5Mhzh*9t*`!Dp2g$}Gh``K`SZJ?X_IiyRD|fDx51w&Va~QB zqUaB;5Erk!Mi`6Ssxb>dXkOfO3w)Oge|*Xxq$&vNmf5kv8jsV(q4bekvN;RpDcbWB zqG0`U0Ew`+A{eB7qL@jPGDTxHBIxJOqUvEIDP(Ia^+bTs5&|YT3-(id7d+uAbf8 zy?8BL1ZSX@T&#F0rB)6V3sv~4m|tNz_>dzVdgQ|nKgv-X(x;`SEjMf4N~@i_bQ}{s zckA9uuYmWn=;eXnWs&kW!49^i|Uv16V&LU#+%fdXAf$y2D94* zt>Z)&GZ5oMAa08Q5}FsY=#&yKa*J6kjE_PYLF!@?IxS*Am=U2Eije@TaEX>4Tx0C=2zkv&MmP!xqv zTcs)$k#-Pq$WWcEh>AFB6^c+H)C#RSn7s54nlvOSE{=k0!NH%!s)LKOt`4q(Aov5~ z=;Wm6A|>9J6k5c1;qgAsyXWxUeSp7SW~$jS4yc-Cq!Mu`u_ypovrW+RV2J!T!rE}gVjecQmrbxV`?fXf|V;7OMZ z$&muI{P{faen#Jv1;V#L&zd{8<~dFufHci2c>^3A0wV>=UiWx+SNq)l?P<>M2UQ1h zn}v$V`~Uy|24YJ`L;(K){{a7>y{D4^000SaNLh0L00Gzl00Gzm`dp@R00007bV*G` z2j&402`~kD@v?>h00O^BL_t(|+U1+QixXiGho46%o(#?L~c%w%`qB~>!} z{N9gwXJ+G3Y^wrwH}P=Nnl=9!Oyr82|EKr49=UkUyWaK4O5kO$mTocUzTX~Ew#03QQKrQv4uMVJtP{eNJr|hy)l^Z%oV^iR{-IS7BtxJ%5q2qrKvN>jqH_Zb zN9a3e*h?WxZt411J(cl N002ovPDHLkV1jWss4oBj literal 0 HcmV?d00001 diff --git a/src/libs/vmisc/share/resources/icon/32x32/expand2-disabled.png b/src/libs/vmisc/share/resources/icon/32x32/expand2-disabled.png new file mode 100644 index 0000000000000000000000000000000000000000..2f44074d25be0485abcdf6f8065e904d7971e5bc GIT binary patch literal 3743 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 JPDHLkV1j7Z_Q(JL literal 0 HcmV?d00001 diff --git a/src/libs/vmisc/share/resources/icon/32x32/expand2-disabled@2x.png b/src/libs/vmisc/share/resources/icon/32x32/expand2-disabled@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..8ea6152e14ac489e96189254e7b5d01eb1aa9bf7 GIT binary patch literal 7417 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 literal 0 HcmV?d00001 diff --git a/src/libs/vmisc/share/resources/icon/32x32/expand2-hover.png b/src/libs/vmisc/share/resources/icon/32x32/expand2-hover.png new file mode 100644 index 0000000000000000000000000000000000000000..632180b8b96079e8bc1ea3c5065cba23c53d172b GIT binary patch literal 1495 zcmV;|1t|K7P)IiUAkm?#H+0CoWJc>$Oh$k6ijEdxl|?>&HN&(!qCs_4-R1waF^Xs*f~ zfG&XOaH59ni6qv^1)$pyYV+QzHP7x}d9$cWt;DiI{d@q701+dV@gqsv?^9TAkoKxz z#f4Jc6kN3Jm71$sp_ms4#a_F#JQWE=SZ3E;GR-wJfM?y#P~D~%7G3}tWaykCG&q%XZ| zxF>S;@)rZu9Ven?y%(bkrzR6O>K|R;xFrGPxd05nQ?sE&EN=3D-ga_im6>7%5CqP` zbUe)nARl;Xv^yD^7%~d5f=eH4om`$sBwFhpUf@CC#mWd_|Kqqs4OIG8FR1msee>tB zM~o@?kPUjqhMW;V9C+P+J__sv4gkA`JEvCoO4R~_!xK`IF<|WPh$)?3_RE08ub-b=u|8#GHsFa5fE*aDdVTN##cTf7dm-9jLp%v= zckOuqxHVG%fMMVlGsV;4o3WDWb;TY22fU>&OPJ)uM0HcJ94*t*cPY9Is0lTDhXPf4 zd-S#u0)7Op-VcBS(QU-!j=pPSrDaPC;=z#5DciY0382($2o|T)sVk)o-n_m0*Nm>% zUS87EGEy81XWD?%_X_~f0kp?Q&8NFB-z`>keqFSxcp;WM+%Yi?v2;_Zf7zBYpHRd? z-J|+%w~dQ%TYM1sCYN}o;{dQ_a?l(+^&L_GSlQ#;rkpMcmQ@PuLM9MkT9SH4 zINNa@*k;7!;NdqrS6=w6uNA9}JH^VqciNUo07Vr5b#ny(>uSEuh{?f|`}&&Bf6{x6 zzh{JkmH&==gz6DdAOMuj6<`_y82tX7{`KcS?YmC42RIe;FBzhF6sJVj%?AL$EubS< zrx6V&D#`laxmPC1-mvf%=>xXBUd!ES7Q+V|3f5`co_TBKRH)JKT xP`v^@Ul8keH(SQ%3*g_z0vpPi4rDFx%b!ov8zSwf0!jb?002ovPDHLkV1mD}zIgxu literal 0 HcmV?d00001 diff --git a/src/libs/vmisc/share/resources/icon/32x32/expand2-hover@2x.png b/src/libs/vmisc/share/resources/icon/32x32/expand2-hover@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..43299180b831c781733b3ca3e1bc3642bb18ff78 GIT binary patch literal 3241 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 aKAr*CW>e1i8;SV<0000%yK59t6on|tTZ zy)zpC2Y~xP7f=JHfXTY;BrpR+bRYpR4eZL;?>o(=#Ghv*3zQ}Rz5!bb9AlnlQ{wIC zq6JD50Iz}FLdTzDwK-G3yQS?Hp`2~=9M`|KV+`1)pf8mWcXlk;3!F8|y-bK}j#g|i z%)QJ$Y`VZlpkF~>CcW5*AJ7n&mwEKW*YHd{zyx|{eut8#T!c$ojSJxda1i*QkSjN_ z0UAT$M&JzY=F2-q(7OhV;-Lvc=#B!n@yvuCbPe#*ggO3m6UNauz$+7;Vq<`*31Qu5 z%Sq8m;15XS>npA=OFa}=l%k1;Z#dkw`(#2?@kM%`hy&0?iu5jclIOL$eV z@Ur6~uufEQ1ET^WipCQJO=xUl?=$vakSZjFqrg3sy2BBY&qNKV05?v*N#C!=peDml}7iMw^(-|)Tk(6v6} z_fNRDVpIr$yV$gZqtH6ie54aG{tps?0N z0^H(;wE@F%LE4Ls5pV)`VYH-boZoa78Q~rSnqz6gxPrc1SeY=VAucagCiG~C%Zr5x zLn_MJKzlu51PmWVw;%#e2s3_%Wn+n-e+A`vqmY{aXTFfXjCX_VgW&l0$5$*+X zZ_pMWN3VvsEc&q#9|8M(7zNH__s@~44+i`)%w17^{Um{L;4si@Rh5wlkrS{I~!B002ovPDHLkV1h@_loS8} literal 0 HcmV?d00001 diff --git a/src/libs/vmisc/share/resources/icon/32x32/rotate-bottom-left-disabled.png b/src/libs/vmisc/share/resources/icon/32x32/rotate-bottom-left-disabled.png new file mode 100644 index 0000000000000000000000000000000000000000..df4cf3d3a2deb924abca024eaa2b031d75ea3e0e GIT binary patch literal 3542 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 literal 0 HcmV?d00001 diff --git a/src/libs/vmisc/share/resources/icon/32x32/rotate-bottom-left-hover.png b/src/libs/vmisc/share/resources/icon/32x32/rotate-bottom-left-hover.png new file mode 100644 index 0000000000000000000000000000000000000000..3bc050adc671fcf7d3ae6ea4bc325b10bc74d839 GIT binary patch literal 1233 zcmV;?1TOoDP)?I<#l%_^)QngKK ze6elXwCYoBqA^X>hhEYLn^Y4YdV4W_vFYW3+L+jf)?S2E8<8Sd0=61Kfdv*=mAyg1aYdy6WO8P{Gw1vNXU;eC&A{|11U|k+pSuGggcneDIre{OIFlkk0bAyh{Jk+^ zbAjx(>oH*2T#!ErC@ov4n(YOb-)X8=oCY_9J$fhyoAaPdwBNc*`FcxBC=B9_+`sC050V)06U&VzOS z7}l@*RS03shJZ!D*X0{>d8DpjeA=gikV356RW_)*1b|dZHG4dsoA%EHI&cD@{g**5 zwM634ftW)GaQm_%3ram+A)xK29yEs>6XB1Ks4GAaC>a_W+6QdE6A@~vQoEzDZFkEP zYESE`Dc`4+;7rMNpdCo2*h&GELbuyVCSs)@=t=W&UDLGwL?SU`0gKaY3cz8?g;q;R z;5=5W90uM@cQR9Qm>Ah47%p9n)yp@|zhqaRzJKRL zuk@<@ejD&y1|k5C0H2tqdDFdOsvU0tSU(z3c}WoyB~|`W6ne1OC5QT=viq;70P27~ zti+Uxy#QP4E1O&a&HqnBL;{uZc>E>cTVN;?0oab;xOi$<1eZ8R0{6L0A%rYm;R{E4 zhn<&Ot_ol+unBN8u{`$z)V@{ZE2;A9zGCCz*`s}4z>l?AwE@kU2s>{Bvg^(LQV8h z2{n3cBp&VQ7#X(hB2?}TY7XhR!IuTRLXht`L( za`H{ZG&4A9Fj8>l`>xh+-ac2BVtza$2SNy^QtA-!Jb=4Eqx_iw#Z|sYc8(*GlV=(Z zr|uMjI0B(Lq+5uI_)5f*;re&aW}W`3&uyF9fek?O?dI|qfse3`Wj7%20b_hVHh)?f zH(XW>g#V4k4)17ESDHq%?5l9U|(+M98JJ(!!T}o&`HOClo*03U()=p00000NkvXXu0mjf+_gVs literal 0 HcmV?d00001 diff --git a/src/libs/vmisc/share/resources/icon/32x32/rotate-bottom-left-hover@2x.png b/src/libs/vmisc/share/resources/icon/32x32/rotate-bottom-left-hover@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..41f4161c4ee79ac32b4191c0b1f478c026952cef GIT binary patch literal 2555 zcmV%dvH|M8Nh${v7788$%bq;4@nj_8?u68OL!<)6p&Wb5#I&I zwxf1Lr&C)|`>>Y!n6~3g$C+BE*r}~$)bU+LTSpLIfT;`*t0(~k@*p80A&`(ao9y2H zaap{3_ugcmS+&2JJG19;&;8DK&-w0mzH=5*rx2(C1dcmp|%XzqWp%;$MXlEc9*rnSt_+0e6L&Y z3tWckDV|74;xsds4Jbt&`e(WA#S18{$qP$XQ5>(~?aww3zV*9iy*&s5ML>VTl4c0A z5s(Ko1J+2!ioZ;!@*6HCp3Vc5&nW1V9g3~-KfU_r%>|xGQr@&+sJ`X^-vpjZrR`MF zPJ#7kR&eJo@gPb9f+&o{-j*1WMf4SU?N1d7C7z;A);v<08Oc?=YK z6P@>F%)GMfhyCrL&kTwlK{bq7K|uA=_F&)>%)O-qOO8a{lQDLzX>I+&Df-NZr*AcU zNWoN@zP3@qr%mPP&nr$!stF_>s}-|!&CR!vg~*Ny=8O`CW`U5>_YKmMQTnAZnB2MDrk{r$ZrqC96%c<~U+z)jbe9T9Uv5Jc+t^ zN{f3A@hjj-ZDOz|$Ut{6MhIJh#p^1M838t6L2}|5vI^)%wGkW7v!Mb1ps<6powo1GK^%GfbQa&b$C}1OS z_Ko)(9%auP?LufI9M)GXD9v?Wd4Ele1BEK=w#3B*;OO#4{m)!-*EvRIGH?KRFwsU6 zT?sY;*G3YpANAubku2pi3VH;ADC<{Hn%i9HwN5(l&Pjb6mB9CbPtUYx7H)e`n^=5r z<zY}>zfjLGj7y@c!GG#A-h8N$);&Ej-lAj?{WC5v zZM^IkhQVJS@GkH=sx==s=6JlJe-uCv1?Suq71rw>n`Y#S16+-|q2nK~O-OtM1W{Lb z%e9%kCkFBZi##ooMU2`GlVqW=ERcV|;St>q%(4^hp18<}H zB8DUr4Fae~oyTWs_}~=o>Kv=b9~&wVC803SlY=EkYO~~#*HItWx<)@OPH9I#1UX0a z&s$j`IZC9@k00qPG8hd$)WaRG12HkaWEIee8pK$p&D^)?1fGCxsMzN)bbUR3XWq1h z#hqp6I~=k@;q>qb1_GO5sDKFN)ANg}7rNi+KGyF$*$@+bm4Q1@zhY65+hi30ume~L zxV4$}Z+2N+Q>-JU)p-L5u_s3bQE<4YIGA>EaaZLm*DY|z_X~wcj7)GV}yV@PuqYpec^Y0J%u?pRdk7x z;EuUdM<^KX8|oV!7&;Xk-1}BX>(la=fuW*Ozx*F>K(d28cGjc}& zm;uzMwB@D2&|uB^frr}bCwF=L&T*BH33@w*cW-#$vsx1+>;`71D=It*h#9*(*LU*z z!}adHn~n|kpEzBgrf0=XSK~labKRiXoDw0mDPY82H0OyTxT<7|yf)^otWc)RnbaX$ zMGxr=$s4fm?T+0q-*uqYq_`XSHn1xdBWT2*QIFQl)@BZOhv{w%(tfB%XxeqsIXWB` zT;;hT(IU19f{@lwS7=n(^3s}5%CY32#BzNaXhf6hd_ z;1Al2OerA3-+)^axxrN>W3O|1&9nj{ zybFAb6ap7{l~T*y8yCb3>+q;j|K7Sofx30=W;SIPFvqOjiTa<a@S*QoF(lYP& zzkhtgj^~@NY1%eu_9Aab-BU1SBLC>ri5gTX0IGl$KmgSR4H8E$|r7QtQ^*%$J`k{{Y;qm^-cf7XaYQ z7;~qEi2?v!;8Osg4;+~YK)2fkcp5NwdXZ(BZ5;r(5n=9hw^ab(YycN>G1imK# z41nWp06a-B|631vo||L<;L0A%ovx<>7~nlnkRA$rNS!Qh8(bJ;I;z?a%$ZfytyII& z*mW2cMKNHwDqo!P4T#7&5xHuNnTjUx6vh_*?|nZ|Q0ryRDsZ%MBdWaUb^zcvaKbZh zvy%4}xR4647ywYS4|A%I>DfC4{(72v!v zCJz88@EKT)1~A6#171Zyi~@zmcYr%0(y0X~ci}Yfqaj3$)?6XS^y(#sow99+FTiZ< zfpHP|)iOXIm~V1w%>nOPGJgZiM}2~-L}Wj3t*q!&^GCpRR1pS1I>2S%0q`-hMg~AH z%d&s&4oLtbziS2@V0fE;8QAi%OK4qH6o$V)Etg-%3GeNkzvwS$PQFEEcbwbd>M`&@gQ+}wNi z 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 XjUIyA^>6f200000NkvXXu0mjfeV0a@ literal 0 HcmV?d00001 diff --git a/src/libs/vmisc/share/resources/icon/32x32/rotate-bottom-right-hover.png b/src/libs/vmisc/share/resources/icon/32x32/rotate-bottom-right-hover.png new file mode 100644 index 0000000000000000000000000000000000000000..31e49da46d8e1500642169ef79e2d84ba2a213c8 GIT binary patch literal 1206 zcmV;n1WEgeP)1uSGiA5kUfk_&ODPDYP;Q$7A<`y*(BLH`8bbq? zL`f7#ltwgO5+WM#hZui|Mhyi0;Uy6g2r54sjiT|Ef+iv$mm>EBT3RUB?Y4B6ote)c zvs;I<-O`lyP0nQZeD8POcjuh*owG1}m;+P|jrSnW#F_@$~%%C zcDck;?Te8TUy%}B`FXgdc1uN*%f#cuEnb=ut6noYvgzbP*>rND%v@d=P4-*BxV}l+ z-B9*=y$bjdAT(oCV)44k-C_%33y-`$?PgKssGCWxy~#Dw3;k{wFirDg;30q~KB|o8 z7kdJ!n5L`jiE{(?!LKgslAuIH{J^(&)4&qoU4Yv6DlU~*7Z&yu<(Vk)}poLy4aGIs5HgySB}J+8ah0Hsgx=dkU8ZRz>;!-aR=nFZW1R`JroOzzTciOPBC^Zl@eE1-EpFfMz3r(Tm6tZ2 zxL-CLp5HNgmZv}7#-TWvJ3w5L&-8QRV40C2I1nDlD4bkfA{Z_U6~JjXd9ZJwNZ0kO z4DlHN{z(R*iN0KbM333idL*8TU+tT4M+Wlp@)XWiYqKotUjVBWK1qJeQa07Ndw)Uoe-r+wRC z4EzDC2h^;(U}Amay!IzQ4Hxyp{Sf#XP&1bWsHrbE0Z?#gZ}ZKrixxn0Fc|zNU2@+w zP>EBgvjJ9YpVl#Xey~7kD(EV)Az(YsHXNTCV|HDTC9h7st{Ex-(0nw~{oB@NUoz*V z_V)I)^}ualEzWB+0M_i8)j4K*egU_i7k=OwcgNB#6+PjHi$^KV5%0Jhvv#gK>JuA) zFM-|Z#N7sd02Tw}-K(4Rd#bK_a*Yx<(Ms6vuNT6#ftCfU%1ZsE*<%f_=0zN%ovpFN zXRH4*dfIG&E~V5;k^YOJ0^SFn2k;haZ7=VeSF9ViACgL?9@vOz0Pb;>rs5~|wZ$4X zpDO5S?KN8pwDhZvb00VXya-@+ORkYES0>Ey`_i(VBjgr$ny%>SG_9Yuo-w|A^Mcpx z5&%mnRYc~U;}~~-oyr%HMj))`D3-l5)t*ul0{MTJw4I7}p4oq`RRkl;tBv>g3<1=I zgV(il2O_HLl9i%1XqvX%w(ZzJ;=Kl3*bHm{sM|J;DT_iD$y?Le1J~HG`go?X4g;%z zLw6#aLf<;@8c+kUtG&EU9;`T|KMQ^aN`S_1-Z(!|1a)8C zILWyu%LKB3Q#gOUMg*S#5?<;TZ%9ksV$xQ$;Y2OBUZY0b0Kf#GHEG~b%707*qoM6N<$g51D2MgRZ+ literal 0 HcmV?d00001 diff --git a/src/libs/vmisc/share/resources/icon/32x32/rotate-bottom-right-hover@2x.png b/src/libs/vmisc/share/resources/icon/32x32/rotate-bottom-right-hover@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..0db7fd538ab2ff995bcb25b0c450ae58137ba4f9 GIT binary patch literal 2440 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 literal 0 HcmV?d00001 diff --git a/src/libs/vmisc/share/resources/icon/32x32/rotate-bottom-right@2x.png b/src/libs/vmisc/share/resources/icon/32x32/rotate-bottom-right@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..671a38f998d92718506c0e037c1777a2bc735c5c GIT binary patch literal 1243 zcmV<11SI>3P)%OQ=;<7{`BqHM`a+38R;JD{rPn+GB8QA{0Rx+La&*8i_P% z5~d*+DhJghlNvM#8U+ge?_5Z&0+Uwg#X2$;+GwSYuFC-01dS22il12;L8>A5+sRg_StV$m31IEnk zK&Z`u83B@J0^>}I2e=w|M$%BI-GOlcuo76;<@p$}QIdkJnpxusECId;tOtJsRs`9u zTU-wD=K$~Y5#Qmrz=*H?L6`#LxM7}!#lU7sbG)qA9pQ}VkKu-QR`k={^q-glz#{t3 z`+n=e9RuwEpar}gAil%zfGhH})zmd$Be1$a{YAL*!C9Fl>yCDp=&uHj5+40D9~-DC z0E_?!B8ks+KB!%EN_-1=H=_6s_XC#?!WyuFf1mSX;VNK@q_eyJT6g4==#K%v6F>TC zjyG}&01I*3R+-{E>%pCkasY1LzgL0y4)+0wS17*2w}4jd4oK$xu^Jp;z$3Lf z0Jq;)G4FfvD9QPt4)H_4wi?BE__di;e<>GAb0o^W3CxG{10P+HoLOZzJ%ywiIYSO#FG)S6Pp!$hC zg-UgRq@|J;6fkjbfx5ruDO8FBB%LYgGhh#JP|`zzrXC2aH-S6*XCnF5c^K++6J&oW&+z!?E1mlx>HHzeC!7NTr}NiyOq+~q?o zyi}m>>t=R3PrGrfQ($$9qnu)8#`%^v&#n3cD^ zpec~tx9g?6lu&AGr+ z5riAC)jOa$h0n&_rv-Oz%Gxm#xCD3@I2lR2;m-eF4#->sB&`JA0Q~aq)9u`b+bKqY zn}A`U`(1q)wwc+d-nB9!|1|NUQzlOV!@kk)IRYfj1wIC@_x5N#*lcD8gKBn(`Y6t$ zbw@QXKO%Xj3E@=nFEl`q503yBhl>Bl0k~E9^9Bg=;h(^@MZ~8au%!WleAouAD=OY> zGCdyp-dzU_xY^8h$FVj!|I7F|AvO;I%VI>n*CMzc_^SazyKxsV6jS^(2P96o5cs13 zg40|ZTX;_gBu;1nTN@z!Mc{(6#P@VS$`Oy&Ao>LGCV@xWs18V+FrRqe?e$_Cx4=DE zh3GVMU5h9G$aopJC$n%qv~f9u4}e#JAIJgL|Fo#=!wVg-NdQ>qA=LRL+i2LSe ze!p@}odQvkqsd>^zM#(yQd1oeB{`Z*i5q}5G3-@|GScJAaG$@Xvqs0wEanYxG(`VE z!p!!V*#g{FX}Gt`HL=@?h0;EfO4fxrz-co(SxO@j@HZ}5r@YyKK!^YU002ovPDHLk FV1mL%OR@j} literal 0 HcmV?d00001 diff --git a/src/libs/vmisc/share/resources/icon/32x32/rotate-top-left-disabled.png b/src/libs/vmisc/share/resources/icon/32x32/rotate-top-left-disabled.png new file mode 100644 index 0000000000000000000000000000000000000000..95b5c2c1ab3198e02cf7590a59ebdfbae82bf000 GIT binary patch literal 3565 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 Hu0mjfI%ZW_ literal 0 HcmV?d00001 diff --git a/src/libs/vmisc/share/resources/icon/32x32/rotate-top-left-hover@2x.png b/src/libs/vmisc/share/resources/icon/32x32/rotate-top-left-hover@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..b14635d5d15ae773151af7fa7e68d216e7eccf37 GIT binary patch literal 2402 zcmV-o37z(dP)3gwgZV#)T__%=KvY#0EUO76H_=Ei2@0L75H_u6^SH)Mu75qA4rOljuA!U zfa#B?%TK;HN&foM47C&5fMKC@Vh)qCk>H2Gn6AR~$J4q0mt$I!hQ`HZPZ>;7hJ~ub zH@m81ffWcx95>QQe_R6+fixgqDU69TW3ottB$MPanI(DB{7fEPouhvC&xk1zudeWi z)LU)Cz*E3uh(KqcLax#6h4ZuT{4M)rS(cH?imdGU{U!CS9$++38?5tajDT+g9{>%A z`MVyNf%xSm#T5|ABuOT-Bu{>5IFG*hWp%X$s76eSfb^9E5`o2tUuF-m05Qyai)4y1 zxpHTu$WLyaL~^c0S!V&(0{;L8_-mjq9Pk8UaoR`(7F|gq)*NG!hK)_~egD;oY9|2v z6llcHE)mH!pa6ISF&A~oD@-CGIhG;W3A8u4FvpnaY<1IA=b-MGqXuYK=$Z7w`Pm}_ zbtwB}x9wbO%z<@P)Rs8Z)gE9a@TyW6ZU;z+dA%H1r*}hgu7#=J%f#!E89y)GHE?jO z%MxccOW;MqEtw>zB#{}a0SbesV27Vrze*`;u}sQ||6Rm@m2jEN6s z$f=_S`Yf@gh{hnFY_7BId-1YAM_vbVfc9`Zzy@UKRXp(9QA}Pq++mI}nFz2bg_Bdo zwoN-%RxkEb?EtdE<$%G!4ZVt-ZzeKlX^u5(VsfG+ksPM}ddy!J9xrXGa8K2#l7W29 zk{Ke6dcwlzvzf9et3EctJR+o4W$Z`{CdA!i^YYK?{TL782fG*!2b@Ffx!qXwmobdL ze^>_+0gZRIxNBWbugiA6wQ<|i6`jBkAQMQj6y|_V8=tocRT5@pKPu?;!hVw%2xC|M;J^dCj)Y>{}fkY^9xf z>ieqx-D+4N`{j<~yVsP@^s@s1{QS1wfX**xfLVw*z8eeH=DBYhuS?6dPJ7ulYwFSU zwy>`UW!tUwyI0lB^iyp|#3U_BVQ(C8R4tgdY9y1sli>tn)OB9BuljQ7&CJ~|T}bU3 z(&57Am2aGQw{F1xmumeD`Axtw#LC}|P#v&AEtvLL8q*#bVMiiWUFW{#yL4{vwb8qt zuMXC39dHJ*PZvPo^#j1s?5;Vv!E^4mhYRB9de5l4s(RhNnp6L}njRq1+lpA_JA<`r)B$P0 zH2zc0FzxZy!n#o7bCT5tIv|Q3% zL|woHpwdXYM)Lt%m4zYM@r<07(xAS{=an0)O6+>ZA0-6iyYP0(_xdsZ03v`gWIPyi zKrS#yDHuB^4NI&^li8^{Voy6=)T9>Ipqf5mVkJ<>-Q0m8j7)(?m4&>*L5!O_)Qc)k z_jzP{#o0u$*$;xiCNM!+~_K{7#XqUEakrfZdLt+iz>YH=}e$hdlb z*$ZUROXIurOfN{wYeN#^*LQLB2gk6)J>gVXal~2SWCi2$XVfWbJH|$dd@aa$IUBFwfH2) zjR%pp>k4G{0O^F;cAwD@q^X$3(4OUy3Ci?s!MBTdRiseHYMM3GQ1Jad+p*ab}3@~fn=RaC^B{nQt z^&uui7WX27KSFp%^PpMMdj(UE|DJr{&~;Tp=;r~UVnUqWXy$k@WS^`zdg4@NBl9*7 zSZY*{-eN**QO1!XvMAXj&}KkPh#*CCz#fbUSbrUHY}Kc9ZDsQjsVEI8V`8j%_obLm zY;15R4mV2x9+x*(hFdzfHz3kl-I%a2#XK}8(Q@#WIyWMqQv1)1NLXruh)APUWC5xz zD>>If;WMKT}e1?=_o_@ovP{7d7KS|Io1k5q{2B3hh;$S?S{w-cKDlFyBZA zO|_2Wn--tZAMl7;Sh>G7uKYm5ovBb=Vy`*+H=8;sDkB`&5we#3h@YX8 z%=q!&#}_?wXnJY(tOUJ8$8*tAHWEmPg;8y!+mG>44|F0$6Twqlof!DXg)bVj32eL% zac=iN%?kWG;5Sj!t4}FMYy9D8D%96Vh%-E;1H$$B?H7iri1WWvbiNb%KmDBl0wa7d U=)6yLSpWb407*qoM6N<$g04uWu>b%7 literal 0 HcmV?d00001 diff --git a/src/libs/vmisc/share/resources/icon/32x32/rotate-top-left.png b/src/libs/vmisc/share/resources/icon/32x32/rotate-top-left.png new file mode 100644 index 0000000000000000000000000000000000000000..561c8817a2868c166159481f8aa2a81d94d9ebe0 GIT binary patch literal 612 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 literal 0 HcmV?d00001 diff --git a/src/libs/vmisc/share/resources/icon/32x32/rotate-top-right-disabled@2x.png b/src/libs/vmisc/share/resources/icon/32x32/rotate-top-right-disabled@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..3f4fd1861e9ee6ec4ce69d92e4e1c8ef39408334 GIT binary patch literal 6309 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 be_!xF(mwjE7dlOAT!(wo6ecMiNl52&st?NQ|Pq zs85OsBs_>QL@_2j7@`pqjhGOLXhJYG3To5_gV6{WL8t-^loFt%UD~q!V7uGyY#p+5kj>g}{wF1-bw;-H@y26l5Y=2|E#N8N3Nd1e7eu&C_ySKB0;nKopc~tln~O zPEEZ&ba3nW+*psD2Ly!>x|Fgnn*iq*EF@WIssJ8(dG27t)|w&JEy^8YJZhTnF8{;j zycbow(5rk@ttcJ{)aymX6~5aaz13w{pTE?s47QsBuoOkfvu)eRBtT8w`&3okBc-eb z?(b}ldRmWlYxC*~;(p!Z2CznK`^2Xmw^xnPZ_|G@&XwP9c~2d4+yBih8S8>ivo;yq-LlT0Bn+Uuf#pfB^74(4AF48d`vTz&b#`c%&~M)w1%- zvLj%`eegNHQH%Ga*nm92W6^+02qcLfmeXF z**N3y9k2)B@M~>e)39v-U!hxF@Nj{%rfbIsWfhQyCX#jk_$0VnNiFMvV5}3swj}eH z727HQ8`BX05C-ajUBE9BaIXU#`|Mg!jQ%KR1XfK~fP*)I$0y(%1!y_ce~)8`51Co# znk$=QBB-kBwk+!m;2GyCN!{logpHI+!mzIc3sUw=Xo z-!CGONE=Y?$`x!sv9edqQS=GI7}YMpNE5KTL;0qh43LFQgC9drU- z;L(;JdbOH0MG=3AXLNN4>`T9e)NfzE3~&`_7W%ai9S7Xn&;4cU!b zE;^%KPKK^HlNbi1zfw)?I9D&%Y^&%|eZtw!ul_F+JE$XdlG4hQZbr`%@61MW2b&pt tlsiA0$sHVZ1f&kP{|i1Oxn;|We*u~C%w2;wLRkO+002ovPDHLkV1lD3KmPy# literal 0 HcmV?d00001 diff --git a/src/libs/vmisc/share/resources/icon/32x32/rotate-top-right-hover@2x.png b/src/libs/vmisc/share/resources/icon/32x32/rotate-top-right-hover@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..754361260dcb1ca1dfc103d065794c3e9b9505f9 GIT binary patch literal 2605 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 P00000NkvXXu0mjf{Akvc literal 0 HcmV?d00001 diff --git a/src/libs/vmisc/share/resources/icon/32x32/rotate-top-right.png b/src/libs/vmisc/share/resources/icon/32x32/rotate-top-right.png new file mode 100644 index 0000000000000000000000000000000000000000..aa5fb25decbf11973560b7a29dcb52d94f84fb72 GIT binary patch literal 606 zcmV-k0-^nhP){UqKL2A(&VwC?X;@{;kA-#@txEeQ}?GIoXLc|t$qaN%mC5(@1C5GbQ8F21vm-(=ow&CBUh{`I1aqn1&BYuX;Tr3$P{n~ z_}8%M-opXCkzhR8Ve5~lyhU?C`0dN6WF#;`{0V2c<@H_=DW_#3-zAvo+VoLS_ zw`x+dp0HhOfS7LuP&wfx;Aak?Qpj5wfZLq_;^xOHDOt_f-Om8L=?qW_^EKdn24FD* zP}c4{>PCHth@4<}Tc*D6FUxXc+e>Q%YEHYvu-kQQQd_`6b|Pw!LrgXuIpA>T2n+@T z2fQ+#woae6D#A>|vJ5Zd`U^NXF+ha4*a6^N>))^e;$92qH#+r!oQ4p*u^yfkMbWJh z8KQ86{ABeFD0Zyt3&2nhXKUB=2@oMxYMkFp+V>6My$`@@#rYHOy-(TqdKZjDL}r1P s3}?bqs`@!;yBh!zVg}e$)lWIQ|BJd=aD1F`y8r+H07*qoM6N<$f-8Fe5&!@I literal 0 HcmV?d00001 diff --git a/src/libs/vmisc/share/resources/icon/32x32/rotate-top-right@2x.png b/src/libs/vmisc/share/resources/icon/32x32/rotate-top-right@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..58458f5111e6775c318a897c81945a1d81db293f GIT binary patch literal 1252 zcmV$TZmOv7{`DA8Pge?ED$Q8G0W^iWjCl7g(%1gX`(`cdPscp zp`gfzAPUqT$|xd&=)tF63d2hlzDW;4SXOk=!Ae3ZD-)ZXi%vTK9`-(RI-E1JFKb^8 z{o#W-v)BHA>-+EZt-bd8mTdEXMo$r>l7=LGE6I|^B|Th(pAZNtfTRIQ&jRlQH^%>- z4-AF$6B2Vm5lOldcnxUxw1fExjUXdH(*3|5+6P~2%0w;j)h}I1IfDzkg1Mw$^?T!ZY11}IgF&Ai4 zz!Bgr;JTUm6xwDAekt%l!GU+O?ZC=P1OP;|bRDTE_-5M)EG|s|ps%DMpT^Drn~3MD z!UO=WiHF{~8U)?W_5zCv5dgRoI9P)qXR{N)O+^UsZViGNz%cM*4T4K*p9T}4Nr>x- z79mv!m(PvF`nw@{!{{xs5%@cV04>}b0|~ILFaer6`ylW`NC9Hrn}PA10!*fjCv{!G z_D*0GusiL&v+#ZcEC){Ipy`veKCR+BisU(Do7FQKH?X@c?|IMCG=u zcsyytVC8Y!W41Q|j{)DeT+TAFy+?L2?@in??;Ow}hnE7ohzcP;0e5BK=q39u)l^bH zi3%fGAgK>n0-UgYJj4A0wgPLj__wVj?Fu;}6L;XW?U5|O$M+l9pVhylOOmuJOn^W* zoa5ekiCPsg2PDYvr-(K;L#+R^*f_AJ&^e$JBn<*bOLf{F=YWAKQXo-omVh3hr*r`v zee`@*w%<0`2TYcp0sxZc5gm}I3P0N(go@q*w$A|fRN*_u>c$_Hy#{UX1zxYf^;+9s zH-1!-0_UAeJ-{K#J8kxr?c4s-hbjdCkkk+SNpz;TC>Zz^7<5a(m9GSCPg4A0O98{J z!9xuM0IXcwp`th4<^y9<@4Km+mD-%;R-z|^1EzPqE}x(ER0Pon-tRF(^Ylfc4u zL%)Gqq(B34 zz+9q()%_`NJw;R(Nc62yyA)`^_7srlDYoE-q`^at1pu)95%5ZiTb{T5OBx@wO@U@e z`VzQxmS#I_Ka`rgO9Z$W_yd^Rp~(brneDN(+_g@D2I5lL$_|&Ofvd9w4_zVvfbIRj z+ifmwu>E^h-mXZ2W=J{!+#dhjZTo>-d|fC&FYq!j8W&^p@pYR3q46)9EC0jk*-I_} O0000 \ No newline at end of file diff --git a/src/libs/vmisc/vvalentinasettings.cpp b/src/libs/vmisc/vvalentinasettings.cpp index 070658471..e2f1052ab 100644 --- a/src/libs/vmisc/vvalentinasettings.cpp +++ b/src/libs/vmisc/vvalentinasettings.cpp @@ -92,6 +92,8 @@ Q_GLOBAL_STATIC_WITH_ARGS(const QString, settingDockWidgetToolOptionsActive, (QLatin1String("dockWidget/toolOptionsActive"))) Q_GLOBAL_STATIC_WITH_ARGS(const QString, settingDockWidgetPatternMessagesActive, (QLatin1String("dockWidget/patternMessagesActive"))) +Q_GLOBAL_STATIC_WITH_ARGS(const QString, settingDockWidgetBackgroundImagesActive, + (QLatin1String("dockWidget/backgroundImagesActive"))) Q_GLOBAL_STATIC_WITH_ARGS(const QString, settingPatternMessagesFontSize, (QLatin1String("font/patternMessagesSize"))) Q_GLOBAL_STATIC_WITH_ARGS(const QString, settingSearchHistoryHistory, (QLatin1String("searchHistory/history"))) @@ -621,6 +623,24 @@ void VValentinaSettings::SetDockWidgetPatternMessagesActive(bool value) setValue(*settingDockWidgetPatternMessagesActive, value); } +//--------------------------------------------------------------------------------------------------------------------- +bool VValentinaSettings::IsDockWidgetBackgroundImagesActive() const +{ + return value(*settingDockWidgetBackgroundImagesActive, GetDefDockWidgetBackgroundImagesActive()).toBool(); +} + +//--------------------------------------------------------------------------------------------------------------------- +bool VValentinaSettings::GetDefDockWidgetBackgroundImagesActive() +{ + return false; +} + +//--------------------------------------------------------------------------------------------------------------------- +void VValentinaSettings::SetDockWidgetBackgroundImagesActive(bool value) +{ + setValue(*settingDockWidgetBackgroundImagesActive, value); +} + //--------------------------------------------------------------------------------------------------------------------- int VValentinaSettings::GetPatternMessageFontSize(int fontSizeDef) const { diff --git a/src/libs/vmisc/vvalentinasettings.h b/src/libs/vmisc/vvalentinasettings.h index e1c42d3ce..032dc29e8 100644 --- a/src/libs/vmisc/vvalentinasettings.h +++ b/src/libs/vmisc/vvalentinasettings.h @@ -153,6 +153,10 @@ public: static bool GetDefDockWidgetPatternMessagesActive(); void SetDockWidgetPatternMessagesActive(bool value); + bool IsDockWidgetBackgroundImagesActive() const; + static bool GetDefDockWidgetBackgroundImagesActive(); + void SetDockWidgetBackgroundImagesActive(bool value); + int GetPatternMessageFontSize(int fontSizeDef) const; static int GetDefMinPatternMessageFontSize(); static int GetDefMaxPatternMessageFontSize(); diff --git a/src/libs/vpropertyexplorer/plugins/vboolproperty.cpp b/src/libs/vpropertyexplorer/plugins/vboolproperty.cpp index 2e776c036..db7ada18e 100644 --- a/src/libs/vpropertyexplorer/plugins/vboolproperty.cpp +++ b/src/libs/vpropertyexplorer/plugins/vboolproperty.cpp @@ -20,61 +20,95 @@ #include "vboolproperty.h" +#include +#include #include #include #include "../vproperty_p.h" - -QVariant VPE::VBoolProperty::TrueText; -QVariant VPE::VBoolProperty::FalseText; - VPE::VBoolProperty::VBoolProperty(const QString& name) : VProperty(name, QVariant::Bool) { d_ptr->VariantValue.setValue(false); d_ptr->VariantValue.convert(QVariant::Bool); - - // I'm not sure, how Qt handles the translations... - if (TrueText.isNull()) - { - TrueText = tr("True"); - } - if (FalseText.isNull()) - { - FalseText = tr("False"); - } } //! Get the data how it should be displayed -QVariant VPE::VBoolProperty::data (int column, int role) const +auto VPE::VBoolProperty::data (int column, int role) const -> QVariant { - if (column == DPC_Data && (Qt::DisplayRole == role || Qt::EditRole == role)) + auto* tmpEditor = qobject_cast(VProperty::d_ptr->editor); + + if (column == DPC_Data && Qt::DisplayRole == role) { - return d_ptr->VariantValue.toBool() ? TrueText : FalseText; + return tmpEditor->checkState(); } - if (column == DPC_Data && Qt::CheckStateRole == role) + + if (column == DPC_Data && Qt::EditRole == role) { - return d_ptr->VariantValue.toBool() ? Qt::Checked : Qt::Unchecked; + return VProperty::d_ptr->VariantValue; } - else - return VProperty::data(column, role); + + return VProperty::data(column, role); } -bool VPE::VBoolProperty::setData(const QVariant &data, int role) +auto VPE::VBoolProperty::createEditor(QWidget *parent, const QStyleOptionViewItem &options, + const QAbstractItemDelegate *delegate) -> QWidget * { - if (Qt::CheckStateRole == role) + Q_UNUSED(options) + Q_UNUSED(delegate) + auto* tmpEditor = new QCheckBox(parent); + tmpEditor->setCheckState(d_ptr->VariantValue.toBool() ? Qt::Checked : Qt::Unchecked); + connect(tmpEditor, &QCheckBox::stateChanged, this, &VBoolProperty::StateChanged); + + VProperty::d_ptr->editor = tmpEditor; + return VProperty::d_ptr->editor; +} + +auto VPE::VBoolProperty::setEditorData(QWidget *editor) -> bool +{ + if (!editor) { - d_ptr->VariantValue = (Qt::Checked == static_cast(data.toInt())); + return false; + } + + auto* tmpEditor = qobject_cast(editor); + if (tmpEditor) + { + tmpEditor->blockSignals(true); + tmpEditor->setCheckState(d_ptr->VariantValue.toBool() ? Qt::Checked : Qt::Unchecked); + tmpEditor->blockSignals(false); return true; } return false; } +auto VPE::VBoolProperty::getEditorData(const QWidget *editor) const -> QVariant +{ + const auto* tmpEditor = qobject_cast(editor); + if (tmpEditor) + { + return tmpEditor->checkState() == Qt::Checked ? Qt::Checked : Qt::Unchecked; + } + + return {0}; +} + +void VPE::VBoolProperty::setValue(const QVariant &value) +{ + VProperty::d_ptr->VariantValue = value; + VProperty::d_ptr->VariantValue.convert(QVariant::Bool); + + if (VProperty::d_ptr->editor != nullptr) + { + setEditorData(VProperty::d_ptr->editor); + } +} + //! Returns item flags -Qt::ItemFlags VPE::VBoolProperty::flags(int column) const +auto VPE::VBoolProperty::flags(int column) const -> Qt::ItemFlags { if (column == DPC_Data) { @@ -84,12 +118,18 @@ Qt::ItemFlags VPE::VBoolProperty::flags(int column) const return VProperty::flags(column); } -QString VPE::VBoolProperty::type() const +auto VPE::VBoolProperty::type() const -> QString { return "bool"; } -VPE::VProperty *VPE::VBoolProperty::clone(bool include_children, VProperty *container) const +auto VPE::VBoolProperty::clone(bool include_children, VProperty *container) const -> VPE::VProperty * { return VProperty::clone(include_children, container ? container : new VBoolProperty(getName())); } + +void VPE::VBoolProperty::StateChanged() +{ + auto *event = new UserChangeEvent(); + QCoreApplication::postEvent ( VProperty::d_ptr->editor, event ); +} diff --git a/src/libs/vpropertyexplorer/plugins/vboolproperty.h b/src/libs/vpropertyexplorer/plugins/vboolproperty.h index a643f84af..8fcb95df3 100644 --- a/src/libs/vpropertyexplorer/plugins/vboolproperty.h +++ b/src/libs/vpropertyexplorer/plugins/vboolproperty.h @@ -48,36 +48,43 @@ public: explicit VBoolProperty(const QString& name); //! Destructor - virtual ~VBoolProperty() override {} + ~VBoolProperty() override = default; //! Get the data how it should be displayed - virtual QVariant data (int column = DPC_Name, int role = Qt::DisplayRole) const override; + auto data(int column = DPC_Name, int role = Qt::DisplayRole) const -> QVariant override; - //! This is used by the model to set the data - //! \param data The data to set - //! \param role The role. Default is Qt::EditRole - //! \return Returns true, if the data was changed, false if not. - virtual bool setData (const QVariant& data, int role = Qt::EditRole) override; + //! Returns an editor widget, or NULL if it doesn't supply one + //! \param parent The widget to which the editor will be added as a child + //! \options Render options + //! \delegate A pointer to the QAbstractItemDelegate requesting the editor. This can be used to connect signals and + //! slots. + auto createEditor(QWidget* parent, const QStyleOptionViewItem& options, + const QAbstractItemDelegate* delegate) -> QWidget* override; + + //! Sets the property's data to the editor (returns false, if the standard delegate should do that) + auto setEditorData(QWidget* editor) -> bool override; + + //! Gets the data from the widget + auto getEditorData(const QWidget* editor) const -> QVariant override; + + //! Sets the value of the property + void setValue(const QVariant& value) override; //! Returns item flags - virtual Qt::ItemFlags flags(int column = DPC_Name) const override; + auto flags(int column = DPC_Name) const -> Qt::ItemFlags override; //! Returns a string containing the type of the property - virtual QString type() const override; + auto type() const -> QString override; //! Clones this property //! \param include_children Indicates whether to also clone the children //! \param container If a property is being passed here, no new VProperty is being created but instead it is tried //! to fill all the data into container. This can also be used when subclassing this function. //! \return Returns the newly created property (or container, if it was not NULL) - virtual VProperty* clone(bool include_children = true, VProperty* container = NULL) const override; + auto clone(bool include_children = true, VProperty* container = NULL) const -> VProperty* override; -protected: - //! The (translatable) text displayed when the property is set to true (default: "True") - static QVariant TrueText; - - //! The (translatable) text displayed when the property is set to false (default: "False") - static QVariant FalseText; +public slots: + void StateChanged(); private: Q_DISABLE_COPY(VBoolProperty) @@ -85,6 +92,6 @@ private: QT_WARNING_POP -} +} // namespace VPE #endif // VBOOLPROPERTY_H diff --git a/src/libs/vtools/tools/backgroundimage/vbackgroundimagecontrols.cpp b/src/libs/vtools/tools/backgroundimage/vbackgroundimagecontrols.cpp new file mode 100644 index 000000000..76c89f86a --- /dev/null +++ b/src/libs/vtools/tools/backgroundimage/vbackgroundimagecontrols.cpp @@ -0,0 +1,1895 @@ +/************************************************************************ + ** + ** @file vbackgroundimagecontrols.cpp + ** @author Roman Telezhynskyi + ** @date 17 1, 2022 + ** + ** @brief + ** @copyright + ** This source code is part of the Valentina project, a pattern making + ** program, whose allow create and modeling patterns of clothing. + ** Copyright (C) 2022 Valentina project + ** All Rights Reserved. + ** + ** Valentina 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, either version 3 of the License, or + ** (at your option) any later version. + ** + ** Valentina 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 Valentina. If not, see . + ** + *************************************************************************/ +#include "vbackgroundimagecontrols.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../ifc/xml/vabstractpattern.h" +#include "vbackgroundimageitem.h" +#include "../vwidgets/global.h" +#include "../vmisc/vabstractvalapplication.h" +#include "../../undocommands/image/scalebackgroundimage.h" +#include "../../undocommands/image/rotatebackgroundimage.h" +#include "../vmisc/vmath.h" +#include "../vwidgets/vmaingraphicsview.h" + +extern auto qt_regionToPath(const QRegion ®ion) -> QPainterPath; + +namespace +{ +//--------------------------------------------------------------------------------------------------------------------- +auto PixmapToPainterPath(const QPixmap &pixmap) -> QPainterPath +{ + if (not pixmap.isNull()) + { + QBitmap mask = pixmap.mask(); + if (not mask.isNull()) + { + return qt_regionToPath(QRegion(mask)); + } + } + + return {}; +} + +//--------------------------------------------------------------------------------------------------------------------- +auto RectTopPoint(const QRectF &rect) -> QPointF +{ + QLineF edge(rect.topLeft(), rect.topRight()); + edge.setLength(edge.length()/2.); + return edge.p2(); +} + +//--------------------------------------------------------------------------------------------------------------------- +auto RectRightPoint(const QRectF &rect) -> QPointF +{ + QLineF edge(rect.topRight(), rect.bottomRight()); + edge.setLength(edge.length()/2.); + return edge.p2(); +} + +//--------------------------------------------------------------------------------------------------------------------- +auto RectBottomPoint(const QRectF &rect) -> QPointF +{ + QLineF edge(rect.bottomLeft(), rect.bottomRight()); + edge.setLength(edge.length()/2.); + return edge.p2(); +} + +//--------------------------------------------------------------------------------------------------------------------- +auto RectLeftPoint(const QRectF &rect) -> QPointF +{ + QLineF edge(rect.bottomLeft(), rect.topLeft()); + edge.setLength(edge.length()/2.); + return edge.p2(); +} +} // namespace + +//--------------------------------------------------------------------------------------------------------------------- +VBackgroundImageControls::VBackgroundImageControls(VAbstractPattern *doc, QGraphicsItem * parent) + : QGraphicsObject(parent), + m_doc(doc) +{ + SCASSERT(doc != nullptr) + setVisible(false); + setFlag(QGraphicsItem::ItemIgnoresTransformations); + setZValue(100); + setAcceptHoverEvents(true); + SetItemOverrideCursor(this, cursorArrowOpenHand, 1, 1); + + InitPixmaps(); + +#if QT_VERSION >= QT_VERSION_CHECK(5, 6, 0) + QGuiApplication *guiApp = qGuiApp; + if (guiApp != nullptr) + { + connect(guiApp, &QGuiApplication::primaryScreenChanged, this, &VBackgroundImageControls::ScreenChanged); + } +#endif +} + + +//--------------------------------------------------------------------------------------------------------------------- +void VBackgroundImageControls::ActivateControls(const QUuid &id) +{ + if (id.isNull()) + { + m_id = id; + emit ActiveImageChanged(m_id); + setVisible(false); + } + else if (m_id == id) + { + prepareGeometryChange(); + if (m_tranformationType == BITransformationType::Scale) + { + m_tranformationType = BITransformationType::Rotate; + ShowOrigin(m_image.BoundingRect().center()); + } + else if (m_tranformationType == BITransformationType::Rotate) + { + m_tranformationType = BITransformationType::Scale; + m_showOrigin = false; + } + } + else + { + prepareGeometryChange(); + m_id = id; + m_image = m_doc->GetBackgroundImage(m_id); + m_tranformationType = BITransformationType::Scale; + m_showOrigin = false; + emit ActiveImageChanged(m_id); + setVisible(true); + } +} + +//--------------------------------------------------------------------------------------------------------------------- +void VBackgroundImageControls::DeactivateControls(QGraphicsItem *item) +{ + if (m_transformationApplied) + { + m_transformationApplied = false; + return; + } + + if (item == nullptr) + { + setVisible(false); + m_id = QUuid(); + emit ActiveImageChanged(m_id); + return; + } + + if (item->type() == type() || + item->type() == UserType + static_cast(Tool::BackgroundPixmapImage) || + item->type() == UserType + static_cast(Tool::BackgroundSVGImage)) + { + return; + } + + setVisible(false); + m_id = QUuid(); + emit ActiveImageChanged(m_id); +} + +//--------------------------------------------------------------------------------------------------------------------- +void VBackgroundImageControls::UpdateControls() +{ + if (not isVisible() || m_id.isNull() || m_tranformationType == BITransformationType::Unknown) + { + return; + } + + prepareGeometryChange(); + m_image = m_doc->GetBackgroundImage(m_id); +} + +//--------------------------------------------------------------------------------------------------------------------- +auto VBackgroundImageControls::boundingRect() const -> QRectF +{ + QRectF boundingRect; + + auto HandlerBoundingRect = [this, &boundingRect](BIHandleCorner corner, BIHandleCornerType type, QPointF pos) + { + QPixmap handler = HandlerPixmap(m_handleCornerHover == corner, type); + boundingRect = boundingRect.united(QRectF(pos, handler.size() / handler.devicePixelRatio())); + }; + + if (m_tranformationType == BITransformationType::Scale) + { + HandlerBoundingRect(BIHandleCorner::TopLeft, BIHandleCornerType::ScaleTopLeftBottomRight, + TopLeftHandlerPosition()); + HandlerBoundingRect(BIHandleCorner::Top, BIHandleCornerType::ScaleTopBottom, + TopHandlerPosition()); + HandlerBoundingRect(BIHandleCorner::TopRight, BIHandleCornerType::ScaleTopRightBottomLeft, + TopRightHandlerPosition()); + HandlerBoundingRect(BIHandleCorner::Right, BIHandleCornerType::ScaleRightLeft, + RightHandlerPosition()); + HandlerBoundingRect(BIHandleCorner::BottomRight, BIHandleCornerType::ScaleTopLeftBottomRight, + BottomRightHandlerPosition()); + HandlerBoundingRect(BIHandleCorner::Bottom, BIHandleCornerType::ScaleTopBottom, + BottomHandlerPosition()); + HandlerBoundingRect(BIHandleCorner::BottomLeft, BIHandleCornerType::ScaleTopRightBottomLeft, + BottomLeftHandlerPosition()); + HandlerBoundingRect(BIHandleCorner::Left, BIHandleCornerType::ScaleRightLeft, + LeftHandlerPosition()); + + } + else if (m_tranformationType == BITransformationType::Rotate) + { + HandlerBoundingRect(BIHandleCorner::TopLeft, BIHandleCornerType::RotateTopLeft, TopLeftHandlerPosition()); + HandlerBoundingRect(BIHandleCorner::TopRight, BIHandleCornerType::RotateTopRight, TopRightHandlerPosition()); + HandlerBoundingRect(BIHandleCorner::BottomRight, BIHandleCornerType::RotateBottomRight, + BottomRightHandlerPosition()); + HandlerBoundingRect(BIHandleCorner::BottomLeft, BIHandleCornerType::RotateBottomLeft, + BottomLeftHandlerPosition()); + } + + boundingRect = boundingRect.united(OriginPath().boundingRect()); + + return boundingRect; +} + +//--------------------------------------------------------------------------------------------------------------------- +auto VBackgroundImageControls::shape() const -> QPainterPath +{ + QPainterPath shape; + shape.addPath(Handles()); + shape.addPath(OriginPath()); + return shape; +} + +//--------------------------------------------------------------------------------------------------------------------- +void VBackgroundImageControls::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) +{ + Q_UNUSED(option) + Q_UNUSED(widget) + + if (m_controlsVisible) + { + if (m_tranformationType == BITransformationType::Scale) + { + painter->drawPixmap(TopLeftHandlerPosition(), + HandlerPixmap(m_handleCornerHover == BIHandleCorner::TopLeft, + BIHandleCornerType::ScaleTopLeftBottomRight)); + painter->drawPixmap(TopHandlerPosition(), + HandlerPixmap(m_handleCornerHover == BIHandleCorner::Top, + BIHandleCornerType::ScaleTopBottom)); + painter->drawPixmap(TopRightHandlerPosition(), + HandlerPixmap(m_handleCornerHover == BIHandleCorner::TopRight, + BIHandleCornerType::ScaleTopRightBottomLeft)); + painter->drawPixmap(RightHandlerPosition(), + HandlerPixmap(m_handleCornerHover == BIHandleCorner::Right, + BIHandleCornerType::ScaleRightLeft)); + painter->drawPixmap(BottomRightHandlerPosition(), + HandlerPixmap(m_handleCornerHover == BIHandleCorner::BottomRight, + BIHandleCornerType::ScaleTopLeftBottomRight)); + painter->drawPixmap(BottomHandlerPosition(), + HandlerPixmap(m_handleCornerHover == BIHandleCorner::Bottom, + BIHandleCornerType::ScaleTopBottom)); + painter->drawPixmap(BottomLeftHandlerPosition(), + HandlerPixmap(m_handleCornerHover == BIHandleCorner::BottomLeft, + BIHandleCornerType::ScaleTopRightBottomLeft)); + painter->drawPixmap(LeftHandlerPosition(), + HandlerPixmap(m_handleCornerHover == BIHandleCorner::Left, + BIHandleCornerType::ScaleRightLeft)); + } + else if (m_tranformationType == BITransformationType::Rotate) + { + painter->drawPixmap(TopLeftHandlerPosition(), + HandlerPixmap(m_handleCornerHover == BIHandleCorner::TopLeft, + BIHandleCornerType::RotateTopLeft)); + + painter->drawPixmap(TopRightHandlerPosition(), + HandlerPixmap(m_handleCornerHover == BIHandleCorner::TopRight, + BIHandleCornerType::RotateTopRight)); + + painter->drawPixmap(BottomRightHandlerPosition(), + HandlerPixmap(m_handleCornerHover == BIHandleCorner::BottomRight, + BIHandleCornerType::RotateBottomRight)); + + painter->drawPixmap(BottomLeftHandlerPosition(), + HandlerPixmap(m_handleCornerHover == BIHandleCorner::BottomLeft, + BIHandleCornerType::RotateBottomLeft)); + } + } + + const qreal sceneScale = SceneScale(scene()); + painter->save(); + QPen pen = painter->pen(); + pen.setStyle(Qt::DashLine); + painter->setPen(pen); + + QRectF rect = m_image.BoundingRect(); + rect = QRectF(rect.topLeft()*sceneScale, rect.bottomRight()*sceneScale); + + painter->drawRect(rect.adjusted(-pen.width(), -pen.width(), pen.width(), pen.width())); + painter->restore(); + + if (m_showOrigin) + { + painter->save(); + painter->setBrush(pen.brush()); + painter->drawPath(OriginCircle1()); + painter->restore(); + + painter->save(); + painter->setBrush(QBrush()); + painter->drawPath(OriginCircle2()); + painter->restore(); + } +} + +//--------------------------------------------------------------------------------------------------------------------- +void VBackgroundImageControls::mousePressEvent(QGraphicsSceneMouseEvent *event) +{ + if(event->button() == Qt::LeftButton && event->type() != QEvent::GraphicsSceneMouseDoubleClick) + { + m_transformationApplied = true; + m_controlsVisible = false; + m_handleCornerHover = SelectedHandleCorner(event->pos()); + m_rotationStartPoint = event->pos(); + + if (m_handleCornerHover != BIHandleCorner::Invalid) + { + if (not m_image.Hold()) + { + SetItemOverrideCursor(this, cursorArrowCloseHand, 1, 1); + } + else + { + setCursor(VAbstractValApplication::VApp()->getSceneView()->viewport()->cursor()); + } + } + else + { + setCursor(VAbstractValApplication::VApp()->getSceneView()->viewport()->cursor()); + } + + const qreal sceneScale = SceneScale(scene()); + m_imageBoundingRect = m_image.BoundingRect(); + m_imageScreenBoundingRect = QRectF(m_imageBoundingRect.topLeft()*sceneScale, + QSizeF(m_imageBoundingRect.width()*sceneScale, + m_imageBoundingRect.height()*sceneScale)); + + m_originalMatrix = m_image.Matrix(); + + switch(m_handleCornerHover) + { + case BIHandleCorner::TopLeft: + m_scaleDiff = event->pos() - m_imageScreenBoundingRect.topLeft(); + break; + case BIHandleCorner::Top: + m_scaleDiff = event->pos() - RectTopPoint(m_imageScreenBoundingRect); + break; + case BIHandleCorner::TopRight: + m_scaleDiff = event->pos() - m_imageScreenBoundingRect.topRight(); + break; + case BIHandleCorner::Right: + m_scaleDiff = event->pos() - RectRightPoint(m_imageScreenBoundingRect); + break; + case BIHandleCorner::BottomRight: + m_scaleDiff = event->pos() - m_imageScreenBoundingRect.bottomRight(); + break; + case BIHandleCorner::Bottom: + m_scaleDiff = event->pos() - RectBottomPoint(m_imageScreenBoundingRect); + break; + case BIHandleCorner::BottomLeft: + m_scaleDiff = event->pos() - m_imageScreenBoundingRect.bottomLeft(); + break; + case BIHandleCorner::Left: + m_scaleDiff = event->pos() - RectLeftPoint(m_imageScreenBoundingRect); + break; + case BIHandleCorner::Invalid: + default: + event->ignore(); + break; + } + + event->accept(); + } + else + { + QGraphicsObject::mousePressEvent(event); + } +} + +//--------------------------------------------------------------------------------------------------------------------- +void VBackgroundImageControls::mouseMoveEvent(QGraphicsSceneMouseEvent *event) +{ + if (m_tranformationType == BITransformationType::Scale) + { + ScaleImage(event); + } + else if (m_tranformationType == BITransformationType::Rotate) + { + RotateImage(event); + } + QGraphicsObject::mouseMoveEvent(event); +} + +//--------------------------------------------------------------------------------------------------------------------- +void VBackgroundImageControls::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) +{ + if(event->button() == Qt::LeftButton) + { + if (SelectedHandleCorner(event->pos()) != BIHandleCorner::Invalid) + { + if (not m_image.Hold()) + { + SetItemOverrideCursor(this, cursorArrowOpenHand, 1, 1); + } + else + { + setCursor(VAbstractValApplication::VApp()->getSceneView()->viewport()->cursor()); + } + } + else + { + setCursor(VAbstractValApplication::VApp()->getSceneView()->viewport()->cursor()); + } + + m_controlsVisible = true; + m_allowChangeMerge = false; + + if (m_tranformationType == BITransformationType::Scale) + { + m_showOrigin = false; + } + else if (m_tranformationType == BITransformationType::Rotate) + { + ShowOrigin(m_image.BoundingRect().center()); + } + + update(); + } + else + { + QGraphicsObject::mouseReleaseEvent(event); + } +} + +//--------------------------------------------------------------------------------------------------------------------- +void VBackgroundImageControls::hoverEnterEvent(QGraphicsSceneHoverEvent *event) +{ + m_handleCornerHover = SelectedHandleCorner(event->pos()); + + if (m_handleCornerHover != BIHandleCorner::Invalid) + { + if (not m_image.Hold()) + { + SetItemOverrideCursor(this, cursorArrowOpenHand, 1, 1); + } + else + { + setCursor(VAbstractValApplication::VApp()->getSceneView()->viewport()->cursor()); + } + } + else + { + setCursor(VAbstractValApplication::VApp()->getSceneView()->viewport()->cursor()); + } + QGraphicsObject::hoverEnterEvent(event); +} + +//--------------------------------------------------------------------------------------------------------------------- +void VBackgroundImageControls::hoverMoveEvent(QGraphicsSceneHoverEvent *event) +{ + m_handleCornerHover = SelectedHandleCorner(event->pos()); + if (m_handleCornerHover != BIHandleCorner::Invalid) + { + if (not m_image.Hold()) + { + SetItemOverrideCursor(this, cursorArrowOpenHand, 1, 1); + } + else + { + setCursor(VAbstractValApplication::VApp()->getSceneView()->viewport()->cursor()); + } + } + else + { + setCursor(VAbstractValApplication::VApp()->getSceneView()->viewport()->cursor()); + } + QGraphicsObject::hoverMoveEvent(event); +} + +//--------------------------------------------------------------------------------------------------------------------- +void VBackgroundImageControls::hoverLeaveEvent(QGraphicsSceneHoverEvent *event) +{ + m_handleCornerHover = BIHandleCorner::Invalid; + + if (SelectedHandleCorner(event->pos()) != BIHandleCorner::Invalid) + { + if (not m_image.Hold()) + { + SetItemOverrideCursor(this, cursorArrowOpenHand, 1, 1); + } + else + { + setCursor(VAbstractValApplication::VApp()->getSceneView()->viewport()->cursor()); + } + } + else + { + setCursor(VAbstractValApplication::VApp()->getSceneView()->viewport()->cursor()); + } + QGraphicsObject::hoverLeaveEvent(event); +} + +//--------------------------------------------------------------------------------------------------------------------- +void VBackgroundImageControls::ScreenChanged() +{ + prepareGeometryChange(); + InitPixmaps(); +} + +//--------------------------------------------------------------------------------------------------------------------- +auto VBackgroundImageControls::Id() const -> const QUuid & +{ + return m_id; +} + +//--------------------------------------------------------------------------------------------------------------------- +void VBackgroundImageControls::InitPixmaps() +{ + m_handlePixmaps.clear(); + m_handleHoverPixmaps.clear(); + m_handleDisabledPixmaps.clear(); + m_handlePaths.clear(); + + auto InitPixmap = [this](BIHandleCornerType type, const QString &fileName) + { + const QFileInfo fileInfo(fileName); + const QString imageName = fileInfo.baseName(); + + const QString fileNameHover = QStringLiteral("%1/%2-hover.%3") + .arg(fileInfo.absolutePath(), imageName, fileInfo.suffix()); + + const QString fileNameDisabled = QStringLiteral("%1/%2-disabled.%3") + .arg(fileInfo.absolutePath(), imageName, fileInfo.suffix()); + +#if QT_VERSION >= QT_VERSION_CHECK(5, 5, 0) + if (QGuiApplication::primaryScreen()->devicePixelRatio() >= 2 ) + { + const QString fileName2x = QStringLiteral("%1/%2@2x.%3") + .arg(fileInfo.absolutePath(), imageName, fileInfo.suffix()); + + const QString fileName2xHover = QStringLiteral("%1/%2-hover@2x.%3") + .arg(fileInfo.absolutePath(), imageName, fileInfo.suffix()); + + const QString fileName2xDisabled = QStringLiteral("%1/%2-disabled@2x.%3") + .arg(fileInfo.absolutePath(), imageName, fileInfo.suffix()); + + m_handlePixmaps.insert(type, QPixmap(fileName2x)); + m_handleHoverPixmaps.insert(type, QPixmap(fileName2xHover)); + m_handleDisabledPixmaps.insert(type, QPixmap(fileName2xDisabled)); + } + else + { + m_handlePixmaps.insert(type, QPixmap(fileName)); + m_handleHoverPixmaps.insert(type, QPixmap(fileNameHover)); + m_handleDisabledPixmaps.insert(type, QPixmap(fileNameDisabled)); + } +#else + m_handlePixmaps.insert(type, QPixmap(fileName)); + m_handleHoverPixmaps.insert(type, QPixmap(fileNameHover)); + m_handleDisabledPixmaps.insert(type, QPixmap(fileNameDisabled)); +#endif + QPainterPath p = PixmapToPainterPath(m_handlePixmaps.value(type)); + p.setFillRule(Qt::WindingFill); + p.closeSubpath(); + m_handlePaths.insert(type, p); + }; + + InitPixmap(BIHandleCornerType::ScaleTopLeftBottomRight, QStringLiteral("://icon/32x32/expand2.png")); + InitPixmap(BIHandleCornerType::ScaleTopBottom, QStringLiteral("://icon/32x32/double-arrow-vertical.png")); + InitPixmap(BIHandleCornerType::ScaleTopRightBottomLeft, QStringLiteral("://icon/32x32/expand1.png")); + InitPixmap(BIHandleCornerType::ScaleRightLeft, QStringLiteral("://icon/32x32/double-arrow-horizontal.png")); + InitPixmap(BIHandleCornerType::RotateTopLeft, QStringLiteral("://icon/32x32/rotate-top-left.png")); + InitPixmap(BIHandleCornerType::RotateTopRight, QStringLiteral("://icon/32x32/rotate-top-right.png")); + InitPixmap(BIHandleCornerType::RotateBottomRight, QStringLiteral("://icon/32x32/rotate-bottom-right.png")); + InitPixmap(BIHandleCornerType::RotateBottomLeft, QStringLiteral("://icon/32x32/rotate-bottom-left.png")); +} + +//--------------------------------------------------------------------------------------------------------------------- +auto VBackgroundImageControls::TopLeftHandlerPosition() const -> QPointF +{ + return ControllersRect().topLeft(); +} + +//--------------------------------------------------------------------------------------------------------------------- +auto VBackgroundImageControls::TopHandlerPosition() const -> QPointF +{ + QRectF rect = ControllersRect(); + QPixmap handler = m_handlePixmaps.value(BIHandleCornerType::ScaleTopBottom); + QSize size = handler.size() / handler.devicePixelRatio(); + return {rect.topLeft().x() + (rect.width()/2. - size.width()/2.), rect.topLeft().y()}; +} + +//--------------------------------------------------------------------------------------------------------------------- +auto VBackgroundImageControls::TopRightHandlerPosition() const -> QPointF +{ + QRectF rect = ControllersRect(); + QPixmap handler = m_handlePixmaps.value(BIHandleCornerType::ScaleTopRightBottomLeft); + QSize size = handler.size() / handler.devicePixelRatio(); + return {rect.topLeft().x() + (rect.width() - size.width()), rect.topLeft().y()}; +} + +//--------------------------------------------------------------------------------------------------------------------- +auto VBackgroundImageControls::RightHandlerPosition() const -> QPointF +{ + QRectF rect = ControllersRect(); + QPixmap handler = m_handlePixmaps.value(BIHandleCornerType::ScaleRightLeft); + QSize size = handler.size() / handler.devicePixelRatio(); + return {rect.topLeft().x() + (rect.width() - size.width()), + rect.topLeft().y() + (rect.height()/2. - size.height()/2.)}; +} + +//--------------------------------------------------------------------------------------------------------------------- +auto VBackgroundImageControls::BottomRightHandlerPosition() const -> QPointF +{ + QRectF rect = ControllersRect(); + QPixmap handler = m_handlePixmaps.value(BIHandleCornerType::ScaleTopLeftBottomRight); + QSize size = handler.size() / handler.devicePixelRatio(); + return {rect.topLeft().x() + (rect.width() - size.width()), + rect.topLeft().y() + (rect.height() - size.height())}; +} + +//--------------------------------------------------------------------------------------------------------------------- +auto VBackgroundImageControls::BottomHandlerPosition() const -> QPointF +{ + QRectF rect = ControllersRect(); + QPixmap handler = m_handlePixmaps.value(BIHandleCornerType::ScaleTopBottom); + QSize size = handler.size() / handler.devicePixelRatio(); + return {rect.topLeft().x() + (rect.width()/2. - size.width()/2.), + rect.topLeft().y() + (rect.height() - size.height())}; +} + +//--------------------------------------------------------------------------------------------------------------------- +auto VBackgroundImageControls::BottomLeftHandlerPosition() const -> QPointF +{ + QRectF rect = ControllersRect(); + QPixmap handler = m_handlePixmaps.value(BIHandleCornerType::ScaleTopRightBottomLeft); + QSize size = handler.size() / handler.devicePixelRatio(); + return {rect.topLeft().x(), rect.topLeft().y() + (rect.height() - size.height())}; +} + +//--------------------------------------------------------------------------------------------------------------------- +auto VBackgroundImageControls::LeftHandlerPosition() const -> QPointF +{ + QRectF rect = ControllersRect(); + QPixmap handler = m_handlePixmaps.value(BIHandleCornerType::ScaleRightLeft); + QSize size = handler.size() / handler.devicePixelRatio(); + return {rect.topLeft().x(), rect.topLeft().y() + (rect.height()/2. - size.height()/2.)}; +} + +//--------------------------------------------------------------------------------------------------------------------- +auto VBackgroundImageControls::ControllerPath(BIHandleCornerType type, QPointF pos) const -> QPainterPath +{ + QTransform t; + t.translate(pos.x(), pos.y()); + + QPainterPath controller = m_handlePaths.value(type); + + controller = t.map(controller); + + return controller; +} + +//--------------------------------------------------------------------------------------------------------------------- +auto VBackgroundImageControls::ScaleTopLeftControl() const -> QPainterPath +{ + return ControllerPath(BIHandleCornerType::ScaleTopLeftBottomRight, TopLeftHandlerPosition()); +} + +//--------------------------------------------------------------------------------------------------------------------- +auto VBackgroundImageControls::ScaleTopControl() const -> QPainterPath +{ + return ControllerPath(BIHandleCornerType::ScaleTopBottom, TopHandlerPosition()); +} + +//--------------------------------------------------------------------------------------------------------------------- +auto VBackgroundImageControls::ScaleTopRightControl() const -> QPainterPath +{ + return ControllerPath(BIHandleCornerType::ScaleTopRightBottomLeft, TopRightHandlerPosition()); +} + +//--------------------------------------------------------------------------------------------------------------------- +auto VBackgroundImageControls::ScaleRightControl() const -> QPainterPath +{ + return ControllerPath(BIHandleCornerType::ScaleRightLeft, RightHandlerPosition()); +} + +//--------------------------------------------------------------------------------------------------------------------- +auto VBackgroundImageControls::ScaleBottomRightControl() const -> QPainterPath +{ + return ControllerPath(BIHandleCornerType::ScaleTopLeftBottomRight, BottomRightHandlerPosition()); +} + +//--------------------------------------------------------------------------------------------------------------------- +auto VBackgroundImageControls::ScaleBottomControl() const -> QPainterPath +{ + return ControllerPath(BIHandleCornerType::ScaleTopBottom, BottomHandlerPosition()); +} + +//--------------------------------------------------------------------------------------------------------------------- +auto VBackgroundImageControls::ScaleBottomLeftControl() const -> QPainterPath +{ + return ControllerPath(BIHandleCornerType::ScaleTopRightBottomLeft, BottomLeftHandlerPosition()); +} + +//--------------------------------------------------------------------------------------------------------------------- +auto VBackgroundImageControls::ScaleLeftControl() const -> QPainterPath +{ + return ControllerPath(BIHandleCornerType::ScaleRightLeft, LeftHandlerPosition()); +} + +//--------------------------------------------------------------------------------------------------------------------- +auto VBackgroundImageControls::RotateTopLeftControl() const -> QPainterPath +{ + return ControllerPath(BIHandleCornerType::RotateTopLeft, TopLeftHandlerPosition()); +} + +//--------------------------------------------------------------------------------------------------------------------- +auto VBackgroundImageControls::RotateTopRightControl() const -> QPainterPath +{ + return ControllerPath(BIHandleCornerType::RotateTopRight, TopRightHandlerPosition()); +} + +//--------------------------------------------------------------------------------------------------------------------- +auto VBackgroundImageControls::RotateBottomRightControl() const -> QPainterPath +{ + return ControllerPath(BIHandleCornerType::RotateBottomRight, BottomRightHandlerPosition()); +} + +//--------------------------------------------------------------------------------------------------------------------- +auto VBackgroundImageControls::RotateBottomLeftControl() const -> QPainterPath +{ + return ControllerPath(BIHandleCornerType::RotateBottomLeft, BottomLeftHandlerPosition()); +} + +//--------------------------------------------------------------------------------------------------------------------- +auto VBackgroundImageControls::ScaleByTopLeft(QGraphicsSceneMouseEvent *event) const -> QTransform +{ + QRectF rectOriginal = m_imageBoundingRect; + QRectF rect = m_imageScreenBoundingRect; + + const qreal adjustmentX = -rect.topLeft().x(); + const qreal adjustmentY = -rect.topLeft().y(); + + rect.translate(adjustmentX, adjustmentY); + + QPointF pos = event->pos(); + pos.rx() += adjustmentX; + pos.ry() += adjustmentY; + + QRectF nowRect; + QPointF diff; + + if (event->modifiers() & Qt::ShiftModifier) + { + nowRect = rect; + + QPointF centerScalePoint = pos - m_scaleDiff; + + if (centerScalePoint.x() > rect.center().x()) + { + centerScalePoint.rx() = rect.center().x() - (centerScalePoint.x() - rect.center().x()); + } + + if (centerScalePoint.y() > rect.center().y()) + { + centerScalePoint.ry() = rect.center().y() - (centerScalePoint.y() - rect.center().y()); + } + + diff = centerScalePoint - rect.topLeft(); + + if (event->modifiers() & Qt::ControlModifier) + { + nowRect.adjust(diff.x(), diff.y(), -diff.x(), -diff.y()); + } + else + { + qreal move; + if (diff.x() > 0 && diff.x() >= diff.y()) + { + move = diff.x(); + } + else if (diff.y() > 0 && diff.y() >= diff.x()) + { + move = diff.y(); + } + else + { + move = qMax(diff.x(), diff.y()); + } + + nowRect.adjust(move, move, -move, -move); + } + } + else + { + QPointF newScalePoint = pos - m_scaleDiff; + + if (newScalePoint.x() > rect.bottomRight().x()) + { + newScalePoint.rx() = rect.bottomRight().x() - (newScalePoint.x() - rect.bottomRight().x()); + } + + if (newScalePoint.y() > rect.bottomRight().y()) + { + newScalePoint.ry() = rect.bottomRight().y()- (newScalePoint.y() - rect.bottomRight().y()); + } + + diff = newScalePoint - rect.topLeft(); + + nowRect = QRectF(newScalePoint, rect.bottomRight()); + } + + qreal scaleX = nowRect.width()/rect.width(); + qreal scaleY = nowRect.height()/rect.height(); + + if (not (event->modifiers() & Qt::ControlModifier)) + { + if (diff.x() > 0 && diff.x() >= diff.y()) + { + scaleY = scaleX; + } + else if (diff.y() > 0 && diff.y() >= diff.x()) + { + scaleX = scaleY; + } + else + { + scaleX = qMin(scaleX, scaleY); + scaleY = qMin(scaleX, scaleY); + } + } + + QPointF newScalePoint = pos - m_scaleDiff; + + QTransform m; + + QPointF scaleCenterOriginal = event->modifiers() & Qt::ShiftModifier ? rectOriginal.center() + : rectOriginal.bottomRight(); + + m.translate(scaleCenterOriginal.x(), scaleCenterOriginal.y()); + + m.scale(scaleX, scaleY); + + QPointF scaleCenter = event->modifiers() & Qt::ShiftModifier ? rect.center() : rect.bottomRight(); + + if (newScalePoint.x() > scaleCenter.x()) + { + m.scale(-1, 1); + } + + if (newScalePoint.y() > scaleCenter.y()) + { + m.scale(1, -1); + } + + m.translate(-scaleCenterOriginal.x(), -scaleCenterOriginal.y()); + + return m; +} + +//--------------------------------------------------------------------------------------------------------------------- +auto VBackgroundImageControls::ScaleByTop(QGraphicsSceneMouseEvent *event) const -> QTransform +{ + QRectF rectOriginal = m_imageBoundingRect; + QRectF rect = m_imageScreenBoundingRect; + + const qreal adjustmentX = -rect.topLeft().x(); + const qreal adjustmentY = -rect.topLeft().y(); + + rect.translate(adjustmentX, adjustmentY); + + QPointF pos = event->pos(); + pos.rx() += adjustmentX; + pos.ry() += adjustmentY; + + QRectF nowRect; + if (event->modifiers() & Qt::ShiftModifier) + { + nowRect = rect; + + QPointF centerScalePoint = pos - m_scaleDiff; + + if (centerScalePoint.y() > rect.center().y()) + { + centerScalePoint.ry() = rect.center().y() - (centerScalePoint.y() - rect.center().y()); + } + + QPointF diff = centerScalePoint - RectTopPoint(rect); + + if (event->modifiers() & Qt::ControlModifier) + { + nowRect.adjust(0, diff.y(), 0, -diff.y()); + } + else + { + nowRect.adjust(diff.x(), diff.y(), -diff.x(), -diff.y()); + } + } + else + { + QPointF newScalePoint = pos - m_scaleDiff; + + if (newScalePoint.y() > RectBottomPoint(rect).y()) + { + newScalePoint.ry() = RectBottomPoint(rect).y() - (newScalePoint.y() - RectBottomPoint(rect).y()); + } + + nowRect = QRectF(QPointF(rect.topLeft().x(), newScalePoint.y()), rect.bottomRight()); + } + + qreal scaleX = nowRect.height()/rect.height(); + qreal scaleY = nowRect.height()/rect.height(); + + if (event->modifiers() & Qt::ControlModifier) + { + scaleX = 1; + } + + QPointF newScalePoint = pos - m_scaleDiff; + + QTransform m; + + QPointF scaleCenterOriginal = event->modifiers() & Qt::ShiftModifier ? rectOriginal.center() + : RectBottomPoint(rectOriginal); + + m.translate(scaleCenterOriginal.x(), scaleCenterOriginal.y()); + m.scale(scaleX, scaleY); + + QPointF scaleCenter = event->modifiers() & Qt::ShiftModifier ? rect.center() : rect.bottomRight(); + + if (newScalePoint.y() > scaleCenter.y()) + { + m.scale(1, -1); + } + + m.translate(-scaleCenterOriginal.x(), -scaleCenterOriginal.y()); + + return m; +} + +//--------------------------------------------------------------------------------------------------------------------- +auto VBackgroundImageControls::ScaleByTopRight(QGraphicsSceneMouseEvent *event) const -> QTransform +{ + QRectF rectOriginal = m_imageBoundingRect; + QRectF rect = m_imageScreenBoundingRect; + + const qreal adjustmentX = -rect.topLeft().x(); + const qreal adjustmentY = -rect.topLeft().y(); + + rect.translate(adjustmentX, adjustmentY); + + QPointF pos = event->pos(); + pos.rx() += adjustmentX; + pos.ry() += adjustmentY; + + QRectF nowRect; + QPointF diff; + + if (event->modifiers() & Qt::ShiftModifier) + { + nowRect = rect; + + QPointF centerScalePoint = pos - m_scaleDiff; + + if (centerScalePoint.x() < rect.center().x()) + { + centerScalePoint.rx() = rect.center().x() + (rect.center().x() - centerScalePoint.x()); + } + + if (centerScalePoint.y() > rect.center().y()) + { + centerScalePoint.ry() = rect.center().y() - (centerScalePoint.y() - rect.center().y()); + } + + diff = centerScalePoint - rect.topRight(); + + if (event->modifiers() & Qt::ControlModifier) + { + nowRect.adjust(-diff.x(), diff.y(), diff.x(), -diff.y()); + } + else + { + qreal move; + if (diff.x() < 0 && qAbs(diff.x()) >= qAbs(diff.y())) + { + move = diff.x() * -1; + } + else if (diff.y() > 0 && qAbs(diff.y()) >= qAbs(diff.x())) + { + move = diff.y(); + } + else + { + if (qAbs(diff.x()) <= qAbs(diff.y())) + { + move = diff.x() * -1; + } + else + { + move = diff.y(); + } + } + + nowRect.adjust(move, move, -move, -move); + } + } + else + { + QPointF newScalePoint = pos - m_scaleDiff; + + if (newScalePoint.x() < rect.bottomLeft().x()) + { + newScalePoint.rx() = (rect.bottomLeft().x() - newScalePoint.x()) - rect.bottomLeft().x(); + } + + if (newScalePoint.y() > rect.bottomLeft().y()) + { + newScalePoint.ry() = rect.bottomLeft().y() - (newScalePoint.y() - rect.bottomLeft().y()); + } + + diff = newScalePoint - rect.topRight(); + + nowRect = QRectF(QPointF(rect.topLeft().x(), newScalePoint.y()), + QPointF(newScalePoint.x(), rect.bottomRight().y())); + } + + qreal scaleX = nowRect.width()/rect.width(); + qreal scaleY = nowRect.height()/rect.height(); + + if (not (event->modifiers() & Qt::ControlModifier)) + { + if (diff.x() < 0 && qAbs(diff.x()) >= qAbs(diff.y())) + { + scaleY = scaleX; + } + else if (diff.y() > 0 && qAbs(diff.y()) >= qAbs(diff.x())) + { + scaleX = scaleY; + } + else + { + scaleX = qMin(scaleX, scaleY); + scaleY = qMin(scaleX, scaleY); + } + } + + QPointF newScalePoint = pos - m_scaleDiff; + + QTransform m; + QPointF scaleCenterOriginal = event->modifiers() & Qt::ShiftModifier ? rectOriginal.center() + : rectOriginal.bottomLeft(); + + m.translate(scaleCenterOriginal.x(), scaleCenterOriginal.y()); + m.scale(scaleX, scaleY); + + QPointF scaleCenter = event->modifiers() & Qt::ShiftModifier ? rect.center() : rect.bottomLeft(); + + if (newScalePoint.x() < scaleCenter.x()) + { + m.scale(-1, 1); + } + + if (newScalePoint.y() > scaleCenter.y()) + { + m.scale(1, -1); + } + + m.translate(-scaleCenterOriginal.x(), -scaleCenterOriginal.y()); + + return m; +} + +//--------------------------------------------------------------------------------------------------------------------- +auto VBackgroundImageControls::ScaleByRight(QGraphicsSceneMouseEvent *event) const -> QTransform +{ + QRectF rectOriginal = m_imageBoundingRect; + QRectF rect = m_imageScreenBoundingRect; + + const qreal adjustmentX = -rect.topLeft().x(); + const qreal adjustmentY = -rect.topLeft().y(); + + rect.translate(adjustmentX, adjustmentY); + + QPointF pos = event->pos(); + pos.rx() += adjustmentX; + pos.ry() += adjustmentY; + + QRectF nowRect; + + if (event->modifiers() & Qt::ShiftModifier) + { + nowRect = rect; + + QPointF centerScalePoint = pos - m_scaleDiff; + + if (centerScalePoint.x() < rect.center().x()) + { + centerScalePoint.rx() = rect.center().x() + (rect.center().x() - centerScalePoint.x()); + } + + QPointF diff = centerScalePoint - RectRightPoint(rect); + + if (event->modifiers() & Qt::ControlModifier) + { + nowRect.adjust(-diff.x(), 0, diff.x(), 0); + } + else + { + nowRect.adjust(-diff.x(), -diff.y(), diff.x(), diff.y()); + } + } + else + { + QPointF newScalePoint = pos - m_scaleDiff; + + if (newScalePoint.x() < RectLeftPoint(rect).x()) + { + newScalePoint.rx() = (RectLeftPoint(rect).x() - newScalePoint.x()) - RectLeftPoint(rect).x(); + } + + nowRect = QRectF(rect.topLeft(), QPointF(newScalePoint.x(), rect.bottomRight().y())); + } + + qreal scaleX = nowRect.width()/rect.width(); + qreal scaleY = nowRect.width()/rect.width(); + + if (event->modifiers() & Qt::ControlModifier) + { + scaleY = 1; + } + + QPointF newScalePoint = pos - m_scaleDiff; + + QTransform m; + + QPointF scaleCenterOriginal = event->modifiers() & Qt::ShiftModifier ? rectOriginal.center() + : RectLeftPoint(rectOriginal); + + m.translate(scaleCenterOriginal.x(), scaleCenterOriginal.y()); + m.scale(scaleX, scaleY); + + QPointF scaleCenter = event->modifiers() & Qt::ShiftModifier ? rect.center() : RectLeftPoint(rect); + + if (newScalePoint.x() < scaleCenter.x()) + { + m.scale(-1, 1); + } + + m.translate(-scaleCenterOriginal.x(), -scaleCenterOriginal.y()); + + return m; +} + +//--------------------------------------------------------------------------------------------------------------------- +auto VBackgroundImageControls::ScaleByBottomRight(QGraphicsSceneMouseEvent *event) const -> QTransform +{ + QRectF rectOriginal = m_imageBoundingRect; + QRectF rect = m_imageScreenBoundingRect; + + // move to origin + const qreal adjustmentX = -rect.topLeft().x(); + const qreal adjustmentY = -rect.topLeft().y(); + + rect.translate(adjustmentX, adjustmentY); + + QPointF pos = event->pos(); + pos.rx() += adjustmentX; + pos.ry() += adjustmentY; + + QRectF nowRect; + QPointF diff; + + if (event->modifiers() & Qt::ShiftModifier) + { + nowRect = rect; + + QPointF centerScalePoint = pos - m_scaleDiff; + + if (centerScalePoint.x() < rect.center().x()) + { + centerScalePoint.rx() = rect.center().x() + (rect.center().x() - centerScalePoint.x()); + } + + if (centerScalePoint.y() < rect.center().y()) + { + centerScalePoint.ry() = rect.center().y() + (rect.center().y() - centerScalePoint.y()); + } + + diff = centerScalePoint - rect.bottomRight(); + + if (event->modifiers() & Qt::ControlModifier) + { + nowRect.adjust(-diff.x(), -diff.y(), diff.x(), diff.y()); + } + else + { + qreal move; + if (diff.x() < 0 && diff.x() <= diff.y()) + { + move = diff.x(); + } + else if (diff.y() < 0 && diff.y() <= diff.x()) + { + move = diff.y(); + } + else + { + move = qMin(diff.x(), diff.y()); + } + nowRect.adjust(-move, -move, move, move); + } + } + else + { + // correct the scale point position to match the rect at origin + QPointF newScalePoint = pos - m_scaleDiff; + + if (newScalePoint.x() < rect.topLeft().x()) + { + newScalePoint.rx() = rect.topLeft().x() + (rect.topLeft().x() - newScalePoint.x()); + } + + if (newScalePoint.y() < rect.topLeft().y()) + { + newScalePoint.ry() = rect.topLeft().y() + (rect.topLeft().y() - newScalePoint.y()); + } + + // calculate scale + diff = newScalePoint - rect.bottomRight(); + + nowRect = QRectF(rect.topLeft(), newScalePoint); + } + + qreal scaleX = nowRect.width()/rect.width(); + qreal scaleY = nowRect.height()/rect.height(); + + if (not (event->modifiers() & Qt::ControlModifier)) + { + if (diff.x() < 0 && diff.x() <= diff.y()) + { + scaleY = scaleX; + } + else if (diff.y() < 0 && diff.y() <= diff.x()) + { + scaleX = scaleY; + } + else + { + scaleX = qMin(scaleX, scaleY); + scaleY = qMin(scaleX, scaleY); + } + } + + // prepare transformation + QPointF newScalePoint = pos - m_scaleDiff; + + QTransform m; + + QPointF scaleCenterOriginal = event->modifiers() & Qt::ShiftModifier ? rectOriginal.center() + : rectOriginal.topLeft(); + + m.translate(scaleCenterOriginal.x(), scaleCenterOriginal.y()); + + m.scale(scaleX, scaleY); + + QPointF scaleCenter = event->modifiers() & Qt::ShiftModifier ? rect.center() : rect.topLeft(); + + if (newScalePoint.x() < scaleCenter.x()) + { + m.scale(-1, 1); + } + + if (newScalePoint.y() < scaleCenter.y()) + { + m.scale(1, -1); + } + + m.translate(-scaleCenterOriginal.x(), -scaleCenterOriginal.y()); + + return m; +} + +//--------------------------------------------------------------------------------------------------------------------- +auto VBackgroundImageControls::ScaleByBottom(QGraphicsSceneMouseEvent *event) const -> QTransform +{ + QRectF rectOriginal = m_imageBoundingRect; + QRectF rect = m_imageScreenBoundingRect; + + const qreal adjustmentX = -rect.topLeft().x(); + const qreal adjustmentY = -rect.topLeft().y(); + + rect.translate(adjustmentX, adjustmentY); + + QPointF pos = event->pos(); + pos.rx() += adjustmentX; + pos.ry() += adjustmentY; + + QRectF nowRect; + if (event->modifiers() & Qt::ShiftModifier) + { + nowRect = rect; + + QPointF centerScalePoint = pos - m_scaleDiff; + + if (centerScalePoint.y() < rect.center().y()) + { + centerScalePoint.ry() = rect.center().y() + (rect.center().y() - centerScalePoint.y()); + } + + QPointF diff = centerScalePoint - RectBottomPoint(rect); + + if (event->modifiers() & Qt::ControlModifier) + { + nowRect.adjust(0, -diff.y(), 0, diff.y()); + } + else + { + nowRect.adjust(-diff.x(), -diff.y(), diff.x(), diff.y()); + } + } + else + { + QPointF newScalePoint = pos - m_scaleDiff; + + if (newScalePoint.y() < RectTopPoint(rect).y()) + { + newScalePoint.ry() = (RectTopPoint(rect).y() - newScalePoint.y()) - RectTopPoint(rect).y(); + } + + nowRect = QRectF(rect.topLeft(), QPointF(rect.bottomRight().x(), newScalePoint.y())); + } + + qreal scaleX = nowRect.height()/rect.height(); + qreal scaleY = nowRect.height()/rect.height(); + + if (event->modifiers() & Qt::ControlModifier) + { + scaleX = 1; + } + + QPointF newScalePoint = pos - m_scaleDiff; + + QTransform m; + + QPointF scaleCenterOriginal = event->modifiers() & Qt::ShiftModifier ? rectOriginal.center() + : RectTopPoint(rectOriginal); + + m.translate(scaleCenterOriginal.x(), scaleCenterOriginal.y()); + m.scale(scaleX, scaleY); + + QPointF scaleCenter = event->modifiers() & Qt::ShiftModifier ? rect.center() : RectTopPoint(rect); + + if (newScalePoint.y() < scaleCenter.y()) + { + m.scale(1, -1); + } + + m.translate(-scaleCenterOriginal.x(), -scaleCenterOriginal.y()); + + return m; +} + +//--------------------------------------------------------------------------------------------------------------------- +auto VBackgroundImageControls::ScaleByBottomLeft(QGraphicsSceneMouseEvent *event) const -> QTransform +{ + QRectF rectOriginal = m_imageBoundingRect; + QRectF rect = m_imageScreenBoundingRect; + + const qreal adjustmentX = -rect.topLeft().x(); + const qreal adjustmentY = -rect.topLeft().y(); + + rect.translate(adjustmentX, adjustmentY); + + QPointF pos = event->pos(); + pos.rx() += adjustmentX; + pos.ry() += adjustmentY; + + QRectF nowRect; + QPointF diff; + + if (event->modifiers() & Qt::ShiftModifier) + { + nowRect = rect; + + QPointF centerScalePoint = pos - m_scaleDiff; + + if (centerScalePoint.x() > rect.center().x()) + { + centerScalePoint.rx() = rect.center().x() - (centerScalePoint.x() - rect.center().x()); + } + + if (centerScalePoint.y() < rect.center().y()) + { + centerScalePoint.ry() = rect.center().y() + (rect.center().y() - centerScalePoint.y()); + } + + diff = centerScalePoint - rect.bottomLeft(); + qDebug() << diff; + + if (event->modifiers() & Qt::ControlModifier) + { + nowRect.adjust(diff.x(), -diff.y(), -diff.x(), diff.y()); + } + else + { + qreal move; + if (diff.x() > 0 && diff.x() >= qAbs(diff.y())) + { + move = diff.x(); + } + else if (diff.y() < 0 && qAbs(diff.y()) >= qAbs(diff.x())) + { + move = diff.y() * -1; + } + else + { + if (qAbs(diff.x()) <= qAbs(diff.y())) + { + move = diff.x(); + } + else + { + move = diff.y()*-1; + } + } + qDebug() << move; + nowRect.adjust(move, move, -move, -move); + } + } + else + { + QPointF newScalePoint = pos - m_scaleDiff; + + if (newScalePoint.x() > rect.topRight().x()) + { + newScalePoint.rx() = rect.topRight().x() - (newScalePoint.x() - rect.topRight().x()); + } + + if (newScalePoint.y() < rect.topRight().y()) + { + newScalePoint.ry() = (rect.topRight().y() - newScalePoint.y()) - rect.topRight().y(); + } + + diff = newScalePoint - rect.topRight(); + + nowRect = QRectF(QPointF(newScalePoint.x(), rect.topLeft().y()), + QPointF(rect.bottomRight().x(), newScalePoint.y())); + } + + qreal scaleX = nowRect.width()/rect.width(); + qreal scaleY = nowRect.height()/rect.height(); + + if (not (event->modifiers() & Qt::ControlModifier)) + { + if (diff.x() > 0 && diff.x() >= diff.y()) + { + scaleY = scaleX; + } + else if (diff.y() < 0 && diff.y() >= diff.x()) + { + scaleX = scaleY; + } + else + { + scaleX = qMin(scaleX, scaleY); + scaleY = qMin(scaleX, scaleY); + } + } + + QPointF newScalePoint = pos - m_scaleDiff; + + QTransform m; + QPointF scaleCenterOriginal = event->modifiers() & Qt::ShiftModifier ? rectOriginal.center() + : rectOriginal.topRight(); + + m.translate(scaleCenterOriginal.x(), scaleCenterOriginal.y()); + m.scale(scaleX, scaleY); + + QPointF scaleCenter = event->modifiers() & Qt::ShiftModifier ? rect.center() : rect.topRight(); + + if (newScalePoint.x() > scaleCenter.x()) + { + m.scale(-1, 1); + } + + if (newScalePoint.y() < scaleCenter.y()) + { + m.scale(1, -1); + } + + m.translate(-scaleCenterOriginal.x(), -scaleCenterOriginal.y()); + + return m; +} + +//--------------------------------------------------------------------------------------------------------------------- +auto VBackgroundImageControls::ScaleByLeft(QGraphicsSceneMouseEvent *event) const -> QTransform +{ + QRectF rectOriginal = m_imageBoundingRect; + QRectF rect = m_imageScreenBoundingRect; + + const qreal adjustmentX = -rect.topLeft().x(); + const qreal adjustmentY = -rect.topLeft().y(); + + rect.translate(adjustmentX, adjustmentY); + + QPointF pos = event->pos(); + pos.rx() += adjustmentX; + pos.ry() += adjustmentY; + + QRectF nowRect; + if (event->modifiers() & Qt::ShiftModifier) + { + nowRect = rect; + + QPointF centerScalePoint = pos - m_scaleDiff; + + if (centerScalePoint.x() > rect.center().x()) + { + centerScalePoint.rx() = rect.center().x() - (centerScalePoint.x() - rect.center().x()); + } + + QPointF diff = centerScalePoint - RectLeftPoint(rect); + + if (event->modifiers() & Qt::ControlModifier) + { + nowRect.adjust(diff.x(), 0, -diff.x(), 0); + } + else + { + nowRect.adjust(diff.x(), diff.y(), -diff.x(), -diff.y()); + } + } + else + { + QPointF newScalePoint = pos - m_scaleDiff; + + if (newScalePoint.x() > RectRightPoint(rect).x()) + { + newScalePoint.rx() = RectRightPoint(rect).x() - (newScalePoint.x() - RectRightPoint(rect).x()); + } + + nowRect = QRectF(QPointF(newScalePoint.x(), rect.topLeft().y()), rect.bottomRight()); + } + + qreal scaleX = nowRect.width()/rect.width(); + qreal scaleY = nowRect.width()/rect.width(); + + if (event->modifiers() & Qt::ControlModifier) + { + scaleY = 1; + } + + QPointF newScalePoint = pos - m_scaleDiff; + + QTransform m; + + QPointF scaleCenterOriginal = event->modifiers() & Qt::ShiftModifier ? rectOriginal.center() + : RectRightPoint(rectOriginal); + + m.translate(scaleCenterOriginal.x(), scaleCenterOriginal.y()); + m.scale(scaleX, scaleY); + + QPointF scaleCenter = event->modifiers() & Qt::ShiftModifier ? rect.center() : RectRightPoint(rect); + + if (newScalePoint.x() > scaleCenter.x()) + { + m.scale(-1, 1); + } + + m.translate(-scaleCenterOriginal.x(), -scaleCenterOriginal.y()); + + return m; +} + +//--------------------------------------------------------------------------------------------------------------------- +auto VBackgroundImageControls::Handles() const -> QPainterPath +{ + QPainterPath path; + + if (m_tranformationType == BITransformationType::Scale) + { + path.addPath(ScaleTopLeftControl()); + path.addPath(ScaleTopControl()); + path.addPath(ScaleTopRightControl()); + path.addPath(ScaleRightControl()); + path.addPath(ScaleBottomRightControl()); + path.addPath(ScaleBottomControl()); + path.addPath(ScaleBottomLeftControl()); + path.addPath(ScaleLeftControl()); + } + else if (m_tranformationType == BITransformationType::Rotate) + { + path.addPath(RotateTopLeftControl()); + path.addPath(RotateTopRightControl()); + path.addPath(RotateBottomRightControl()); + path.addPath(RotateBottomLeftControl()); + } + + return path; +} + +//--------------------------------------------------------------------------------------------------------------------- +auto VBackgroundImageControls::ControllersRect() const -> QRectF +{ + const qreal scale = SceneScale(scene()); + QPixmap handler = m_handlePixmaps.value(BIHandleCornerType::ScaleTopLeftBottomRight); + QRectF imageRect = m_image.BoundingRect(); + + imageRect = QRectF(imageRect.topLeft()*scale, QSizeF(imageRect.width()*scale, imageRect.height()*scale)); + QRectF rect = imageRect; + + if (imageRect.width() < handler.width()) + { + qreal diff = handler.width() - imageRect.width(); + rect.adjust(0, 0, diff, 0); + } + + if (imageRect.height() < handler.height()) + { + qreal diff = handler.height() - imageRect.height(); + rect.adjust(0, 0, 0, diff); + } + + const qreal gap = 2; + rect.adjust(- (handler.width() + gap), - (handler.height() + gap), handler.width() + gap, handler.height() + gap); + + return rect; +} + +//--------------------------------------------------------------------------------------------------------------------- +auto VBackgroundImageControls::SelectedHandleCorner(const QPointF &pos) const -> BIHandleCorner +{ + if (m_image.Hold()) + { + return BIHandleCorner::Invalid; + } + + QMap corners; + + if (m_tranformationType == BITransformationType::Scale) + { + corners = + { + {BIHandleCorner::TopLeft, ScaleTopLeftControl()}, + {BIHandleCorner::Top, ScaleTopControl()}, + {BIHandleCorner::TopRight, ScaleTopRightControl()}, + {BIHandleCorner::Right, ScaleRightControl()}, + {BIHandleCorner::BottomRight, ScaleBottomRightControl()}, + {BIHandleCorner::Bottom, ScaleBottomControl()}, + {BIHandleCorner::BottomLeft, ScaleBottomLeftControl()}, + {BIHandleCorner::Left, ScaleLeftControl()}, + }; + } + else if (m_tranformationType == BITransformationType::Rotate) + { + corners = + { + {BIHandleCorner::TopLeft, RotateTopLeftControl()}, + {BIHandleCorner::TopRight, RotateTopRightControl()}, + {BIHandleCorner::BottomRight, RotateBottomRightControl()}, + {BIHandleCorner::BottomLeft, RotateBottomLeftControl()}, + }; + } + + QPainterPath circle; + circle.addEllipse(pos.x()-4, pos.y()-4, 8, 8); + + auto CheckCorner = [circle](const QPainterPath &handler) + { + return handler.intersects(circle) || handler.contains(circle); + }; + + auto i = corners.constBegin(); + while (i != corners.constEnd()) + { + if (CheckCorner(i.value())) + { + return i.key(); + } + ++i; + } + + return BIHandleCorner::Invalid; +} + +//--------------------------------------------------------------------------------------------------------------------- +auto VBackgroundImageControls::HandlerPixmap(bool hover, BIHandleCornerType type) const -> QPixmap +{ + if (not m_image.Hold()) + { + return hover ? m_handleHoverPixmaps.value(type) : m_handlePixmaps.value(type); + } + + return m_handleDisabledPixmaps.value(type); +} + +//--------------------------------------------------------------------------------------------------------------------- +void VBackgroundImageControls::ShowOrigin(const QPointF &pos) +{ + prepareGeometryChange(); + m_showOrigin = true; + m_originPos = pos; +} + +//--------------------------------------------------------------------------------------------------------------------- +auto VBackgroundImageControls::OriginCircle1() const -> QPainterPath +{ + const qreal radius1 = 4; + QPainterPath path; + const qreal sceneScale = SceneScale(scene()); + QPointF screeOrigin = m_originPos * sceneScale; + path.addEllipse({screeOrigin.x()-radius1, screeOrigin.y()-radius1, radius1*2., radius1*2.}); + return path; +} + +//--------------------------------------------------------------------------------------------------------------------- +auto VBackgroundImageControls::OriginCircle2() const -> QPainterPath +{ + const qreal radius2 = 8; + QPainterPath path; + const qreal sceneScale = SceneScale(scene()); + QPointF screeOrigin = m_originPos * sceneScale; + path.addEllipse({screeOrigin.x()-radius2, screeOrigin.y()-radius2, radius2*2., radius2*2.}); + return path; +} + +//--------------------------------------------------------------------------------------------------------------------- +auto VBackgroundImageControls::OriginPath() const -> QPainterPath +{ + QPainterPath path; + + if (m_showOrigin) + { + path.addPath(OriginCircle1()); + path.addPath(OriginCircle2()); + } + + return path; +} + +//--------------------------------------------------------------------------------------------------------------------- +void VBackgroundImageControls::ScaleImage(QGraphicsSceneMouseEvent *event) +{ + QTransform imageMatrix = m_originalMatrix; + const bool shiftModifier = event->modifiers() & Qt::ShiftModifier; + + switch(m_handleCornerHover) + { + case BIHandleCorner::TopLeft: + ShowOrigin(shiftModifier ? m_imageBoundingRect.center() + : m_imageBoundingRect.bottomRight()); + imageMatrix *= ScaleByTopLeft(event); + break; + case BIHandleCorner::Top: + ShowOrigin(shiftModifier ? m_imageBoundingRect.center() + : RectBottomPoint(m_imageBoundingRect)); + imageMatrix *= ScaleByTop(event); + break; + case BIHandleCorner::TopRight: + ShowOrigin(shiftModifier ? m_imageBoundingRect.center() + : m_imageBoundingRect.bottomLeft()); + imageMatrix *= ScaleByTopRight(event); + break; + case BIHandleCorner::Right: + ShowOrigin(shiftModifier ? m_imageBoundingRect.center() + : RectLeftPoint(m_imageBoundingRect)); + imageMatrix *= ScaleByRight(event); + break; + case BIHandleCorner::BottomRight: + ShowOrigin(shiftModifier ? m_imageBoundingRect.center() + : m_imageBoundingRect.topLeft()); + imageMatrix *= ScaleByBottomRight(event); + break; + case BIHandleCorner::Bottom: + ShowOrigin(shiftModifier ? m_imageBoundingRect.center() + : RectTopPoint(m_imageBoundingRect)); + imageMatrix *= ScaleByBottom(event); + break; + case BIHandleCorner::BottomLeft: + ShowOrigin(shiftModifier ? m_imageBoundingRect.center() + : m_imageBoundingRect.topRight()); + imageMatrix *= ScaleByBottomLeft(event); + break; + case BIHandleCorner::Left: + ShowOrigin(shiftModifier ? m_imageBoundingRect.center() + : RectRightPoint(m_imageBoundingRect)); + imageMatrix *= ScaleByLeft(event); + break; + case BIHandleCorner::Invalid: + default: + break; + } + + if (m_handleCornerHover != BIHandleCorner::Invalid) + { + auto *command = new ScaleBackgroundImage(m_image.Id(), imageMatrix, m_doc, m_allowChangeMerge); + VAbstractApplication::VApp()->getUndoStack()->push(command); + m_allowChangeMerge = true; + } +} + +//--------------------------------------------------------------------------------------------------------------------- +void VBackgroundImageControls::RotateImage(QGraphicsSceneMouseEvent *event) +{ + const bool shiftModifier = event->modifiers() & Qt::ShiftModifier; + + switch(m_handleCornerHover) + { + case BIHandleCorner::TopLeft: + ShowOrigin(shiftModifier ? m_imageBoundingRect.bottomRight() + : m_imageBoundingRect.center()); + break; + case BIHandleCorner::TopRight: + ShowOrigin(shiftModifier ? m_imageBoundingRect.bottomLeft() + : m_imageBoundingRect.center()); + break; + case BIHandleCorner::BottomRight: + ShowOrigin(shiftModifier ? m_imageBoundingRect.topLeft() + : m_imageBoundingRect.center()); + break; + case BIHandleCorner::BottomLeft: + ShowOrigin(shiftModifier ? m_imageBoundingRect.topRight() + : m_imageBoundingRect.center()); + break; + default: + break; + } + + if (m_handleCornerHover != BIHandleCorner::Invalid) + { + QPointF rotationNewPoint = event->pos(); + const qreal sceneScale = SceneScale(scene()); + QPointF screenOriginPos = m_originPos*sceneScale; + + QLineF initPosition(screenOriginPos, m_rotationStartPoint); + QLineF initRotationPosition(screenOriginPos, rotationNewPoint); + + qreal rotateOn = initPosition.angleTo(initRotationPosition); + + if (rotateOn > 180) + { + rotateOn = rotateOn - 360.; + } + + if (event->modifiers() & Qt::ControlModifier) + { + const qreal sign = std::copysign(1.0, rotateOn); + const int steps = qFloor(qAbs(rotateOn / 15)); + rotateOn = 15 * steps * sign; + } + + QTransform imageMatrix = m_originalMatrix; + + QTransform m; + m.translate(m_originPos.x(), m_originPos.y()); + m.rotate(-rotateOn); + m.translate(-m_originPos.x(), -m_originPos.y()); + imageMatrix *= m; + + auto *command = new RotateBackgroundImage(m_image.Id(), imageMatrix, m_doc, m_allowChangeMerge); + VAbstractApplication::VApp()->getUndoStack()->push(command); + m_allowChangeMerge = true; + } +} diff --git a/src/libs/vtools/tools/backgroundimage/vbackgroundimagecontrols.h b/src/libs/vtools/tools/backgroundimage/vbackgroundimagecontrols.h new file mode 100644 index 000000000..ef607f722 --- /dev/null +++ b/src/libs/vtools/tools/backgroundimage/vbackgroundimagecontrols.h @@ -0,0 +1,185 @@ +/************************************************************************ + ** + ** @file vbackgroundimagecontrols.h + ** @author Roman Telezhynskyi + ** @date 17 1, 2022 + ** + ** @brief + ** @copyright + ** This source code is part of the Valentina project, a pattern making + ** program, whose allow create and modeling patterns of clothing. + ** Copyright (C) 2022 Valentina project + ** All Rights Reserved. + ** + ** Valentina 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, either version 3 of the License, or + ** (at your option) any later version. + ** + ** Valentina 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 Valentina. If not, see . + ** + *************************************************************************/ +#ifndef VBACKGROUNDIMAGECONTROLS_H +#define VBACKGROUNDIMAGECONTROLS_H + +#include +#include + +#include "../vmisc/def.h" +#include "../ifc/xml/vbackgroundpatternimage.h" + +#if QT_VERSION < QT_VERSION_CHECK(5, 13, 0) +#include "../vmisc/defglobal.h" +#endif // QT_VERSION < QT_VERSION_CHECK(5, 13, 0) + +class VAbstractPattern; +class QScreen; + +enum class BITransformationType {Scale, Rotate, Unknown}; + +enum class BIHandleCorner : int +{ + Invalid, + TopLeft, + Top, + TopRight, + Right, + BottomRight, + Bottom, + BottomLeft, + Left +}; + +enum class BIHandleCornerType +{ + ScaleTopLeftBottomRight, + ScaleTopBottom, + ScaleTopRightBottomLeft, + ScaleRightLeft, + RotateTopLeft, + RotateTopRight, + RotateBottomRight, + RotateBottomLeft +}; + +class VBackgroundImageControls : public QGraphicsObject +{ + Q_OBJECT +public: + explicit VBackgroundImageControls(VAbstractPattern *doc, QGraphicsItem * parent = nullptr); + ~VBackgroundImageControls() override = default; + + auto type() const -> int override {return Type;} + enum { Type = UserType + static_cast(Tool::BackgroundImageControls)}; + + auto Id() const -> const QUuid &; + +signals: + void ActiveImageChanged(const QUuid &id); + +public slots: + void ActivateControls(const QUuid &id); + void DeactivateControls(QGraphicsItem* pItem); + void UpdateControls(); + +protected: + auto boundingRect() const -> QRectF override; + auto shape() const -> QPainterPath override; + void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override; + + void mousePressEvent(QGraphicsSceneMouseEvent * event) override; + void mouseMoveEvent(QGraphicsSceneMouseEvent * event) override; + void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) override; + void hoverEnterEvent(QGraphicsSceneHoverEvent *event) override; + void hoverMoveEvent(QGraphicsSceneHoverEvent *event) override; + void hoverLeaveEvent(QGraphicsSceneHoverEvent *event) override; + +private slots: + void ScreenChanged(); + +private: + Q_DISABLE_COPY_MOVE(VBackgroundImageControls) + + QUuid m_id{}; + VAbstractPattern *m_doc; + VBackgroundPatternImage m_image{}; + BITransformationType m_tranformationType{BITransformationType::Unknown}; + + QMap m_handlePixmaps{}; + QMap m_handleHoverPixmaps{}; + QMap m_handleDisabledPixmaps{}; + QMap m_handlePaths{}; + + BIHandleCorner m_handleCornerHover{BIHandleCorner::Invalid}; + + bool m_controlsVisible{true}; + bool m_allowChangeMerge{false}; + bool m_transformationApplied{false}; + + QPointF m_scaleDiff{}; + QTransform m_originalMatrix{}; + QRectF m_imageBoundingRect{}; + QRectF m_imageScreenBoundingRect{}; + QPointF m_rotationStartPoint{}; + + bool m_showOrigin{false}; + QPointF m_originPos{}; + + void InitPixmaps(); + + auto TopLeftHandlerPosition() const -> QPointF; + auto TopHandlerPosition() const -> QPointF; + auto TopRightHandlerPosition() const -> QPointF; + auto RightHandlerPosition() const -> QPointF; + auto BottomRightHandlerPosition() const -> QPointF; + auto BottomHandlerPosition() const -> QPointF; + auto BottomLeftHandlerPosition() const -> QPointF; + auto LeftHandlerPosition() const -> QPointF; + + auto ControllerPath(BIHandleCornerType type, QPointF pos) const -> QPainterPath; + auto ScaleTopLeftControl() const -> QPainterPath; + auto ScaleTopControl() const -> QPainterPath; + auto ScaleTopRightControl() const -> QPainterPath; + auto ScaleRightControl() const -> QPainterPath; + auto ScaleBottomRightControl() const -> QPainterPath; + auto ScaleBottomControl() const -> QPainterPath; + auto ScaleBottomLeftControl() const -> QPainterPath; + auto ScaleLeftControl() const -> QPainterPath; + + auto RotateTopLeftControl() const -> QPainterPath; + auto RotateTopRightControl() const -> QPainterPath; + auto RotateBottomRightControl() const -> QPainterPath; + auto RotateBottomLeftControl() const -> QPainterPath; + + auto ScaleByTopLeft(QGraphicsSceneMouseEvent * event) const -> QTransform; + auto ScaleByTop(QGraphicsSceneMouseEvent * event) const -> QTransform; + auto ScaleByTopRight(QGraphicsSceneMouseEvent * event) const -> QTransform; + auto ScaleByRight(QGraphicsSceneMouseEvent * event) const -> QTransform; + auto ScaleByBottomRight(QGraphicsSceneMouseEvent * event) const -> QTransform; + auto ScaleByBottom(QGraphicsSceneMouseEvent * event) const -> QTransform; + auto ScaleByBottomLeft(QGraphicsSceneMouseEvent * event) const -> QTransform; + auto ScaleByLeft(QGraphicsSceneMouseEvent * event) const -> QTransform; + + auto Handles() const -> QPainterPath; + auto ControllersRect() const -> QRectF; + + auto SelectedHandleCorner(const QPointF &pos) const -> BIHandleCorner; + + auto HandlerPixmap(bool hover, BIHandleCornerType type) const -> QPixmap; + + void ShowOrigin(const QPointF &pos); + auto OriginCircle1() const -> QPainterPath; + auto OriginCircle2() const -> QPainterPath; + auto OriginPath() const -> QPainterPath; + + void ScaleImage(QGraphicsSceneMouseEvent * event); + void RotateImage(QGraphicsSceneMouseEvent * event); +}; + +#endif // VBACKGROUNDIMAGECONTROLS_H diff --git a/src/libs/vtools/tools/backgroundimage/vbackgroundimageitem.cpp b/src/libs/vtools/tools/backgroundimage/vbackgroundimageitem.cpp new file mode 100644 index 000000000..d4d44913e --- /dev/null +++ b/src/libs/vtools/tools/backgroundimage/vbackgroundimageitem.cpp @@ -0,0 +1,734 @@ +/************************************************************************ + ** + ** @file vbackgroundimageitem.cpp + ** @author Roman Telezhynskyi + ** @date 13 1, 2022 + ** + ** @brief + ** @copyright + ** This source code is part of the Valentina project, a pattern making + ** program, whose allow create and modeling patterns of clothing. + ** Copyright (C) 2022 Valentina project + ** All Rights Reserved. + ** + ** Valentina 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, either version 3 of the License, or + ** (at your option) any later version. + ** + ** Valentina 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 Valentina. If not, see . + ** + *************************************************************************/ + +#include "vbackgroundimageitem.h" +#include "../vwidgets/global.h" +#include "../vmisc/vabstractvalapplication.h" +#include "../vwidgets/vmaingraphicsview.h" +#include "../ifc/xml/vabstractpattern.h" +#include "../../undocommands/image/movebackgroundimage.h" +#include "../../undocommands/image/holdbackgroundimage.h" +#include "../../undocommands/image/rotatebackgroundimage.h" +#include "../../undocommands/image/scalebackgroundimage.h" +#include "../../undocommands/image/renamebackgroundimage.h" +#include "../../undocommands/image/hidebackgroundimage.h" +#include "../../undocommands/image/resetbackgroundimage.h" +#include "../toolsdef.h" + +#include +#include +#include +#include +#include +#include +#include + +//--------------------------------------------------------------------------------------------------------------------- +VBackgroundImageItem::VBackgroundImageItem(const VBackgroundPatternImage &image, VAbstractPattern *doc, + QGraphicsItem *parent) + : QGraphicsObject{parent}, + m_image(image), + m_doc(doc) +{ + SCASSERT(doc != nullptr) + + setAcceptHoverEvents(true); + + connect(doc, &VAbstractPattern::BackgroundImageTransformationChanged, this, + &VBackgroundImageItem::ImageTransformationChanged); + connect(doc, &VAbstractPattern::BackgroundImageHoldChanged, this, &VBackgroundImageItem::HoldChanged); + connect(doc, &VAbstractPattern::BackgroundImageVisibilityChanged, this, &VBackgroundImageItem::VisibilityChanged); + connect(doc, &VAbstractPattern::BackgroundImageNameChanged, this, &VBackgroundImageItem::NameChanged); + connect(doc, &VAbstractPattern::BackgroundImagesHoldChanged, this, &VBackgroundImageItem::UpdateHoldState); + connect(doc, &VAbstractPattern::BackgroundImagesVisibilityChanged, this, + &VBackgroundImageItem::UpdateVisibilityState); + connect(doc, &VAbstractPattern::BackgroundImagesZValueChanged, this, &VBackgroundImageItem::ZValueChanged); + connect(doc, &VAbstractPattern::BackgroundImagePositionChanged, this, &VBackgroundImageItem::PositionChanged); + + InitImage(); +} + +//--------------------------------------------------------------------------------------------------------------------- +auto VBackgroundImageItem::Image() const -> const VBackgroundPatternImage & +{ + return m_image; +} + +//--------------------------------------------------------------------------------------------------------------------- +void VBackgroundImageItem::SetImage(const VBackgroundPatternImage &newImage) +{ + prepareGeometryChange(); + m_image = newImage; + InitImage(); + m_stale = true; +} + +//--------------------------------------------------------------------------------------------------------------------- +auto VBackgroundImageItem::pen() -> QPen +{ + return {QBrush(), 1.0}; +} + +//--------------------------------------------------------------------------------------------------------------------- +auto VBackgroundImageItem::name() const -> QString +{ + return m_image.Name(); +} + +//--------------------------------------------------------------------------------------------------------------------- +void VBackgroundImageItem::setName(const QString &newName) +{ + VAbstractApplication::VApp()->getUndoStack()->push(new RenameBackgroundImage(m_image.Id(), newName, m_doc)); +} + +//--------------------------------------------------------------------------------------------------------------------- +auto VBackgroundImageItem::IsHold() const -> bool +{ + return m_image.Hold(); +} + +//--------------------------------------------------------------------------------------------------------------------- +void VBackgroundImageItem::SetHold(bool hold) +{ + VAbstractApplication::VApp()->getUndoStack()->push(new HoldBackgroundImage(m_image.Id(), hold, m_doc)); +} + +//--------------------------------------------------------------------------------------------------------------------- +auto VBackgroundImageItem::IsVisible() const -> bool +{ + return m_image.Visible(); +} + +//--------------------------------------------------------------------------------------------------------------------- +void VBackgroundImageItem::SetVisible(bool visible) +{ + VAbstractApplication::VApp()->getUndoStack()->push(new HideBackgroundImage(m_image.Id(), not visible, m_doc)); +} + +//--------------------------------------------------------------------------------------------------------------------- +void VBackgroundImageItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) +{ + Q_UNUSED(option) + Q_UNUSED(widget) + + if (m_showHover) + { + painter->save(); + + QBrush brush(QColor(177, 216, 250, 25)); + painter->setBrush(brush); + + painter->drawRect(boundingRect()); + + painter->restore(); + } +} + +//--------------------------------------------------------------------------------------------------------------------- +void VBackgroundImageItem::PositionChanged(QUuid id) +{ + if (m_image.Id() != id) + { + return; + } + + QTransform oldMatrix = m_image.Matrix(); + m_image = m_doc->GetBackgroundImage(id); + QTransform newMatrix = m_image.Matrix(); + + if (not VFuzzyComparePossibleNulls(oldMatrix.m31(), newMatrix.m31()) || + not VFuzzyComparePossibleNulls(oldMatrix.m32(), newMatrix.m32())) + { + prepareGeometryChange(); + update(); + } + + emit UpdateControls(); +} + +//--------------------------------------------------------------------------------------------------------------------- +void VBackgroundImageItem::ImageTransformationChanged(QUuid id) +{ + if (m_image.Id() != id) + { + return; + } + + prepareGeometryChange(); + m_image = m_doc->GetBackgroundImage(id); + + emit UpdateControls(); +} + +//--------------------------------------------------------------------------------------------------------------------- +void VBackgroundImageItem::HoldChanged(QUuid id) +{ + if (m_image.Id() != id) + { + return; + } + + UpdateHoldState(); +} + +//--------------------------------------------------------------------------------------------------------------------- +void VBackgroundImageItem::VisibilityChanged(QUuid id) +{ + if (m_image.Id() != id) + { + return; + } + + UpdateVisibilityState(); +} + +//--------------------------------------------------------------------------------------------------------------------- +void VBackgroundImageItem::NameChanged(QUuid id) +{ + if (m_image.Id() != id) + { + return; + } + + m_image = m_doc->GetBackgroundImage(id); +} + +//--------------------------------------------------------------------------------------------------------------------- +void VBackgroundImageItem::EnableSelection(bool enable) +{ + m_selectable = enable; + setFlag(QGraphicsItem::ItemSendsGeometryChanges, m_selectable && not m_image.Hold()); + setFlag(QGraphicsItem::ItemIsFocusable, m_selectable && not m_image.Hold()); + + if (not m_selectable) + { + emit ActivateControls(QUuid()); + } +} + +//--------------------------------------------------------------------------------------------------------------------- +void VBackgroundImageItem::DeleteFromMenu() +{ + DeleteToolWithConfirm(); +} + +//--------------------------------------------------------------------------------------------------------------------- +auto VBackgroundImageItem::itemChange(GraphicsItemChange change, const QVariant &value) -> QVariant +{ + if (change == ItemPositionChange && (scene() != nullptr)) + { + // Each time we move something we call recalculation scene rect. In some cases this can cause moving + // objects positions. And this cause infinite redrawing. That's why we wait the finish of saving the last move. + static bool changeFinished = true; + if (changeFinished) + { + changeFinished = false; + + // value - this is new position. + const QPointF newPos = value.toPointF(); + const QPointF diff = newPos - m_lastMoveDistance; + + auto *command = new MoveBackgroundImage(m_image.Id(), diff.x(), diff.y(), m_doc, m_allowChangeMerge); + VAbstractApplication::VApp()->getUndoStack()->push(command); + + const QList viewList = scene()->views(); + if (not viewList.isEmpty()) + { + if (auto *view = qobject_cast(viewList.at(0))) + { + view->EnsureItemVisibleWithDelay(this, VMainGraphicsView::scrollDelay); + } + } + + changeFinished = true; + m_lastMoveDistance = newPos; + } + return pos(); + } + + return value; +} + +//--------------------------------------------------------------------------------------------------------------------- +void VBackgroundImageItem::mousePressEvent(QGraphicsSceneMouseEvent *event) +{ + if (not m_selectable) + { + event->ignore(); + return; + } + + if (not Image().Hold()) + { + if (flags() & QGraphicsItem::ItemIsMovable) + { + if (event->button() == Qt::LeftButton && event->type() != QEvent::GraphicsSceneMouseDoubleClick) + { + SetItemOverrideCursor(this, cursorArrowCloseHand, 1, 1); + } + } + + if (event->button() == Qt::LeftButton && event->type() != QEvent::GraphicsSceneMouseDoubleClick) + { + m_lastMoveDistance = QPointF(); + emit Selected(m_image.Id()); + event->accept(); + } + else + { + QGraphicsObject::mousePressEvent(event); + } + } + else + { + if (event->button() == Qt::LeftButton && event->type() != QEvent::GraphicsSceneMouseDoubleClick) + { + emit ActivateControls(m_image.Id()); + } + QGraphicsObject::mousePressEvent(event); + } +} + +//--------------------------------------------------------------------------------------------------------------------- +void VBackgroundImageItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event) +{ + QGraphicsObject::mouseMoveEvent(event); + m_allowChangeMerge = true; + m_wasMoved = true; +} + +//--------------------------------------------------------------------------------------------------------------------- +void VBackgroundImageItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) +{ + if (event->button() == Qt::LeftButton && event->type() != QEvent::GraphicsSceneMouseDoubleClick && + (flags() & QGraphicsItem::ItemIsMovable)) + { + m_lastMoveDistance = QPointF(); + SetItemOverrideCursor(this, cursorArrowOpenHand, 1, 1); + m_allowChangeMerge = false; + if (not m_wasMoved && m_selectable) + { + emit ActivateControls(m_image.Id()); + } + m_wasMoved = false; + } + + QGraphicsObject::mouseReleaseEvent(event); +} + +//--------------------------------------------------------------------------------------------------------------------- +void VBackgroundImageItem::hoverEnterEvent(QGraphicsSceneHoverEvent *event) +{ + if (m_selectable && flags() & QGraphicsItem::ItemIsMovable) + { + m_showHover = true; + SetItemOverrideCursor(this, cursorArrowOpenHand, 1, 1); + } + else + { + setCursor(VAbstractValApplication::VApp()->getSceneView()->viewport()->cursor()); + } + QGraphicsObject::hoverEnterEvent(event); +} + +//--------------------------------------------------------------------------------------------------------------------- +void VBackgroundImageItem::hoverMoveEvent(QGraphicsSceneHoverEvent *event) +{ + if (m_selectable && flags() & QGraphicsItem::ItemIsMovable) + { + SetItemOverrideCursor(this, cursorArrowOpenHand, 1, 1); + } + else + { + setCursor(VAbstractValApplication::VApp()->getSceneView()->viewport()->cursor()); + } + QGraphicsObject::hoverMoveEvent(event); +} + +//--------------------------------------------------------------------------------------------------------------------- +void VBackgroundImageItem::hoverLeaveEvent(QGraphicsSceneHoverEvent *event) +{ + m_showHover = false; + setCursor(VAbstractValApplication::VApp()->getSceneView()->viewport()->cursor()); + QGraphicsObject::hoverLeaveEvent(event); +} + +//--------------------------------------------------------------------------------------------------------------------- +void VBackgroundImageItem::contextMenuEvent(QGraphicsSceneContextMenuEvent *event) +{ + if (not m_selectable) + { + return; + } + + QMenu menu; + + QAction *holdOption = menu.addAction(tr("Hold")); + holdOption->setCheckable(true); + holdOption->setChecked(m_image.Hold()); + + QAction *actionVisible = menu.addAction(tr("Visible")); + actionVisible->setCheckable(true); + actionVisible->setChecked(m_image.Visible()); + + #if defined(Q_OS_MAC) + const QString actionShowTitle = tr("Show in Finder"); +#else + const QString actionShowTitle = tr("Show in Explorer"); +#endif + QAction *actionShow = menu.addAction(QIcon::fromTheme(QStringLiteral("system-search")), actionShowTitle); + actionShow->setVisible(false); + actionShow->setEnabled(QFileInfo::exists(m_image.FilePath())); + + QAction *actionSaveAs = menu.addAction(QIcon::fromTheme(QStringLiteral("document-save-as")), tr("Save as …")); + actionSaveAs->setVisible(false); + + if (not m_image.FilePath().isEmpty()) + { + actionShow->setVisible(true); + } + else if (not m_image.ContentData().isEmpty()) + { + actionSaveAs->setVisible(true); + } + + QAction *actionReset = menu.addAction(tr("Reset transformation")); + actionReset->setEnabled(not m_image.Hold()); + + QAction *actionRemove = menu.addAction(QIcon::fromTheme(QStringLiteral("edit-delete")), tr("Delete")); + + QAction *selectedAction = menu.exec(event->screenPos()); + if (selectedAction == holdOption) + { + SetHold(selectedAction->isChecked()); + } + else if (selectedAction == actionVisible) + { + SetVisible(selectedAction->isChecked()); + } + else if (selectedAction == actionShow) + { + emit ShowImageInExplorer(m_image.Id()); + } + else if (selectedAction == actionSaveAs) + { + emit SaveImage(m_image.Id()); + } + else if (selectedAction == actionReset) + { + VAbstractApplication::VApp()->getUndoStack()->push(new ResetBackgroundImage(m_image.Id(), m_doc)); + } + else if (selectedAction == actionRemove) + { + DeleteFromMenu(); + } +} + +//--------------------------------------------------------------------------------------------------------------------- +void VBackgroundImageItem::keyPressEvent(QKeyEvent *event) +{ + const int move = event->modifiers() & Qt::ShiftModifier ? 10 : 1; + if (event->key() == Qt::Key_Left) + { + TranslateImageOn(-move, 0); + event->accept(); + return; + } + + if (event->key() == Qt::Key_Right) + { + TranslateImageOn(move, 0); + event->accept(); + return; + } + + if (event->key() == Qt::Key_Up) + { + TranslateImageOn(0, -move); + event->accept(); + return; + } + + if (event->key() == Qt::Key_Down) + { + TranslateImageOn(0, move); + event->accept(); + return; + } + + int angle = 15; + if(event->modifiers() & Qt::ControlModifier) + { + angle = 90; + } + else if(event->modifiers() & Qt::AltModifier) + { + angle = 1; + } + + if (event->key() == Qt::Key_BracketLeft) + { + RotateImageByAngle(angle); + event->accept(); + return; + } + + if (event->key() == Qt::Key_BracketRight) + { + RotateImageByAngle(-angle); + event->accept(); + return; + } + + if (event->key() == Qt::Key_Period || event->key() == Qt::Key_Greater) + { + if (event->modifiers() & Qt::ControlModifier) + { + ScaleImageByFactor(2); + } + else + { + ScaleImageByAdjustSize(2); + } + } + + if (event->key() == Qt::Key_Comma || event->key() == Qt::Key_Less) + { + if (event->modifiers() & Qt::ControlModifier) + { + ScaleImageByFactor(0.5); + } + else + { + ScaleImageByAdjustSize(-2); + } + } + + QGraphicsObject::keyPressEvent(event); +} + +//--------------------------------------------------------------------------------------------------------------------- +void VBackgroundImageItem::keyReleaseEvent(QKeyEvent *event) +{ + if (not Image().Hold()) + { + if (event->key() == Qt::Key_Delete) + { + if (ConfirmDeletion() == QMessageBox::Yes) + { + DeleteToolWithConfirm(false); + event->accept(); + return; + } + } + else if (event->key() == Qt::Key_Left || + event->key() == Qt::Key_Right || + event->key() == Qt::Key_Up || + event->key() == Qt::Key_Down || + event->key() == Qt::Key_BracketLeft || + event->key() == Qt::Key_BracketRight || + event->key() == Qt::Key_Period || + event->key() == Qt::Key_Greater || + event->key() == Qt::Key_Comma || + event->key() == Qt::Key_Less) + { + if (not event->isAutoRepeat()) + { + m_allowChangeMerge = false; + } + event->accept(); + return; + } + } + QGraphicsObject::keyReleaseEvent(event); +} + +//--------------------------------------------------------------------------------------------------------------------- +auto VBackgroundImageItem::Stale() const -> bool +{ + return m_stale; +} + +//--------------------------------------------------------------------------------------------------------------------- +void VBackgroundImageItem::MakeFresh() const +{ + m_stale = false; +} + +//--------------------------------------------------------------------------------------------------------------------- +void VBackgroundImageItem::DeleteToolWithConfirm(bool ask) +{ + if (ask) + { + if (ConfirmDeletion() == QMessageBox::No) + { + return; + } + } + + emit ActivateControls(QUuid()); + emit DeleteImage(m_image.Id()); +} + +//--------------------------------------------------------------------------------------------------------------------- +void VBackgroundImageItem::UpdateHoldState() +{ + m_image = m_doc->GetBackgroundImage(m_image.Id()); + setFlag(QGraphicsItem::ItemIsMovable, not m_image.Hold()); + setFlag(QGraphicsItem::ItemSendsGeometryChanges, not m_image.Hold()); + setFlag(QGraphicsItem::ItemIsFocusable, not m_image.Hold());// For keyboard input focus + emit UpdateControls(); +} + +//--------------------------------------------------------------------------------------------------------------------- +void VBackgroundImageItem::UpdateVisibilityState() +{ + m_image = m_doc->GetBackgroundImage(m_image.Id()); + + setVisible(m_image.Visible()); + + if (not m_image.Visible()) + { + emit ActivateControls(QUuid()); + } +} + +//--------------------------------------------------------------------------------------------------------------------- +void VBackgroundImageItem::ZValueChanged() +{ + m_image = m_doc->GetBackgroundImage(m_image.Id()); + + SetupZValue(); +} + +//--------------------------------------------------------------------------------------------------------------------- +void VBackgroundImageItem::InitImage() +{ + SetupZValue(); + + setFlag(QGraphicsItem::ItemIsMovable, not m_image.Hold()); + setFlag(QGraphicsItem::ItemSendsGeometryChanges, not m_image.Hold()); + setFlag(QGraphicsItem::ItemIsFocusable, not m_image.Hold());// For keyboard input focus + + setVisible(m_image.Visible()); +} + +//--------------------------------------------------------------------------------------------------------------------- +void VBackgroundImageItem::TranslateImageOn(qreal dx, qreal dy) +{ + auto *command = new MoveBackgroundImage(m_image.Id(), dx, dy, m_doc, m_allowChangeMerge); + VAbstractApplication::VApp()->getUndoStack()->push(command); + + UpdateSceneRect(); + + m_allowChangeMerge = true; +} + +//--------------------------------------------------------------------------------------------------------------------- +void VBackgroundImageItem::RotateImageByAngle(qreal angle) +{ + QTransform imageMatrix = m_image.Matrix(); + + QPointF originPos = m_image.BoundingRect().center(); + + QTransform m; + m.translate(originPos.x(), originPos.y()); + m.rotate(-angle); + m.translate(-originPos.x(), -originPos.y()); + imageMatrix *= m; + + auto *command = new RotateBackgroundImage(m_image.Id(), imageMatrix, m_doc, m_allowChangeMerge); + VAbstractApplication::VApp()->getUndoStack()->push(command); + + UpdateSceneRect(); + + m_allowChangeMerge = true; +} + +//--------------------------------------------------------------------------------------------------------------------- +void VBackgroundImageItem::ScaleImageByAdjustSize(qreal value) +{ + QRectF rect = m_image.BoundingRect(); + QRectF adjusted = rect; + adjusted.adjust(-value, -value, value, value); + + qreal factor = adjusted.width() / rect.width(); + ScaleImageByFactor(factor); +} + +//--------------------------------------------------------------------------------------------------------------------- +void VBackgroundImageItem::ScaleImageByFactor(qreal factor) +{ + QTransform imageMatrix = m_image.Matrix(); + QPointF originPos = m_image.BoundingRect().center(); + + QTransform m; + m.translate(originPos.x(), originPos.y()); + m.scale(factor, factor); + m.translate(-originPos.x(), -originPos.y()); + imageMatrix *= m; + + auto *command = new ScaleBackgroundImage(m_image.Id(), imageMatrix, m_doc, m_allowChangeMerge); + VAbstractApplication::VApp()->getUndoStack()->push(command); + + UpdateSceneRect(); + + m_allowChangeMerge = true; +} + +//--------------------------------------------------------------------------------------------------------------------- +void VBackgroundImageItem::UpdateSceneRect() +{ + const QList viewList = scene()->views(); + if (not viewList.isEmpty()) + { + if (auto *view = qobject_cast(viewList.at(0))) + { + setFlag(QGraphicsItem::ItemSendsGeometryChanges, false); + VMainGraphicsView::NewSceneRect(scene(), view); + setFlag(QGraphicsItem::ItemSendsGeometryChanges, not m_image.Hold()); + } + } +} + +//--------------------------------------------------------------------------------------------------------------------- +void VBackgroundImageItem::SetupZValue() +{ + if (qFuzzyIsNull(m_image.ZValue())) + { + setZValue(-1); + } + else if (m_image.ZValue() > 0) + { + setZValue(-1 * m_image.ZValue() - 1); + } + else + { + setZValue(m_image.ZValue() - 1); + } +} diff --git a/src/libs/vtools/tools/backgroundimage/vbackgroundimageitem.h b/src/libs/vtools/tools/backgroundimage/vbackgroundimageitem.h new file mode 100644 index 000000000..a779ab9cb --- /dev/null +++ b/src/libs/vtools/tools/backgroundimage/vbackgroundimageitem.h @@ -0,0 +1,133 @@ +/************************************************************************ + ** + ** @file vbackgroundimageitem.h + ** @author Roman Telezhynskyi + ** @date 13 1, 2022 + ** + ** @brief + ** @copyright + ** This source code is part of the Valentina project, a pattern making + ** program, whose allow create and modeling patterns of clothing. + ** Copyright (C) 2022 Valentina project + ** All Rights Reserved. + ** + ** Valentina 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, either version 3 of the License, or + ** (at your option) any later version. + ** + ** Valentina 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 Valentina. If not, see . + ** + *************************************************************************/ +#ifndef VBACKGROUNDIMAGEITEM_H +#define VBACKGROUNDIMAGEITEM_H + +#include + +#include "../ifc/xml/vbackgroundpatternimage.h" +#include "../vmisc/def.h" + +#if QT_VERSION < QT_VERSION_CHECK(5, 13, 0) +#include "../vmisc/defglobal.h" +#endif // QT_VERSION < QT_VERSION_CHECK(5, 13, 0) + +class VAbstractPattern; + +class VBackgroundImageItem : public QGraphicsObject +{ + Q_OBJECT +public: + VBackgroundImageItem(const VBackgroundPatternImage &image, VAbstractPattern *doc, QGraphicsItem *parent = nullptr); + ~VBackgroundImageItem() override = default; + + auto type() const -> int override {return Type;} + enum {Type = UserType + static_cast(Tool::BackgroundImage)}; + + auto Image() const -> const VBackgroundPatternImage &; + void SetImage(const VBackgroundPatternImage &newImage); + + static auto pen() -> QPen; + + auto name() const -> QString; + void setName(const QString &newName); + + auto IsHold() const -> bool; + void SetHold(bool hold); + + auto IsVisible() const -> bool; + void SetVisible(bool visible); + + void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override; + +signals: + void Selected(const QUuid &id); + void UpdateControls(); + void ActivateControls(const QUuid &id); + void DeleteImage(const QUuid &id); + void ShowImageInExplorer(const QUuid &id); + void SaveImage(const QUuid &id); + +public slots: + void PositionChanged(QUuid id); + void ImageTransformationChanged(QUuid id); + void HoldChanged(QUuid id); + void VisibilityChanged(QUuid id); + void NameChanged(QUuid id); + void EnableSelection(bool enable); + void DeleteFromMenu(); + +protected: + auto itemChange(GraphicsItemChange change, const QVariant &value) -> QVariant override; + void mousePressEvent( QGraphicsSceneMouseEvent * event) override; + void mouseMoveEvent ( QGraphicsSceneMouseEvent * event ) override; + void mouseReleaseEvent ( QGraphicsSceneMouseEvent * event ) override; + void hoverEnterEvent ( QGraphicsSceneHoverEvent * event ) override; + void hoverMoveEvent ( QGraphicsSceneHoverEvent * event ) override; + void hoverLeaveEvent(QGraphicsSceneHoverEvent *event) override; + void contextMenuEvent ( QGraphicsSceneContextMenuEvent * event ) override; + void keyPressEvent(QKeyEvent *event) override; + void keyReleaseEvent(QKeyEvent * event) override; + + auto Stale() const -> bool; + void MakeFresh() const; + void DeleteToolWithConfirm(bool ask = true); + + auto Invalid() const -> bool; + void SetInvalid(bool newInvalid); + +private slots: + void UpdateHoldState(); + void UpdateVisibilityState(); + void ZValueChanged(); + +private: + Q_DISABLE_COPY_MOVE(VBackgroundImageItem) + + VBackgroundPatternImage m_image; + VAbstractPattern *m_doc; + mutable bool m_stale{true}; + bool m_allowChangeMerge{false}; + QPointF m_lastMoveDistance{}; + bool m_wasMoved{false}; + bool m_showHover{false}; + bool m_selectable{true}; + + void InitImage(); + + void TranslateImageOn(qreal dx, qreal dy); + void RotateImageByAngle(qreal angle); + void ScaleImageByAdjustSize(qreal value); + void ScaleImageByFactor(qreal factor); + + void UpdateSceneRect(); + + void SetupZValue(); +}; + +#endif // VBACKGROUNDIMAGEITEM_H diff --git a/src/libs/vtools/tools/backgroundimage/vbackgroundpixmapitem.cpp b/src/libs/vtools/tools/backgroundimage/vbackgroundpixmapitem.cpp new file mode 100644 index 000000000..71052ff4a --- /dev/null +++ b/src/libs/vtools/tools/backgroundimage/vbackgroundpixmapitem.cpp @@ -0,0 +1,245 @@ +/************************************************************************ + ** + ** @file vbackgroundpixmapitem.cpp + ** @author Roman Telezhynskyi + ** @date 15 1, 2022 + ** + ** @brief + ** @copyright + ** This source code is part of the Valentina project, a pattern making + ** program, whose allow create and modeling patterns of clothing. + ** Copyright (C) 2022 Valentina project + ** All Rights Reserved. + ** + ** Valentina 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, either version 3 of the License, or + ** (at your option) any later version. + ** + ** Valentina 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 Valentina. If not, see . + ** + *************************************************************************/ +#include "vbackgroundpixmapitem.h" + +#include +#include +#include +#include +#include + +extern auto qt_regionToPath(const QRegion ®ion) -> QPainterPath; + +namespace +{ +auto InvalidImage() -> QPixmap +{ + QImageReader imageReader(QStringLiteral("://icon/svg/broken_path.svg")); + return std::move(QPixmap::fromImageReader(&imageReader)); +} +} + +//--------------------------------------------------------------------------------------------------------------------- +VBackgroundPixmapItem::VBackgroundPixmapItem(const VBackgroundPatternImage &image, VAbstractPattern *doc, + QGraphicsItem *parent) + : VBackgroundImageItem(image, doc, parent) +{} + +//--------------------------------------------------------------------------------------------------------------------- +void VBackgroundPixmapItem::SetTransformationMode(Qt::TransformationMode mode) +{ + m_transformationMode = mode; + update(); +} + +//--------------------------------------------------------------------------------------------------------------------- +auto VBackgroundPixmapItem::boundingRect() const -> QRectF +{ + QPixmap pixmap = Pixmap(); + if (pixmap.isNull()) + { + return {}; + } + + return Image().Matrix().mapRect(QRectF(QPointF(0, 0), pixmap.size() / pixmap.devicePixelRatio())); +} + +//--------------------------------------------------------------------------------------------------------------------- +auto VBackgroundPixmapItem::shape() const -> QPainterPath +{ + if (!m_hasShape) + { + UpdateShape(); + m_hasShape = true; + } + return Image().Matrix().map(m_shape); +} + +//--------------------------------------------------------------------------------------------------------------------- +auto VBackgroundPixmapItem::contains(const QPointF &point) const -> bool +{ + return QGraphicsItem::contains(point); +} + +//--------------------------------------------------------------------------------------------------------------------- +void VBackgroundPixmapItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) +{ + painter->setRenderHint(QPainter::SmoothPixmapTransform, (m_transformationMode == Qt::SmoothTransformation)); + + painter->save(); + painter->setTransform(Image().Matrix(), true); + + painter->drawPixmap(QPointF(), Pixmap()); + + painter->restore(); + + VBackgroundImageItem::paint(painter, option, widget); +} + +//--------------------------------------------------------------------------------------------------------------------- +auto VBackgroundPixmapItem::isObscuredBy(const QGraphicsItem *item) const -> bool +{ + return QGraphicsItem::isObscuredBy(item); +} + +//--------------------------------------------------------------------------------------------------------------------- +auto VBackgroundPixmapItem::opaqueArea() const -> QPainterPath +{ + return shape(); +} + +//--------------------------------------------------------------------------------------------------------------------- +auto VBackgroundPixmapItem::ShapeMode() const -> enum ShapeMode +{ + return m_shapeMode; +} + +//--------------------------------------------------------------------------------------------------------------------- +void VBackgroundPixmapItem::SetShapeMode(enum ShapeMode mode) +{ + if (m_shapeMode == mode) + { + return; + } + m_shapeMode = mode; + m_hasShape = false; +} + +//--------------------------------------------------------------------------------------------------------------------- +auto VBackgroundPixmapItem::supportsExtension(Extension extension) const -> bool +{ + Q_UNUSED(extension); + return false; +} + +//--------------------------------------------------------------------------------------------------------------------- +void VBackgroundPixmapItem::setExtension(Extension extension, const QVariant &variant) +{ + Q_UNUSED(extension); + Q_UNUSED(variant); +} + +//--------------------------------------------------------------------------------------------------------------------- +auto VBackgroundPixmapItem::extension(const QVariant &variant) const -> QVariant +{ + Q_UNUSED(variant); + return {}; +} + +//--------------------------------------------------------------------------------------------------------------------- +void VBackgroundPixmapItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event) +{ + SetTransformationMode(Qt::FastTransformation); + VBackgroundImageItem::mouseMoveEvent(event); +} + +//--------------------------------------------------------------------------------------------------------------------- +void VBackgroundPixmapItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) +{ + SetTransformationMode(Qt::SmoothTransformation); + VBackgroundImageItem::mouseReleaseEvent(event); +} + +//--------------------------------------------------------------------------------------------------------------------- +void VBackgroundPixmapItem::UpdateShape() const +{ + QPixmap pixmap = Pixmap(); + m_shape = QPainterPath(); + switch (m_shapeMode) + { + case ShapeMode::MaskShape: + { + QBitmap mask = pixmap.mask(); + if (!mask.isNull()) + { + m_shape = qt_regionToPath(QRegion(mask)); + break; + } + Q_FALLTHROUGH(); + } + case ShapeMode::BoundingRectShape: + m_shape.addRect(QRectF(0, 0, pixmap.width(), pixmap.height())); + break; + case ShapeMode::HeuristicMaskShape: +#ifndef QT_NO_IMAGE_HEURISTIC_MASK + m_shape = qt_regionToPath(QRegion(pixmap.createHeuristicMask())); +#else + m_shape.addRect(QRectF(0, 0, m_shape.width(), m_shape.height())); +#endif + break; + default: + break; + } +} + +//--------------------------------------------------------------------------------------------------------------------- +auto VBackgroundPixmapItem::Pixmap() const -> QPixmap +{ + if (Stale()) + { + m_pixmap = QPixmap(); + + VBackgroundPatternImage image = Image(); + if (not image.IsValid()) + { + m_pixmap = InvalidImage(); + MakeFresh(); + return m_pixmap; + } + + if (not image.FilePath().isEmpty()) + { + QImageReader imageReader(image.FilePath()); + m_pixmap = QPixmap::fromImageReader(&imageReader); + if (m_pixmap.isNull()) + { + m_pixmap = InvalidImage(); + } + MakeFresh(); + return m_pixmap; + } + + if (not image.ContentData().isEmpty()) + { + QByteArray array = QByteArray::fromBase64(image.ContentData()); + QBuffer buffer(&array); + buffer.open(QIODevice::ReadOnly); + + QImageReader imageReader(&buffer); + m_pixmap = QPixmap::fromImageReader(&imageReader); + if (m_pixmap.isNull()) + { + m_pixmap = InvalidImage(); + } + MakeFresh(); + return m_pixmap; + } + } + + return m_pixmap; +} diff --git a/src/libs/vtools/tools/backgroundimage/vbackgroundpixmapitem.h b/src/libs/vtools/tools/backgroundimage/vbackgroundpixmapitem.h new file mode 100644 index 000000000..fa52beaf6 --- /dev/null +++ b/src/libs/vtools/tools/backgroundimage/vbackgroundpixmapitem.h @@ -0,0 +1,88 @@ +/************************************************************************ + ** + ** @file vbackgroundpixmapitem.h + ** @author Roman Telezhynskyi + ** @date 15 1, 2022 + ** + ** @brief + ** @copyright + ** This source code is part of the Valentina project, a pattern making + ** program, whose allow create and modeling patterns of clothing. + ** Copyright (C) 2022 Valentina project + ** All Rights Reserved. + ** + ** Valentina 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, either version 3 of the License, or + ** (at your option) any later version. + ** + ** Valentina 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 Valentina. If not, see . + ** + *************************************************************************/ +#ifndef VBACKGROUNDPIXMAPITEM_H +#define VBACKGROUNDPIXMAPITEM_H + +#include "vbackgroundimageitem.h" + +#if QT_VERSION < QT_VERSION_CHECK(5, 13, 0) +#include "../vmisc/defglobal.h" +#endif // QT_VERSION < QT_VERSION_CHECK(5, 13, 0) + +enum class ShapeMode +{ + MaskShape, + BoundingRectShape, + HeuristicMaskShape +}; + +class VBackgroundPixmapItem : public VBackgroundImageItem +{ +public: + VBackgroundPixmapItem(const VBackgroundPatternImage &image, VAbstractPattern *doc, QGraphicsItem *parent = nullptr); + ~VBackgroundPixmapItem() override = default; + + auto type() const -> int override {return Type;} + enum {Type = UserType + static_cast(Tool::BackgroundPixmapImage)}; + + auto boundingRect() const -> QRectF override; + auto shape() const -> QPainterPath override; + auto contains(const QPointF &point) const -> bool override; + + void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override; + + auto isObscuredBy(const QGraphicsItem *item) const -> bool override; + auto opaqueArea() const -> QPainterPath override; + + auto ShapeMode() const -> ShapeMode; + void SetShapeMode(enum ShapeMode mode); + +protected: + auto supportsExtension(Extension extension) const -> bool override; + void setExtension(Extension extension, const QVariant &variant) override; + auto extension(const QVariant &variant) const -> QVariant override; + void mouseMoveEvent(QGraphicsSceneMouseEvent *event) override; + void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) override; + +private: + Q_DISABLE_COPY_MOVE(VBackgroundPixmapItem) + + mutable QPixmap m_pixmap{}; + Qt::TransformationMode m_transformationMode{Qt::SmoothTransformation}; + enum ShapeMode m_shapeMode{ShapeMode::MaskShape}; + mutable QPainterPath m_shape{}; + mutable bool m_hasShape{false}; + + void SetTransformationMode(Qt::TransformationMode mode); + + void UpdateShape() const; + + auto Pixmap() const -> QPixmap; +}; + +#endif // VBACKGROUNDPIXMAPITEM_H diff --git a/src/libs/vtools/tools/backgroundimage/vbackgroundsvgitem.cpp b/src/libs/vtools/tools/backgroundimage/vbackgroundsvgitem.cpp new file mode 100644 index 000000000..3c4e0836d --- /dev/null +++ b/src/libs/vtools/tools/backgroundimage/vbackgroundsvgitem.cpp @@ -0,0 +1,123 @@ +/************************************************************************ + ** + ** @file vbackgroundsvgitem.cpp + ** @author Roman Telezhynskyi + ** @date 15 1, 2022 + ** + ** @brief + ** @copyright + ** This source code is part of the Valentina project, a pattern making + ** program, whose allow create and modeling patterns of clothing. + ** Copyright (C) 2022 Valentina project + ** All Rights Reserved. + ** + ** Valentina 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, either version 3 of the License, or + ** (at your option) any later version. + ** + ** Valentina 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 Valentina. If not, see . + ** + *************************************************************************/ +#include "vbackgroundsvgitem.h" + +#include +#include +#include +#include + +//--------------------------------------------------------------------------------------------------------------------- +VBackgroundSVGItem::VBackgroundSVGItem(const VBackgroundPatternImage &image, VAbstractPattern *doc, + QGraphicsItem *parent) + : VBackgroundImageItem(image, doc, parent), + m_renderer(new QSvgRenderer()) +{ + setCacheMode(QGraphicsItem::DeviceCoordinateCache); + + QObject::connect(m_renderer, &QSvgRenderer::repaintNeeded, this, &VBackgroundSVGItem::RepaintItem); +} + +//--------------------------------------------------------------------------------------------------------------------- +VBackgroundSVGItem::~VBackgroundSVGItem() +{ + delete m_renderer; +} + +//--------------------------------------------------------------------------------------------------------------------- +auto VBackgroundSVGItem::boundingRect() const -> QRectF +{ + return Image().Matrix().mapRect(QRectF(QPointF(0, 0), Renderer()->defaultSize())); +} + +//--------------------------------------------------------------------------------------------------------------------- +void VBackgroundSVGItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) +{ + QSvgRenderer *renderer = Renderer(); + + if (not renderer->isValid()) + { + return; + } + + painter->save(); + painter->setTransform(Image().Matrix(), true); + + renderer->render(painter, QRectF(QPointF(0, 0), renderer->defaultSize())); + + painter->restore(); + + VBackgroundImageItem::paint(painter, option, widget); +} + +//--------------------------------------------------------------------------------------------------------------------- +void VBackgroundSVGItem::RepaintItem() +{ + update(); +} + +//--------------------------------------------------------------------------------------------------------------------- +auto VBackgroundSVGItem::Renderer() const -> QSvgRenderer * +{ + if (Stale()) + { + const QString brokenImage = QStringLiteral("://icon/svg/broken_path.svg"); + m_renderer->load(brokenImage); + + VBackgroundPatternImage image = Image(); + if (not image.IsValid()) + { + MakeFresh(); + return m_renderer; + } + + if (not image.FilePath().isEmpty()) + { + m_renderer->load(image.FilePath()); + if (not m_renderer->isValid()) + { + m_renderer->load(brokenImage); + } + MakeFresh(); + return m_renderer; + } + + if (not image.ContentData().isEmpty()) + { + m_renderer->load(QByteArray::fromBase64(image.ContentData())); + if (not m_renderer->isValid()) + { + m_renderer->load(brokenImage); + } + MakeFresh(); + return m_renderer; + } + } + + return m_renderer; +} diff --git a/src/libs/vtools/tools/backgroundimage/vbackgroundsvgitem.h b/src/libs/vtools/tools/backgroundimage/vbackgroundsvgitem.h new file mode 100644 index 000000000..1e4c934ca --- /dev/null +++ b/src/libs/vtools/tools/backgroundimage/vbackgroundsvgitem.h @@ -0,0 +1,63 @@ +/************************************************************************ + ** + ** @file vbackgroundsvgitem.h + ** @author Roman Telezhynskyi + ** @date 15 1, 2022 + ** + ** @brief + ** @copyright + ** This source code is part of the Valentina project, a pattern making + ** program, whose allow create and modeling patterns of clothing. + ** Copyright (C) 2022 Valentina project + ** All Rights Reserved. + ** + ** Valentina 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, either version 3 of the License, or + ** (at your option) any later version. + ** + ** Valentina 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 Valentina. If not, see . + ** + *************************************************************************/ +#ifndef VBACKGROUNDSVGITEM_H +#define VBACKGROUNDSVGITEM_H + +#include "vbackgroundimageitem.h" + +#if QT_VERSION < QT_VERSION_CHECK(5, 13, 0) +#include "../vmisc/defglobal.h" +#endif // QT_VERSION < QT_VERSION_CHECK(5, 13, 0) + +class QSvgRenderer; + +class VBackgroundSVGItem : public VBackgroundImageItem +{ + Q_OBJECT +public: + VBackgroundSVGItem(const VBackgroundPatternImage &image, VAbstractPattern *doc, QGraphicsItem *parent = nullptr); + ~VBackgroundSVGItem() override; + + auto type() const -> int override {return Type;} + enum {Type = UserType + static_cast(Tool::BackgroundSVGImage)}; + + auto boundingRect() const -> QRectF override; + void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override; + +private slots: + void RepaintItem(); + +private: + Q_DISABLE_COPY_MOVE(VBackgroundSVGItem) + + QSvgRenderer *m_renderer{nullptr}; + + auto Renderer() const -> QSvgRenderer *; +}; + +#endif // VBACKGROUNDSVGITEM_H diff --git a/src/libs/vtools/tools/tools.pri b/src/libs/vtools/tools/tools.pri index 93eec3025..6fddcfced 100644 --- a/src/libs/vtools/tools/tools.pri +++ b/src/libs/vtools/tools/tools.pri @@ -2,7 +2,11 @@ # This need for corect working file translations.pro HEADERS += \ + $$PWD/backgroundimage/vbackgroundimagecontrols.h \ + $$PWD/backgroundimage/vbackgroundpixmapitem.h \ + $$PWD/backgroundimage/vbackgroundsvgitem.h \ $$PWD/toolsdef.h \ + $$PWD/backgroundimage/vbackgroundimageitem.h \ $$PWD/vdatatool.h \ $$PWD/vabstracttool.h \ $$PWD/tools.h \ @@ -65,7 +69,11 @@ HEADERS += \ $$PWD/nodeDetails/vtoolplacelabel.h SOURCES += \ + $$PWD/backgroundimage/vbackgroundimagecontrols.cpp \ + $$PWD/backgroundimage/vbackgroundpixmapitem.cpp \ + $$PWD/backgroundimage/vbackgroundsvgitem.cpp \ $$PWD/toolsdef.cpp \ + $$PWD/backgroundimage/vbackgroundimageitem.cpp \ $$PWD/vdatatool.cpp \ $$PWD/vabstracttool.cpp \ $$PWD/drawTools/toolpoint/toolsinglepoint/vtooltriangle.cpp \ diff --git a/src/libs/vtools/tools/toolsdef.cpp b/src/libs/vtools/tools/toolsdef.cpp index 76cde66f3..69b052977 100644 --- a/src/libs/vtools/tools/toolsdef.cpp +++ b/src/libs/vtools/tools/toolsdef.cpp @@ -29,16 +29,21 @@ #include "toolsdef.h" #include +#include #include +#include #include #include #include #include #include +#include #include "../vgeometry/vgobject.h" #include "../qmuparser/qmudef.h" #include "../vpatterndb/vcontainer.h" +#include "../vpropertyexplorer/checkablemessagebox.h" +#include "../vmisc/vabstractvalapplication.h" //--------------------------------------------------------------------------------------------------------------------- QVector SourceToObjects(const QVector &source) @@ -116,3 +121,28 @@ QMap OperationLineStylesPics() map.insert(TypeLineDefault, QIcon()); return map; } + +//--------------------------------------------------------------------------------------------------------------------- +auto ConfirmDeletion() -> int +{ + if (not VAbstractApplication::VApp()->Settings()->GetConfirmItemDelete()) + { + return QMessageBox::Yes; + } + + Utils::CheckableMessageBox msgBox(VAbstractValApplication::VApp()->getMainWindow()); + msgBox.setWindowTitle(QObject::tr("Confirm deletion")); + msgBox.setText(QObject::tr("Do you really want to delete?")); + msgBox.setStandardButtons(QDialogButtonBox::Yes | QDialogButtonBox::No); + msgBox.setDefaultButton(QDialogButtonBox::No); + msgBox.setIconPixmap(QApplication::style()->standardIcon(QStyle::SP_MessageBoxQuestion).pixmap(32, 32) ); + + int dialogResult = msgBox.exec(); + + if (dialogResult == QDialog::Accepted) + { + VAbstractApplication::VApp()->Settings()->SetConfirmItemDelete(not msgBox.isChecked()); + } + + return dialogResult == QDialog::Accepted ? QMessageBox::Yes : QMessageBox::No; +} diff --git a/src/libs/vtools/tools/toolsdef.h b/src/libs/vtools/tools/toolsdef.h index 52a849942..e68211300 100644 --- a/src/libs/vtools/tools/toolsdef.h +++ b/src/libs/vtools/tools/toolsdef.h @@ -60,4 +60,6 @@ bool SourceAliasValid(const SourceItem &item, const QSharedPointer &ob QMap OperationLineStylesPics(); +int ConfirmDeletion(); + #endif // TOOLSDEF_H diff --git a/src/libs/vtools/tools/vabstracttool.cpp b/src/libs/vtools/tools/vabstracttool.cpp index 81bf46e33..46a6a0728 100644 --- a/src/libs/vtools/tools/vabstracttool.cpp +++ b/src/libs/vtools/tools/vabstracttool.cpp @@ -30,15 +30,11 @@ #include #include -#include #include #include #include #include -#include #include -#include -#include #include #include #include @@ -48,14 +44,13 @@ #include #include #include -#include #include #include #include #include +#include #include "../vgeometry/vpointf.h" -#include "../vpropertyexplorer/checkablemessagebox.h" #include "../vwidgets/vmaingraphicsview.h" #include "../ifc/exception/vexception.h" #include "../ifc/exception/vexceptionundo.h" @@ -77,6 +72,7 @@ #include "nodeDetails/nodedetails.h" #include "../dialogs/support/dialogundo.h" #include "../dialogs/support/dialogeditwrongformula.h" +#include "toolsdef.h" template class QSharedPointer; @@ -246,31 +242,6 @@ void VAbstractTool::PerformDelete() VAbstractApplication::VApp()->getUndoStack()->push(delTool); } -//--------------------------------------------------------------------------------------------------------------------- -int VAbstractTool::ConfirmDeletion() -{ - if (false == VAbstractApplication::VApp()->Settings()->GetConfirmItemDelete()) - { - return QMessageBox::Yes; - } - - Utils::CheckableMessageBox msgBox(VAbstractValApplication::VApp()->getMainWindow()); - msgBox.setWindowTitle(tr("Confirm deletion")); - msgBox.setText(tr("Do you really want to delete?")); - msgBox.setStandardButtons(QDialogButtonBox::Yes | QDialogButtonBox::No); - msgBox.setDefaultButton(QDialogButtonBox::No); - msgBox.setIconPixmap(QApplication::style()->standardIcon(QStyle::SP_MessageBoxQuestion).pixmap(32, 32) ); - - int dialogResult = msgBox.exec(); - - if (dialogResult == QDialog::Accepted) - { - VAbstractApplication::VApp()->Settings()->SetConfirmItemDelete(not msgBox.isChecked()); - } - - return dialogResult == QDialog::Accepted ? QMessageBox::Yes : QMessageBox::No; -} - //--------------------------------------------------------------------------------------------------------------------- const QStringList VAbstractTool::Colors() { diff --git a/src/libs/vtools/tools/vabstracttool.h b/src/libs/vtools/tools/vabstracttool.h index aa156f780..b7d4de8ba 100644 --- a/src/libs/vtools/tools/vabstracttool.h +++ b/src/libs/vtools/tools/vabstracttool.h @@ -160,7 +160,6 @@ protected: virtual void RemoveReferens() {} virtual void DeleteToolWithConfirm(bool ask = true); virtual void PerformDelete(); - static int ConfirmDeletion(); template static quint32 CreateNode(VContainer *data, quint32 id); diff --git a/src/libs/vtools/tools/vtoolseamallowance.cpp b/src/libs/vtools/tools/vtoolseamallowance.cpp index 6b71582a3..23a68d068 100644 --- a/src/libs/vtools/tools/vtoolseamallowance.cpp +++ b/src/libs/vtools/tools/vtoolseamallowance.cpp @@ -55,6 +55,7 @@ #include "../vwidgets/vabstractmainwindow.h" #include "../qmuparser/qmutokenparser.h" #include "../vlayout/vlayoutdef.h" +#include "toolsdef.h" #include #include @@ -549,7 +550,7 @@ void VToolSeamAllowance::ConnectOutsideSignals() connect(doc, &VAbstractPattern::CheckLayout, this, &VToolSeamAllowance::UpdateGrainline); connect(m_sceneDetails, &VMainGraphicsScene::EnableToolMove, this, &VToolSeamAllowance::EnableToolMove); - connect(m_sceneDetails, &VMainGraphicsScene::ItemClicked, this, &VToolSeamAllowance::ResetChildren); + connect(m_sceneDetails, &VMainGraphicsScene::ItemByMousePress, this, &VToolSeamAllowance::ResetChildren); connect(m_sceneDetails, &VMainGraphicsScene::DimensionsChanged, this, &VToolSeamAllowance::UpdateDetailLabel); connect(m_sceneDetails, &VMainGraphicsScene::DimensionsChanged, this, &VToolSeamAllowance::UpdatePatternInfo); connect(m_sceneDetails, &VMainGraphicsScene::LanguageChanged, this, &VToolSeamAllowance::retranslateUi); diff --git a/src/libs/vtools/undocommands/image/addbackgroundimage.cpp b/src/libs/vtools/undocommands/image/addbackgroundimage.cpp new file mode 100644 index 000000000..e3b39ec41 --- /dev/null +++ b/src/libs/vtools/undocommands/image/addbackgroundimage.cpp @@ -0,0 +1,58 @@ +/************************************************************************ + ** + ** @file addbackgroundimage.cpp + ** @author Roman Telezhynskyi + ** @date 13 1, 2022 + ** + ** @brief + ** @copyright + ** This source code is part of the Valentina project, a pattern making + ** program, whose allow create and modeling patterns of clothing. + ** Copyright (C) 2022 Valentina project + ** All Rights Reserved. + ** + ** Valentina 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, either version 3 of the License, or + ** (at your option) any later version. + ** + ** Valentina 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 Valentina. If not, see . + ** + *************************************************************************/ +#include "addbackgroundimage.h" + +//--------------------------------------------------------------------------------------------------------------------- +AddBackgroundImage::AddBackgroundImage(const VBackgroundPatternImage &image, VAbstractPattern *doc, + QUndoCommand *parent) + : VUndoCommand(QDomElement(), doc, parent), + m_image(image) +{ + setText(tr("add background image")); +} + +//--------------------------------------------------------------------------------------------------------------------- +void AddBackgroundImage::undo() +{ + qCDebug(vUndo, "Undo."); + + doc->DeleteBackgroundImage(m_image.Id()); + emit DeleteItem(m_image.Id()); +} + +//--------------------------------------------------------------------------------------------------------------------- +void AddBackgroundImage::redo() +{ + qCDebug(vUndo, "Redo."); + + // Find and remove if already exists + doc->DeleteBackgroundImage(m_image.Id()); + + doc->SaveBackgroundImage(m_image); + emit AddItem(m_image.Id()); +} diff --git a/src/libs/vtools/undocommands/image/addbackgroundimage.h b/src/libs/vtools/undocommands/image/addbackgroundimage.h new file mode 100644 index 000000000..979e43e14 --- /dev/null +++ b/src/libs/vtools/undocommands/image/addbackgroundimage.h @@ -0,0 +1,54 @@ +/************************************************************************ + ** + ** @file addbackgroundimage.h + ** @author Roman Telezhynskyi + ** @date 13 1, 2022 + ** + ** @brief + ** @copyright + ** This source code is part of the Valentina project, a pattern making + ** program, whose allow create and modeling patterns of clothing. + ** Copyright (C) 2022 Valentina project + ** All Rights Reserved. + ** + ** Valentina 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, either version 3 of the License, or + ** (at your option) any later version. + ** + ** Valentina 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 Valentina. If not, see . + ** + *************************************************************************/ +#ifndef ADDBACKGROUNDIMAGE_H +#define ADDBACKGROUNDIMAGE_H + +#include "../vundocommand.h" +#include "../ifc/xml/vbackgroundpatternimage.h" + +#if QT_VERSION < QT_VERSION_CHECK(5, 13, 0) +#include "../vmisc/defglobal.h" +#endif // QT_VERSION < QT_VERSION_CHECK(5, 13, 0) + +class AddBackgroundImage : public VUndoCommand +{ + Q_OBJECT +public: + AddBackgroundImage(const VBackgroundPatternImage& image, VAbstractPattern *doc, QUndoCommand *parent = nullptr); + ~AddBackgroundImage() override =default; + void undo() override; + void redo() override; +signals: + void AddItem(const QUuid &id); + void DeleteItem(const QUuid &id); +private: + Q_DISABLE_COPY_MOVE(AddBackgroundImage) + VBackgroundPatternImage m_image; +}; + +#endif // ADDBACKGROUNDIMAGE_H diff --git a/src/libs/vtools/undocommands/image/deletebackgroundimage.cpp b/src/libs/vtools/undocommands/image/deletebackgroundimage.cpp new file mode 100644 index 000000000..14517493a --- /dev/null +++ b/src/libs/vtools/undocommands/image/deletebackgroundimage.cpp @@ -0,0 +1,78 @@ +/************************************************************************ + ** + ** @file deletebackgroundimage.cpp + ** @author Roman Telezhynskyi + ** @date 13 1, 2022 + ** + ** @brief + ** @copyright + ** This source code is part of the Valentina project, a pattern making + ** program, whose allow create and modeling patterns of clothing. + ** Copyright (C) 2022 Valentina project + ** All Rights Reserved. + ** + ** Valentina 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, either version 3 of the License, or + ** (at your option) any later version. + ** + ** Valentina 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 Valentina. If not, see . + ** + *************************************************************************/ +#include "deletebackgroundimage.h" + +//--------------------------------------------------------------------------------------------------------------------- +DeleteBackgroundImage::DeleteBackgroundImage(const VBackgroundPatternImage &image, VAbstractPattern *doc, + QUndoCommand *parent) + : VUndoCommand(QDomElement(), doc, parent), + m_image(image) +{ + setText(tr("delete background image")); + + QVector allImages = doc->GetBackgroundImages(); + + for (int i = 0; i < allImages.size(); ++i) + { + if (allImages.at(i).Id() == image.Id()) + { + m_index = i; + break; + } + } +} + +//--------------------------------------------------------------------------------------------------------------------- +void DeleteBackgroundImage::undo() +{ + qCDebug(vUndo, "Undo."); + + QVector allImages = doc->GetBackgroundImages(); + + if (m_index == -1) + { + doc->SaveBackgroundImage(m_image); + } + else + { + allImages.insert(m_index, m_image); + doc->SaveBackgroundImages(allImages); + } + + emit AddItem(m_image.Id()); +} + +//--------------------------------------------------------------------------------------------------------------------- +void DeleteBackgroundImage::redo() +{ + qCDebug(vUndo, "Redo."); + + doc->DeleteBackgroundImage(m_image.Id()); + + emit DeleteItem(m_image.Id()); +} diff --git a/src/libs/vtools/undocommands/image/deletebackgroundimage.h b/src/libs/vtools/undocommands/image/deletebackgroundimage.h new file mode 100644 index 000000000..74f13a3b7 --- /dev/null +++ b/src/libs/vtools/undocommands/image/deletebackgroundimage.h @@ -0,0 +1,56 @@ +/************************************************************************ + ** + ** @file deletebackgroundimage.h + ** @author Roman Telezhynskyi + ** @date 13 1, 2022 + ** + ** @brief + ** @copyright + ** This source code is part of the Valentina project, a pattern making + ** program, whose allow create and modeling patterns of clothing. + ** Copyright (C) 2022 Valentina project + ** All Rights Reserved. + ** + ** Valentina 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, either version 3 of the License, or + ** (at your option) any later version. + ** + ** Valentina 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 Valentina. If not, see . + ** + *************************************************************************/ +#ifndef DELETEBACKGROUNDIMAGE_H +#define DELETEBACKGROUNDIMAGE_H + +#include "../vundocommand.h" +#include "../ifc/xml/vbackgroundpatternimage.h" + +#if QT_VERSION < QT_VERSION_CHECK(5, 13, 0) +#include "../vmisc/defglobal.h" +#endif // QT_VERSION < QT_VERSION_CHECK(5, 13, 0) + + +class DeleteBackgroundImage : public VUndoCommand +{ + Q_OBJECT +public: + DeleteBackgroundImage(const VBackgroundPatternImage& image, VAbstractPattern *doc, QUndoCommand *parent = nullptr); + ~DeleteBackgroundImage() override =default; + void undo() override; + void redo() override; +signals: + void AddItem(const QUuid &id); + void DeleteItem(const QUuid &id); +private: + Q_DISABLE_COPY_MOVE(DeleteBackgroundImage) + VBackgroundPatternImage m_image; + int m_index{-1}; +}; + +#endif // DELETEBACKGROUNDIMAGE_H diff --git a/src/libs/vtools/undocommands/image/hideallbackgroundimages.cpp b/src/libs/vtools/undocommands/image/hideallbackgroundimages.cpp new file mode 100644 index 000000000..a18443de1 --- /dev/null +++ b/src/libs/vtools/undocommands/image/hideallbackgroundimages.cpp @@ -0,0 +1,79 @@ +/************************************************************************ + ** + ** @file hideallbackgroundimages.cpp + ** @author Roman Telezhynskyi + ** @date 26 1, 2022 + ** + ** @brief + ** @copyright + ** This source code is part of the Valentina project, a pattern making + ** program, whose allow create and modeling patterns of clothing. + ** Copyright (C) 2022 Valentina project + ** All Rights Reserved. + ** + ** Valentina 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, either version 3 of the License, or + ** (at your option) any later version. + ** + ** Valentina 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 Valentina. If not, see . + ** + *************************************************************************/ +#include "hideallbackgroundimages.h" +#include "../ifc/xml/vbackgroundpatternimage.h" + +//--------------------------------------------------------------------------------------------------------------------- +HideAllBackgroundImages::HideAllBackgroundImages(bool hide, VAbstractPattern *doc, QUndoCommand *parent) + : VUndoCommand(QDomElement(), doc, parent), + m_hide(hide) +{ + if (hide) + { + setText(tr("hide all background images")); + } + else + { + setText(tr("show all background images")); + } + + QVector images = doc->GetBackgroundImages(); + + for (const auto& image : images) + { + m_oldVisibility.insert(image.Id(), image.Visible()); + } +} + +//--------------------------------------------------------------------------------------------------------------------- +void HideAllBackgroundImages::undo() +{ + QVector images = doc->GetBackgroundImages(); + + for (auto &image : images) + { + image.SetVisible(m_oldVisibility.value(image.Id(), image.Visible())); + } + + doc->SaveBackgroundImages(images); + emit doc->BackgroundImagesVisibilityChanged(); +} + +//--------------------------------------------------------------------------------------------------------------------- +void HideAllBackgroundImages::redo() +{ + QVector images = doc->GetBackgroundImages(); + + for (auto &image : images) + { + image.SetVisible(not m_hide); + } + + doc->SaveBackgroundImages(images); + emit doc->BackgroundImagesVisibilityChanged(); +} diff --git a/src/libs/vtools/undocommands/image/hideallbackgroundimages.h b/src/libs/vtools/undocommands/image/hideallbackgroundimages.h new file mode 100644 index 000000000..17ddd044a --- /dev/null +++ b/src/libs/vtools/undocommands/image/hideallbackgroundimages.h @@ -0,0 +1,51 @@ +/************************************************************************ + ** + ** @file hideallbackgroundimages.h + ** @author Roman Telezhynskyi + ** @date 26 1, 2022 + ** + ** @brief + ** @copyright + ** This source code is part of the Valentina project, a pattern making + ** program, whose allow create and modeling patterns of clothing. + ** Copyright (C) 2022 Valentina project + ** All Rights Reserved. + ** + ** Valentina 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, either version 3 of the License, or + ** (at your option) any later version. + ** + ** Valentina 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 Valentina. If not, see . + ** + *************************************************************************/ +#ifndef HIDEALLBACKGROUNDIMAGES_H +#define HIDEALLBACKGROUNDIMAGES_H + +#include "../vundocommand.h" + +#if QT_VERSION < QT_VERSION_CHECK(5, 13, 0) +#include "../vmisc/defglobal.h" +#endif // QT_VERSION < QT_VERSION_CHECK(5, 13, 0) + + +class HideAllBackgroundImages : public VUndoCommand +{ +public: + HideAllBackgroundImages(bool hide, VAbstractPattern *doc, QUndoCommand *parent = nullptr); + ~HideAllBackgroundImages() override =default; + void undo() override; + void redo() override; +private: + Q_DISABLE_COPY_MOVE(HideAllBackgroundImages) + bool m_hide; + QMap m_oldVisibility{}; +}; + +#endif // HIDEALLBACKGROUNDIMAGES_H diff --git a/src/libs/vtools/undocommands/image/hidebackgroundimage.cpp b/src/libs/vtools/undocommands/image/hidebackgroundimage.cpp new file mode 100644 index 000000000..70c8dff05 --- /dev/null +++ b/src/libs/vtools/undocommands/image/hidebackgroundimage.cpp @@ -0,0 +1,75 @@ +/************************************************************************ + ** + ** @file hidebackgroundimage.cpp + ** @author Roman Telezhynskyi + ** @date 26 1, 2022 + ** + ** @brief + ** @copyright + ** This source code is part of the Valentina project, a pattern making + ** program, whose allow create and modeling patterns of clothing. + ** Copyright (C) 2022 Valentina project + ** All Rights Reserved. + ** + ** Valentina 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, either version 3 of the License, or + ** (at your option) any later version. + ** + ** Valentina 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 Valentina. If not, see . + ** + *************************************************************************/ +#include "hidebackgroundimage.h" +#include "../ifc/xml/vbackgroundpatternimage.h" + +//--------------------------------------------------------------------------------------------------------------------- +HideBackgroundImage::HideBackgroundImage(QUuid id, bool hide, VAbstractPattern *doc, QUndoCommand *parent) + : VUndoCommand(QDomElement(), doc, parent), + m_id(id), + m_hide(hide) +{ + if (hide) + { + setText(tr("hide a background image")); + } + else + { + setText(tr("show a background image")); + } + + VBackgroundPatternImage image = doc->GetBackgroundImage(m_id); + + m_oldVisibility = image.Visible(); +} + +//--------------------------------------------------------------------------------------------------------------------- +void HideBackgroundImage::undo() +{ + VBackgroundPatternImage image = doc->GetBackgroundImage(m_id); + + if (not image.IsNull()) + { + image.SetVisible(m_oldVisibility); + doc->SaveBackgroundImage(image); + emit doc->BackgroundImageVisibilityChanged(m_id); + } +} + +//--------------------------------------------------------------------------------------------------------------------- +void HideBackgroundImage::redo() +{ + VBackgroundPatternImage image = doc->GetBackgroundImage(m_id); + + if (not image.IsNull()) + { + image.SetVisible(not m_hide); + doc->SaveBackgroundImage(image); + emit doc->BackgroundImageVisibilityChanged(m_id); + } +} diff --git a/src/libs/vtools/undocommands/image/hidebackgroundimage.h b/src/libs/vtools/undocommands/image/hidebackgroundimage.h new file mode 100644 index 000000000..b363bb848 --- /dev/null +++ b/src/libs/vtools/undocommands/image/hidebackgroundimage.h @@ -0,0 +1,52 @@ +/************************************************************************ + ** + ** @file hidebackgroundimage.h + ** @author Roman Telezhynskyi + ** @date 26 1, 2022 + ** + ** @brief + ** @copyright + ** This source code is part of the Valentina project, a pattern making + ** program, whose allow create and modeling patterns of clothing. + ** Copyright (C) 2022 Valentina project + ** All Rights Reserved. + ** + ** Valentina 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, either version 3 of the License, or + ** (at your option) any later version. + ** + ** Valentina 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 Valentina. If not, see . + ** + *************************************************************************/ +#ifndef HIDEBACKGROUNDIMAGE_H +#define HIDEBACKGROUNDIMAGE_H + +#include "../vundocommand.h" + +#if QT_VERSION < QT_VERSION_CHECK(5, 13, 0) +#include "../vmisc/defglobal.h" +#endif // QT_VERSION < QT_VERSION_CHECK(5, 13, 0) + + +class HideBackgroundImage : public VUndoCommand +{ +public: + HideBackgroundImage(QUuid id, bool hide, VAbstractPattern *doc, QUndoCommand *parent = nullptr); + ~HideBackgroundImage() override =default; + void undo() override; + void redo() override; +private: + Q_DISABLE_COPY_MOVE(HideBackgroundImage) + QUuid m_id; + bool m_hide; + bool m_oldVisibility{false}; +}; + +#endif // HIDEBACKGROUNDIMAGE_H diff --git a/src/libs/vtools/undocommands/image/holdallbackgroundimages.cpp b/src/libs/vtools/undocommands/image/holdallbackgroundimages.cpp new file mode 100644 index 000000000..12c9d47a2 --- /dev/null +++ b/src/libs/vtools/undocommands/image/holdallbackgroundimages.cpp @@ -0,0 +1,79 @@ +/************************************************************************ + ** + ** @file holdallbackgroundimages.cpp + ** @author Roman Telezhynskyi + ** @date 26 1, 2022 + ** + ** @brief + ** @copyright + ** This source code is part of the Valentina project, a pattern making + ** program, whose allow create and modeling patterns of clothing. + ** Copyright (C) 2022 Valentina project + ** All Rights Reserved. + ** + ** Valentina 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, either version 3 of the License, or + ** (at your option) any later version. + ** + ** Valentina 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 Valentina. If not, see . + ** + *************************************************************************/ +#include "holdallbackgroundimages.h" +#include "../ifc/xml/vbackgroundpatternimage.h" + +//--------------------------------------------------------------------------------------------------------------------- +HoldAllBackgroundImages::HoldAllBackgroundImages(bool hold, VAbstractPattern *doc, QUndoCommand *parent) + : VUndoCommand(QDomElement(), doc, parent), + m_hold(hold) +{ + if (hold) + { + setText(tr("hold all background images")); + } + else + { + setText(tr("unhold background images")); + } + + QVector images = doc->GetBackgroundImages(); + + for (const auto& image : images) + { + m_oldHold.insert(image.Id(), image.Hold()); + } +} + +//--------------------------------------------------------------------------------------------------------------------- +void HoldAllBackgroundImages::undo() +{ + QVector images = doc->GetBackgroundImages(); + + for (auto &image : images) + { + image.SetHold(m_oldHold.value(image.Id(), image.Hold())); + } + + doc->SaveBackgroundImages(images); + emit doc->BackgroundImagesHoldChanged(); +} + +//--------------------------------------------------------------------------------------------------------------------- +void HoldAllBackgroundImages::redo() +{ + QVector images = doc->GetBackgroundImages(); + + for (auto &image : images) + { + image.SetHold(m_hold); + } + + doc->SaveBackgroundImages(images); + emit doc->BackgroundImagesHoldChanged(); +} diff --git a/src/libs/vtools/undocommands/image/holdallbackgroundimages.h b/src/libs/vtools/undocommands/image/holdallbackgroundimages.h new file mode 100644 index 000000000..9a3e57fce --- /dev/null +++ b/src/libs/vtools/undocommands/image/holdallbackgroundimages.h @@ -0,0 +1,50 @@ +/************************************************************************ + ** + ** @file holdallbackgroundimages.h + ** @author Roman Telezhynskyi + ** @date 26 1, 2022 + ** + ** @brief + ** @copyright + ** This source code is part of the Valentina project, a pattern making + ** program, whose allow create and modeling patterns of clothing. + ** Copyright (C) 2022 Valentina project + ** All Rights Reserved. + ** + ** Valentina 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, either version 3 of the License, or + ** (at your option) any later version. + ** + ** Valentina 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 Valentina. If not, see . + ** + *************************************************************************/ +#ifndef HOLDALLBACKGROUNDIMAGES_H +#define HOLDALLBACKGROUNDIMAGES_H + +#include "../vundocommand.h" + +#if QT_VERSION < QT_VERSION_CHECK(5, 13, 0) +#include "../vmisc/defglobal.h" +#endif // QT_VERSION < QT_VERSION_CHECK(5, 13, 0) + +class HoldAllBackgroundImages : public VUndoCommand +{ +public: + HoldAllBackgroundImages(bool hold, VAbstractPattern *doc, QUndoCommand *parent = nullptr); + ~HoldAllBackgroundImages() override =default; + void undo() override; + void redo() override; +private: + Q_DISABLE_COPY_MOVE(HoldAllBackgroundImages) + bool m_hold; + QMap m_oldHold{}; +}; + +#endif // HOLDALLBACKGROUNDIMAGES_H diff --git a/src/libs/vtools/undocommands/image/holdbackgroundimage.cpp b/src/libs/vtools/undocommands/image/holdbackgroundimage.cpp new file mode 100644 index 000000000..259fabe71 --- /dev/null +++ b/src/libs/vtools/undocommands/image/holdbackgroundimage.cpp @@ -0,0 +1,75 @@ +/************************************************************************ + ** + ** @file holdbackgroundimage.cpp + ** @author Roman Telezhynskyi + ** @date 22 1, 2022 + ** + ** @brief + ** @copyright + ** This source code is part of the Valentina project, a pattern making + ** program, whose allow create and modeling patterns of clothing. + ** Copyright (C) 2022 Valentina project + ** All Rights Reserved. + ** + ** Valentina 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, either version 3 of the License, or + ** (at your option) any later version. + ** + ** Valentina 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 Valentina. If not, see . + ** + *************************************************************************/ +#include "holdbackgroundimage.h" +#include "../ifc/xml/vbackgroundpatternimage.h" + +//--------------------------------------------------------------------------------------------------------------------- +HoldBackgroundImage::HoldBackgroundImage(QUuid id, bool hold, VAbstractPattern *doc, QUndoCommand *parent) + : VUndoCommand(QDomElement(), doc, parent), + m_id(id), + m_hold(hold) +{ + if (hold) + { + setText(tr("hold background image")); + } + else + { + setText(tr("unhold background image")); + } + + VBackgroundPatternImage image = doc->GetBackgroundImage(m_id); + + m_oldHold = image.Hold(); +} + +//--------------------------------------------------------------------------------------------------------------------- +void HoldBackgroundImage::undo() +{ + VBackgroundPatternImage image = doc->GetBackgroundImage(m_id); + + if (not image.IsNull()) + { + image.SetHold(m_oldHold); + doc->SaveBackgroundImage(image); + emit doc->BackgroundImageHoldChanged(m_id); + } +} + +//--------------------------------------------------------------------------------------------------------------------- +void HoldBackgroundImage::redo() +{ + VBackgroundPatternImage image = doc->GetBackgroundImage(m_id); + + if (not image.IsNull()) + { + image.SetHold(m_hold); + doc->SaveBackgroundImage(image); + emit doc->BackgroundImageHoldChanged(m_id); + } +} diff --git a/src/libs/vtools/undocommands/image/holdbackgroundimage.h b/src/libs/vtools/undocommands/image/holdbackgroundimage.h new file mode 100644 index 000000000..56a71d00b --- /dev/null +++ b/src/libs/vtools/undocommands/image/holdbackgroundimage.h @@ -0,0 +1,52 @@ +/************************************************************************ + ** + ** @file holdbackgroundimage.h + ** @author Roman Telezhynskyi + ** @date 22 1, 2022 + ** + ** @brief + ** @copyright + ** This source code is part of the Valentina project, a pattern making + ** program, whose allow create and modeling patterns of clothing. + ** Copyright (C) 2022 Valentina project + ** All Rights Reserved. + ** + ** Valentina 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, either version 3 of the License, or + ** (at your option) any later version. + ** + ** Valentina 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 Valentina. If not, see . + ** + *************************************************************************/ +#ifndef HOLDBACKGROUNDIMAGE_H +#define HOLDBACKGROUNDIMAGE_H + +#include "../vundocommand.h" + +#if QT_VERSION < QT_VERSION_CHECK(5, 13, 0) +#include "../vmisc/defglobal.h" +#endif // QT_VERSION < QT_VERSION_CHECK(5, 13, 0) + + +class HoldBackgroundImage : public VUndoCommand +{ +public: + HoldBackgroundImage(QUuid id, bool hold, VAbstractPattern *doc, QUndoCommand *parent = nullptr); + ~HoldBackgroundImage() override =default; + void undo() override; + void redo() override; +private: + Q_DISABLE_COPY_MOVE(HoldBackgroundImage) + QUuid m_id; + bool m_hold; + bool m_oldHold{false}; +}; + +#endif // HOLDBACKGROUNDIMAGE_H diff --git a/src/libs/vtools/undocommands/image/movebackgroundimage.cpp b/src/libs/vtools/undocommands/image/movebackgroundimage.cpp new file mode 100644 index 000000000..8e4586d10 --- /dev/null +++ b/src/libs/vtools/undocommands/image/movebackgroundimage.cpp @@ -0,0 +1,126 @@ +/************************************************************************ + ** + ** @file movebackgroundimage.cpp + ** @author Roman Telezhynskyi + ** @date 18 1, 2022 + ** + ** @brief + ** @copyright + ** This source code is part of the Valentina project, a pattern making + ** program, whose allow create and modeling patterns of clothing. + ** Copyright (C) 2022 Valentina project + ** All Rights Reserved. + ** + ** Valentina 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, either version 3 of the License, or + ** (at your option) any later version. + ** + ** Valentina 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 Valentina. If not, see . + ** + *************************************************************************/ +#include "movebackgroundimage.h" +#include "../ifc/xml/vbackgroundpatternimage.h" + +//--------------------------------------------------------------------------------------------------------------------- +MoveBackgroundImage::MoveBackgroundImage(QUuid id, qreal dx, qreal dy, VAbstractPattern *doc, bool allowMerge, + QUndoCommand *parent) + : VUndoCommand(QDomElement(), doc, parent), + m_id(id), + m_dx(dx), + m_dy(dy), + m_allowMerge(allowMerge) +{ + setText(tr("move background image")); + + VBackgroundPatternImage image = doc->GetBackgroundImage(m_id); + + m_oldPos = image.Matrix(); +} + +//--------------------------------------------------------------------------------------------------------------------- +void MoveBackgroundImage::undo() +{ + VBackgroundPatternImage image = doc->GetBackgroundImage(m_id); + + if (not image.IsNull()) + { + image.SetMatrix(m_oldPos); + doc->SaveBackgroundImage(image); + emit doc->BackgroundImagePositionChanged(m_id); + } +} + +//--------------------------------------------------------------------------------------------------------------------- +void MoveBackgroundImage::redo() +{ + VBackgroundPatternImage image = doc->GetBackgroundImage(m_id); + + if (not image.IsNull()) + { + QTransform matrix = image.Matrix(); + QTransform m; + m.translate(m_dx, m_dy); + matrix *= m; + image.SetMatrix(matrix); + doc->SaveBackgroundImage(image); + emit doc->BackgroundImagePositionChanged(m_id); + } +} + +//--------------------------------------------------------------------------------------------------------------------- +auto MoveBackgroundImage::mergeWith(const QUndoCommand *command) -> bool +{ + if (command->id() != id()) + { + return false; + } + + const auto *moveCommand = dynamic_cast(command); + SCASSERT(moveCommand != nullptr) + + if (moveCommand->ImageId() != m_id || not moveCommand->AllowMerge()) + { + return false; + } + + m_dx = moveCommand->Dx(); + m_dy = moveCommand->Dy(); + return true; +} + +//--------------------------------------------------------------------------------------------------------------------- +auto MoveBackgroundImage::id() const -> int +{ + return static_cast(UndoCommand::MoveBackGroundImage); +} + +//--------------------------------------------------------------------------------------------------------------------- +auto MoveBackgroundImage::ImageId() const -> QUuid +{ + return m_id; +} + +//--------------------------------------------------------------------------------------------------------------------- +auto MoveBackgroundImage::Dx() const -> qreal +{ + return m_dx; +} + +//--------------------------------------------------------------------------------------------------------------------- +auto MoveBackgroundImage::Dy() const -> qreal +{ + return m_dy; +} + +//--------------------------------------------------------------------------------------------------------------------- +auto MoveBackgroundImage::AllowMerge() const -> bool +{ + return m_allowMerge; +} diff --git a/src/libs/vtools/undocommands/image/movebackgroundimage.h b/src/libs/vtools/undocommands/image/movebackgroundimage.h new file mode 100644 index 000000000..54d707bc9 --- /dev/null +++ b/src/libs/vtools/undocommands/image/movebackgroundimage.h @@ -0,0 +1,69 @@ +/************************************************************************ + ** + ** @file movebackgroundimage.h + ** @author Roman Telezhynskyi + ** @date 18 1, 2022 + ** + ** @brief + ** @copyright + ** This source code is part of the Valentina project, a pattern making + ** program, whose allow create and modeling patterns of clothing. + ** Copyright (C) 2022 Valentina project + ** All Rights Reserved. + ** + ** Valentina 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, either version 3 of the License, or + ** (at your option) any later version. + ** + ** Valentina 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 Valentina. If not, see . + ** + *************************************************************************/ +#ifndef MOVEBACKGROUNDIMAGE_H +#define MOVEBACKGROUNDIMAGE_H + +#include "../vundocommand.h" + +#include +#include + +#if QT_VERSION < QT_VERSION_CHECK(5, 13, 0) +#include "../vmisc/defglobal.h" +#endif // QT_VERSION < QT_VERSION_CHECK(5, 13, 0) + +class MoveBackgroundImage : public VUndoCommand +{ + Q_OBJECT +public: + MoveBackgroundImage(QUuid id, qreal dx, qreal dy, VAbstractPattern *doc, bool allowMerge = false, + QUndoCommand *parent = nullptr); + ~MoveBackgroundImage() override = default; + + void undo() override; + void redo() override; + // cppcheck-suppress unusedFunction + auto mergeWith(const QUndoCommand *command) -> bool override; + auto id() const -> int override; + + auto ImageId() const -> QUuid; + auto Dx() const -> qreal; + auto Dy() const -> qreal; + auto AllowMerge() const -> bool; + +private: + Q_DISABLE_COPY_MOVE(MoveBackgroundImage) + + QUuid m_id; + qreal m_dx; + qreal m_dy; + QTransform m_oldPos{}; + bool m_allowMerge; +}; + +#endif // MOVEBACKGROUNDIMAGE_H diff --git a/src/libs/vtools/undocommands/image/renamebackgroundimage.cpp b/src/libs/vtools/undocommands/image/renamebackgroundimage.cpp new file mode 100644 index 000000000..158f6437d --- /dev/null +++ b/src/libs/vtools/undocommands/image/renamebackgroundimage.cpp @@ -0,0 +1,68 @@ +/************************************************************************ + ** + ** @file renamebackgroundimage.cpp + ** @author Roman Telezhynskyi + ** @date 26 1, 2022 + ** + ** @brief + ** @copyright + ** This source code is part of the Valentina project, a pattern making + ** program, whose allow create and modeling patterns of clothing. + ** Copyright (C) 2022 Valentina project + ** All Rights Reserved. + ** + ** Valentina 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, either version 3 of the License, or + ** (at your option) any later version. + ** + ** Valentina 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 Valentina. If not, see . + ** + *************************************************************************/ +#include "renamebackgroundimage.h" +#include "../ifc/xml/vbackgroundpatternimage.h" + +//--------------------------------------------------------------------------------------------------------------------- +RenameBackgroundImage::RenameBackgroundImage(QUuid id, const QString &name, VAbstractPattern *doc, QUndoCommand *parent) + : VUndoCommand(QDomElement(), doc, parent), + m_id(id), + m_name(name) +{ + setText(tr("rename background image")); + + VBackgroundPatternImage image = doc->GetBackgroundImage(m_id); + + m_oldName = image.Name(); +} + +//--------------------------------------------------------------------------------------------------------------------- +void RenameBackgroundImage::undo() +{ + VBackgroundPatternImage image = doc->GetBackgroundImage(m_id); + + if (not image.IsNull()) + { + image.SetName(m_oldName); + doc->SaveBackgroundImage(image); + emit doc->BackgroundImageNameChanged(m_id); + } +} + +//--------------------------------------------------------------------------------------------------------------------- +void RenameBackgroundImage::redo() +{ + VBackgroundPatternImage image = doc->GetBackgroundImage(m_id); + + if (not image.IsNull()) + { + image.SetName(m_name); + doc->SaveBackgroundImage(image); + emit doc->BackgroundImageNameChanged(m_id); + } +} diff --git a/src/libs/vtools/undocommands/image/renamebackgroundimage.h b/src/libs/vtools/undocommands/image/renamebackgroundimage.h new file mode 100644 index 000000000..fa7808dde --- /dev/null +++ b/src/libs/vtools/undocommands/image/renamebackgroundimage.h @@ -0,0 +1,52 @@ +/************************************************************************ + ** + ** @file renamebackgroundimage.h + ** @author Roman Telezhynskyi + ** @date 26 1, 2022 + ** + ** @brief + ** @copyright + ** This source code is part of the Valentina project, a pattern making + ** program, whose allow create and modeling patterns of clothing. + ** Copyright (C) 2022 Valentina project + ** All Rights Reserved. + ** + ** Valentina 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, either version 3 of the License, or + ** (at your option) any later version. + ** + ** Valentina 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 Valentina. If not, see . + ** + *************************************************************************/ +#ifndef RENAMEBACKGROUNDIMAGE_H +#define RENAMEBACKGROUNDIMAGE_H + +#include "../vundocommand.h" + +#if QT_VERSION < QT_VERSION_CHECK(5, 13, 0) +#include "../vmisc/defglobal.h" +#endif // QT_VERSION < QT_VERSION_CHECK(5, 13, 0) + + +class RenameBackgroundImage : public VUndoCommand +{ +public: + RenameBackgroundImage(QUuid id, const QString &name, VAbstractPattern *doc, QUndoCommand *parent = nullptr); + ~RenameBackgroundImage() override =default; + void undo() override; + void redo() override; +private: + Q_DISABLE_COPY_MOVE(RenameBackgroundImage) + QUuid m_id; + QString m_name; + QString m_oldName{}; +}; + +#endif // RENAMEBACKGROUNDIMAGE_H diff --git a/src/libs/vtools/undocommands/image/resetbackgroundimage.cpp b/src/libs/vtools/undocommands/image/resetbackgroundimage.cpp new file mode 100644 index 000000000..54fca82dc --- /dev/null +++ b/src/libs/vtools/undocommands/image/resetbackgroundimage.cpp @@ -0,0 +1,66 @@ +/************************************************************************ + ** + ** @file resetbackgroundimage.cpp + ** @author Roman Telezhynskyi + ** @date 28 1, 2022 + ** + ** @brief + ** @copyright + ** This source code is part of the Valentina project, a pattern making + ** program, whose allow create and modeling patterns of clothing. + ** Copyright (C) 2022 Valentina project + ** All Rights Reserved. + ** + ** Valentina 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, either version 3 of the License, or + ** (at your option) any later version. + ** + ** Valentina 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 Valentina. If not, see . + ** + *************************************************************************/ +#include "resetbackgroundimage.h" +#include "../ifc/xml/vbackgroundpatternimage.h" + +//--------------------------------------------------------------------------------------------------------------------- +ResetBackgroundImage::ResetBackgroundImage(QUuid id, VAbstractPattern *doc, QUndoCommand *parent) + : VUndoCommand(QDomElement(), doc, parent), + m_id(id) +{ + setText(tr("reset background image transformation")); + + VBackgroundPatternImage image = doc->GetBackgroundImage(m_id); + m_oldMatrix = image.Matrix(); +} + +//--------------------------------------------------------------------------------------------------------------------- +void ResetBackgroundImage::undo() +{ + VBackgroundPatternImage image = doc->GetBackgroundImage(m_id); + + if (not image.IsNull()) + { + image.SetMatrix(m_oldMatrix); + doc->SaveBackgroundImage(image); + emit doc->BackgroundImageTransformationChanged(m_id); + } +} + +//--------------------------------------------------------------------------------------------------------------------- +void ResetBackgroundImage::redo() +{ + VBackgroundPatternImage image = doc->GetBackgroundImage(m_id); + + if (not image.IsNull()) + { + image.SetMatrix(QTransform()); + doc->SaveBackgroundImage(image); + emit doc->BackgroundImageTransformationChanged(m_id); + } +} diff --git a/src/libs/vtools/undocommands/image/resetbackgroundimage.h b/src/libs/vtools/undocommands/image/resetbackgroundimage.h new file mode 100644 index 000000000..341983a77 --- /dev/null +++ b/src/libs/vtools/undocommands/image/resetbackgroundimage.h @@ -0,0 +1,56 @@ +/************************************************************************ + ** + ** @file resetbackgroundimage.h + ** @author Roman Telezhynskyi + ** @date 28 1, 2022 + ** + ** @brief + ** @copyright + ** This source code is part of the Valentina project, a pattern making + ** program, whose allow create and modeling patterns of clothing. + ** Copyright (C) 2022 Valentina project + ** All Rights Reserved. + ** + ** Valentina 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, either version 3 of the License, or + ** (at your option) any later version. + ** + ** Valentina 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 Valentina. If not, see . + ** + *************************************************************************/ +#ifndef RESETBACKGROUNDIMAGE_H +#define RESETBACKGROUNDIMAGE_H + +#include "../vundocommand.h" + +#include +#include + +#if QT_VERSION < QT_VERSION_CHECK(5, 13, 0) +#include "../vmisc/defglobal.h" +#endif // QT_VERSION < QT_VERSION_CHECK(5, 13, 0) + +class ResetBackgroundImage : public VUndoCommand +{ +public: + ResetBackgroundImage(QUuid id, VAbstractPattern *doc, QUndoCommand *parent = nullptr); + ~ResetBackgroundImage() override = default; + + void undo() override; + void redo() override; + +private: + Q_DISABLE_COPY_MOVE(ResetBackgroundImage) + + QUuid m_id; + QTransform m_oldMatrix{}; +}; + +#endif // RESETBACKGROUNDIMAGE_H diff --git a/src/libs/vtools/undocommands/image/rotatebackgroundimage.cpp b/src/libs/vtools/undocommands/image/rotatebackgroundimage.cpp new file mode 100644 index 000000000..2f8923921 --- /dev/null +++ b/src/libs/vtools/undocommands/image/rotatebackgroundimage.cpp @@ -0,0 +1,114 @@ +/************************************************************************ + ** + ** @file rotatebackgroundimage.cpp + ** @author Roman Telezhynskyi + ** @date 21 1, 2022 + ** + ** @brief + ** @copyright + ** This source code is part of the Valentina project, a pattern making + ** program, whose allow create and modeling patterns of clothing. + ** Copyright (C) 2022 Valentina project + ** All Rights Reserved. + ** + ** Valentina 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, either version 3 of the License, or + ** (at your option) any later version. + ** + ** Valentina 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 Valentina. If not, see . + ** + *************************************************************************/ +#include "rotatebackgroundimage.h" +#include "../ifc/xml/vbackgroundpatternimage.h" + +//--------------------------------------------------------------------------------------------------------------------- +RotateBackgroundImage::RotateBackgroundImage(QUuid id, const QTransform &matrix, VAbstractPattern *doc, bool allowMerge, + QUndoCommand *parent) + : VUndoCommand(QDomElement(), doc, parent), + m_id(id), + m_matrix(matrix), + m_allowMerge(allowMerge) +{ + setText(tr("rotate background image")); + + VBackgroundPatternImage image = doc->GetBackgroundImage(m_id); + + m_oldMatrix = image.Matrix(); +} + +//--------------------------------------------------------------------------------------------------------------------- +void RotateBackgroundImage::undo() +{ + VBackgroundPatternImage image = doc->GetBackgroundImage(m_id); + + if (not image.IsNull()) + { + image.SetMatrix(m_oldMatrix); + doc->SaveBackgroundImage(image); + emit doc->BackgroundImageTransformationChanged(m_id); + } +} + +//--------------------------------------------------------------------------------------------------------------------- +void RotateBackgroundImage::redo() +{ + VBackgroundPatternImage image = doc->GetBackgroundImage(m_id); + + if (not image.IsNull()) + { + image.SetMatrix(m_matrix); + doc->SaveBackgroundImage(image); + emit doc->BackgroundImageTransformationChanged(m_id); + } +} + +//--------------------------------------------------------------------------------------------------------------------- +bool RotateBackgroundImage::mergeWith(const QUndoCommand *command) +{ + if (command->id() != id()) + { + return false; + } + + const auto *moveCommand = dynamic_cast(command); + SCASSERT(moveCommand != nullptr) + + if (moveCommand->ImageId() != m_id || not moveCommand->AllowMerge()) + { + return false; + } + + m_matrix = moveCommand->Matrix(); + return true; +} + +//--------------------------------------------------------------------------------------------------------------------- +int RotateBackgroundImage::id() const +{ + return static_cast(UndoCommand::RotateBackGroundImage); +} + +//--------------------------------------------------------------------------------------------------------------------- +QUuid RotateBackgroundImage::ImageId() const +{ + return m_id; +} + +//--------------------------------------------------------------------------------------------------------------------- +QTransform RotateBackgroundImage::Matrix() const +{ + return m_matrix; +} + +//--------------------------------------------------------------------------------------------------------------------- +bool RotateBackgroundImage::AllowMerge() const +{ + return m_allowMerge; +} diff --git a/src/libs/vtools/undocommands/image/rotatebackgroundimage.h b/src/libs/vtools/undocommands/image/rotatebackgroundimage.h new file mode 100644 index 000000000..3536b9d59 --- /dev/null +++ b/src/libs/vtools/undocommands/image/rotatebackgroundimage.h @@ -0,0 +1,67 @@ +/************************************************************************ + ** + ** @file rotatebackgroundimage.h + ** @author Roman Telezhynskyi + ** @date 21 1, 2022 + ** + ** @brief + ** @copyright + ** This source code is part of the Valentina project, a pattern making + ** program, whose allow create and modeling patterns of clothing. + ** Copyright (C) 2022 Valentina project + ** All Rights Reserved. + ** + ** Valentina 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, either version 3 of the License, or + ** (at your option) any later version. + ** + ** Valentina 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 Valentina. If not, see . + ** + *************************************************************************/ +#ifndef ROTATEBACKGROUNDIMAGE_H +#define ROTATEBACKGROUNDIMAGE_H + +#include "../vundocommand.h" + +#include +#include + +#if QT_VERSION < QT_VERSION_CHECK(5, 13, 0) +#include "../vmisc/defglobal.h" +#endif // QT_VERSION < QT_VERSION_CHECK(5, 13, 0) + + +class RotateBackgroundImage : public VUndoCommand +{ +public: + RotateBackgroundImage(QUuid id, const QTransform &matrix, VAbstractPattern *doc, bool allowMerge = false, + QUndoCommand *parent = nullptr); + ~RotateBackgroundImage() override = default; + + void undo() override; + void redo() override; + // cppcheck-suppress unusedFunction + auto mergeWith(const QUndoCommand *command) -> bool override; + auto id() const -> int override; + + auto ImageId() const -> QUuid; + auto Matrix() const -> QTransform; + auto AllowMerge() const -> bool; + +private: + Q_DISABLE_COPY_MOVE(RotateBackgroundImage) + + QUuid m_id; + QTransform m_matrix; + QTransform m_oldMatrix{}; + bool m_allowMerge; +}; + +#endif // ROTATEBACKGROUNDIMAGE_H diff --git a/src/libs/vtools/undocommands/image/scalebackgroundimage.cpp b/src/libs/vtools/undocommands/image/scalebackgroundimage.cpp new file mode 100644 index 000000000..f41d61980 --- /dev/null +++ b/src/libs/vtools/undocommands/image/scalebackgroundimage.cpp @@ -0,0 +1,114 @@ +/************************************************************************ + ** + ** @file scalebackgroundimage.cpp + ** @author Roman Telezhynskyi + ** @date 18 1, 2022 + ** + ** @brief + ** @copyright + ** This source code is part of the Valentina project, a pattern making + ** program, whose allow create and modeling patterns of clothing. + ** Copyright (C) 2022 Valentina project + ** All Rights Reserved. + ** + ** Valentina 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, either version 3 of the License, or + ** (at your option) any later version. + ** + ** Valentina 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 Valentina. If not, see . + ** + *************************************************************************/ +#include "scalebackgroundimage.h" +#include "../ifc/xml/vbackgroundpatternimage.h" + +//--------------------------------------------------------------------------------------------------------------------- +ScaleBackgroundImage::ScaleBackgroundImage(QUuid id, const QTransform &matrix, VAbstractPattern *doc, bool allowMerge, + QUndoCommand *parent) + : VUndoCommand(QDomElement(), doc, parent), + m_id(id), + m_matrix(matrix), + m_allowMerge(allowMerge) +{ + setText(tr("scale background image")); + + VBackgroundPatternImage image = doc->GetBackgroundImage(m_id); + + m_oldMatrix = image.Matrix(); +} + +//--------------------------------------------------------------------------------------------------------------------- +void ScaleBackgroundImage::undo() +{ + VBackgroundPatternImage image = doc->GetBackgroundImage(m_id); + + if (not image.IsNull()) + { + image.SetMatrix(m_oldMatrix); + doc->SaveBackgroundImage(image); + emit doc->BackgroundImageTransformationChanged(m_id); + } +} + +//--------------------------------------------------------------------------------------------------------------------- +void ScaleBackgroundImage::redo() +{ + VBackgroundPatternImage image = doc->GetBackgroundImage(m_id); + + if (not image.IsNull()) + { + image.SetMatrix(m_matrix); + doc->SaveBackgroundImage(image); + emit doc->BackgroundImageTransformationChanged(m_id); + } +} + +//--------------------------------------------------------------------------------------------------------------------- +bool ScaleBackgroundImage::mergeWith(const QUndoCommand *command) +{ + if (command->id() != id()) + { + return false; + } + + const auto *moveCommand = dynamic_cast(command); + SCASSERT(moveCommand != nullptr) + + if (moveCommand->ImageId() != m_id || not moveCommand->AllowMerge()) + { + return false; + } + + m_matrix = moveCommand->Matrix(); + return true; +} + +//--------------------------------------------------------------------------------------------------------------------- +int ScaleBackgroundImage::id() const +{ + return static_cast(UndoCommand::ScaleBackGroundImage); +} + +//--------------------------------------------------------------------------------------------------------------------- +QUuid ScaleBackgroundImage::ImageId() const +{ + return m_id; +} + +//--------------------------------------------------------------------------------------------------------------------- +QTransform ScaleBackgroundImage::Matrix() const +{ + return m_matrix; +} + +//--------------------------------------------------------------------------------------------------------------------- +bool ScaleBackgroundImage::AllowMerge() const +{ + return m_allowMerge; +} diff --git a/src/libs/vtools/undocommands/image/scalebackgroundimage.h b/src/libs/vtools/undocommands/image/scalebackgroundimage.h new file mode 100644 index 000000000..66b928a99 --- /dev/null +++ b/src/libs/vtools/undocommands/image/scalebackgroundimage.h @@ -0,0 +1,68 @@ +/************************************************************************ + ** + ** @file scalebackgroundimage.h + ** @author Roman Telezhynskyi + ** @date 18 1, 2022 + ** + ** @brief + ** @copyright + ** This source code is part of the Valentina project, a pattern making + ** program, whose allow create and modeling patterns of clothing. + ** Copyright (C) 2022 Valentina project + ** All Rights Reserved. + ** + ** Valentina 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, either version 3 of the License, or + ** (at your option) any later version. + ** + ** Valentina 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 Valentina. If not, see . + ** + *************************************************************************/ +#ifndef SCALEBACKGROUNDIMAGE_H +#define SCALEBACKGROUNDIMAGE_H + +#include "../vundocommand.h" + +#include +#include + +#if QT_VERSION < QT_VERSION_CHECK(5, 13, 0) +#include "../vmisc/defglobal.h" +#endif // QT_VERSION < QT_VERSION_CHECK(5, 13, 0) + + +class ScaleBackgroundImage : public VUndoCommand +{ + Q_OBJECT +public: + ScaleBackgroundImage(QUuid id, const QTransform &matrix, VAbstractPattern *doc, bool allowMerge = false, + QUndoCommand *parent = nullptr); + ~ScaleBackgroundImage() override = default; + + void undo() override; + void redo() override; + // cppcheck-suppress unusedFunction + auto mergeWith(const QUndoCommand *command) -> bool override; + auto id() const -> int override; + + auto ImageId() const -> QUuid; + auto Matrix() const -> QTransform; + auto AllowMerge() const -> bool; + +private: + Q_DISABLE_COPY_MOVE(ScaleBackgroundImage) + + QUuid m_id; + QTransform m_matrix; + QTransform m_oldMatrix{}; + bool m_allowMerge; +}; + +#endif // SCALEBACKGROUNDIMAGE_H diff --git a/src/libs/vtools/undocommands/image/zvaluemovebackgroundimage.cpp b/src/libs/vtools/undocommands/image/zvaluemovebackgroundimage.cpp new file mode 100644 index 000000000..2d9612330 --- /dev/null +++ b/src/libs/vtools/undocommands/image/zvaluemovebackgroundimage.cpp @@ -0,0 +1,158 @@ +/************************************************************************ + ** + ** @file zvaluemovebackgroundimage.cpp + ** @author Roman Telezhynskyi + ** @date 27 1, 2022 + ** + ** @brief + ** @copyright + ** This source code is part of the Valentina project, a pattern making + ** program, whose allow create and modeling patterns of clothing. + ** Copyright (C) 2022 Valentina project + ** All Rights Reserved. + ** + ** Valentina 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, either version 3 of the License, or + ** (at your option) any later version. + ** + ** Valentina 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 Valentina. If not, see . + ** + *************************************************************************/ +#include "zvaluemovebackgroundimage.h" +#include "../ifc/xml/vbackgroundpatternimage.h" + +namespace +{ +//--------------------------------------------------------------------------------------------------------------------- +auto CorrectedZValues(const QList> &order) -> QHash +{ + QHash correctedZValues; + + for (int i = 0; i < order.size(); ++i) + { + const QVector &level = order.at(i); + for (const auto &imgId : level) + { + correctedZValues.insert(imgId, i); + } + } + + return correctedZValues; +} +} // namespace + +//--------------------------------------------------------------------------------------------------------------------- +ZValueMoveBackgroundImage::ZValueMoveBackgroundImage(QUuid id, ZValueMove move, VAbstractPattern *doc, + QUndoCommand *parent) + : VUndoCommand(QDomElement(), doc, parent), + m_id(id), + m_move(move) +{ + setText(tr("z value move a background image")); + + QVector images = doc->GetBackgroundImages(); + + for (const auto &image: images) + { + m_oldValues.insert(image.Id(), image.ZValue()); + } +} + +//--------------------------------------------------------------------------------------------------------------------- +void ZValueMoveBackgroundImage::undo() +{ + QVector images = doc->GetBackgroundImages(); + + for (auto &image: images) + { + image.SetZValue(m_oldValues.value(image.Id(), image.ZValue())); + } + + doc->SaveBackgroundImages(images); + emit doc->BackgroundImagesZValueChanged(); +} + +//--------------------------------------------------------------------------------------------------------------------- +void ZValueMoveBackgroundImage::redo() +{ + QVector images = doc->GetBackgroundImages(); + + auto Levels = [this](const QVector &images, bool skip) + { + QMap> levels; + + for (const auto &image: images) + { + if (skip && image.Id() == m_id) + { + continue; + } + + if (levels.contains(image.ZValue())) + { + QVector lavel_images = levels.value(image.ZValue()); + lavel_images.append(image.Id()); + levels[image.ZValue()] = lavel_images; + } + else + { + levels[image.ZValue()] = {image.Id()}; + } + } + + return levels.values(); + }; + + QList> order; + + if (m_move == ZValueMove::Top) + { + order = Levels(images, true); + order.prepend({m_id}); + } + else if (m_move == ZValueMove::Up) + { + for (auto &image: images) + { + if (image.Id() != m_id) + { + image.SetZValue(image.ZValue() + 1); + } + } + + order = Levels(images, false); + } + else if (m_move == ZValueMove::Down) + { + for (auto &image: images) + { + if (image.Id() != m_id) + { + image.SetZValue(image.ZValue() - 1); + } + } + + order = Levels(images, false); + } + else if (m_move == ZValueMove::Bottom) + { + order = Levels(images, true); + order.append({m_id}); + } + + QHash correctedZValues = CorrectedZValues(order); + for (auto &image: images) + { + image.SetZValue(correctedZValues.value(image.Id(), image.ZValue())); + } + + doc->SaveBackgroundImages(images); + emit doc->BackgroundImagesZValueChanged(); +} diff --git a/src/libs/vtools/undocommands/image/zvaluemovebackgroundimage.h b/src/libs/vtools/undocommands/image/zvaluemovebackgroundimage.h new file mode 100644 index 000000000..91d98b945 --- /dev/null +++ b/src/libs/vtools/undocommands/image/zvaluemovebackgroundimage.h @@ -0,0 +1,59 @@ +/************************************************************************ + ** + ** @file zvaluemovebackgroundimage.h + ** @author Roman Telezhynskyi + ** @date 27 1, 2022 + ** + ** @brief + ** @copyright + ** This source code is part of the Valentina project, a pattern making + ** program, whose allow create and modeling patterns of clothing. + ** Copyright (C) 2022 Valentina project + ** All Rights Reserved. + ** + ** Valentina 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, either version 3 of the License, or + ** (at your option) any later version. + ** + ** Valentina 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 Valentina. If not, see . + ** + *************************************************************************/ +#ifndef ZVALUEMOVEBACKGROUNDIMAGE_H +#define ZVALUEMOVEBACKGROUNDIMAGE_H + +#include "../vundocommand.h" + +#if QT_VERSION < QT_VERSION_CHECK(5, 13, 0) +#include "../vmisc/defglobal.h" +#endif // QT_VERSION < QT_VERSION_CHECK(5, 13, 0) + +enum class ZValueMove +{ + Top, + Up, + Down, + Bottom +}; + +class ZValueMoveBackgroundImage : public VUndoCommand +{ +public: + ZValueMoveBackgroundImage(QUuid id, ZValueMove move, VAbstractPattern *doc, QUndoCommand *parent = nullptr); + ~ZValueMoveBackgroundImage() override =default; + void undo() override; + void redo() override; +private: + Q_DISABLE_COPY_MOVE(ZValueMoveBackgroundImage) + QUuid m_id; + ZValueMove m_move; + QHash m_oldValues{}; +}; + +#endif // ZVALUEMOVEBACKGROUNDIMAGE_H diff --git a/src/libs/vtools/undocommands/undocommands.pri b/src/libs/vtools/undocommands/undocommands.pri index 37d8a34b6..98385852a 100644 --- a/src/libs/vtools/undocommands/undocommands.pri +++ b/src/libs/vtools/undocommands/undocommands.pri @@ -4,6 +4,18 @@ HEADERS += \ $$PWD/addtocalc.h \ $$PWD/addpatternpiece.h \ + $$PWD/image/addbackgroundimage.h \ + $$PWD/image/deletebackgroundimage.h \ + $$PWD/image/hideallbackgroundimages.h \ + $$PWD/image/hidebackgroundimage.h \ + $$PWD/image/holdallbackgroundimages.h \ + $$PWD/image/holdbackgroundimage.h \ + $$PWD/image/movebackgroundimage.h \ + $$PWD/image/renamebackgroundimage.h \ + $$PWD/image/resetbackgroundimage.h \ + $$PWD/image/rotatebackgroundimage.h \ + $$PWD/image/scalebackgroundimage.h \ + $$PWD/image/zvaluemovebackgroundimage.h \ $$PWD/movespoint.h \ $$PWD/movespline.h \ $$PWD/movesplinepath.h \ @@ -31,6 +43,18 @@ HEADERS += \ SOURCES += \ $$PWD/addtocalc.cpp \ $$PWD/addpatternpiece.cpp \ + $$PWD/image/addbackgroundimage.cpp \ + $$PWD/image/deletebackgroundimage.cpp \ + $$PWD/image/hideallbackgroundimages.cpp \ + $$PWD/image/hidebackgroundimage.cpp \ + $$PWD/image/holdallbackgroundimages.cpp \ + $$PWD/image/holdbackgroundimage.cpp \ + $$PWD/image/movebackgroundimage.cpp \ + $$PWD/image/renamebackgroundimage.cpp \ + $$PWD/image/resetbackgroundimage.cpp \ + $$PWD/image/rotatebackgroundimage.cpp \ + $$PWD/image/scalebackgroundimage.cpp \ + $$PWD/image/zvaluemovebackgroundimage.cpp \ $$PWD/movespoint.cpp \ $$PWD/movespline.cpp \ $$PWD/movesplinepath.cpp \ diff --git a/src/libs/vtools/undocommands/vundocommand.h b/src/libs/vtools/undocommands/vundocommand.h index dcb9b7461..3ef89afee 100644 --- a/src/libs/vtools/undocommands/vundocommand.h +++ b/src/libs/vtools/undocommands/vundocommand.h @@ -60,7 +60,10 @@ enum class UndoCommand: qint8 RenamePP, MoveLabel, MoveDoubleLabel, - RotationMoveLabel + RotationMoveLabel, + MoveBackGroundImage, + ScaleBackGroundImage, + RotateBackGroundImage }; class VPattern; diff --git a/src/libs/vwidgets/vmaingraphicsscene.cpp b/src/libs/vwidgets/vmaingraphicsscene.cpp index e15d474e2..95ba2de12 100644 --- a/src/libs/vwidgets/vmaingraphicsscene.cpp +++ b/src/libs/vwidgets/vmaingraphicsscene.cpp @@ -44,6 +44,7 @@ #include "global.h" #include "../vmisc/vabstractapplication.h" + //--------------------------------------------------------------------------------------------------------------------- /** * @brief VMainGraphicsScene default constructor. @@ -98,9 +99,7 @@ void VMainGraphicsScene::mousePressEvent(QGraphicsSceneMouseEvent *event) QGraphicsScene::mousePressEvent(event); - QTransform t; - QGraphicsItem* pItem = itemAt(event->scenePos(), t); - emit ItemClicked(pItem); + emit ItemByMousePress(itemAt(event->scenePos(), {})); } //--------------------------------------------------------------------------------------------------------------------- @@ -109,10 +108,23 @@ void VMainGraphicsScene::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) if (event->button() == Qt::LeftButton && event->type() != QEvent::GraphicsSceneMouseDoubleClick) { emit MouseLeftReleased(); + emit ItemByMouseRelease(itemAt(event->scenePos(), {})); } QGraphicsScene::mouseReleaseEvent(event); } +//--------------------------------------------------------------------------------------------------------------------- +void VMainGraphicsScene::SetAcceptDrop(bool newAcceptDrop) +{ + m_acceptDrop = newAcceptDrop; +} + +//--------------------------------------------------------------------------------------------------------------------- +bool VMainGraphicsScene::AcceptDrop() const +{ + return m_acceptDrop; +} + //--------------------------------------------------------------------------------------------------------------------- bool VMainGraphicsScene::IsNonInteractive() const { @@ -138,10 +150,10 @@ void VMainGraphicsScene::InitOrigins() { // X axis const QLineF lineX(QPointF(25, 0), QPointF(-5, 0)); - QGraphicsLineItem *xLine1 = new QGraphicsLineItem(lineX); + auto *xLine1 = new QGraphicsLineItem(lineX); xLine1->setPen(originsPen); xLine1->setFlag(QGraphicsItem::ItemIgnoresTransformations); - xLine1->setZValue(-1.0); + xLine1->setZValue(-0.5); addItem(xLine1); origins.append(xLine1); @@ -149,10 +161,10 @@ void VMainGraphicsScene::InitOrigins() QLineF arrowLeftLine = lineX; arrowLeftLine.setAngle(arrowLeftLine.angle()-arrowAngle); arrowLeftLine.setLength(arrowLength); - QGraphicsLineItem *xLine2 = new QGraphicsLineItem(arrowLeftLine); + auto *xLine2 = new QGraphicsLineItem(arrowLeftLine); xLine2->setPen(originsPen); xLine2->setFlag(QGraphicsItem::ItemIgnoresTransformations); - xLine2->setZValue(-1.0); + xLine2->setZValue(-0.5); addItem(xLine2); origins.append(xLine2); @@ -160,18 +172,18 @@ void VMainGraphicsScene::InitOrigins() QLineF arrowRightLine = lineX; arrowRightLine.setAngle(arrowRightLine.angle()+arrowAngle); arrowRightLine.setLength(arrowLength); - QGraphicsLineItem *xLine3 = new QGraphicsLineItem(arrowRightLine); + auto *xLine3 = new QGraphicsLineItem(arrowRightLine); xLine3->setPen(originsPen); xLine3->setFlag(QGraphicsItem::ItemIgnoresTransformations); - xLine3->setZValue(-1.0); + xLine3->setZValue(-0.5); addItem(xLine3); origins.append(xLine3); // X axis text - QGraphicsSimpleTextItem *xOrigin = new QGraphicsSimpleTextItem(QStringLiteral("X"), xLine1); + auto *xOrigin = new QGraphicsSimpleTextItem(QStringLiteral("X"), xLine1); xOrigin->setBrush(axisTextBrush); xOrigin->setFlag(QGraphicsItem::ItemIgnoresTransformations); - xOrigin->setZValue(-1.0); + xOrigin->setZValue(-0.5); xOrigin->setPos(30, -(xOrigin->boundingRect().height()/2)); origins.append(xOrigin); } @@ -179,10 +191,10 @@ void VMainGraphicsScene::InitOrigins() { // Y axis const QLineF lineY(QPointF(0, 25), QPointF(0, -5)); - QGraphicsLineItem *yLine1 = new QGraphicsLineItem(lineY); + auto *yLine1 = new QGraphicsLineItem(lineY); yLine1->setPen(originsPen); yLine1->setFlag(QGraphicsItem::ItemIgnoresTransformations); - yLine1->setZValue(-1.0); + yLine1->setZValue(-0.5); addItem(yLine1); origins.append(yLine1); @@ -190,10 +202,10 @@ void VMainGraphicsScene::InitOrigins() QLineF arrowLeftLine = lineY; arrowLeftLine.setAngle(arrowLeftLine.angle()-arrowAngle); arrowLeftLine.setLength(arrowLength); - QGraphicsLineItem *yLine2 = new QGraphicsLineItem(arrowLeftLine); + auto *yLine2 = new QGraphicsLineItem(arrowLeftLine); yLine2->setPen(originsPen); yLine2->setFlag(QGraphicsItem::ItemIgnoresTransformations); - yLine2->setZValue(-1.0); + yLine2->setZValue(-0.5); addItem(yLine2); origins.append(yLine2); @@ -201,18 +213,18 @@ void VMainGraphicsScene::InitOrigins() QLineF arrowRightLine = lineY; arrowRightLine.setAngle(arrowRightLine.angle()+arrowAngle); arrowRightLine.setLength(arrowLength); - QGraphicsLineItem *yLine3 = new QGraphicsLineItem(arrowRightLine); + auto *yLine3 = new QGraphicsLineItem(arrowRightLine); yLine3->setPen(originsPen); yLine3->setFlag(QGraphicsItem::ItemIgnoresTransformations); - yLine3->setZValue(-1.0); + yLine3->setZValue(-0.5); addItem(yLine3); origins.append(yLine3); // Y axis text - QGraphicsSimpleTextItem *yOrigin = new QGraphicsSimpleTextItem(QStringLiteral("Y"), yLine1); + auto *yOrigin = new QGraphicsSimpleTextItem(QStringLiteral("Y"), yLine1); yOrigin->setBrush(axisTextBrush); yOrigin->setFlag(QGraphicsItem::ItemIgnoresTransformations); - yOrigin->setZValue(-1.0); + yOrigin->setZValue(-0.5); yOrigin->setPos(-(yOrigin->boundingRect().width()/2), 30); origins.append(yOrigin); } diff --git a/src/libs/vwidgets/vmaingraphicsscene.h b/src/libs/vwidgets/vmaingraphicsscene.h index aced01546..ede6ec022 100644 --- a/src/libs/vwidgets/vmaingraphicsscene.h +++ b/src/libs/vwidgets/vmaingraphicsscene.h @@ -67,6 +67,9 @@ public: bool IsNonInteractive() const; void SetNonInteractive(bool nonInteractive); + void SetAcceptDrop(bool newAcceptDrop); + auto AcceptDrop() const -> bool; + public slots: void ChoosedItem(quint32 id, const SceneObject &type); void SelectedItem(bool selected, quint32 object, quint32 tool); @@ -110,7 +113,9 @@ signals: void MouseLeftPressed(); void MouseLeftReleased(); - void ItemClicked(QGraphicsItem* pItem); + void ItemByMousePress(QGraphicsItem* pItem); + void ItemByMouseRelease(QGraphicsItem* pItem); + void AddBackgroundImage(const QPointF &pos, const QString &fileName); /** * @brief ChoosedObject send option choosed object. @@ -165,6 +170,8 @@ private: /** @brief m_nonInteractive all item on scene in non interactive. */ bool m_nonInteractive{false}; + + bool m_acceptDrop{false}; }; //--------------------------------------------------------------------------------------------------------------------- diff --git a/src/libs/vwidgets/vmaingraphicsview.cpp b/src/libs/vwidgets/vmaingraphicsview.cpp index d170bae74..daacb96ab 100644 --- a/src/libs/vwidgets/vmaingraphicsview.cpp +++ b/src/libs/vwidgets/vmaingraphicsview.cpp @@ -49,6 +49,8 @@ #include #include #include +#include +#include #include "../vmisc/def.h" #include "../vmisc/vmath.h" @@ -59,6 +61,7 @@ #include "../vmisc/vcommonsettings.h" #include "vabstractmainwindow.h" #include "global.h" +#include "../ifc/xml/utils.h" const qreal maxSceneSize = ((20.0 * 1000.0) / 25.4) * PrintDPI; // 20 meters in pixels @@ -430,6 +433,8 @@ VMainGraphicsView::VMainGraphicsView(QWidget *parent) m_oldCursor(), m_currentCursor(Qt::ArrowCursor) { + setAcceptDrops(true); + VCommonSettings *settings = qobject_cast(VAbstractApplication::VApp()->Settings()); if (settings && settings->IsOpenGLRender()) { @@ -653,6 +658,67 @@ void VMainGraphicsView::mouseDoubleClickEvent(QMouseEvent *event) QGraphicsView::mouseDoubleClickEvent(event); } +//--------------------------------------------------------------------------------------------------------------------- +void VMainGraphicsView::dragEnterEvent(QDragEnterEvent *event) +{ + const QMimeData *mime = event->mimeData(); + auto *currentScene = qobject_cast(scene()); + if (currentScene != nullptr && currentScene->AcceptDrop() && mime != nullptr && mime->hasText()) + { + QUrl urlPath(mime->text().simplified()); + if (urlPath.isLocalFile()) + { + const QString fileName = urlPath.toLocalFile(); + QFileInfo f(fileName); + if (f.exists() && IsMimeTypeImage(QMimeDatabase().mimeTypeForFile(fileName))) + { + event->acceptProposedAction(); + } + } + } +} + +//--------------------------------------------------------------------------------------------------------------------- +void VMainGraphicsView::dragMoveEvent(QDragMoveEvent *event) +{ + const QMimeData *mime = event->mimeData(); + auto *currentScene = qobject_cast(scene()); + if (currentScene != nullptr && currentScene->AcceptDrop() && mime != nullptr && mime->hasText()) + { + QUrl urlPath(mime->text().simplified()); + if (urlPath.isLocalFile()) + { + const QString fileName = urlPath.toLocalFile(); + QFileInfo f(fileName); + if (f.exists() && IsMimeTypeImage(QMimeDatabase().mimeTypeForFile(fileName))) + { + event->acceptProposedAction(); + } + } + } +} + +//--------------------------------------------------------------------------------------------------------------------- +void VMainGraphicsView::dropEvent(QDropEvent *event) +{ + const QMimeData *mime = event->mimeData(); + auto *currentScene = qobject_cast(scene()); + if (currentScene != nullptr && currentScene->AcceptDrop() && mime != nullptr && mime->hasText()) + { + QUrl urlPath(mime->text().simplified()); + if (urlPath.isLocalFile()) + { + const QString fileName = urlPath.toLocalFile(); + QFileInfo f(fileName); + if (f.exists() && IsMimeTypeImage(QMimeDatabase().mimeTypeForFile(fileName))) + { + emit currentScene->AddBackgroundImage(mapToScene(event->pos()), fileName); + event->acceptProposedAction(); + } + } + } +} + //--------------------------------------------------------------------------------------------------------------------- qreal VMainGraphicsView::MinScale() { diff --git a/src/libs/vwidgets/vmaingraphicsview.h b/src/libs/vwidgets/vmaingraphicsview.h index f6db798d2..d5155e2a2 100644 --- a/src/libs/vwidgets/vmaingraphicsview.h +++ b/src/libs/vwidgets/vmaingraphicsview.h @@ -158,10 +158,13 @@ public slots: void ZoomFitBest(); void ResetScrollingAnimation(); protected: - virtual void mousePressEvent(QMouseEvent *event) override; - virtual void mouseMoveEvent(QMouseEvent *event) override; - virtual void mouseReleaseEvent(QMouseEvent *event) override; - virtual void mouseDoubleClickEvent(QMouseEvent *event) override; + void mousePressEvent(QMouseEvent *event) override; + void mouseMoveEvent(QMouseEvent *event) override; + void mouseReleaseEvent(QMouseEvent *event) override; + void mouseDoubleClickEvent(QMouseEvent *event) override; + void dragEnterEvent(QDragEnterEvent *event) override; + void dragMoveEvent(QDragMoveEvent *event) override; + void dropEvent(QDropEvent *event) override; private: Q_DISABLE_COPY(VMainGraphicsView) GraphicsViewZoom* zoom;