curve-drag-point.cpp revision 0d68d82e47abab250c99dd534da2e2d26b697b2d
25c28e83beb90e7c80452a7c818c5e6f73a07dc8Piotr Jasiukajtis/** @file
25c28e83beb90e7c80452a7c818c5e6f73a07dc8Piotr Jasiukajtis * Control point that is dragged during path drag
25c28e83beb90e7c80452a7c818c5e6f73a07dc8Piotr Jasiukajtis */
25c28e83beb90e7c80452a7c818c5e6f73a07dc8Piotr Jasiukajtis/* Authors:
25c28e83beb90e7c80452a7c818c5e6f73a07dc8Piotr Jasiukajtis * Krzysztof KosiƄski <tweenk.pl@gmail.com>
25c28e83beb90e7c80452a7c818c5e6f73a07dc8Piotr Jasiukajtis *
25c28e83beb90e7c80452a7c818c5e6f73a07dc8Piotr Jasiukajtis * Copyright (C) 2009 Authors
25c28e83beb90e7c80452a7c818c5e6f73a07dc8Piotr Jasiukajtis * Released under GNU GPL, read the file 'COPYING' for more information
25c28e83beb90e7c80452a7c818c5e6f73a07dc8Piotr Jasiukajtis */
25c28e83beb90e7c80452a7c818c5e6f73a07dc8Piotr Jasiukajtis
25c28e83beb90e7c80452a7c818c5e6f73a07dc8Piotr Jasiukajtis#include <glib/gi18n.h>
25c28e83beb90e7c80452a7c818c5e6f73a07dc8Piotr Jasiukajtis#include <2geom/bezier-curve.h>
25c28e83beb90e7c80452a7c818c5e6f73a07dc8Piotr Jasiukajtis#include "desktop.h"
25c28e83beb90e7c80452a7c818c5e6f73a07dc8Piotr Jasiukajtis#include "ui/tool/control-point-selection.h"
25c28e83beb90e7c80452a7c818c5e6f73a07dc8Piotr Jasiukajtis#include "ui/tool/curve-drag-point.h"
25c28e83beb90e7c80452a7c818c5e6f73a07dc8Piotr Jasiukajtis#include "ui/tool/event-utils.h"
25c28e83beb90e7c80452a7c818c5e6f73a07dc8Piotr Jasiukajtis#include "ui/tool/multi-path-manipulator.h"
25c28e83beb90e7c80452a7c818c5e6f73a07dc8Piotr Jasiukajtis#include "ui/tool/path-manipulator.h"
25c28e83beb90e7c80452a7c818c5e6f73a07dc8Piotr Jasiukajtis#include "ui/tool/node.h"
25c28e83beb90e7c80452a7c818c5e6f73a07dc8Piotr Jasiukajtis
25c28e83beb90e7c80452a7c818c5e6f73a07dc8Piotr Jasiukajtisnamespace Inkscape {
25c28e83beb90e7c80452a7c818c5e6f73a07dc8Piotr Jasiukajtisnamespace UI {
25c28e83beb90e7c80452a7c818c5e6f73a07dc8Piotr Jasiukajtis
25c28e83beb90e7c80452a7c818c5e6f73a07dc8Piotr Jasiukajtis/**
25c28e83beb90e7c80452a7c818c5e6f73a07dc8Piotr Jasiukajtis * @class CurveDragPoint
25c28e83beb90e7c80452a7c818c5e6f73a07dc8Piotr Jasiukajtis * An invisible point used to drag curves. This point is used by PathManipulator to allow editing
25c28e83beb90e7c80452a7c818c5e6f73a07dc8Piotr Jasiukajtis * of path segments by dragging them. It is defined in a separate file so that the node tool
25c28e83beb90e7c80452a7c818c5e6f73a07dc8Piotr Jasiukajtis * can check if the mouseovered control point is a curve drag point and update the cursor
25c28e83beb90e7c80452a7c818c5e6f73a07dc8Piotr Jasiukajtis * accordingly, without the need to drag in the full PathManipulator header.
ddc0e0b53c661f6e439e3b7072b3ef353eadb4afRichard Lowe */
25c28e83beb90e7c80452a7c818c5e6f73a07dc8Piotr Jasiukajtis
25c28e83beb90e7c80452a7c818c5e6f73a07dc8Piotr Jasiukajtis// This point should be invisible to the user - use the invisible_cset from control-point.h
25c28e83beb90e7c80452a7c818c5e6f73a07dc8Piotr Jasiukajtis// TODO make some methods from path-manipulator.cpp public so that this point doesn't have
25c28e83beb90e7c80452a7c818c5e6f73a07dc8Piotr Jasiukajtis// to be declared as a friend
25c28e83beb90e7c80452a7c818c5e6f73a07dc8Piotr Jasiukajtis
25c28e83beb90e7c80452a7c818c5e6f73a07dc8Piotr Jasiukajtisbool CurveDragPoint::_drags_stroke = false;
25c28e83beb90e7c80452a7c818c5e6f73a07dc8Piotr Jasiukajtisbool CurveDragPoint::_segment_was_degenerate = false;
25c28e83beb90e7c80452a7c818c5e6f73a07dc8Piotr Jasiukajtis
25c28e83beb90e7c80452a7c818c5e6f73a07dc8Piotr JasiukajtisCurveDragPoint::CurveDragPoint(PathManipulator &pm)
25c28e83beb90e7c80452a7c818c5e6f73a07dc8Piotr Jasiukajtis : ControlPoint(pm._multi_path_manipulator._path_data.node_data.desktop, Geom::Point(),
25c28e83beb90e7c80452a7c818c5e6f73a07dc8Piotr Jasiukajtis Gtk::ANCHOR_CENTER, SP_CTRL_SHAPE_CIRCLE, 1.0, &invisible_cset,
25c28e83beb90e7c80452a7c818c5e6f73a07dc8Piotr Jasiukajtis pm._multi_path_manipulator._path_data.dragpoint_group)
25c28e83beb90e7c80452a7c818c5e6f73a07dc8Piotr Jasiukajtis , _pm(pm)
25c28e83beb90e7c80452a7c818c5e6f73a07dc8Piotr Jasiukajtis{
25c28e83beb90e7c80452a7c818c5e6f73a07dc8Piotr Jasiukajtis setVisible(false);
25c28e83beb90e7c80452a7c818c5e6f73a07dc8Piotr Jasiukajtis}
25c28e83beb90e7c80452a7c818c5e6f73a07dc8Piotr Jasiukajtis
25c28e83beb90e7c80452a7c818c5e6f73a07dc8Piotr Jasiukajtisbool CurveDragPoint::_eventHandler(SPEventContext *event_context, GdkEvent *event)
25c28e83beb90e7c80452a7c818c5e6f73a07dc8Piotr Jasiukajtis{
25c28e83beb90e7c80452a7c818c5e6f73a07dc8Piotr Jasiukajtis // do not process any events when the manipulator is empty
25c28e83beb90e7c80452a7c818c5e6f73a07dc8Piotr Jasiukajtis if (_pm.empty()) {
25c28e83beb90e7c80452a7c818c5e6f73a07dc8Piotr Jasiukajtis setVisible(false);
25c28e83beb90e7c80452a7c818c5e6f73a07dc8Piotr Jasiukajtis return false;
25c28e83beb90e7c80452a7c818c5e6f73a07dc8Piotr Jasiukajtis }
25c28e83beb90e7c80452a7c818c5e6f73a07dc8Piotr Jasiukajtis return ControlPoint::_eventHandler(event_context, event);
25c28e83beb90e7c80452a7c818c5e6f73a07dc8Piotr Jasiukajtis}
25c28e83beb90e7c80452a7c818c5e6f73a07dc8Piotr Jasiukajtis
25c28e83beb90e7c80452a7c818c5e6f73a07dc8Piotr Jasiukajtisbool CurveDragPoint::grabbed(GdkEventMotion */*event*/)
25c28e83beb90e7c80452a7c818c5e6f73a07dc8Piotr Jasiukajtis{
25c28e83beb90e7c80452a7c818c5e6f73a07dc8Piotr Jasiukajtis _pm._selection.hideTransformHandles();
25c28e83beb90e7c80452a7c818c5e6f73a07dc8Piotr Jasiukajtis NodeList::iterator second = first.next();
25c28e83beb90e7c80452a7c818c5e6f73a07dc8Piotr Jasiukajtis
25c28e83beb90e7c80452a7c818c5e6f73a07dc8Piotr Jasiukajtis // move the handles to 1/3 the length of the segment for line segments
25c28e83beb90e7c80452a7c818c5e6f73a07dc8Piotr Jasiukajtis if (first->front()->isDegenerate() && second->back()->isDegenerate()) {
25c28e83beb90e7c80452a7c818c5e6f73a07dc8Piotr Jasiukajtis _segment_was_degenerate = true;
25c28e83beb90e7c80452a7c818c5e6f73a07dc8Piotr Jasiukajtis
25c28e83beb90e7c80452a7c818c5e6f73a07dc8Piotr Jasiukajtis // delta is a vector equal 1/3 of distance from first to second
25c28e83beb90e7c80452a7c818c5e6f73a07dc8Piotr Jasiukajtis Geom::Point delta = (second->position() - first->position()) / 3.0;
25c28e83beb90e7c80452a7c818c5e6f73a07dc8Piotr Jasiukajtis first->front()->move(first->front()->position() + delta);
25c28e83beb90e7c80452a7c818c5e6f73a07dc8Piotr Jasiukajtis second->back()->move(second->back()->position() - delta);
25c28e83beb90e7c80452a7c818c5e6f73a07dc8Piotr Jasiukajtis
25c28e83beb90e7c80452a7c818c5e6f73a07dc8Piotr Jasiukajtis _pm.update();
61ec6b12089c560a32ebd9efdbb057ff92665e60Gordon Ross } else {
25c28e83beb90e7c80452a7c818c5e6f73a07dc8Piotr Jasiukajtis _segment_was_degenerate = false;
61ec6b12089c560a32ebd9efdbb057ff92665e60Gordon Ross }
25c28e83beb90e7c80452a7c818c5e6f73a07dc8Piotr Jasiukajtis return false;
25c28e83beb90e7c80452a7c818c5e6f73a07dc8Piotr Jasiukajtis}
25c28e83beb90e7c80452a7c818c5e6f73a07dc8Piotr Jasiukajtis
25c28e83beb90e7c80452a7c818c5e6f73a07dc8Piotr Jasiukajtisvoid CurveDragPoint::dragged(Geom::Point &new_pos, GdkEventMotion *event)
25c28e83beb90e7c80452a7c818c5e6f73a07dc8Piotr Jasiukajtis{
25c28e83beb90e7c80452a7c818c5e6f73a07dc8Piotr Jasiukajtis NodeList::iterator second = first.next();
25c28e83beb90e7c80452a7c818c5e6f73a07dc8Piotr Jasiukajtis
25c28e83beb90e7c80452a7c818c5e6f73a07dc8Piotr Jasiukajtis // special cancel handling - retract handles when if the segment was degenerate
25c28e83beb90e7c80452a7c818c5e6f73a07dc8Piotr Jasiukajtis if (_is_drag_cancelled(event) && _segment_was_degenerate) {
25c28e83beb90e7c80452a7c818c5e6f73a07dc8Piotr Jasiukajtis first->front()->retract();
25c28e83beb90e7c80452a7c818c5e6f73a07dc8Piotr Jasiukajtis second->back()->retract();
25c28e83beb90e7c80452a7c818c5e6f73a07dc8Piotr Jasiukajtis _pm.update();
25c28e83beb90e7c80452a7c818c5e6f73a07dc8Piotr Jasiukajtis return;
25c28e83beb90e7c80452a7c818c5e6f73a07dc8Piotr Jasiukajtis }
25c28e83beb90e7c80452a7c818c5e6f73a07dc8Piotr Jasiukajtis
25c28e83beb90e7c80452a7c818c5e6f73a07dc8Piotr Jasiukajtis // Magic Bezier Drag Equations follow!
25c28e83beb90e7c80452a7c818c5e6f73a07dc8Piotr Jasiukajtis // "weight" describes how the influence of the drag should be distributed
25c28e83beb90e7c80452a7c818c5e6f73a07dc8Piotr Jasiukajtis // among the handles; 0 = front handle only, 1 = back handle only.
25c28e83beb90e7c80452a7c818c5e6f73a07dc8Piotr Jasiukajtis double weight, t = _t;
25c28e83beb90e7c80452a7c818c5e6f73a07dc8Piotr Jasiukajtis if (t <= 1.0 / 6.0) weight = 0;
25c28e83beb90e7c80452a7c818c5e6f73a07dc8Piotr Jasiukajtis else if (t <= 0.5) weight = (pow((6 * t - 1) / 2.0, 3)) / 2;
25c28e83beb90e7c80452a7c818c5e6f73a07dc8Piotr Jasiukajtis else if (t <= 5.0 / 6.0) weight = (1 - pow((6 * (1-t) - 1) / 2.0, 3)) / 2 + 0.5;
25c28e83beb90e7c80452a7c818c5e6f73a07dc8Piotr Jasiukajtis else weight = 1;
25c28e83beb90e7c80452a7c818c5e6f73a07dc8Piotr Jasiukajtis
25c28e83beb90e7c80452a7c818c5e6f73a07dc8Piotr Jasiukajtis Geom::Point delta = new_pos - position();
Geom::Point offset0 = ((1-weight)/(3*t*(1-t)*(1-t))) * delta;
Geom::Point offset1 = (weight/(3*t*t*(1-t))) * delta;
first->front()->move(first->front()->position() + offset0);
second->back()->move(second->back()->position() + offset1);
_pm.update();
}
void CurveDragPoint::ungrabbed(GdkEventButton *)
{
_pm._updateDragPoint(_desktop->d2w(position()));
_pm._commit(_("Drag curve"));
_pm._selection.restoreTransformHandles();
}
bool CurveDragPoint::clicked(GdkEventButton *event)
{
// This check is probably redundant
if (!first || event->button != 1) return false;
// the next iterator can be invalid if we click very near the end of path
NodeList::iterator second = first.next();
if (!second) return false;
// insert nodes on Ctrl+Alt+click
if (held_control(*event) && held_alt(*event)) {
_insertNode(false);
return true;
}
if (held_shift(*event)) {
// if both nodes of the segment are selected, deselect;
// otherwise add to selection
if (first->selected() && second->selected()) {
_pm._selection.erase(first.ptr());
_pm._selection.erase(second.ptr());
} else {
_pm._selection.insert(first.ptr());
_pm._selection.insert(second.ptr());
}
} else {
// without Shift, take selection
_pm._selection.clear();
_pm._selection.insert(first.ptr());
_pm._selection.insert(second.ptr());
}
return true;
}
bool CurveDragPoint::doubleclicked(GdkEventButton *event)
{
if (event->button != 1 || !first || !first.next()) return false;
_insertNode(true);
return true;
}
void CurveDragPoint::_insertNode(bool take_selection)
{
// The purpose of this call is to make way for the just created node.
// Otherwise clicks on the new node would only work after the user moves the mouse a bit.
// PathManipulator will restore visibility when necessary.
setVisible(false);
NodeList::iterator inserted = _pm.subdivideSegment(first, _t);
if (take_selection) {
_pm._selection.clear();
}
_pm._selection.insert(inserted.ptr());
_pm.update(true);
_pm._commit(_("Add node"));
}
Glib::ustring CurveDragPoint::_getTip(unsigned state)
{
if (_pm.empty()) return "";
if (!first || !first.next()) return "";
bool linear = first->front()->isDegenerate() && first.next()->back()->isDegenerate();
if (state_held_shift(state)) {
return C_("Path segment tip",
"<b>Shift</b>: click to toggle segment selection");
}
if (state_held_control(state) && state_held_alt(state)) {
return C_("Path segment tip",
"<b>Ctrl+Alt</b>: click to insert a node");
}
if (linear) {
return C_("Path segment tip",
"<b>Linear segment</b>: drag to convert to a Bezier segment, "
"doubleclick to insert node, click to select (more: Shift, Ctrl+Alt)");
} else {
return C_("Path segment tip",
"<b>Bezier segment</b>: drag to shape the segment, doubleclick to insert node, "
"click to select (more: Shift, Ctrl+Alt)");
}
}
} // namespace UI
} // namespace Inkscape
/*
Local Variables:
mode:c++
c-file-style:"stroustrup"
c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
indent-tabs-mode:nil
fill-column:99
End:
*/
// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :