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