X-Git-Url: https://git.tokkee.org/?a=blobdiff_plain;f=src%2Fui%2Ftool%2Fnode-tool.cpp;h=e75f31370394913fa6b40834e56e31717340992d;hb=0dc33d4ce43e0bb49c63aa53b826ec4a1ff68e28;hp=a57057c92b0d7c77375679d111b190d08ae83191;hpb=44aa4bdf094f63e3f9e5ccf1c84b835c3e06863b;p=inkscape.git diff --git a/src/ui/tool/node-tool.cpp b/src/ui/tool/node-tool.cpp index a57057c92..e75f31370 100644 --- a/src/ui/tool/node-tool.cpp +++ b/src/ui/tool/node-tool.cpp @@ -3,6 +3,7 @@ */ /* Authors: * Krzysztof Kosiński + * Abhishek Sharma * * Copyright (C) 2009 Authors * Released under GNU GPL, read the file 'COPYING' for more information @@ -12,10 +13,12 @@ #include #include "desktop.h" #include "desktop-handles.h" +#include "display/sp-canvas-group.h" #include "display/canvas-bpath.h" #include "display/curve.h" #include "display/sp-canvas.h" #include "document.h" +#include "live_effects/lpeobject.h" #include "message-context.h" #include "selection.h" #include "shape-editor.h" // temporary! @@ -24,6 +27,7 @@ #include "sp-mask.h" #include "sp-object-group.h" #include "sp-path.h" +#include "sp-text.h" #include "ui/tool/node-tool.h" #include "ui/tool/control-point-selection.h" #include "ui/tool/curve-drag-point.h" @@ -32,10 +36,68 @@ #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" +/** @struct InkNodeTool + * + * 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 accomodate 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. + */ + namespace { SPCanvasGroup *create_control_group(SPDesktop *d); void ink_node_tool_class_init(InkNodeToolClass *klass); @@ -108,12 +170,14 @@ void ink_node_tool_init(InkNodeTool *nt) event_context->hot_y = 1; new (&nt->_selection_changed_connection) sigc::connection(); + new (&nt->_selection_modified_connection) sigc::connection(); new (&nt->_mouseover_changed_connection) sigc::connection(); //new (&nt->_mgroup) Inkscape::UI::ManipulatorGroup(nt->desktop); new (&nt->_selected_nodes) CSelPtr(); new (&nt->_multipath) MultiPathPtr(); new (&nt->_selector) SelectorPtr(); new (&nt->_path_data) PathSharedDataPtr(); + new (&nt->_shape_editors) ShapeEditors(); } void ink_node_tool_dispose(GObject *object) @@ -123,10 +187,12 @@ void ink_node_tool_dispose(GObject *object) nt->enableGrDrag(false); nt->_selection_changed_connection.disconnect(); + nt->_selection_modified_connection.disconnect(); nt->_mouseover_changed_connection.disconnect(); nt->_multipath.~MultiPathPtr(); nt->_selected_nodes.~CSelPtr(); nt->_selector.~SelectorPtr(); + nt->_shape_editors.~ShapeEditors(); Inkscape::UI::PathSharedData &data = *nt->_path_data; destroy_group(data.node_data.node_group); @@ -138,15 +204,12 @@ void ink_node_tool_dispose(GObject *object) nt->_path_data.~PathSharedDataPtr(); nt->_selection_changed_connection.~connection(); + nt->_selection_modified_connection.~connection(); nt->_mouseover_changed_connection.~connection(); if (nt->_node_message_context) { delete nt->_node_message_context; } - if (nt->shape_editor) { - nt->shape_editor->unset_item(SH_KNOTHOLDER); - delete nt->shape_editor; - } G_OBJECT_CLASS(g_type_class_peek(g_type_parent(INK_TYPE_NODE_TOOL)))->dispose(object); } @@ -183,6 +246,12 @@ void ink_node_tool_setup(SPEventContext *ec) sigc::bind<0>( sigc::ptr_fun(&ink_node_tool_selection_changed), nt)); + /*nt->_selection_modified_connection.disconnect(); + nt->_selection_modified_connection = + selection->connectModified( + sigc::hide(sigc::bind<0>( + sigc::ptr_fun(&ink_node_tool_selection_modified), + nt)));*/ nt->_mouseover_changed_connection.disconnect(); nt->_mouseover_changed_connection = Inkscape::UI::ControlPoint::signal_mouseover_change.connect( @@ -222,12 +291,13 @@ void ink_node_tool_setup(SPEventContext *ec) nt->single_node_transform_handles = false; nt->flash_tempitem = NULL; nt->flashed_item = NULL; - // TODO remove this! - nt->shape_editor = new ShapeEditor(nt->desktop); + nt->_last_over = NULL; // read prefs before adding items to selection to prevent momentarily showing the outline sp_event_context_read(nt, "show_handles"); sp_event_context_read(nt, "show_outline"); + sp_event_context_read(nt, "live_outline"); + sp_event_context_read(nt, "live_objects"); sp_event_context_read(nt, "show_path_direction"); sp_event_context_read(nt, "show_transform_handles"); sp_event_context_read(nt, "single_node_transform_handles"); @@ -254,10 +324,17 @@ void ink_node_tool_set(SPEventContext *ec, Inkscape::Preferences::Entry *value) Glib::ustring entry_name = value->getEntryName(); if (entry_name == "show_handles") { - nt->_multipath->showHandles(value->getBool(true)); + nt->show_handles = value->getBool(true); + nt->_multipath->showHandles(nt->show_handles); } else if (entry_name == "show_outline") { nt->show_outline = value->getBool(); nt->_multipath->showOutline(nt->show_outline); + } else if (entry_name == "live_outline") { + nt->live_outline = value->getBool(); + nt->_multipath->setLiveOutline(nt->live_outline); + } else if (entry_name == "live_objects") { + nt->live_objects = value->getBool(); + nt->_multipath->setLiveObjects(nt->live_objects); } else if (entry_name == "show_path_direction") { nt->show_path_direction = value->getBool(); nt->_multipath->showPathDirection(nt->show_path_direction); @@ -283,80 +360,84 @@ void ink_node_tool_set(SPEventContext *ec, Inkscape::Preferences::Entry *value) } } -void store_clip_mask_items(SPItem *clipped, SPObject *obj, std::map > &s, Geom::Matrix const &postm, guint32 color) +/** Recursively collect ShapeRecords */ +void gather_items(InkNodeTool *nt, SPItem *base, SPObject *obj, Inkscape::UI::ShapeRole role, + std::set &s) { + using namespace Inkscape::UI; if (!obj) return; - if (SP_IS_GROUP(obj) || SP_IS_OBJECTGROUP(obj)) { - //TODO is checking for obj->children != NULL above better? + + //XML Tree being used directly here while it shouldn't be. + if (SP_IS_PATH(obj) && obj->getRepr()->attribute("inkscape:original-d") != NULL) { + ShapeRecord r; + r.item = static_cast(obj); + r.edit_transform = Geom::identity(); // TODO wrong? + r.role = role; + s.insert(r); + } else if (role != SHAPE_ROLE_NORMAL && (SP_IS_GROUP(obj) || SP_IS_OBJECTGROUP(obj))) { for (SPObject *c = obj->children; c; c = c->next) { - store_clip_mask_items(clipped, c, s, postm, color); + gather_items(nt, base, c, role, s); } } else if (SP_IS_ITEM(obj)) { - s.insert(std::make_pair(SP_ITEM(obj), - std::make_pair(sp_item_i2d_affine(clipped) * postm, color))); + SPItem *item = static_cast(obj); + ShapeRecord r; + r.item = item; + // TODO add support for objectBoundingBox + r.edit_transform = base ? base->i2doc_affine() : Geom::identity(); + r.role = role; + if (s.insert(r).second) { + // this item was encountered the first time + if (nt->edit_clipping_paths && item->clip_ref) { + gather_items(nt, item, item->clip_ref->getObject(), SHAPE_ROLE_CLIPPING_PATH, s); + } + if (nt->edit_masks && item->mask_ref) { + gather_items(nt, item, item->mask_ref->getObject(), SHAPE_ROLE_MASK, s); + } + } } } -struct IsPath { - bool operator()(SPItem *i) const { return SP_IS_PATH(i); } -}; - void ink_node_tool_selection_changed(InkNodeTool *nt, Inkscape::Selection *sel) { using namespace Inkscape::UI; - // TODO this is ugly!!! - typedef std::map > TransMap; - typedef std::map > PathMap; + + std::set shapes; + GSList const *ilist = sel->itemList(); - TransMap items; - Inkscape::Preferences *prefs = Inkscape::Preferences::get(); for (GSList *i = const_cast(ilist); i; i = i->next) { SPObject *obj = static_cast(i->data); if (SP_IS_ITEM(obj)) { - items.insert(std::make_pair(SP_ITEM(obj), - std::make_pair(Geom::identity(), - prefs->getColor("/tools/nodes/outline_color", 0xff0000ff)))); - if (nt->edit_clipping_paths && SP_ITEM(i->data)->clip_ref) { - store_clip_mask_items(SP_ITEM(i->data), - SP_OBJECT(SP_ITEM(i->data)->clip_ref->getObject()), items, - nt->desktop->dt2doc(), - prefs->getColor("/tools/nodes/clipping_path_color", 0x00ff00ff)); - } - if (nt->edit_masks && SP_ITEM(i->data)->mask_ref) { - store_clip_mask_items(SP_ITEM(i->data), - SP_OBJECT(SP_ITEM(i->data)->mask_ref->getObject()), items, - nt->desktop->dt2doc(), - prefs->getColor("/tools/nodes/mask_color", 0x0000ffff)); - } + gather_items(nt, NULL, static_cast(obj), SHAPE_ROLE_NORMAL, shapes); } } - // ugly hack: set the first editable non-path item for knotholder - // maybe use multiple ShapeEditors for now, to allow editing many shapes at once? - bool something_set = false; - for (TransMap::iterator i = items.begin(); i != items.end(); ++i) { - SPItem *obj = i->first; - if (SP_IS_SHAPE(obj) && !SP_IS_PATH(obj)) { - nt->shape_editor->set_item(obj, SH_KNOTHOLDER); - something_set = true; - break; + // use multiple ShapeEditors for now, to allow editing many shapes at once + // needs to be rethought + for (ShapeEditors::iterator i = nt->_shape_editors.begin(); + i != nt->_shape_editors.end(); ) + { + ShapeRecord s; + s.item = i->first; + if (shapes.find(s) == shapes.end()) { + nt->_shape_editors.erase(i++); + } else { + ++i; } } - if (!something_set) { - nt->shape_editor->unset_item(SH_KNOTHOLDER); - } - - PathMap p; - for (TransMap::iterator i = items.begin(); i != items.end(); ++i) { - if (SP_IS_PATH(i->first)) { - p.insert(std::make_pair(SP_PATH(i->first), - std::make_pair(i->second.first, i->second.second))); + + for (std::set::iterator i = shapes.begin(); i != shapes.end(); ++i) { + ShapeRecord const &r = *i; + if ((SP_IS_SHAPE(r.item) || SP_IS_TEXT(r.item)) && + nt->_shape_editors.find(r.item) == nt->_shape_editors.end()) + { + ShapeEditor *si = new ShapeEditor(nt->desktop); + si->set_item(r.item, SH_KNOTHOLDER); + nt->_shape_editors.insert(const_cast(r.item), si); } } - nt->_multipath->setItems(p); + nt->_multipath->setItems(shapes); ink_node_tool_update_tip(nt, NULL); nt->desktop->updateNow(); } @@ -381,15 +462,19 @@ gint ink_node_tool_root_handler(SPEventContext *event_context, GdkEvent *event) switch (event->type) { - case GDK_MOTION_NOTIFY: - // create outline - if (prefs->getBool("/tools/nodes/pathflash_enabled")) { - if (prefs->getBool("/tools/nodes/pathflash_unselected") && !nt->_multipath->empty()) - break; - - SPItem *over_item = sp_event_context_find_item (desktop, event_point(event->button), + case GDK_MOTION_NOTIFY: { + combine_motion_events(desktop->canvas, event->motion, 0); + SPItem *over_item = sp_event_context_find_item (desktop, event_point(event->button), FALSE, TRUE); + if (over_item != nt->_last_over) { + nt->_last_over = over_item; + ink_node_tool_update_tip(nt, event); + } + + // create pathflash outline + if (prefs->getBool("/tools/nodes/pathflash_enabled")) { if (over_item == nt->flashed_item) break; + if (!prefs->getBool("/tools/nodes/pathflash_selected") && selection->includes(over_item)) break; if (nt->flash_tempitem) { desktop->remove_temporary_canvasitem(nt->flash_tempitem); nt->flash_tempitem = NULL; @@ -399,7 +484,7 @@ gint ink_node_tool_root_handler(SPEventContext *event_context, GdkEvent *event) nt->flashed_item = over_item; SPCurve *c = sp_path_get_curve_for_edit(SP_PATH(over_item)); - c->transform(sp_item_i2d_affine(over_item)); + c->transform(over_item->i2d_affine()); SPCanvasItem *flash = sp_canvas_bpath_new(sp_desktop_tempgroup(desktop), c); sp_canvas_bpath_set_stroke(SP_CANVAS_BPATH(flash), prefs->getInt("/tools/nodes/highlight_color", 0xff0000ff), 1.0, @@ -409,7 +494,9 @@ gint ink_node_tool_root_handler(SPEventContext *event_context, GdkEvent *event) prefs->getInt("/tools/nodes/pathflash_timeout", 500)); c->unref(); } - return true; + } 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: switch (get_group0_keyval(&event->key)) { @@ -422,18 +509,22 @@ gint ink_node_tool_root_handler(SPEventContext *event_context, GdkEvent *event) ink_node_tool_update_tip(nt, event); return TRUE; case GDK_a: - if (held_control(event->key)) { - if (held_alt(event->key)) { - nt->_multipath->selectAll(); - } else { - // select all nodes in subpaths that have something selected - // if nothing is selected, select everything - nt->_multipath->selectSubpaths(); - } + case GDK_A: + if (held_control(event->key) && held_alt(event->key)) { + nt->_selected_nodes->selectAll(); + // Ctrl+A is handled in selection-chemistry.cpp via verb ink_node_tool_update_tip(nt, event); return TRUE; } break; + case GDK_h: + case GDK_H: + if (held_only_control(event->key)) { + Inkscape::Preferences *prefs = Inkscape::Preferences::get(); + prefs->setBool("/tools/nodes/show_handles", !nt->show_handles); + return TRUE; + } + break; default: break; } @@ -458,26 +549,54 @@ void ink_node_tool_update_tip(InkNodeTool *nt, GdkEvent *event) unsigned new_state = state_after_event(event); if (new_state == event->key.state) return; if (state_held_shift(new_state)) { - nt->_node_message_context->set(Inkscape::NORMAL_MESSAGE, - C_("Node tool tip", "Shift: drag to add nodes to the selection, " - "click to toggle object selection")); + if (nt->_last_over) { + nt->_node_message_context->set(Inkscape::NORMAL_MESSAGE, + C_("Node tool tip", "Shift: drag to add nodes to the selection, " + "click to toggle object selection")); + } else { + nt->_node_message_context->set(Inkscape::NORMAL_MESSAGE, + C_("Node tool tip", "Shift: drag to add nodes to the selection")); + } return; } } unsigned sz = nt->_selected_nodes->size(); + unsigned total = nt->_selected_nodes->allPoints().size(); if (sz != 0) { - char *dyntip = g_strdup_printf(C_("Node tool tip", - "Selected %d nodes. Drag to select nodes, click to select a single object " - "or unselect all objects"), sz); - nt->_node_message_context->set(Inkscape::NORMAL_MESSAGE, dyntip); - g_free(dyntip); - } else if (nt->_multipath->empty()) { - nt->_node_message_context->set(Inkscape::NORMAL_MESSAGE, - C_("Node tool tip", "Drag or click to select objects to edit")); + char *nodestring = g_strdup_printf( + ngettext("%u of %u node selected.", "%u of %u nodes selected.", total), + sz, total); + if (nt->_last_over) { + // TRANSLATORS: The %s below is where the "%u of %u nodes selected" sentence gets put + char *dyntip = g_strdup_printf(C_("Node tool tip", + "%s Drag to select nodes, click to edit only this object (more: Shift)"), + nodestring); + nt->_node_message_context->set(Inkscape::NORMAL_MESSAGE, dyntip); + g_free(dyntip); + } else { + char *dyntip = g_strdup_printf(C_("Node tool tip", + "%s Drag to select nodes, click clear the selection"), + nodestring); + nt->_node_message_context->set(Inkscape::NORMAL_MESSAGE, dyntip); + g_free(dyntip); + } + g_free(nodestring); + } else if (!nt->_multipath->empty()) { + if (nt->_last_over) { + nt->_node_message_context->set(Inkscape::NORMAL_MESSAGE, C_("Node tool tip", + "Drag to select nodes, click to edit only this object")); + } else { + nt->_node_message_context->set(Inkscape::NORMAL_MESSAGE, C_("Node tool tip", + "Drag to select nodes, click to clear the selection")); + } } else { - nt->_node_message_context->set(Inkscape::NORMAL_MESSAGE, - C_("Node tool tip", "Drag to select nodes, click to select an object " - "or clear the selection")); + if (nt->_last_over) { + nt->_node_message_context->set(Inkscape::NORMAL_MESSAGE, C_("Node tool tip", + "Drag to select objects to edit, click to edit this object (more: Shift)")); + } else { + nt->_node_message_context->set(Inkscape::NORMAL_MESSAGE, C_("Node tool tip", + "Drag to select objects to edit")); + } } } @@ -496,15 +615,15 @@ void ink_node_tool_select_area(InkNodeTool *nt, Geom::Rect const &sel, GdkEventB if (nt->_multipath->empty()) { // if multipath is empty, select rubberbanded items rather than nodes Inkscape::Selection *selection = nt->desktop->selection; - GSList *items = sp_document_items_in_box( - sp_desktop_document(nt->desktop), nt->desktop->dkey, sel); + GSList *items = sp_desktop_document(nt->desktop)->getItemsInBox(nt->desktop->dkey, sel); selection->setList(items); g_slist_free(items); } else { - nt->_multipath->selectArea(sel, !held_shift(*event)); + if (!held_shift(*event)) nt->_selected_nodes->clear(); + nt->_selected_nodes->selectArea(sel); } } -void ink_node_tool_select_point(InkNodeTool *nt, Geom::Point const &sel, GdkEventButton *event) +void ink_node_tool_select_point(InkNodeTool *nt, Geom::Point const &/*sel*/, GdkEventButton *event) { using namespace Inkscape::UI; // pull in event helpers if (!event) return; @@ -517,17 +636,23 @@ void ink_node_tool_select_point(InkNodeTool *nt, Geom::Point const &sel, GdkEven if (item_clicked == NULL) { // nothing under cursor // if no Shift, deselect - if (!(event->state & GDK_SHIFT_MASK)) { - selection->clear(); + // if there are nodes selected, the first click should deselect the nodes + // and the second should deselect the items + if (!state_held_shift(event->state)) { + if (nt->_selected_nodes->empty()) { + selection->clear(); + } else { + nt->_selected_nodes->clear(); + } } - return; - } - if (held_shift(*event)) { - selection->toggle(item_clicked); } else { - selection->set(item_clicked); + if (held_shift(*event)) { + selection->toggle(item_clicked); + } else { + selection->set(item_clicked); + } + nt->desktop->updateNow(); } - nt->desktop->updateNow(); } void ink_node_tool_mouseover_changed(InkNodeTool *nt, Inkscape::UI::ControlPoint *p) @@ -560,4 +685,4 @@ void ink_node_tool_mouseover_changed(InkNodeTool *nt, Inkscape::UI::ControlPoint fill-column:99 End: */ -// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:encoding=utf-8:textwidth=99 : +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4:fileencoding=utf-8:textwidth=99 :