File indexing completed on 2025-02-02 04:11:23
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 "glaxnimate_window_p.hpp" 0008 0009 #include <queue> 0010 0011 #include <QClipboard> 0012 #include <QImageReader> 0013 #include <QFileDialog> 0014 #include <QMimeData> 0015 #include <QMimeType> 0016 #include <QMimeDatabase> 0017 0018 #include "app/settings/widget_builder.hpp" 0019 0020 #include "command/shape_commands.hpp" 0021 #include "command/structure_commands.hpp" 0022 #include "command/undo_macro_guard.hpp" 0023 0024 #include "model/shapes/image.hpp" 0025 #include "model/shapes/group.hpp" 0026 #include "model/shapes/path.hpp" 0027 #include "model/shapes/precomp_layer.hpp" 0028 #include "model/simple_visitor.hpp" 0029 #include "model/shapes/text.hpp" 0030 0031 #include "settings/clipboard_settings.hpp" 0032 #include "widgets/dialogs/shape_parent_dialog.hpp" 0033 #include "widgets/shape_style/shape_style_preview_widget.hpp" 0034 0035 #include "item_models/drag_data.hpp" 0036 0037 model::Composition* GlaxnimateWindow::Private::current_composition() 0038 { 0039 return comp; 0040 } 0041 0042 model::VisualNode* GlaxnimateWindow::Private::current_document_node() 0043 { 0044 if ( auto dn = ui.view_document_node->current_node() ) 0045 return dn; 0046 return comp; 0047 } 0048 0049 void GlaxnimateWindow::Private::layer_new_layer() 0050 { 0051 auto layer = std::make_unique<model::Layer>(current_document.get()); 0052 layer->animation->last_frame.set(comp->animation->last_frame.get()); 0053 QPointF pos = comp->rect().center(); 0054 layer->transform.get()->anchor_point.set(pos); 0055 layer->transform.get()->position.set(pos); 0056 parent->layer_new_impl(std::move(layer)); 0057 } 0058 0059 void GlaxnimateWindow::Private::layer_new_fill() 0060 { 0061 auto layer = std::make_unique<model::Fill>(current_document.get()); 0062 layer->color.set(ui.fill_style_widget->current_color()); 0063 parent->layer_new_impl(std::move(layer)); 0064 } 0065 0066 void GlaxnimateWindow::Private::layer_new_stroke() 0067 { 0068 auto layer = std::make_unique<model::Stroke>(current_document.get()); 0069 layer->set_pen_style(ui.stroke_style_widget->pen_style()); 0070 parent->layer_new_impl(std::move(layer)); 0071 } 0072 0073 void GlaxnimateWindow::Private::layer_new_group() 0074 { 0075 auto layer = std::make_unique<model::Group>(current_document.get()); 0076 QPointF pos = comp->rect().center(); 0077 layer->transform.get()->anchor_point.set(pos); 0078 layer->transform.get()->position.set(pos); 0079 parent->layer_new_impl(std::move(layer)); 0080 } 0081 0082 void GlaxnimateWindow::Private::layer_delete() 0083 { 0084 auto current = parent->current_shape(); 0085 if ( !current ) 0086 return; 0087 parent->delete_shapes_impl(i18n("Delete %1", current->object_name()), {current}); 0088 } 0089 0090 void GlaxnimateWindow::Private::layer_duplicate() 0091 { 0092 auto current = parent->current_shape(); 0093 if ( !current ) 0094 return; 0095 0096 auto cmd = command::duplicate_shape(current); 0097 current->push_command(cmd); 0098 set_current_document_node(cmd->object()); 0099 } 0100 0101 std::vector<model::VisualNode*> GlaxnimateWindow::Private::cleaned_selection() 0102 { 0103 return scene.cleaned_selection(); 0104 } 0105 0106 0107 void GlaxnimateWindow::Private::duplicate_selection() 0108 { 0109 auto selection = cleaned_selection(); 0110 0111 if ( !selection.empty() ) 0112 { 0113 std::vector<model::VisualNode*> duplicated; 0114 duplicated.reserve(selection.size()); 0115 0116 for ( const auto& node : selection ) 0117 { 0118 if ( auto shape = node->cast<model::ShapeElement>() ) 0119 { 0120 auto cmd = command::duplicate_shape(shape); 0121 current_document->push_command(cmd); 0122 duplicated.push_back(cmd->object()); 0123 } 0124 } 0125 0126 scene.user_select(duplicated, graphics::DocumentScene::Replace); 0127 } 0128 } 0129 0130 0131 void GlaxnimateWindow::Private::move_current(command::ReorderCommand::SpecialPosition pos) 0132 { 0133 auto current = parent->current_shape(); 0134 if ( !current ) 0135 return; 0136 auto cmd = std::make_unique<command::ReorderCommand>(current, pos); 0137 if ( !cmd->has_action() ) 0138 return; 0139 current->push_command(cmd.release()); 0140 } 0141 0142 void GlaxnimateWindow::Private::group_shapes() 0143 { 0144 auto data = command::GroupShapes::collect_shapes(cleaned_selection()); 0145 if ( data.parent ) 0146 current_document->push_command( 0147 new command::GroupShapes(data) 0148 ); 0149 } 0150 0151 void GlaxnimateWindow::Private::ungroup_shapes() 0152 { 0153 model::Group* group = qobject_cast<model::Group*>(current_document_node()); 0154 0155 if ( !group ) 0156 { 0157 auto sp = parent->current_shape_container(); 0158 if ( !sp ) 0159 return; 0160 group = qobject_cast<model::Group*>(sp->object()); 0161 } 0162 0163 if ( group ) 0164 current_document->push_command(new command::UngroupShapes(group)); 0165 } 0166 0167 0168 void GlaxnimateWindow::Private::move_to() 0169 { 0170 auto sel = cleaned_selection(); 0171 std::vector<model::ShapeElement*> shapes; 0172 shapes.reserve(sel.size()); 0173 for ( const auto& node : sel ) 0174 { 0175 if ( auto shape = qobject_cast<model::ShapeElement*>(node) ) 0176 shapes.push_back(shape); 0177 } 0178 0179 if ( shapes.empty() ) 0180 return; 0181 0182 0183 if ( auto parent = ShapeParentDialog(&document_node_model, this->parent).get_shape_parent() ) 0184 { 0185 command::UndoMacroGuard macro(i18n("Move Shapes"), current_document.get()); 0186 for ( auto shape : shapes ) 0187 if ( shape->owner() != parent ) 0188 shape->push_command(new command::MoveShape(shape, shape->owner(), parent, parent->size())); 0189 } 0190 } 0191 0192 QStringList GlaxnimateWindow::Private::get_open_image_files(const QString& title, const QString& dir, QString* out_dir, bool multiple) 0193 { 0194 QFileDialog dialog(parent, title, dir); 0195 QStringList filters; 0196 QStringList all_ext; 0197 0198 QMimeDatabase db; 0199 for ( const auto& baf : QImageReader::supportedMimeTypes() ) 0200 { 0201 QMimeType mime(db.mimeTypeForName(baf)); 0202 if ( mime.isValid() ) 0203 { 0204 const QString patterns = mime.globPatterns().join(QLatin1Char(' ')); 0205 all_ext += patterns; 0206 filters.push_back(mime.comment() + QLatin1String(" (") + patterns + QLatin1Char(')')); 0207 } 0208 } 0209 filters.push_front(i18n("All Supported files (%1)", all_ext.join(' '))); 0210 dialog.setNameFilters(filters); 0211 0212 dialog.setAcceptMode(QFileDialog::AcceptOpen); 0213 dialog.setOption(QFileDialog::DontUseNativeDialog, !app::settings::get<bool>("open_save", "native_dialog")); 0214 0215 if ( multiple ) 0216 dialog.setFileMode(QFileDialog::ExistingFiles); 0217 else 0218 dialog.setFileMode(QFileDialog::ExistingFile); 0219 0220 if ( dialog.exec() == QDialog::Rejected ) 0221 return {}; 0222 0223 if ( out_dir ) 0224 *out_dir = dialog.directory().path(); 0225 0226 return dialog.selectedFiles(); 0227 } 0228 0229 0230 void GlaxnimateWindow::Private::import_image() 0231 { 0232 QString path = app::settings::get<QString>("open_save", "import_path"); 0233 if ( path.isEmpty() ) 0234 path = current_document->io_options().path.absolutePath(); 0235 0236 QStringList image_files = get_open_image_files(i18n("Import Image"), path, &path, true); 0237 if ( image_files.isEmpty() ) 0238 return; 0239 0240 app::settings::set("open_save", "import_path", path); 0241 0242 /// \todo dialog asking whether to embed 0243 command::UndoMacroGuard macro(i18n("Import Image"), current_document.get()); 0244 0245 model::Image* select = nullptr; 0246 0247 for ( const auto& image_file : image_files ) 0248 { 0249 auto bitmap = std::make_unique<model::Bitmap>(current_document.get()); 0250 bitmap->filename.set(image_file); 0251 if ( bitmap->pixmap().isNull() ) 0252 { 0253 show_warning(i18n("Import Image"), i18n("Could not import image")); 0254 continue; 0255 } 0256 0257 auto defs = current_document->assets(); 0258 auto bmp_ptr = bitmap.get(); 0259 current_document->push_command(new command::AddObject(&defs->images->values, std::move(bitmap), defs->images->values.size())); 0260 0261 auto image = std::make_unique<model::Image>(current_document.get()); 0262 image->image.set(bmp_ptr); 0263 QPointF p(bmp_ptr->pixmap().width() / 2.0, bmp_ptr->pixmap().height() / 2.0); 0264 image->transform->anchor_point.set(p); 0265 image->transform->position.set(p); 0266 auto comp = current_composition(); 0267 select = image.get(); 0268 image->name.set(QFileInfo(image_file).baseName()); 0269 current_document->push_command(new command::AddShape(&comp->shapes, std::move(image), comp->shapes.size())); 0270 } 0271 0272 if ( select ) 0273 set_current_document_node(select); 0274 } 0275 0276 template<class T> 0277 static void remove_assets(T& prop, int& count) 0278 { 0279 for ( int i = 0; i < prop.size(); ) 0280 { 0281 if ( prop[i]->remove_if_unused(true) ) 0282 count++; 0283 else 0284 i++; 0285 } 0286 } 0287 0288 void GlaxnimateWindow::Private::cleanup_document() 0289 { 0290 command::UndoMacroGuard guard(i18n("Cleanup Document"), current_document.get()); 0291 int count = 0; 0292 0293 remove_assets(current_document->assets()->gradients->values, count); 0294 remove_assets(current_document->assets()->gradient_colors->values, count); 0295 remove_assets(current_document->assets()->colors->values, count); 0296 remove_assets(current_document->assets()->images->values, count); 0297 0298 status_message(i18n("Removed %1 assets", count), 0); 0299 } 0300 0301 void GlaxnimateWindow::Private::convert_to_path(const std::vector<model::ShapeElement*>& shapes, std::vector<model::ShapeElement*>* out) 0302 { 0303 if ( shapes.empty() ) 0304 return; 0305 0306 QString macro_name = i18n("Convert to path"); 0307 if ( shapes.size() == 1 ) 0308 macro_name = i18n("Convert %1 to path", (*shapes.begin())->name.get()); 0309 0310 std::unordered_map<model::Layer*, model::Layer*> converted_layers; 0311 0312 command::UndoMacroGuard guard(macro_name, current_document.get(), false); 0313 for ( auto shape : shapes ) 0314 { 0315 auto path = shape->to_path(); 0316 0317 if ( out ) 0318 out->push_back(path.get()); 0319 0320 if ( path ) 0321 { 0322 if ( auto lay = shape->cast<model::Layer>() ) 0323 converted_layers[lay] = static_cast<model::Layer*>(path.get()); 0324 0325 guard.start(); 0326 current_document->push_command( 0327 new command::AddObject<model::ShapeElement>( 0328 shape->owner(), 0329 std::move(path), 0330 shape->position() 0331 ) 0332 ); 0333 current_document->push_command( 0334 new command::RemoveObject<model::ShapeElement>(shape, shape->owner()) 0335 ); 0336 } 0337 } 0338 0339 // Maintain parenting of layers that have been converted 0340 for ( const auto& p : converted_layers ) 0341 { 0342 if ( auto src_parent = p.first->parent.get() ) 0343 { 0344 auto it = converted_layers.find(src_parent); 0345 if ( it != converted_layers.end() ) 0346 p.second->parent.set(it->second); 0347 } 0348 } 0349 } 0350 0351 void GlaxnimateWindow::Private::to_path() 0352 { 0353 std::vector<model::ShapeElement*> shapes; 0354 0355 for ( auto selected : scene.cleaned_selection() ) 0356 { 0357 if ( selected->docnode_locked_recursive() ) 0358 continue; 0359 0360 if ( auto shape = selected->cast<model::ShapeElement>() ) 0361 { 0362 if ( !shape->cast<model::Styler>() ) 0363 shapes.push_back(shape); 0364 } 0365 } 0366 0367 convert_to_path(shapes, nullptr); 0368 } 0369 0370 void GlaxnimateWindow::Private::switch_composition(model::Composition* new_comp, int i) 0371 { 0372 if ( i != ui.tab_bar->currentIndex() ) 0373 { 0374 QSignalBlocker g(ui.tab_bar); 0375 ui.tab_bar->setCurrentIndex(i); 0376 } 0377 0378 if ( comp ) 0379 { 0380 int old_i = current_document->assets()->compositions->values.index_of(static_cast<model::Composition*>(comp)); 0381 comp_selections[old_i].selection = scene.selection(); 0382 if ( ui.view_document_node->currentIndex().isValid() ) 0383 comp_selections[old_i].current = ui.view_document_node->current_node(); 0384 else 0385 comp_selections[old_i].current = comp; 0386 0387 QObject::disconnect(comp->animation.get(), &model::AnimationContainer::first_frame_changed, ui.play_controls, nullptr); 0388 QObject::disconnect(comp, &model::Composition::fps_changed, ui.play_controls, &FrameControlsWidget::set_fps); 0389 } 0390 0391 comp = new_comp; 0392 0393 ui.play_controls->set_range(comp->animation->first_frame.get(), comp->animation->last_frame.get()); 0394 ui.play_controls->set_fps(comp->fps.get()); 0395 ui.play_controls_2->set_range(comp->animation->first_frame.get(), comp->animation->last_frame.get()); 0396 ui.play_controls_2->set_fps(comp->fps.get()); 0397 0398 QObject::connect(comp->animation.get(), &model::AnimationContainer::first_frame_changed, ui.play_controls, [this](float frame){ui.play_controls->set_min(frame);}); 0399 QObject::connect(comp->animation.get(), &model::AnimationContainer::last_frame_changed, ui.play_controls, [this](float frame){ui.play_controls->set_max(frame);}); 0400 QObject::connect(comp, &model::Composition::fps_changed, ui.play_controls, &FrameControlsWidget::set_fps); 0401 0402 auto possible = current_document->comp_graph().possible_descendants(comp, current_document.get()); 0403 std::set<model::Composition*> comps(possible.begin(), possible.end()); 0404 for ( QAction* action : ui.menu_new_comp_layer->actions() ) 0405 action->setEnabled(comps.count(action->data().value<model::Composition*>())); 0406 0407 ui.view_document_node->set_composition(comp); 0408 ui.timeline_widget->set_composition(comp); 0409 scene.set_composition(comp); 0410 scene.user_select(comp_selections[i].selection, graphics::DocumentScene::Replace); 0411 auto current = comp_selections[i].current; 0412 ui.view_document_node->set_current_node(current); 0413 0414 ui.canvas->viewport()->update(); 0415 } 0416 0417 void GlaxnimateWindow::Private::setup_composition(model::Composition* comp, int index) 0418 { 0419 CompState state; 0420 if ( !comp->shapes.empty() ) 0421 state = comp->shapes[0]; 0422 else 0423 state = comp; 0424 0425 if ( index == -1 ) 0426 index = comp_selections.size(); 0427 0428 comp_selections.insert(comp_selections.begin() + index, std::move(state)); 0429 0430 QAction* action = nullptr; 0431 0432 ui.menu_new_comp_layer->setEnabled(true); 0433 action = new QAction(comp->instance_icon(), comp->object_name(), comp); 0434 if ( ui.menu_new_comp_layer->actions().empty() || index >= ui.menu_new_comp_layer->actions().size() ) 0435 ui.menu_new_comp_layer->addAction(action); 0436 else 0437 ui.menu_new_comp_layer->insertAction(ui.menu_new_comp_layer->actions()[index-1], action); 0438 action->setData(QVariant::fromValue(comp)); 0439 0440 connect(comp, &model::DocumentNode::name_changed, action, [comp, action](){ 0441 action->setText(comp->object_name()); 0442 }); 0443 connect(comp, &model::VisualNode::docnode_group_color_changed, action, [comp, action](){ 0444 action->setIcon(comp->instance_icon()); 0445 }); 0446 0447 } 0448 0449 void GlaxnimateWindow::Private::add_composition() 0450 { 0451 auto old_comp = this->comp; 0452 std::unique_ptr<model::Composition> comp = std::make_unique<model::Composition>(current_document.get()); 0453 0454 auto lay = std::make_unique<model::Layer>(current_document.get()); 0455 current_document->set_best_name(lay.get()); 0456 lay->animation->first_frame.set(old_comp->animation->first_frame.get()); 0457 lay->animation->last_frame.set(old_comp->animation->last_frame.get()); 0458 0459 comp->animation->first_frame.set(old_comp->animation->first_frame.get()); 0460 comp->animation->last_frame.set(old_comp->animation->last_frame.get()); 0461 comp->fps.set(old_comp->fps.get()); 0462 comp->width.set(old_comp->width.get()); 0463 comp->height.set(old_comp->height.get()); 0464 0465 QPointF center(comp->width.get() / 2, comp->height.get() / 2); 0466 lay->transform->anchor_point.set(center); 0467 lay->transform->position.set(center); 0468 comp->shapes.insert(std::move(lay)); 0469 0470 current_document->set_best_name(comp.get()); 0471 current_document->push_command(new command::AddObject(¤t_document->assets()->compositions->values, std::move(comp))); 0472 ui.tab_bar->setCurrentIndex(ui.tab_bar->count()-1); 0473 } 0474 0475 void GlaxnimateWindow::Private::objects_to_new_composition( 0476 model::Composition* comp, 0477 const std::vector<model::VisualNode*>& objects, 0478 model::ObjectListProperty<model::ShapeElement>* layer_parent, 0479 int layer_index 0480 ) 0481 { 0482 if ( objects.empty() ) 0483 return; 0484 0485 int new_comp_index = current_document->assets()->compositions->values.size(); 0486 command::UndoMacroGuard guard(i18n("New Composition from Selection"), current_document.get()); 0487 0488 auto ucomp = std::make_unique<model::Composition>(current_document.get()); 0489 model::Composition* new_comp = ucomp.get(); 0490 new_comp->width.set(comp->width.get()); 0491 new_comp->height.set(comp->height.get()); 0492 new_comp->fps.set(comp->fps.get()); 0493 new_comp->animation->first_frame.set(comp->animation->first_frame.get()); 0494 new_comp->animation->last_frame.set(comp->animation->last_frame.get()); 0495 if ( objects.size() > 1 || objects[0]->name.get().isEmpty() ) 0496 current_document->set_best_name(new_comp); 0497 else 0498 new_comp->name.set(objects[0]->name.get()); 0499 current_document->push_command(new command::AddObject(¤t_document->assets()->compositions->values, std::move(ucomp))); 0500 0501 0502 for ( auto node : objects ) 0503 { 0504 if ( auto shape = node->cast<model::ShapeElement>() ) 0505 current_document->push_command(new command::MoveShape( 0506 shape, shape->owner(), &new_comp->shapes, new_comp->shapes.size() 0507 )); 0508 } 0509 0510 comp_selections.back().current = objects[0]; 0511 comp_selections.back().selection = objects; 0512 0513 auto pcl = std::make_unique<model::PreCompLayer>(current_document.get()); 0514 pcl->composition.set(new_comp); 0515 pcl->size.set(new_comp->size()); 0516 current_document->set_best_name(pcl.get()); 0517 auto pcl_ptr = pcl.get(); 0518 current_document->push_command(new command::AddShape(layer_parent, std::move(pcl), layer_index)); 0519 0520 switch_composition(new_comp, new_comp_index); 0521 0522 int old_comp_index = current_document->assets()->compositions->values.index_of(static_cast<model::Composition*>(comp)); 0523 comp_selections[old_comp_index] = pcl_ptr; 0524 } 0525 0526 0527 void GlaxnimateWindow::Private::on_remove_precomp(int index) 0528 { 0529 model::Composition* precomp = current_document->assets()->compositions->values[index]; 0530 0531 if ( precomp == comp ) 0532 { 0533 auto& comps = current_document->assets()->compositions->values; 0534 if ( comps.empty() ) 0535 add_composition(); 0536 0537 switch_composition(comps[qMax(comps.size(), index)], 0); 0538 } 0539 0540 delete ui.menu_new_comp_layer->actions()[index]; 0541 comp_selections.erase(comp_selections.begin()+index); 0542 } 0543 0544 void GlaxnimateWindow::Private::layer_new_comp_action(QAction* action) 0545 { 0546 parent->layer_new_comp(action->data().value<model::Composition*>()); 0547 } 0548 0549 void GlaxnimateWindow::Private::shape_to_composition(model::ShapeElement* node) 0550 { 0551 if ( !node ) 0552 return; 0553 0554 auto parent = node->docnode_parent(); 0555 if ( !parent ) 0556 return; 0557 0558 auto ancestor = parent; 0559 auto grand_ancestor = ancestor->docnode_parent(); 0560 while ( grand_ancestor && !ancestor->is_instance<model::Composition>() ) 0561 { 0562 ancestor = grand_ancestor; 0563 grand_ancestor = ancestor->docnode_parent(); 0564 } 0565 0566 auto owner_comp = ancestor->cast<model::Composition>(); 0567 if ( !owner_comp ) 0568 return; 0569 0570 auto prop = parent->get_property("shapes"); 0571 if ( !prop ) 0572 return; 0573 0574 auto shape_prop = static_cast<model::ObjectListProperty<model::ShapeElement>*>(prop); 0575 objects_to_new_composition(owner_comp, {node}, shape_prop, shape_prop->index_of(node)); 0576 } 0577 0578 QPointF GlaxnimateWindow::Private::align_point(const QRectF& rect, AlignDirection direction, AlignPosition position) 0579 { 0580 qreal x; 0581 qreal y; 0582 0583 if ( direction == AlignDirection::Horizontal ) 0584 { 0585 switch ( position ) 0586 { 0587 case AlignPosition::Begin: x = rect.left(); break; 0588 case AlignPosition::Center: x = rect.center().x(); break; 0589 case AlignPosition::End: x = rect.right(); break; 0590 } 0591 y = rect.center().y(); 0592 } 0593 else 0594 { 0595 switch ( position ) 0596 { 0597 case AlignPosition::Begin: y = rect.top(); break; 0598 case AlignPosition::Center: y = rect.center().y(); break; 0599 case AlignPosition::End: y = rect.bottom(); break; 0600 } 0601 x = rect.center().x(); 0602 } 0603 0604 return {x, y}; 0605 } 0606 0607 namespace { 0608 0609 struct AlignData 0610 { 0611 model::VisualNode* node; 0612 QTransform transform; 0613 QPointF bounds_point; 0614 }; 0615 0616 } // namespace 0617 0618 void GlaxnimateWindow::Private::align(AlignDirection direction, AlignPosition position, bool outside) 0619 { 0620 std::vector<model::VisualNode*> selection = cleaned_selection(); 0621 0622 if ( selection.empty() ) 0623 return; 0624 0625 QRectF bounds; 0626 0627 std::vector<AlignData> data; 0628 data.reserve(selection.size()); 0629 0630 auto bound_pos = position; 0631 if ( outside ) 0632 { 0633 if ( bound_pos == AlignPosition::Begin ) 0634 bound_pos = AlignPosition::End; 0635 else 0636 bound_pos = AlignPosition::Begin; 0637 } 0638 0639 for ( const auto& item : selection ) 0640 { 0641 auto t = item->time(); 0642 QRectF local_bounds(item->local_bounding_rect(t)); 0643 if ( !local_bounds.isValid() ) 0644 continue; 0645 0646 QTransform transform = item->transform_matrix(t); 0647 auto transformed_bounds = transform.map(local_bounds).boundingRect(); 0648 data.push_back({item, transform.inverted(), align_point(transformed_bounds, direction, bound_pos)}); 0649 0650 if ( !bounds.isValid() ) 0651 bounds = transformed_bounds; 0652 else 0653 bounds |= transformed_bounds; 0654 } 0655 0656 QPointF reference; 0657 0658 if ( ui.action_align_to_selection->isChecked() ) 0659 { 0660 reference = align_point(bounds, direction, position); 0661 } 0662 else if ( ui.action_align_to_canvas->isChecked() ) 0663 { 0664 reference = align_point(comp->rect(), direction, position); 0665 } 0666 else if ( ui.action_align_to_canvas_group->isChecked() ) 0667 { 0668 reference = align_point(comp->rect(), direction, position); 0669 QPointF bounds_point = align_point(bounds, direction, bound_pos); 0670 for ( auto& item : data ) 0671 item.bounds_point = bounds_point; 0672 } 0673 0674 command::UndoMacroGuard guard(i18n("Align Selection"), current_document.get()); 0675 0676 for ( const auto& item : data ) 0677 { 0678 QPointF target_point = reference; 0679 if ( direction == AlignDirection::Horizontal ) 0680 target_point.setY(item.bounds_point.y()); 0681 else 0682 target_point.setX(item.bounds_point.x()); 0683 0684 QPointF delta = item.transform.map(target_point) - item.transform.map(item.bounds_point); 0685 0686 if ( auto path = item.node->cast<model::Path>() ) 0687 { 0688 auto bezier = path->shape.get(); 0689 for ( auto& point : bezier ) 0690 point.translate(delta); 0691 path->shape.set_undoable(QVariant::fromValue(bezier)); 0692 } 0693 else if ( item.node->has("transform") ) 0694 { 0695 auto m = item.node->local_transform_matrix(item.node->time()); 0696 auto a = m.map(item.transform.map(target_point)); 0697 auto b = m.map(item.transform.map(item.bounds_point)); 0698 delta = a - b; 0699 0700 auto trans = item.node->get("transform").value<model::Transform*>(); 0701 auto point = trans->position.get() + delta; 0702 trans->position.set_undoable(point); 0703 } 0704 else if ( item.node->has("position") ) 0705 { 0706 auto point = item.node->get("position").toPointF() + delta; 0707 item.node->get_property("position")->set_undoable(point); 0708 } 0709 } 0710 } 0711 0712 void GlaxnimateWindow::Private::dropped(const QMimeData* data) 0713 { 0714 if ( data->hasFormat("application/x.glaxnimate-asset-uuid") ) 0715 { 0716 command::UndoMacroGuard guard(i18n("Drop"), current_document.get(), false); 0717 0718 item_models::DragDecoder<> decoder(data->data("application/x.glaxnimate-asset-uuid"), current_document.get()); 0719 0720 std::vector<model::VisualNode*> selection; 0721 0722 auto cont = parent->current_shape_container(); 0723 int position = cont->size(); 0724 0725 for ( auto asset : decoder ) 0726 { 0727 std::unique_ptr<model::ShapeElement> element; 0728 0729 if ( auto precomp = asset->cast<model::Composition>() ) 0730 { 0731 auto layer = std::make_unique<model::PreCompLayer>(current_document.get()); 0732 layer->composition.set(precomp); 0733 layer->size.set(precomp->size()); 0734 element = std::move(layer); 0735 } 0736 else if ( auto bmp = asset->cast<model::Bitmap>() ) 0737 { 0738 auto image = std::make_unique<model::Image>(current_document.get()); 0739 image->image.set(bmp); 0740 element = std::move(image); 0741 } 0742 0743 if ( element ) 0744 { 0745 selection.push_back(element.get()); 0746 element->name.set(asset->name.get()); 0747 guard.start(); 0748 current_document->push_command(new command::AddShape(cont, std::move(element), position)); 0749 } 0750 } 0751 0752 if ( !selection.empty() ) 0753 scene.user_select(selection, graphics::DocumentScene::Replace); 0754 } 0755 0756 } 0757 0758 static bool get_text(model::Group* group, std::vector<model::TextShape*>& shapes) 0759 { 0760 bool found = false; 0761 0762 for ( const auto& node : group->shapes ) 0763 { 0764 auto mo = node->metaObject(); 0765 0766 if ( mo->inherits(&model::TextShape::staticMetaObject) ) 0767 { 0768 shapes.push_back(static_cast<model::TextShape*>(node.get())); 0769 found = true; 0770 } 0771 else if ( mo->inherits(&model::Group::staticMetaObject) ) 0772 { 0773 found = get_text(static_cast<model::Group*>(node.get()), shapes) && found; 0774 } 0775 } 0776 0777 return found; 0778 } 0779 0780 void GlaxnimateWindow::Private::text_put_on_path() 0781 { 0782 std::vector<model::TextShape*> shapes; 0783 model::ShapeElement* path = nullptr; 0784 0785 for ( const auto& node : scene.selection() ) 0786 { 0787 auto mo = node->metaObject(); 0788 0789 if ( mo->inherits(&model::TextShape::staticMetaObject) ) 0790 { 0791 shapes.push_back(static_cast<model::TextShape*>(node)); 0792 } 0793 else if ( mo->inherits(&model::Group::staticMetaObject) ) 0794 { 0795 if ( !get_text(static_cast<model::Group*>(node), shapes) ) 0796 path = static_cast<model::ShapeElement*>(node); 0797 } 0798 else if ( 0799 mo->inherits(&model::ShapeElement::staticMetaObject) && 0800 !mo->inherits(&model::Image::staticMetaObject) && 0801 !mo->inherits(&model::PreCompLayer::staticMetaObject) && 0802 !mo->inherits(&model::Styler::staticMetaObject) 0803 ) 0804 { 0805 path = static_cast<model::ShapeElement*>(node); 0806 } 0807 } 0808 0809 if ( shapes.empty() || !path ) 0810 return; 0811 0812 command::UndoMacroGuard guard(i18n("Put text on path"), current_document.get()); 0813 for ( auto shape : shapes ) 0814 shape->path.set_undoable(QVariant::fromValue(path)); 0815 } 0816 0817 void GlaxnimateWindow::Private::text_remove_from_path() 0818 { 0819 std::vector<model::TextShape*> shapes; 0820 0821 for ( const auto& node : scene.selection() ) 0822 { 0823 auto mo = node->metaObject(); 0824 0825 if ( mo->inherits(&model::TextShape::staticMetaObject) ) 0826 shapes.push_back(static_cast<model::TextShape*>(node)); 0827 else if ( mo->inherits(&model::Group::staticMetaObject) ) 0828 get_text(static_cast<model::Group*>(node), shapes); 0829 } 0830 0831 if ( shapes.empty() ) 0832 return; 0833 0834 command::UndoMacroGuard guard(i18n("Remove text from path"), current_document.get()); 0835 for ( auto shape : shapes ) 0836 shape->path.set_undoable({}); 0837 }