/**
* @file
* New node tool - implementation.
*/
/* Authors:
* Krzysztof Kosiński <tweenk@gmail.com>
* Abhishek Sharma
*
* Copyright (C) 2009 Authors
* Released under GNU GPL, read the file 'COPYING' for more information
*/
#include "ui/tool/curve-drag-point.h"
#include "desktop.h"
#include "display/sp-canvas-group.h"
#include "display/canvas-bpath.h"
#include "display/sp-canvas.h"
#include "document.h"
#include "live_effects/effect.h"
#include "live_effects/lpeobject.h"
#include "message-context.h"
#include "selection.h"
#include "ui/shape-editor.h" // temporary!
#include "live_effects/effect.h"
#include "snap.h"
#include "sp-namedview.h"
#include "sp-clippath.h"
#include "sp-item-group.h"
#include "sp-mask.h"
#include "sp-object-group.h"
#include "sp-path.h"
#include "sp-text.h"
#include "ui/control-manager.h"
#include "ui/tools/node-tool.h"
#include "ui/tool/control-point-selection.h"
#include "ui/tool/event-utils.h"
#include "ui/tool/manipulator.h"
#include "ui/tool/multi-path-manipulator.h"
#include "ui/tool/path-manipulator.h"
#include "ui/tool/selector.h"
#include "ui/tool/shape-record.h"
#include "pixmaps/cursor-node.xpm"
#include "pixmaps/cursor-node-d.xpm"
#include "selection-chemistry.h"
#include <gdk/gdkkeysyms.h>
/** @struct NodeTool
*
* Node tool event context.
*
* @par Architectural overview of the tool
* @par
* Here's a breakdown of what each object does.
* - Handle: shows a handle and keeps the node type constraint (smooth / symmetric) by updating
* the other handle's position when dragged. Its move() method cannot violate the constraints.
* - Node: keeps node type constraints for auto nodes and smooth nodes at ends of linear segments.
* Its move() method cannot violate constraints. Handles linear grow and dispatches spatial grow
* to MultiPathManipulator. Keeps a reference to its NodeList.
* - NodeList: exposes an iterator-based interface to nodes. It is possible to obtain an iterator
* to a node from the node. Keeps a reference to its SubpathList.
* - SubpathList: list of NodeLists that represents an editable pathvector. Keeps a reference
* to its PathManipulator.
* - PathManipulator: performs most of the single-path actions like reverse subpaths,
* delete segment, shift selection, etc. Keeps a reference to MultiPathManipulator.
* - MultiPathManipulator: performs additional operations for actions that are not per-path,
* for example node joins and segment joins. Tracks the control transforms for PMs that edit
* clipping paths and masks. It is more or less equivalent to ShapeEditor and in the future
* it might handle all shapes. Handles XML commit of actions that affect all paths or
* the node selection and removes PathManipulators that have no nodes left after e.g. node
* deletes.
* - ControlPointSelection: keeps track of node selection and a set of nodes that can potentially
* be selected. There can be more than one selection. Performs actions that require no
* knowledge about the path, only about the nodes, like dragging and transforms. It is not
* specific to nodes and can accommodate any control point derived from SelectableControlPoint.
* Transforms nodes in response to transform handle events.
* - TransformHandleSet: displays nodeset transform handles and emits transform events. The aim
* is to eventually use a common class for object and control point transforms.
* - SelectableControlPoint: base for any type of selectable point. It can belong to only one
* selection.
*
* @par Functionality that resides in weird places
* @par
*
* This list is probably incomplete.
* - Curve dragging: CurveDragPoint, controlled by PathManipulator
* - Single handle shortcuts: MultiPathManipulator::event(), ModifierTracker
* - Linear and spatial grow: Node, spatial grow routed to ControlPointSelection
* - Committing handle actions performed with the mouse: PathManipulator
* - Sculpting: ControlPointSelection
*
* @par Plans for the future
* @par
* - MultiPathManipulator should become a generic shape editor that manages all active manipulator,
* more or less like the old ShapeEditor.
* - Knotholder should be rewritten into one manipulator class per shape, using the control point
* classes. Interesting features like dragging rectangle sides could be added along the way.
* - Better handling of clip and mask editing, particularly in response to undo.
* - High level refactoring of the event context hierarchy. All aspects of tools, like toolbox
* controls, icons, event handling should be collected in one class, though each aspect
* of a tool might be in an separate class for better modularity. The long term goal is to allow
* tools to be defined in extensions or shared library plugins.
*/
using Inkscape::ControlManager;
namespace Inkscape {
namespace UI {
namespace Tools {
}
, _multipath(NULL)
, edit_clipping_paths(false)
, edit_masks(false)
, flashed_item(NULL)
, _path_data(NULL)
, _last_over(NULL)
, cursor_drag(false)
, show_handles(false)
, show_outline(false)
, live_outline(false)
, live_objects(false)
, show_path_direction(false)
, show_transform_handles(false)
, single_node_transform_handles(false)
{
}
{
return reinterpret_cast<SPCanvasGroup*>(sp_canvas_item_new(
}
{
}
this->enableGrDrag(false);
if (this->flash_tempitem) {
}
if (this->helperpath_tmpitem) {
}
if (this->helperpath_tmpitem) {
}
this->_selection_changed_connection.disconnect();
//this->_selection_modified_connection.disconnect();
this->_mouseover_changed_connection.disconnect();
this->_sizeUpdatedConn.disconnect();
delete this->_multipath;
delete this->_selected_nodes;
delete this->_selector;
destroy_group(this->_transform_handle_group);
}
// selector has to be created here, so that its hidden control point is on the bottom
// Prepare canvas groups for controls. This guarantees correct z-order, so that
// for example a dragpoint won't obscure a node
this->_selection_changed_connection.disconnect();
this->_selection_changed_connection =
this->_mouseover_changed_connection.disconnect();
this->_mouseover_changed_connection =
Inkscape::UI::ControlPoint::signal_mouseover_change.connect(sigc::mem_fun(this, &NodeTool::mouseover_changed));
);
this->_selected_nodes = new Inkscape::UI::ControlPointSelection(this->desktop, this->_transform_handle_group);
this->_multipath = new Inkscape::UI::MultiPathManipulator(data, this->_selection_changed_connection);
(void*)NULL
)
);
// Hide both signal parameters and bind the function parameter to 0
// sigc::signal<void, SelectableControlPoint *, bool>
// <=>
// void update_tip(GdkEvent *event)
)))
);
this->helperpath_tmpitem = NULL;
this->cursor_drag = false;
this->show_transform_handles = true;
this->single_node_transform_handles = false;
this->flash_tempitem = NULL;
this->flashed_item = NULL;
this->_last_over = NULL;
this->helperpath_tmpitem = NULL;
// read prefs before adding items to selection to prevent momentarily showing the outline
sp_event_context_read(this, "show_handles");
sp_event_context_read(this, "show_outline");
sp_event_context_read(this, "live_outline");
sp_event_context_read(this, "live_objects");
sp_event_context_read(this, "show_path_direction");
sp_event_context_read(this, "show_transform_handles");
sp_event_context_read(this, "single_node_transform_handles");
sp_event_context_read(this, "edit_clipping_paths");
sp_event_context_read(this, "edit_masks");
this->selection_changed(selection);
this->update_tip(NULL);
this->enableSelectionCue();
}
this->enableGrDrag();
}
this->update_helperpath();
}
// show helper paths of the applied LPE, if any
if (this->helperpath_tmpitem) {
this->helperpath_tmpitem = NULL;
}
for (Inkscape::UI::ControlPointSelection::Set::iterator i = selectionNodes.begin(); i != selectionNodes.end(); ++i) {
if ((*i)->selected()) {
}
}
cc->set_pathvector(*p);
}
if (!c->is_empty()) {
sp_canvas_bpath_set_stroke(SP_CANVAS_BPATH(helperpath), 0x0000ff9A, 1.0, SP_STROKE_LINEJOIN_MITER, SP_STROKE_LINECAP_BUTT);
}
c->unref();
}
}
}
if (entry_name == "show_handles") {
} else if (entry_name == "show_outline") {
} else if (entry_name == "live_outline") {
} else if (entry_name == "live_objects") {
} else if (entry_name == "show_path_direction") {
} else if (entry_name == "show_transform_handles") {
this->_selected_nodes->showTransformHandles(
this->show_transform_handles, this->single_node_transform_handles);
} else if (entry_name == "single_node_transform_handles") {
this->_selected_nodes->showTransformHandles(
this->show_transform_handles, this->single_node_transform_handles);
} else if (entry_name == "edit_clipping_paths") {
} else if (entry_name == "edit_masks") {
} else {
}
}
/** Recursively collect ShapeRecords */
{
if (!obj) {
return;
}
//XML Tree being used directly here while it shouldn't be.
ShapeRecord r;
s.insert(r);
}
} else if (SP_IS_ITEM(obj)) {
ShapeRecord r;
// TODO add support for objectBoundingBox
// this item was encountered the first time
}
}
}
}
}
if (SP_IS_ITEM(obj)) {
}
}
// use multiple ShapeEditors for now, to allow editing many shapes at once
// needs to be rethought
i != this->_shape_editors.end(); )
{
ShapeRecord s;
this->_shape_editors.erase(i++);
} else {
++i;
}
}
ShapeRecord const &r = *i;
if ((SP_IS_SHAPE(r.item) || SP_IS_TEXT(r.item) || SP_IS_GROUP(r.item) || SP_IS_OBJECTGROUP(r.item)) &&
{
}
}
this->update_tip(NULL);
}
/* things to handle here:
* 1. selection of items
* 2. passing events to manipulators
* 3. some keybindings
*/
return true;
}
return true;
}
return true;
}
{
case GDK_MOTION_NOTIFY: {
this->update_helperpath();
this->update_helperpath();
// We will show a pre-snap indication for when the user adds a node through double-clicking
// Adding a node will only work when a path has been selected; if that's not the case then snapping is useless
m.unSetup();
}
}
if (over_item != this->_last_over) {
this->_last_over = over_item;
//ink_node_tool_update_tip(nt, event);
this->update_tip(event);
}
// create pathflash outline
if (over_item == this->flashed_item) {
break;
}
break;
}
if (this->flash_tempitem) {
this->flash_tempitem = NULL;
this->flashed_item = NULL;
}
if (!SP_IS_SHAPE(over_item)) {
break; // for now, handle only shapes
}
this->flashed_item = over_item;
if (!c) {
break; // break out when curve doesn't exist
}
//prefs->getInt("/tools/nodes/highlight_color", 0xff0000ff), 1.0,
c->unref();
}
} break; // do not return true, because we need to pass this event to the parent context
// otherwise some features cease to work
case GDK_KEY_PRESS:
{
case GDK_KEY_Escape: // deselect everything
if (this->_selected_nodes->empty()) {
} else {
this->_selected_nodes->clear();
}
//ink_node_tool_update_tip(nt, event);
this->update_tip(event);
return TRUE;
case GDK_KEY_a:
case GDK_KEY_A:
this->_selected_nodes->selectAll();
// Ctrl+A is handled in selection-chemistry.cpp via verb
//ink_node_tool_update_tip(nt, event);
this->update_tip(event);
return TRUE;
}
break;
case GDK_KEY_h:
case GDK_KEY_H:
return TRUE;
}
break;
default:
break;
}
//ink_node_tool_update_tip(nt, event);
this->update_tip(event);
break;
case GDK_KEY_RELEASE:
//ink_node_tool_update_tip(nt, event);
this->update_tip(event);
break;
case GDK_BUTTON_RELEASE:
if (this->_selector->doubleClicked()) {
// If the selector received the doubleclick event, then we're at some distance from
// the path; otherwise, the doubleclick event would have been received by
// CurveDragPoint; we will insert nodes into the path anyway but only if we can snap
// to the path. Otherwise the position would not be very well defined.
m.unSetup();
if (sp.getSnapped()) {
// The first click of the double click will have cleared the path selection, because
// we clicked aside of the path. We need to undo this on double click
// The selection has been restored, and the signal selection_changed has been emitted,
// which has again forced a restore of the _mmap variable of the MultiPathManipulator (this->_multipath)
// Now we can insert the new nodes as if nothing has happened!
}
}
}
break;
default:
break;
}
}
return;
}
if (state_held_shift(new_state)) {
if (this->_last_over) {
"click to toggle object selection"));
} else {
}
return;
}
}
if (sz != 0) {
char *nodestring;
if (sz == 2) {
// if there are only two nodes selected, display the angle
// of a line going through them relative to the X axis.
for (Inkscape::UI::ControlPointSelection::Set::iterator i = selection_nodes.begin(); i != selection_nodes.end(); ++i) {
if ((*i)->selected()) {
}
}
}
else {
}
if (this->_last_over) {
// TRANSLATORS: The %s below is where the "%u of %u nodes selected" sentence gets put
"%s Drag to select nodes, click to edit only this object (more: Shift)"),
} else {
"%s Drag to select nodes, click clear the selection"),
}
} else if (!this->_multipath->empty()) {
if (this->_last_over) {
"Drag to select nodes, click to edit only this object"));
} else {
"Drag to select nodes, click to clear the selection"));
}
} else {
if (this->_last_over) {
"Drag to select objects to edit, click to edit this object (more: Shift)"));
} else {
"Drag to select objects to edit"));
}
}
}
if (this->_multipath->empty()) {
// if multipath is empty, select rubberbanded items rather than nodes
} else {
if (!held_shift(*event)) {
this->_selected_nodes->clear();
}
}
}
if (!event) {
return;
}
return;
}
// if no Shift, deselect
// if there are nodes selected, the first click should deselect the nodes
// and the second should deselect the items
if (this->_selected_nodes->empty()) {
} else {
this->_selected_nodes->clear();
}
}
} else {
if (held_shift(*event)) {
} else {
}
}
}
if (cdp && !this->cursor_drag) {
this->cursor_shape = cursor_node_d_xpm;
this->hot_x = 1;
this->hot_y = 1;
this->sp_event_context_update_cursor();
this->cursor_drag = true;
} else if (!cdp && this->cursor_drag) {
this->cursor_shape = cursor_node_xpm;
this->hot_x = 1;
this->hot_y = 1;
this->sp_event_context_update_cursor();
this->cursor_drag = false;
}
}
this->_multipath->updateHandles();
}
}
}
}
//} // anonymous namespace
/*
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 :