File indexing completed on 2024-12-15 04:01:01

0001 /*
0002  * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia <dev@dragon.best>
0003  *
0004  * SPDX-License-Identifier: GPL-3.0-or-later
0005  */
0006 
0007 #include "shape_commands.hpp"
0008 #include "model/shapes/group.hpp"
0009 #include "model/assets/composition.hpp"
0010 #include "model/document.hpp"
0011 
0012 using namespace glaxnimate;
0013 
0014 namespace {
0015 
0016 /**
0017  * \returns The parent node for \p shape
0018  */
0019 model::VisualNode* shape_parent(model::ShapeElement* shape)
0020 {
0021     return static_cast<model::VisualNode*>(shape->owner()->object());
0022 }
0023 
0024 /**
0025  * \returns The parent node for \p shape
0026  */
0027 model::VisualNode* shape_parent(model::VisualNode* shape)
0028 {
0029     if ( auto se = qobject_cast<model::ShapeElement*>(shape) )
0030         return shape_parent(se);
0031     return nullptr;
0032 }
0033 
0034 /**
0035  * \brief Represents a sequence of nested nodes to reach
0036  */
0037 struct PathToLayer
0038 {
0039     PathToLayer() = default;
0040 
0041     explicit PathToLayer(model::VisualNode* node)
0042     {
0043         composition = nullptr;
0044         while ( node && !composition )
0045         {
0046             composition = qobject_cast<model::Composition*>(node);
0047             if ( composition )
0048                 break;
0049 
0050             if ( auto group = qobject_cast<model::Group*>(node) )
0051             {
0052                 steps.push_back(group);
0053                 node = shape_parent(group);
0054             }
0055             else
0056             {
0057                 return;
0058             }
0059         }
0060     }
0061 
0062     std::vector<model::Group*> steps;
0063     model::Composition* composition = nullptr;
0064 
0065     model::ShapeListProperty* lowest() const
0066     {
0067         if ( !steps.empty() )
0068             return &steps.front()->shapes;
0069         return &composition->shapes;
0070     }
0071 
0072     model::ShapeListProperty* combine(const PathToLayer& other)
0073     {
0074         if ( other.composition != composition )
0075             return nullptr;
0076 
0077         int i = 0;
0078         for ( int e = std::min(steps.size(), other.steps.size()); i < e; i++ )
0079             if ( steps[i] != other.steps[i] )
0080                 break;
0081 
0082         if ( i < int(steps.size()) )
0083             steps.erase(steps.begin()+i, steps.end());
0084 
0085         return lowest();
0086     }
0087 };
0088 
0089 } // namespace
0090 
0091 command::GroupShapes::Data command::GroupShapes::collect_shapes(const std::vector<model::VisualNode *>& selection)
0092 {
0093     if ( selection.empty() )
0094         return {};
0095 
0096     Data data;
0097     PathToLayer collected;
0098 
0099     int i = 0;
0100     for ( ; i < int(selection.size()) && !data.parent; i++ )
0101     {
0102         collected = PathToLayer(shape_parent(selection[i]));
0103         data.parent = collected.lowest();
0104     }
0105 
0106     for ( ; i < int(selection.size()) && data.parent; i++ )
0107     {
0108         data.parent = collected.combine(PathToLayer(shape_parent(selection[i])));
0109         if ( !data.parent )
0110             return {};
0111     }
0112 
0113     data.elements.reserve(selection.size());
0114     for ( auto n : selection )
0115         data.elements.push_back(static_cast<model::ShapeElement*>(n));
0116     return data;
0117 }
0118 
0119 command::GroupShapes::GroupShapes(const command::GroupShapes::Data& data)
0120     : detail::RedoInCtor(i18n("Group Shapes"))
0121 {
0122     if ( data.parent )
0123     {
0124         std::unique_ptr<model::Group> grp = std::make_unique<model::Group>(data.parent->object()->document());
0125         group = grp.get();
0126         data.parent->object()->document()->set_best_name(group);
0127         (new AddShape(data.parent, std::move(grp), data.parent->size(), this))->redo();
0128 
0129         for ( int i = 0; i < int(data.elements.size()); i++ )
0130         {
0131             (new MoveShape(data.elements[i], data.elements[i]->owner(), &group->shapes, i, this))->redo();
0132         }
0133     }
0134 }
0135 
0136 void command::detail::RedoInCtor::redo()
0137 {
0138     if ( !did )
0139     {
0140         QUndoCommand::redo();
0141         did = true;
0142     }
0143 }
0144 
0145 void command::detail::RedoInCtor::undo()
0146 {
0147     QUndoCommand::undo();
0148     did = false;
0149 }
0150 
0151 
0152 command::UngroupShapes::UngroupShapes(model::Group* group)
0153     : detail::RedoInCtor(i18n("Ungroup Shapes"))
0154 {
0155     int pos = group->owner()->index_of(group);
0156     (new RemoveShape(group, group->owner(), this))->redo();
0157     for ( int i = 0, e = group->shapes.size(); i < e; i++ )
0158     {
0159         (new MoveShape(group->shapes[0], group->shapes[0]->owner(), group->owner(), pos+i, this))->redo();
0160     }
0161 }
0162 
0163 
0164 command::AddShape * command::duplicate_shape ( model::ShapeElement* shape )
0165 {
0166     std::unique_ptr<model::ShapeElement> new_shape (
0167         static_cast<model::ShapeElement*>(shape->clone().release())
0168     );
0169     new_shape->refresh_uuid();
0170     new_shape->recursive_rename();
0171     new_shape->set_time(shape->docnode_parent()->time());
0172 
0173     return new command::AddShape(
0174         shape->owner(),
0175         std::move(new_shape),
0176         shape->owner()->index_of(shape)+1,
0177         nullptr,
0178         i18n("Duplicate %1", shape->object_name())
0179     );
0180 }
0181