File indexing completed on 2025-02-02 04:11:25

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 "selection_manager.hpp"
0008 
0009 #include <QGuiApplication>
0010 #include <QMimeData>
0011 #include <QClipboard>
0012 
0013 #include "model/assets/assets.hpp"
0014 #include "model/shapes/precomp_layer.hpp"
0015 #include "model/assets/pending_asset.hpp"
0016 
0017 #include "command/shape_commands.hpp"
0018 #include "command/structure_commands.hpp"
0019 #include "command/undo_macro_guard.hpp"
0020 
0021 #include "glaxnimate_app.hpp"
0022 
0023 using namespace glaxnimate::gui;
0024 using namespace glaxnimate;
0025 
0026 
0027 model::ShapeElement* glaxnimate::gui::SelectionManager::current_shape() const
0028 {
0029     model::DocumentNode* curr = current_document_node();
0030     if ( curr )
0031     {
0032         if ( auto curr_shape = qobject_cast<model::ShapeElement*>(curr) )
0033             return curr_shape;
0034     }
0035     return nullptr;
0036 }
0037 
0038 model::ShapeListProperty* glaxnimate::gui::SelectionManager::current_shape_container() const
0039 {
0040     model::DocumentNode* sh = current_document_node();
0041 
0042     if ( !sh )
0043         return &current_composition()->shapes;
0044 
0045     if ( auto lay = qobject_cast<model::Composition*>(sh) )
0046         return &lay->shapes;
0047 
0048     if ( !qobject_cast<model::Layer*>(sh) )
0049         sh = sh->docnode_parent();
0050 
0051     while ( sh )
0052     {
0053         if ( auto grp = qobject_cast<model::Group*>(sh) )
0054             return &grp->shapes;
0055         if ( auto lay = qobject_cast<model::Composition*>(sh) )
0056             return &lay->shapes;
0057         sh = sh->docnode_parent();
0058     }
0059 
0060     return &current_composition()->shapes;
0061 }
0062 
0063 std::vector<model::VisualNode *> glaxnimate::gui::SelectionManager::copy() const
0064 {
0065     auto selection = cleaned_selection();
0066 
0067     if ( !selection.empty() )
0068     {
0069         QMimeData* data = new QMimeData;
0070         for ( const auto& serializer : supported_mimes() )
0071         {
0072             serializer->to_mime_data(*data, std::vector<model::DocumentNode*>(selection.begin(), selection.end()));
0073         }
0074 
0075         GlaxnimateApp::instance()->set_clipboard_data(data);
0076     }
0077 
0078     return selection;
0079 }
0080 
0081 void glaxnimate::gui::SelectionManager::cut()
0082 {
0083     auto selection = copy();
0084 
0085     delete_shapes_impl(i18n("Cut"), selection);
0086 }
0087 
0088 void glaxnimate::gui::SelectionManager::delete_selected()
0089 {
0090     delete_shapes_impl(i18n("Delete"), cleaned_selection());
0091 }
0092 
0093 void glaxnimate::gui::SelectionManager::delete_shapes_impl(const QString &undo_string, const std::vector<model::VisualNode *>& selection)
0094 {
0095     if ( selection.empty() )
0096         return;
0097 
0098     auto doc = document();
0099     command::UndoMacroGuard macro(undo_string, doc);
0100     auto current = this->current_document_node();
0101 
0102     for ( auto item : selection )
0103     {
0104         if ( auto shape = qobject_cast<model::ShapeElement*>(item) )
0105         {
0106             if ( !shape->docnode_locked_recursive() )
0107             {
0108                 if ( current->is_descendant_of(shape) )
0109                     current = shape->docnode_visual_parent();
0110                 doc->push_command(new command::RemoveShape(shape, shape->owner()));
0111             }
0112         }
0113     }
0114 
0115     if ( current )
0116         set_current_document_node(current);
0117 }
0118 
0119 void glaxnimate::gui::SelectionManager::paste()
0120 {
0121     paste_impl(false);
0122 }
0123 
0124 void glaxnimate::gui::SelectionManager::paste_as_composition()
0125 {
0126     paste_impl(true);
0127 }
0128 
0129 template<class T>
0130 static void paste_assets(model::SubObjectProperty<T> (model::Assets::* p), model::Document* source, model::Document* current_document)
0131 {
0132     T* subject = (source->assets()->*p).get();
0133     T* target = (current_document->assets()->*p).get();
0134 
0135     for ( auto& item : subject->values.raw() )
0136     {
0137         if ( !current_document->assets()->find_by_uuid(item->uuid.get()) )
0138         {
0139             item->transfer(current_document);
0140             current_document->push_command(new command::AddObject(
0141                 &target->values,
0142                 std::move(item),
0143                 target->values.size()
0144             ));
0145         }
0146     }
0147 }
0148 
0149 void glaxnimate::gui::SelectionManager::paste_impl(bool as_comp)
0150 {
0151     const QMimeData* data = GlaxnimateApp::instance()->get_clipboard_data();
0152 
0153     io::mime::DeserializedData raw_pasted;
0154     for ( const auto& serializer : supported_mimes() )
0155     {
0156         raw_pasted = serializer->from_mime_data(*data);
0157         if ( !raw_pasted.empty() )
0158             break;
0159     }
0160     if ( raw_pasted.empty() )
0161     {
0162 //        status_message(i18n("Nothing to paste"));
0163         return;
0164     }
0165 
0166     paste_document(raw_pasted.document.get(), i18n("Paste"), as_comp);
0167 }
0168 
0169 void glaxnimate::gui::SelectionManager::paste_document(model::Document* document, const QString& macro_name, bool as_comp)
0170 {
0171     auto doc = this->document();
0172 
0173     auto& comps = document->assets()->compositions->values;
0174 
0175     command::UndoMacroGuard macro(macro_name, doc);
0176     paste_assets(&model::Assets::colors, document, doc);
0177     paste_assets(&model::Assets::images, document, doc);
0178     paste_assets(&model::Assets::gradient_colors, document, doc);
0179     paste_assets(&model::Assets::gradients, document, doc);
0180 
0181     model::ShapeListProperty* shape_cont = current_shape_container();
0182     std::vector<model::VisualNode*> select;
0183 
0184     if ( comps.empty() )
0185         return;
0186     if ( comps.size() > 1 )
0187         as_comp = true;
0188 
0189     model::Composition* comp = comps[0];
0190 
0191     if ( !as_comp )
0192     {
0193         if ( !comp->shapes.empty() )
0194         {
0195             int shape_insertion_point = shape_cont->size();
0196             for ( auto& shape : comp->shapes.raw() )
0197             {
0198                 auto ptr = shape.get();
0199                 shape->clear_owner();
0200                 shape->refresh_uuid();
0201                 if ( !as_comp )
0202                     select.push_back(ptr);
0203                 shape->transfer(doc);
0204                 doc->push_command(new command::AddShape(shape_cont, std::move(shape), shape_insertion_point++));
0205                 if ( !as_comp )
0206                     ptr->recursive_rename();
0207             }
0208         }
0209     }
0210     else
0211     {
0212         paste_assets(&model::Assets::compositions, document, doc);
0213     }
0214 
0215     for ( const auto& pending : document->pending_assets() )
0216         doc->add_pending_asset(pending);
0217 
0218     if ( as_comp )
0219     {
0220         select.push_back(layer_new_comp(comp));
0221     }
0222 
0223     set_selection(select);
0224 }
0225 
0226 void glaxnimate::gui::SelectionManager::layer_new_impl(std::unique_ptr<model::ShapeElement> layer)
0227 {
0228     auto doc = document();
0229     if ( layer->name.get().isEmpty() )
0230         doc->set_best_name(layer.get(), {});
0231     auto curr_dn = current_document_node();
0232     layer->set_time(curr_dn ? curr_dn->time() : doc->current_time());
0233 
0234     model::ShapeElement* ptr = layer.get();
0235 
0236     auto cont = current_shape_container();
0237     int position = cont->index_of(current_shape());
0238     if ( position >= 0 )
0239         position += 1;
0240     doc->push_command(new command::AddShape(cont, std::move(layer), position));
0241 
0242     set_current_document_node(ptr);
0243 }
0244 
0245 
0246 model::PreCompLayer* glaxnimate::gui::SelectionManager::layer_new_comp(model::Composition* comp)
0247 {
0248     auto doc = document();
0249     auto layer = std::make_unique<model::PreCompLayer>(doc);
0250     layer->composition.set(comp);
0251     layer->name.set(comp->name.get());
0252     layer->size.set(comp->size());
0253     QPointF pos = current_composition()->rect().center();
0254     layer->transform.get()->anchor_point.set(pos);
0255     layer->transform.get()->position.set(pos);
0256     auto ptr = layer.get();
0257     layer_new_impl(std::move(layer));
0258     return ptr;
0259 }