Warning, file /graphics/glaxnimate/src/gui/tools/edit_tool.cpp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).

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 "edit_tool.hpp"
0008 
0009 #include <algorithm>
0010 
0011 #include <QMenu>
0012 #include <QPointer>
0013 #include <QActionGroup>
0014 
0015 #include <QtColorWidgets/ColorDialog>
0016 
0017 #include <KLocalizedString>
0018 
0019 #include "app/application.hpp"
0020 
0021 #include "math/bezier/operations.hpp"
0022 #include "math/bezier/cubic_struts.hpp"
0023 #include "model/shapes/shape.hpp"
0024 #include "model/shapes/text.hpp"
0025 #include "model/shapes/rect.hpp"
0026 #include "model/shapes/ellipse.hpp"
0027 #include "model/shapes/polystar.hpp"
0028 #include "model/shapes/precomp_layer.hpp"
0029 #include "model/shapes/image.hpp"
0030 #include "command/animation_commands.hpp"
0031 #include "command/object_list_commands.hpp"
0032 #include "command/undo_macro_guard.hpp"
0033 
0034 #include "graphics/bezier_item.hpp"
0035 #include "graphics/item_data.hpp"
0036 #include "graphics/graphics_editor.hpp"
0037 #include "graphics/gradient_editor.hpp"
0038 #include "handle_menu.hpp"
0039 
0040 using namespace glaxnimate::gui;
0041 using namespace glaxnimate;
0042 
0043 tools::Autoreg<tools::EditTool> tools::EditTool::autoreg{max_priority + 1};
0044 
0045 class tools::EditTool::Private
0046 {
0047 public:
0048     enum DragMode
0049     {
0050         None,
0051         Click,
0052         RubberBand,
0053         ForwardEvents,
0054         VertexClick,
0055         VertexDrag,
0056         VertexAdd,
0057         MoldCurve
0058     };
0059 
0060     struct Selection
0061     {
0062         struct SelectedBezier
0063         {
0064             graphics::BezierItem* item;
0065             QTransform transform;
0066             QPointF start_point;
0067 
0068             SelectedBezier(graphics::BezierItem* item)
0069                 : item(item)
0070             {}
0071 
0072             void start_drag(const QPointF& drag_start_point)
0073             {
0074                 transform = item->target_object()->docnode_fuzzy_parent()->transform_matrix(item->target_object()->time()).inverted();
0075                 start_point = transform.map(drag_start_point);
0076             }
0077 
0078             void drag(
0079                 const QPointF& scene_pos,
0080                 std::vector<model::AnimatableBase*>& props,
0081                 QVariantList& before,
0082                 QVariantList& after
0083             )
0084             {
0085                 props.push_back(item->target_property());
0086 
0087                 math::bezier::Bezier bezier = item->bezier();
0088                 before.push_back(QVariant::fromValue(bezier));
0089 
0090                 QPointF pos = transform.map(scene_pos);
0091                 QPointF delta = pos - start_point;
0092                 for ( int i : item->selected_indices() )
0093                     bezier[i].translate(delta);
0094                 after.push_back(QVariant::fromValue(bezier));
0095                 start_point = pos;
0096             }
0097         };
0098 
0099         std::map<graphics::BezierItem*, SelectedBezier> selected;
0100         QPointer<graphics::PointItem> initial = nullptr;
0101 
0102         QPointF drag_start;
0103 
0104         bool empty() const
0105         {
0106             return selected.empty();
0107         }
0108 
0109         void clear()
0110         {
0111             initial = nullptr;
0112             for ( const auto& p : selected )
0113                 p.second.item->clear_selected_indices();
0114             selected.clear();
0115         }
0116 
0117         void add_bezier_point_item(graphics::PointItem* item)
0118         {
0119             auto grandpa = item->parent_editor();
0120             if ( selected.find(grandpa) == selected.end() )
0121                 add_bezier_item(grandpa);
0122             grandpa->select_index(item->index());
0123         }
0124 
0125         void toggle_handle(graphics::MoveHandle* item)
0126         {
0127             auto parent = static_cast<graphics::PointItem*>(item->parentItem());
0128             auto grandpa = parent->parent_editor();
0129             auto it = selected.find(grandpa);
0130             if ( it == selected.end() )
0131             {
0132                 add_bezier_item(grandpa);
0133                 grandpa->select_index(parent->index());
0134             }
0135             else
0136             {
0137                 grandpa->toggle_index(parent->index());
0138                 if ( grandpa->selected_indices().empty() )
0139                     selected.erase(it);
0140             }
0141         }
0142 
0143         void add_bezier_item(graphics::BezierItem* item)
0144         {
0145             selected.emplace(item, SelectedBezier(item));
0146             QObject::connect(item, &QObject::destroyed, item, [this, item]{
0147                 selected.erase(item);
0148             });
0149         }
0150 
0151         void start_drag()
0152         {
0153             for ( auto& p : selected )
0154                 p.second.start_drag(drag_start);
0155         }
0156 
0157         void drag(const MouseEvent& event, bool commit)
0158         {
0159             std::vector<model::AnimatableBase*> props;
0160             QVariantList before;
0161             QVariantList after;
0162 
0163             for ( auto& p : selected )
0164             {
0165                 p.second.drag(event.scene_pos, props, before, after);
0166             }
0167 
0168             event.window->document()->push_command(new command::SetMultipleAnimated(
0169                 i18n("Drag nodes"),
0170                 props,
0171                 before,
0172                 after,
0173                 commit
0174             ));
0175         }
0176     };
0177 
0178     struct PathCache
0179     {
0180         QTransform forward_transform;
0181         QTransform inverse_transform;
0182 
0183         PathCache() = default;
0184 
0185         PathCache(graphics::BezierItem* item)
0186         :   forward_transform(item->target_object()->transform_matrix(item->target_object()->time())),
0187             inverse_transform(forward_transform.inverted())
0188         {}
0189     };
0190 
0191     void add_bezier_editor(graphics::BezierItem* editor)
0192     {
0193         active[editor] = PathCache(editor);
0194 
0195         connect(editor, &QObject::destroyed, editor, [this, editor]{
0196             active.erase(editor);
0197         });
0198         connect(editor->target_object(), &model::VisualNode::transform_matrix_changed, editor, [this, editor]{
0199             active[editor] = PathCache(editor);
0200         });
0201     }
0202 
0203     void extract_editor(graphics::DocumentScene * scene, model::VisualNode* node)
0204     {
0205         auto editor_parent = scene->get_editor(node);
0206         if ( !editor_parent )
0207             return;
0208 
0209         for ( auto editor_child : editor_parent->childItems() )
0210         {
0211             if ( auto editor = qgraphicsitem_cast<graphics::BezierItem*>(editor_child) )
0212                 add_bezier_editor(editor);
0213         }
0214     }
0215 
0216     void show_position_editor(graphics::DocumentScene * scene, model::VisualNode* node, model::Transform* transform)
0217     {
0218         if ( transform->position.keyframe_count() >= 2 )
0219         {
0220             auto editor = std::make_unique<graphics::BezierItem>(&transform->position, node);
0221             auto bezit = editor.get();
0222 
0223             QObject::connect(
0224                 node, &model::VisualNode::transform_matrix_changed,
0225                 bezit, [bezit, node](const QTransform& t){
0226                     bezit->setTransform(node->local_transform_matrix(node->time()).inverted() * t);
0227                 }
0228             );
0229             bezit->setTransform(node->local_transform_matrix(node->time()).inverted() * node->transform_matrix(node->time()));
0230 
0231             scene->show_custom_editor(node, std::move(editor));
0232 
0233             add_bezier_editor(bezit);
0234         }
0235     }
0236 
0237     void impl_extract_selection_recursive_item(graphics::DocumentScene * scene, model::VisualNode* node)
0238     {
0239         auto meta = node->metaObject();
0240         if ( meta->inherits(&model::Shape::staticMetaObject) || meta->inherits(&model::ShapeOperator::staticMetaObject)  || meta->inherits(&model::TextShape::staticMetaObject) )
0241         {
0242             scene->show_editors(node);
0243 
0244             if (
0245                 meta->inherits(&model::Path::staticMetaObject) ||
0246                 meta->inherits(&model::Rect::staticMetaObject) ||
0247                 meta->inherits(&model::Ellipse::staticMetaObject) ||
0248                 meta->inherits(&model::PolyStar::staticMetaObject)
0249             )
0250                 extract_editor(scene, node);
0251         }
0252         else if ( meta->inherits(&model::Group::staticMetaObject) )
0253         {
0254             show_position_editor(scene, node, static_cast<model::Group*>(node)->transform.get());
0255             for ( const auto& sub : static_cast<model::Group*>(node)->shapes )
0256                 impl_extract_selection_recursive_item(scene, sub.get());
0257         }
0258         else if ( meta->inherits(&model::PreCompLayer::staticMetaObject) )
0259         {
0260             show_position_editor(scene, node, static_cast<model::PreCompLayer*>(node)->transform.get());
0261         }
0262         else if ( meta->inherits(&model::Image::staticMetaObject) )
0263         {
0264             show_position_editor(scene, node, static_cast<model::Image*>(node)->transform.get());
0265         }
0266     }
0267 
0268     static void node_type_action(QMenu* menu, QActionGroup* group, graphics::PointItem* item, math::bezier::PointType type)
0269     {
0270         QIcon icon;
0271         QString label;
0272         switch ( type )
0273         {
0274             case math::bezier::PointType::Corner:
0275                 icon = QIcon::fromTheme("node-type-cusp");
0276                 label = i18n("Cusp");
0277                 break;
0278             case math::bezier::PointType::Smooth:
0279                 icon = QIcon::fromTheme("node-type-smooth");
0280                 label = i18n("Smooth");
0281                 break;
0282             case math::bezier::PointType::Symmetrical:
0283                 icon = QIcon::fromTheme("node-type-auto-smooth");
0284                 label = i18n("Symmetrical");
0285                 break;
0286         }
0287         QAction* action = menu->addAction(icon, label, item, [item, type, label]{
0288             auto point = item->point();
0289             point.type = type;
0290             point.adjust_handles_from_type();
0291             item->modify(point, i18n("Set %1 Node", label));
0292         });
0293 
0294         action->setCheckable(true);
0295         action->setActionGroup(group);
0296 
0297         if ( item->point().type == type )
0298             action->setChecked(true);
0299     }
0300 
0301     static void vertex_menu(QMenu& menu, graphics::PointItem* item, int role, graphics::MoveHandle* handle)
0302     {
0303         auto grp = new QActionGroup(&menu);
0304 
0305         menu.addSection(i18n("Node"));
0306 
0307         node_type_action(&menu, grp, item, math::bezier::PointType::Corner);
0308         node_type_action(&menu, grp, item, math::bezier::PointType::Smooth);
0309         node_type_action(&menu, grp, item, math::bezier::PointType::Symmetrical);
0310 
0311         menu.addSeparator();
0312 
0313         if ( role == graphics::MoveHandle::Vertex )
0314         {
0315             menu.addAction(QIcon::fromTheme("format-remove-node"), i18n("Remove Node"), item, [item]{
0316                 item->parent_editor()->remove_point(item->index());
0317             });
0318 
0319             menu.addAction(QIcon::fromTheme("show-node-handles"), i18n("Show Tangents"), item, [item]{
0320                 item->show_tan_in(true);
0321                 item->show_tan_out(true);
0322             });
0323 
0324             if ( item->index() > 0 && item->parent_editor()->bezier().closed() )
0325             {
0326                 menu.addAction(QIcon::fromTheme("edit-node"), i18n("Make First Node"), item, [item]{
0327                     item->parent_editor()->make_first(item->index());
0328                 });
0329             }
0330 
0331         }
0332         else
0333         {
0334             menu.addAction(QIcon::fromTheme("show-node-handles"), i18n("Remove Tangent"), item, [item, handle]{
0335                 item->remove_tangent(handle);
0336             });
0337         }
0338     }
0339 
0340     static void gradient_menu(QMenu& menu, graphics::MoveHandle* handle, QWidget* dialog_parent)
0341     {
0342         auto item = static_cast<graphics::GradientEditor*>(handle->parentItem());
0343         int index = handle->data(graphics::GradientStopIndex).toInt();
0344 
0345         if ( !item->styler() || !item->gradient() )
0346             return;
0347 
0348         auto gradient = item->gradient();
0349         auto gradient_colors = item->gradient()->colors.get();
0350         auto doc = gradient->document();
0351 
0352         QMenu* use_menu = new QMenu(i18n("Gradient Colors"), &menu);
0353         use_menu->setIcon(QIcon::fromTheme("color-gradient"));
0354 
0355         for ( const auto& colors : doc->assets()->gradient_colors->values )
0356         {
0357             use_menu->addAction(
0358                 colors->instance_icon(),
0359                 colors->name.get()
0360             )->setData(QVariant::fromValue(colors.get()));
0361         }
0362 
0363         connect(use_menu, &QMenu::triggered, gradient, [gradient](QAction* act){
0364             gradient->colors.set_undoable(act->data());
0365         });
0366 
0367         menu.addAction(use_menu->menuAction());
0368 
0369         menu.addAction(QIcon::fromTheme("color-management"), i18nc("@action:inmenu", "Stop Color..."), gradient_colors, [gradient_colors, index, dialog_parent]{
0370             color_widgets::ColorDialog dialog(dialog_parent);
0371             auto colors = gradient_colors->colors.get();
0372             dialog.setColor(colors[index].second);
0373             if ( dialog.exec() != QDialog::Accepted )
0374                 return;
0375 
0376             colors[index].second = dialog.color();
0377             gradient_colors->colors.set_undoable(QVariant::fromValue(colors));
0378         });
0379 
0380 
0381         menu.addAction(QIcon::fromTheme("list-add"), i18nc("@action:inmenu", "Add Stop"), gradient_colors, [gradient_colors, index]{
0382             gradient_colors->split_segment(index);
0383         });
0384 
0385         menu.addAction(QIcon::fromTheme("list-remove"), i18nc("@action:inmenu", "Remove Stop"), gradient_colors, [gradient_colors, index]{
0386             gradient_colors->remove_stop(index);
0387         });
0388 
0389     }
0390 
0391     static void context_menu(EditTool* thus, const MouseEvent& event)
0392     {
0393 #ifndef Q_OS_ANDROID
0394         auto handle = thus->under_mouse(event, true, SelectionMode::Shape).handle;
0395         if ( !handle )
0396             return;
0397 
0398         QMenu menu;
0399 
0400         auto role = handle->role();
0401         if ( role == graphics::MoveHandle::Vertex || role == graphics::MoveHandle::Tangent )
0402             vertex_menu(menu, static_cast<graphics::PointItem*>(handle->parentItem()), role, handle);
0403         else if ( role == graphics::MoveHandle::GradientStop )
0404             gradient_menu(menu, handle, event.window->as_widget());
0405 
0406         add_property_menu_actions(&menu, handle, event.window);
0407 
0408         if ( !menu.actions().empty() )
0409             menu.exec(QCursor::pos());
0410 #else
0411         Q_UNUSED(thus);
0412         Q_UNUSED(event);
0413 #endif
0414     }
0415 
0416     void mold_bezier(const QPointF& scene_pos, bool commit)
0417     {
0418         using namespace glaxnimate::math::bezier;
0419         if ( !insert_item )
0420             return;
0421 
0422         // see https://pomax.github.io/bezierinfo/#molding
0423         QPointF B = active[insert_item].inverse_transform.map(scene_pos);
0424         BezierStruts struts_ideal = cubic_struts_idealized(mold_original, B);
0425         auto struts_proj = cubic_struts_projection(mold_original, B, insert_params);
0426         qreal falloff = 512;
0427         qreal dist = math::length(insert_params.point - B);
0428         qreal interp = math::min(falloff, dist) / falloff;
0429 
0430         BezierStruts struts_interp = {
0431             B,
0432             math::lerp(struts_proj.t, struts_ideal.t, interp),
0433             math::lerp(struts_proj.e1, struts_ideal.e1, interp),
0434             math::lerp(struts_proj.e2, struts_ideal.e2, interp),
0435         };
0436 
0437         // adjust interpolated struts so the pass through B
0438         QPointF offset = B - math::lerp(struts_interp.e1, struts_interp.e2, struts_interp.t);
0439         struts_interp.e1 += offset;
0440         struts_interp.e2 += offset;
0441 
0442         auto bez = insert_item->bezier();
0443         bez.set_segment(insert_params.index, cubic_segment_from_struts(mold_original, struts_interp));
0444 
0445         insert_item->set_bezier(bez, commit);
0446     }
0447 
0448     void delete_nodes_bezier(graphics::BezierItem* item, bool dissolve)
0449     {
0450         if ( item->selected_indices().empty() )
0451             return;
0452 
0453         if ( auto bez_prop = item->target_bezier_property() )
0454         {
0455             int sz1 = item->selected_indices().size() + 1;
0456             if ( sz1 >= item->bezier().size() && std::all_of(bez_prop->begin(), bez_prop->end(), [sz1](const auto& kf){ return sz1 > kf.get().size(); }) )
0457             {
0458                 // At the moment it always is a Path, but it might change in the future (ie: masks)
0459                 if ( auto path = item->target_object()->cast<model::Path>() )
0460                 {
0461                     item->target_object()->push_command(new command::RemoveObject<model::ShapeElement>(path, path->owner()));
0462                     return;
0463                 }
0464             }
0465             bez_prop->remove_points(item->selected_indices());
0466         }
0467         else if ( auto pos_prop = item->target_position_property() )
0468         {
0469             int sz1 = item->selected_indices().size() + 1;
0470             if ( sz1 >= item->bezier().size() )
0471             {
0472                 QVariant value;
0473                 for ( int i = 0; i < pos_prop->keyframe_count(); i++ )
0474                 {
0475                     if ( !item->selected_indices().count(i) )
0476                     {
0477                         value = pos_prop->keyframe(i)->value();
0478                         break;
0479                     }
0480                 }
0481                 pos_prop->clear_keyframes_undoable(value);
0482                 return;
0483             }
0484             pos_prop->remove_points(item->selected_indices());
0485         }
0486 
0487         auto bez = item->bezier();
0488 
0489         if ( dissolve && item->selected_indices().size() == 1 )
0490         {
0491             math::bezier::Bezier new_bez = item->bezier();
0492 
0493             int index = *item->selected_indices().begin();
0494             if ( bez.closed() || (index > 0 && index < bez.size() - 1) )
0495             {
0496                 auto old_point = bez[index];
0497                 old_point.set_point_type(math::bezier::Smooth);
0498                 auto segment = new_bez.segment(index-1);
0499                 qreal d1 = math::length(new_bez[index-1].pos - old_point.pos);
0500                 qreal d2 = math::length(new_bez[index].pos - old_point.pos);
0501                 qreal t = d1 / (d1 + d2);
0502 
0503                 auto approx = math::bezier::cubic_segment_from_struts(
0504                     segment,
0505                     {
0506                         old_point.pos,
0507                         t,
0508                         old_point.tan_in,
0509                         old_point.tan_out
0510                     }
0511                 );
0512 
0513                 new_bez.set_segment(index-1, approx);
0514             }
0515             item->set_bezier(new_bez);
0516         }
0517 
0518         item->clear_selected_indices();
0519     }
0520 
0521     void delete_nodes(bool dissolve)
0522     {
0523         if ( selection.empty() )
0524             return;
0525 
0526         auto doc = selection.selected.begin()->first->target_object()->document();
0527         command::UndoMacroGuard macro(dissolve ? i18n("Dissolve Nodes") : i18n("Delete Nodes"), doc);
0528 
0529         auto selected = std::move(selection.selected);
0530         selection.selected = {};
0531         for ( const auto& p : selected )
0532             delete_nodes_bezier(p.first, dissolve);
0533 
0534         selection.initial = nullptr;
0535     }
0536 
0537     bool mold_grab(const MouseEvent& event)
0538     {
0539         return insert_item && insert_params.distance <= drag_dist / event.view->get_zoom_factor() && insert_params.factor > 0.1 && insert_params.factor < 0.9;
0540     }
0541 
0542     DragMode drag_mode;
0543     QPointF rubber_p1;
0544     QPointF rubber_p2;
0545     QPointer<model::Shape> highlight = nullptr;
0546     Selection selection;
0547 
0548     std::map<graphics::BezierItem*, PathCache> active;
0549     math::bezier::ProjectResult insert_params;
0550     math::bezier::Point insert_preview{{}};
0551     QPointer<graphics::BezierItem> insert_item = nullptr;
0552     math::bezier::BezierSegment mold_original;
0553 
0554     Qt::CursorShape cursor = Qt::ArrowCursor;
0555 
0556     graphics::DocumentScene * active_scene = nullptr;
0557 
0558     static const int drag_dist = 36; // distance squared
0559 };
0560 
0561 tools::EditTool::EditTool()
0562     : d(std::make_unique<Private>())
0563 {}
0564 
0565 tools::EditTool::~EditTool() = default;
0566 
0567 void tools::EditTool::mouse_press(const MouseEvent& event)
0568 {
0569     d->highlight = nullptr;
0570 
0571     if ( event.press_button == Qt::LeftButton && d->drag_mode == Private::None )
0572     {
0573         d->drag_mode = Private::Click;
0574         d->rubber_p1 = event.pos();
0575 
0576         auto clicked_on = under_mouse(event, true, SelectionMode::Shape);
0577         if ( clicked_on.handle )
0578         {
0579             if ( clicked_on.handle->role() == graphics::MoveHandle::Vertex )
0580             {
0581                 d->drag_mode = Private::VertexClick;
0582                 d->selection.drag_start = event.scene_pos;
0583                 d->selection.initial = static_cast<graphics::PointItem*>(clicked_on.handle->parentItem());
0584             }
0585             else
0586             {
0587                 if ( clicked_on.handle->role() == graphics::MoveHandle::GradientStop )
0588                 {
0589                     auto editor = static_cast<graphics::GradientEditor*>(clicked_on.handle->parentItem());
0590                     Q_EMIT gradient_stop_changed(
0591                         editor->styler(),
0592                         clicked_on.handle->data(graphics::GradientStopIndex).toInt()
0593                     );
0594                 }
0595 
0596                 d->drag_mode = Private::ForwardEvents;
0597                 event.forward_to_scene();
0598             }
0599         }
0600         else if ( d->mold_grab(event) )
0601         {
0602             d->drag_mode = Private::MoldCurve;
0603             d->insert_params = math::bezier::project(d->insert_item->bezier(), d->active[d->insert_item].inverse_transform.map(event.scene_pos));
0604             d->mold_original = d->insert_item->bezier().segment(d->insert_params.index);
0605             set_cursor(Qt::ClosedHandCursor);
0606         }
0607     }
0608 }
0609 
0610 void tools::EditTool::mouse_move(const MouseEvent& event)
0611 {
0612     if ( event.press_button == Qt::LeftButton )
0613     {
0614         switch ( d->drag_mode )
0615         {
0616             case Private::None:
0617                 break;
0618             case Private::ForwardEvents:
0619                 event.forward_to_scene();
0620                 break;
0621             case Private::Click:
0622                 d->drag_mode = Private::RubberBand;
0623                 [[fallthrough]];
0624             case Private::RubberBand:
0625                 d->rubber_p2 = event.pos();
0626                 break;
0627             case Private::VertexClick:
0628                 d->drag_mode = Private::VertexDrag;
0629                 if ( d->selection.initial && !d->selection.initial->parent_editor()->selected_indices().count(d->selection.initial->index()) )
0630                 {
0631                     graphics::PointItem* initial = d->selection.initial;
0632                     d->selection.clear();
0633                     d->selection.add_bezier_point_item(initial);
0634                 }
0635                 d->selection.start_drag();
0636                 [[fallthrough]];
0637             case Private::VertexDrag:
0638                 d->selection.drag(event, false);
0639                 break;
0640             case Private::VertexAdd:
0641                 break;
0642             case Private::MoldCurve:
0643                 d->mold_bezier(event.scene_pos, false);
0644                 break;
0645         }
0646     }
0647     else if ( event.buttons() == Qt::NoButton )
0648     {
0649         // find closest point on a bezier
0650         d->insert_params = {};
0651         d->insert_item = nullptr;
0652         for ( const auto& it : d->active )
0653         {
0654 
0655             auto closest = math::bezier::project(it.first->bezier(), it.second.inverse_transform.map(event.scene_pos));
0656             if ( closest.distance < d->insert_params.distance )
0657             {
0658                 d->insert_params = closest;
0659                 d->insert_item = it.first;
0660             }
0661         }
0662         // get tangents
0663         if ( d->insert_item && d->drag_mode == Private::VertexAdd )
0664         {
0665             d->insert_preview = d->insert_item->bezier().split_segment_point(
0666                 d->insert_params.index, d->insert_params.factor
0667             );
0668             d->insert_preview.transform(d->active[d->insert_item].forward_transform);
0669         }
0670 
0671         // update cursor
0672         auto um = under_mouse(event, true, SelectionMode::Shape);
0673         if ( d->drag_mode == Private::None && !um.handle && d->mold_grab(event) )
0674             set_cursor(Qt::OpenHandCursor);
0675         else if ( d->drag_mode != Private::VertexAdd )
0676             set_cursor(Qt::ArrowCursor);
0677 
0678         // Find shape to highlight
0679         d->highlight = nullptr;
0680         for ( auto node : um.nodes )
0681         {
0682             if ( auto path = node->node()->cast<model::Shape>() )
0683             {
0684                 if ( !event.scene->has_editors(path) )
0685                     d->highlight = path;
0686                 break;
0687             }
0688         }
0689     }
0690 }
0691 
0692 void tools::EditTool::mouse_release(const MouseEvent& event)
0693 {
0694     if ( event.button() == Qt::LeftButton )
0695     {
0696         switch ( d->drag_mode )
0697         {
0698             case Private::None:
0699                 break;
0700             case Private::ForwardEvents:
0701                 event.forward_to_scene();
0702                 break;
0703             case Private::RubberBand:
0704             {
0705                 d->rubber_p2 = event.pos();
0706                 d->drag_mode = Private::None;
0707                 if ( !(event.modifiers() & Qt::ShiftModifier) )
0708                     d->selection.clear();
0709                 auto items = event.scene->items(
0710                     event.view->mapToScene(
0711                         QRect(d->rubber_p1.toPoint(), d->rubber_p2.toPoint()).normalized().normalized()
0712                     ),
0713                     Qt::IntersectsItemShape,
0714                     Qt::DescendingOrder,
0715                     event.view->viewportTransform()
0716                 );
0717                 for ( auto item : items )
0718                     if ( item->data(graphics::ItemData::HandleRole).toInt() == graphics::MoveHandle::Vertex )
0719                         d->selection.add_bezier_point_item(static_cast<graphics::PointItem*>(item->parentItem()));
0720                 event.view->viewport()->update();
0721                 break;
0722             }
0723             case Private::Click:
0724             {
0725                 std::vector<model::VisualNode*> selection;
0726 
0727                 auto nodes = under_mouse(event, true, SelectionMode::Group).nodes;
0728 
0729                 if ( !nodes.empty() )
0730                     selection.push_back(nodes[0]->node());
0731 
0732                 auto mode = graphics::DocumentScene::Replace;
0733                 if ( event.modifiers() & (Qt::ShiftModifier|Qt::ControlModifier) )
0734                     mode = graphics::DocumentScene::Toggle;
0735 
0736                 event.scene->user_select(selection, mode);
0737                 break;
0738             }
0739             case Private::VertexClick:
0740                 if ( event.modifiers() & (Qt::ControlModifier|Qt::AltModifier) )
0741                 {
0742                     if ( d->selection.initial->point().type == math::bezier::PointType::Corner )
0743                         d->selection.initial->set_point_type(math::bezier::PointType::Smooth);
0744                     else
0745                         d->selection.initial->set_point_type(math::bezier::PointType::Corner);
0746                 }
0747                 else
0748                 {
0749                     if ( !(event.modifiers() & Qt::ShiftModifier) )
0750                         d->selection.clear();
0751                     d->selection.toggle_handle(handle_under_mouse(event));
0752                 }
0753                 d->selection.initial = nullptr;
0754                 break;
0755             case Private::VertexDrag:
0756                 d->selection.drag(event, true);
0757                 d->selection.initial = nullptr;
0758                 break;
0759             case Private::VertexAdd:
0760                 if ( d->insert_item )
0761                 {
0762                     d->insert_item->split_segment(d->insert_params.index, d->insert_params.factor);
0763                     exit_add_point_mode();
0764                 }
0765                 break;
0766             case Private::MoldCurve:
0767                 d->mold_bezier(event.scene_pos, true);
0768                 set_cursor(Qt::OpenHandCursor);
0769                 break;
0770         }
0771 
0772         d->drag_mode = Private::None;
0773     }
0774     else if ( event.button() == Qt::RightButton )
0775     {
0776         Private::context_menu(this, event);
0777     }
0778 
0779 }
0780 
0781 void tools::EditTool::mouse_double_click(const MouseEvent& event)
0782 {
0783     edit_clicked(event);
0784 }
0785 
0786 
0787 void tools::EditTool::paint(const PaintEvent& event)
0788 {
0789     if ( d->drag_mode == Private::RubberBand )
0790     {
0791         event.painter->setBrush(Qt::transparent);
0792         QColor select_color = event.view->palette().color(QPalette::Highlight);
0793         QPen pen(select_color, 1);
0794         pen.setCosmetic(true);
0795         event.painter->setPen(pen);
0796         select_color.setAlpha(128);
0797         event.painter->setBrush(select_color);
0798         event.painter->drawRect(QRectF(d->rubber_p1, d->rubber_p2));
0799     }
0800     else if ( d->drag_mode == Private::VertexAdd )
0801     {
0802         if ( d->insert_item )
0803         {
0804             QColor select_color = event.view->palette().color(QPalette::Highlight);
0805             QPen pen(select_color, 1);
0806             pen.setCosmetic(true);
0807             event.painter->setPen(pen);
0808             event.painter->drawLine(
0809                 event.view->mapFromScene(d->insert_preview.tan_in),
0810                 event.view->mapFromScene(d->insert_preview.tan_out)
0811             );
0812 
0813             select_color.setAlpha(128);
0814             event.painter->setBrush(select_color);
0815             event.painter->drawEllipse(event.view->mapFromScene(d->insert_preview.pos), 6, 6);
0816         }
0817     }
0818     else if ( d->highlight )
0819     {
0820         QColor select_color = event.view->palette().color(QPalette::Highlight);
0821         QPen pen(select_color, 1);
0822         QPainterPath p;
0823         d->highlight->to_bezier(d->highlight->time()).add_to_painter_path(p);
0824         QTransform trans = d->highlight->transform_matrix(d->highlight->time()) * event.view->viewportTransform();
0825         p = trans.map(p);
0826         event.painter->setPen(pen);
0827         event.painter->setBrush(Qt::NoBrush);
0828         event.painter->drawPath(p);
0829     }
0830 }
0831 
0832 void tools::EditTool::key_press(const KeyEvent& event)
0833 {
0834     Q_UNUSED(event);
0835 }
0836 
0837 void tools::EditTool::key_release(const KeyEvent& event)
0838 {
0839     if ( event.key() == Qt::Key_Escape || event.key() == Qt::Key_Back )
0840     {
0841         if ( d->drag_mode == Private::VertexAdd )
0842         {
0843             exit_add_point_mode();
0844             event.repaint();
0845         }
0846         event.accept();
0847     }
0848 }
0849 
0850 QCursor tools::EditTool::cursor()
0851 {
0852     return d->cursor;
0853 }
0854 
0855 void tools::EditTool::on_selected(graphics::DocumentScene * scene, model::VisualNode * node)
0856 {
0857     d->impl_extract_selection_recursive_item(scene, node);
0858 }
0859 
0860 void tools::EditTool::enable_event(const Event& event)
0861 {
0862     d->highlight = nullptr;
0863     exit_add_point_mode();
0864     d->active_scene = event.scene;
0865 }
0866 
0867 void tools::EditTool::disable_event(const Event&)
0868 {
0869     d->highlight = nullptr;
0870     d->active.clear();
0871     exit_add_point_mode();
0872     d->active_scene = nullptr;
0873 }
0874 
0875 QWidget* tools::EditTool::on_create_widget()
0876 {
0877     return new QWidget();
0878 }
0879 
0880 void tools::EditTool::selection_set_vertex_type(math::bezier::PointType t)
0881 {
0882     if ( d->selection.empty() )
0883         return;
0884 
0885     auto doc = d->selection.selected.begin()->first->target_object()->document();
0886     command::UndoMacroGuard macro(i18n("Set node type"), doc);
0887     for ( const auto& p : d->selection.selected )
0888     {
0889         auto bez = p.first->bezier();
0890         for ( int index : p.first->selected_indices() )
0891             bez[index] = bez.point_with_type(index, t);
0892         p.first->set_bezier(bez);
0893     }
0894 }
0895 
0896 void tools::EditTool::selection_delete()
0897 {
0898     d->delete_nodes(false);
0899 }
0900 
0901 void tools::EditTool::selection_dissolve()
0902 {
0903     d->delete_nodes(true);
0904 }
0905 
0906 
0907 void tools::EditTool::selection_straighten()
0908 {
0909     if ( d->selection.empty() )
0910         return;
0911 
0912     auto doc = d->selection.selected.begin()->first->target_object()->document();
0913 
0914     command::UndoMacroGuard macro(i18n("Straighten segments"), doc, false);
0915 
0916     for ( const auto& p : d->selection.selected )
0917     {
0918         auto bez = p.first->bezier();
0919         bool modified = false;
0920 
0921         for ( int index : p.first->selected_indices() )
0922         {
0923             int prev_index = index-1;
0924             if ( index == 0 && bez.closed() )
0925                 prev_index = bez.size() - 1;
0926             int next_index = index+1;
0927             if ( index == bez.size()-1 && bez.closed() )
0928                 next_index = 0;
0929 
0930             if ( p.first->selected_indices().count(prev_index) )
0931             {
0932                 bez[index].tan_in = bez[index].pos;
0933                 bez[index].type = math::bezier::Corner;
0934                 modified = true;
0935             }
0936 
0937             if ( p.first->selected_indices().count(next_index) )
0938             {
0939                 bez[index].tan_out = bez[index].pos;
0940                 bez[index].type = math::bezier::Corner;
0941                 modified = true;
0942             }
0943 
0944         }
0945 
0946         if ( modified )
0947         {
0948             if ( !macro.started() )
0949                 macro.start();
0950             p.first->set_bezier(bez);
0951         }
0952     }
0953 }
0954 
0955 void tools::EditTool::selection_curve()
0956 {
0957     if ( d->selection.empty() )
0958         return;
0959 
0960     auto doc = d->selection.selected.begin()->first->target_object()->document();
0961 
0962     command::UndoMacroGuard macro(i18n("Curve segments"), doc, false);
0963 
0964     for ( const auto& p : d->selection.selected )
0965     {
0966         auto bez = p.first->bezier();
0967         bool modified = false;
0968         for ( int index : p.first->selected_indices() )
0969         {
0970             bool mod_in = false;
0971             int prev_index = index-1;
0972             if ( index == 0 && bez.closed() )
0973                 prev_index = bez.size() - 1;
0974 
0975             int next_index = index+1;
0976             if ( index == bez.size()-1 && bez.closed() )
0977                 next_index = 0;
0978 
0979             if ( bez[index].tan_in == bez[index].pos && p.first->selected_indices().count(prev_index) )
0980             {
0981                 bez[index].tan_in = math::lerp(bez[index].pos, bez[prev_index].pos, 1/3.);
0982                 modified = mod_in = true;
0983             }
0984 
0985             if ( bez[index].tan_out == bez[index].pos && p.first->selected_indices().count(next_index) )
0986             {
0987                 bez[index].tan_out = math::lerp(bez[index].pos, bez[next_index].pos, 1/3.);
0988                 modified = true;
0989                 if ( mod_in )
0990                 {
0991                     bez[index].set_point_type(math::bezier::Smooth);
0992                 }
0993             }
0994         }
0995 
0996         if ( modified )
0997         {
0998             if ( !macro.started() )
0999                 macro.start();
1000             p.first->set_bezier(bez);
1001         }
1002     }
1003 }
1004 
1005 void tools::EditTool::add_point_mode()
1006 {
1007     d->drag_mode = Private::VertexAdd;
1008     set_cursor(Qt::DragCopyCursor);
1009 }
1010 
1011 void tools::EditTool::exit_add_point_mode()
1012 {
1013     d->insert_item = nullptr;
1014     d->insert_params = {};
1015     d->drag_mode = Private::None;
1016     set_cursor(Qt::ArrowCursor);
1017 }
1018 
1019 void tools::EditTool::set_cursor(Qt::CursorShape shape)
1020 {
1021     if ( shape != d->cursor )
1022         Q_EMIT cursor_changed(d->cursor = shape);
1023 }