X-Git-Url: https://git.tokkee.org/?a=blobdiff_plain;f=src%2Fui%2Ftool%2Fnode-tool.cpp;h=8661e79465dbf13ef75c43501da10230c1d3eb5c;hb=030609cb99174ea85e69635c494ccaaaa20b2ac5;hp=735ddf87ef61c97a8f2f4a3ca7d227ea7e59a59c;hpb=285b96e08e3f8ac1c5969281940b334182b9431b;p=inkscape.git diff --git a/src/ui/tool/node-tool.cpp b/src/ui/tool/node-tool.cpp index 735ddf87e..8661e7946 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 @@ -25,6 +26,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" @@ -62,13 +64,26 @@ * 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. Performs actions that require no + * - 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, @@ -161,6 +176,7 @@ void ink_node_tool_init(InkNodeTool *nt) 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) @@ -175,6 +191,7 @@ void ink_node_tool_dispose(GObject *object) 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); @@ -192,10 +209,6 @@ void ink_node_tool_dispose(GObject *object) 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); } @@ -277,12 +290,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"); @@ -309,10 +323,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); @@ -345,11 +366,12 @@ void gather_items(InkNodeTool *nt, SPItem *base, SPObject *obj, Inkscape::UI::Sh using namespace Inkscape::UI; if (!obj) return; - if (SP_IS_PATH(obj) && obj->repr->attribute("inkscape:original-d") != NULL) { + //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 = SHAPE_ROLE_LPE_PARAM; + 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) { @@ -360,7 +382,7 @@ void gather_items(InkNodeTool *nt, SPItem *base, SPObject *obj, Inkscape::UI::Sh ShapeRecord r; r.item = item; // TODO add support for objectBoundingBox - r.edit_transform = base ? sp_item_i2doc_affine(base) : Geom::identity(); + r.edit_transform = base ? base->i2doc_affine() : Geom::identity(); r.role = role; if (s.insert(r).second) { // this item was encountered the first time @@ -374,19 +396,12 @@ void gather_items(InkNodeTool *nt, SPItem *base, SPObject *obj, Inkscape::UI::Sh } } -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; std::set shapes; - // TODO this is ugly!!! - //typedef std::map > TransMap; - //typedef std::map > PathMap; GSList const *ilist = sel->itemList(); for (GSList *i = const_cast(ilist); i; i = i->next) { @@ -396,20 +411,30 @@ void ink_node_tool_selection_changed(InkNodeTool *nt, Inkscape::Selection *sel) } } - // 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; + // 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; + } + } + for (std::set::iterator i = shapes.begin(); i != shapes.end(); ++i) { ShapeRecord const &r = *i; - if (SP_IS_SHAPE(r.item) && !SP_IS_PATH(r.item)) { - nt->shape_editor->set_item(r.item, SH_KNOTHOLDER); - something_set = true; - break; + 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); } } - if (!something_set) { - nt->shape_editor->unset_item(SH_KNOTHOLDER); - } nt->_multipath->setItems(shapes); ink_node_tool_update_tip(nt, NULL); @@ -436,11 +461,17 @@ 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")) { - 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) { @@ -452,7 +483,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, @@ -462,7 +493,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)) { @@ -475,18 +508,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; } @@ -511,26 +548,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")); + } } } @@ -549,15 +614,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; @@ -570,17 +635,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) @@ -613,4 +684,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 :