Code

Add entry fields for x/y coordinates of selected nodes (fixes bugs #170116 and #172113)
authorcilix42 <cilix42@users.sourceforge.net>
Wed, 5 Dec 2007 04:09:29 +0000 (04:09 +0000)
committercilix42 <cilix42@users.sourceforge.net>
Wed, 5 Dec 2007 04:09:29 +0000 (04:09 +0000)
src/node-context.cpp
src/nodepath.cpp
src/nodepath.h
src/widgets/toolbox.cpp

index 3e7903227c3c11d01bb8de5f483b15551fd013f1..1cac6f164651829e88a97815a351daf962d3ea99 100644 (file)
@@ -482,6 +482,13 @@ sp_node_context_root_handler(SPEventContext *event_context, GdkEvent *event)
                         ret = TRUE;
                     }
                     break;
+                case GDK_x:
+                case GDK_X:
+                    if (MOD__ALT_ONLY) {
+                        desktop->setToolboxFocusTo ("altx-nodes");
+                        ret = TRUE;
+                    }
+                    break;
                 case GDK_Left: // move selection left
                 case GDK_KP_Left:
                 case GDK_KP_4:
index 1239f961bc2f399f149b650b34f36bb5852dc4cd..4c0727d15fae3baf1c2fd69cc67b47713e53df3b 100644 (file)
@@ -1372,6 +1372,50 @@ sp_node_selected_move_screen(Inkscape::NodePath::Path *nodepath, gdouble dx, gdo
     }
 }
 
+/**
+ * Move selected nodes to the absolute position given
+ */
+void sp_node_selected_move_absolute(Inkscape::NodePath::Path *nodepath, NR::Coord val, NR::Dim2 axis)
+{
+    for (GList *l = nodepath->selected; l != NULL; l = l->next) {
+        Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
+        NR::Point npos(axis == NR::X ? val : n->pos[NR::X], axis == NR::Y ? val : n->pos[NR::Y]);
+        sp_node_moveto(n, npos);
+    }
+
+    sp_nodepath_update_repr(nodepath, _("Move nodes"));
+}
+
+/**
+ * If the coordinates of all selected nodes coincide, return the common coordinate; otherwise return NR::Nothing
+ */
+NR::Maybe<NR::Coord> sp_node_selected_common_coord (Inkscape::NodePath::Path *nodepath, NR::Dim2 axis)
+{
+    g_return_val_if_fail(nodepath->selected, NR::Nothing());
+
+    // determine coordinate of first selected node
+    GList *nsel = nodepath->selected;
+    Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) nsel->data;
+    NR::Coord coord = n->pos[axis];
+    bool coincide = true;
+
+    // compare it to the coordinates of all the other selected nodes
+    for (GList *l = nsel->next; l != NULL; l = l->next) {
+        n = (Inkscape::NodePath::Node *) l->data;
+        if (n->pos[axis] != coord) {
+            coincide = false;
+        }
+    }
+    if (coincide) {
+        return coord;
+    } else {
+        NR::Rect bbox = sp_node_selected_bbox(nodepath);
+        // currently we return the coordinate of the bounding box midpoint because I don't know how
+        // to erase the spin button entry field :), but maybe this can be useful behaviour anyway
+        return bbox.midpoint()[axis];
+    }
+}
+
 /** If they don't yet exist, creates knot and line for the given side of the node */
 static void sp_node_ensure_knot_exists (SPDesktop *desktop, Inkscape::NodePath::Node *node, Inkscape::NodePath::NodeSide *side)
 {
@@ -3933,13 +3977,7 @@ void sp_nodepath_flip (Inkscape::NodePath::Path *nodepath, NR::Dim2 axis, NR::Ma
     } else {
         // scale nodes as an "object":
 
-        Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
-        NR::Rect box (n0->pos, n0->pos); // originally includes the first selected node
-        for (GList *l = nodepath->selected; l != NULL; l = l->next) {
-            Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
-            box.expandTo (n->pos); // contain all selected nodes
-        }
-
+        NR::Rect box = sp_node_selected_bbox (nodepath);
         if (!center) {
             center = box.midpoint();
         }
@@ -3960,6 +3998,19 @@ void sp_nodepath_flip (Inkscape::NodePath::Path *nodepath, NR::Dim2 axis, NR::Ma
     sp_nodepath_update_repr(nodepath, _("Flip nodes"));
 }
 
+NR::Rect sp_node_selected_bbox (Inkscape::NodePath::Path *nodepath)
+{
+    g_assert (nodepath->selected);
+
+    Inkscape::NodePath::Node *n0 = (Inkscape::NodePath::Node *) nodepath->selected->data;
+    NR::Rect box (n0->pos, n0->pos); // originally includes the first selected node
+    for (GList *l = nodepath->selected; l != NULL; l = l->next) {
+        Inkscape::NodePath::Node *n = (Inkscape::NodePath::Node *) l->data;
+        box.expandTo (n->pos); // contain all selected nodes
+    }
+    return box;
+}
+
 //-----------------------------------------------
 /**
  * Return new subpath under given nodepath.
@@ -4392,6 +4443,8 @@ sp_nodepath_update_statusbar(Inkscape::NodePath::Path *nodepath)//!!!move to Sha
     Inkscape::MessageContext *mc = SP_NODE_CONTEXT (ec)->_node_message_context;
     if (!mc) return;
 
+    inkscape_active_desktop()->emitToolSubselectionChanged(NULL);
+
     if (selected_nodes == 0) {
         Inkscape::Selection *sel = desktop->selection;
         if (!sel || sel->isEmpty()) {
index ee828f4aca4d245a4a955c6b45e77c607b1f1de6..8f17ecb14b966a76596abfb54120f7912cf53dd1 100644 (file)
@@ -302,6 +302,9 @@ void sp_node_selected_set_type (Inkscape::NodePath::Path *nodepath, Inkscape::No
 void sp_node_selected_set_line_type (Inkscape::NodePath::Path *nodepath, NRPathcode code);
 void sp_node_selected_move (Inkscape::NodePath::Path *nodepath, gdouble dx, gdouble dy);
 void sp_node_selected_move_screen (Inkscape::NodePath::Path *nodepath, gdouble dx, gdouble dy);
+void sp_node_selected_move_absolute (Inkscape::NodePath::Path *nodepath, NR::Coord val, NR::Dim2 axis);
+NR::Rect sp_node_selected_bbox (Inkscape::NodePath::Path *nodepath);
+NR::Maybe<NR::Coord> sp_node_selected_common_coord (Inkscape::NodePath::Path *nodepath, NR::Dim2 axis);
 
 void sp_nodepath_show_handles(Inkscape::NodePath::Path *nodepath, bool show);
 
index 3609a48dd981ff6c1b92e33c1b803532699b707f..a4d24203f95bfae6fe2ab1f8adba82ba69379c5b 100644 (file)
@@ -242,6 +242,9 @@ static gchar const * ui_descr =
         "    <toolitem action='StrokeToPath' />"
         "    <separator />"
         "    <toolitem action='NodesShowHandlesAction' />"
+        "    <separator />"
+        "    <toolitem action='NodeXAction' />"
+        "    <toolitem action='NodeYAction' />"
         "  </toolbar>"
 
         "  <toolbar name='TweakToolbar'>"
@@ -686,6 +689,53 @@ sp_commands_toolbox_new()
     return hb;
 }
 
+static EgeAdjustmentAction * create_adjustment_action( gchar const *name,
+                                                       gchar const *label, gchar const *shortLabel, gchar const *tooltip,
+                                                       gchar const *path, gchar const *data, gdouble def,
+                                                       GtkWidget *focusTarget,
+                                                       GtkWidget *us,
+                                                       GObject *dataKludge,
+                                                       gboolean altx, gchar const *altx_mark,
+                                                       gdouble lower, gdouble upper, gdouble step, gdouble page,
+                                                       gchar const** descrLabels, gdouble const* descrValues, guint descrCount,
+                                                       void (*callback)(GtkAdjustment *, GObject *),
+                                                       gdouble climb = 0.1, guint digits = 3, double factor = 1.0 )
+{
+    GtkAdjustment* adj = GTK_ADJUSTMENT( gtk_adjustment_new( prefs_get_double_attribute(path, data, def) * factor,
+                                                             lower, upper, step, page, page ) );
+    if (us) {
+        sp_unit_selector_add_adjustment( SP_UNIT_SELECTOR(us), adj );
+    }
+
+    gtk_signal_connect( GTK_OBJECT(adj), "value-changed", GTK_SIGNAL_FUNC(callback), dataKludge );
+
+    EgeAdjustmentAction* act = ege_adjustment_action_new( adj, name, label, tooltip, 0, climb, digits );
+    if ( shortLabel ) {
+        g_object_set( act, "short_label", shortLabel, NULL );
+    }
+
+    if ( (descrCount > 0) && descrLabels && descrValues ) {
+        ege_adjustment_action_set_descriptions( act, descrLabels, descrValues, descrCount );
+    }
+
+    if ( focusTarget ) {
+        ege_adjustment_action_set_focuswidget( act, focusTarget );
+    }
+
+    if ( altx && altx_mark ) {
+        g_object_set( G_OBJECT(act), "self-id", altx_mark, NULL );
+    }
+
+    if ( dataKludge ) {
+        g_object_set_data( dataKludge, data, adj );
+    }
+
+    // Using a cast just to make sure we pass in the right kind of function pointer
+    g_object_set( G_OBJECT(act), "tool-post", static_cast<EgeWidgetFixup>(sp_set_font_size_smaller), NULL );
+
+    return act;
+}
+
 
 //####################################
 //# node editing callbacks
@@ -794,11 +844,104 @@ static void toggle_show_handles (GtkToggleAction *act, gpointer /*data*/) {
     if (shape_editor) shape_editor->show_handles(show);
 }
 
+/* is called when the node selection is modified */
+static void
+sp_node_toolbox_coord_changed(gpointer /*shape_editor*/, GObject *tbl)
+{
+    GtkAction* xact = GTK_ACTION( g_object_get_data( tbl, "nodes_x_action" ) );
+    GtkAction* yact = GTK_ACTION( g_object_get_data( tbl, "nodes_y_action" ) );
+    GtkAdjustment *xadj = ege_adjustment_action_get_adjustment(EGE_ADJUSTMENT_ACTION(xact));
+    GtkAdjustment *yadj = ege_adjustment_action_get_adjustment(EGE_ADJUSTMENT_ACTION(yact));
+
+    // quit if run by the attr_changed listener
+    if (g_object_get_data( tbl, "freeze" )) {
+        return;
+    }
+
+    // in turn, prevent listener from responding
+    g_object_set_data( tbl, "freeze", GINT_TO_POINTER(TRUE));
+
+    ShapeEditor *shape_editor = get_current_shape_editor();
+    if (shape_editor && shape_editor->has_nodepath()) {
+        Inkscape::NodePath::Path *nodepath = shape_editor->get_nodepath();
+        int n_selected = 0;
+        if (nodepath) {
+            n_selected = nodepath->numSelected();
+        }
+
+        if (n_selected == 0) {
+            gtk_action_set_sensitive(xact, FALSE);
+            gtk_action_set_sensitive(yact, FALSE);
+        } else {
+            gtk_action_set_sensitive(xact, TRUE);
+            gtk_action_set_sensitive(yact, TRUE);
+            NR::Coord oldx = gtk_adjustment_get_value(xadj);
+            NR::Coord oldy = gtk_adjustment_get_value(xadj);
+
+            if (n_selected == 1) {
+                NR::Point sel_node = nodepath->singleSelectedCoords();
+                if (oldx != sel_node[NR::X] || oldy != sel_node[NR::Y]) {
+                    gtk_adjustment_set_value(xadj, sel_node[NR::X]);
+                    gtk_adjustment_set_value(yadj, sel_node[NR::Y]);
+                }
+            } else {
+                NR::Maybe<NR::Coord> x = sp_node_selected_common_coord(nodepath, NR::X);
+                NR::Maybe<NR::Coord> y = sp_node_selected_common_coord(nodepath, NR::Y);
+                if ((x && ((*x) != oldx)) || (y && ((*y) != oldy))) {
+                    /* Note: Currently x and y will always have a value, even if the coordinates of the
+                       selected nodes don't coincide (in this case we use the coordinates of the center
+                       of the bounding box). So the entries are never set to zero. */
+                    gtk_adjustment_set_value(xadj, x ? (*x) : 0.0); // FIXME: Maybe we should clear the entry
+                    gtk_adjustment_set_value(yadj, y ? (*y) : 0.0); //        fields, not set them to zero.
+                }
+            }
+        }
+    }
+
+    g_object_set_data( tbl, "freeze", GINT_TO_POINTER(FALSE) );
+}
+
+static void
+sp_node_path_value_changed(GtkAdjustment *adj, GObject *tbl, gchar const *value_name)
+{
+    // quit if run by the attr_changed listener
+    if (g_object_get_data( tbl, "freeze" )) {
+        return;
+    }
+
+    // in turn, prevent listener from responding
+    g_object_set_data( tbl, "freeze", GINT_TO_POINTER(TRUE));
+
+    ShapeEditor *shape_editor = get_current_shape_editor();
+    if (shape_editor && shape_editor->has_nodepath()) {
+        if (!strcmp(value_name, "x")) {
+            sp_node_selected_move_absolute(shape_editor->get_nodepath(), gtk_adjustment_get_value(adj), NR::X);
+        }
+        if (!strcmp(value_name, "y")) {
+            sp_node_selected_move_absolute(shape_editor->get_nodepath(), gtk_adjustment_get_value(adj), NR::Y);
+        }
+    }
+
+    g_object_set_data( tbl, "freeze", GINT_TO_POINTER(FALSE) );
+}
+
+static void
+sp_node_path_x_value_changed(GtkAdjustment *adj, GObject *tbl)
+{
+    sp_node_path_value_changed(adj, tbl, "x");
+}
+
+static void
+sp_node_path_y_value_changed(GtkAdjustment *adj, GObject *tbl)
+{
+    sp_node_path_value_changed(adj, tbl, "y");
+}
+
 //################################
 //##    Node Editing Toolbox    ##
 //################################
 
-static void sp_node_toolbox_prep(SPDesktop *desktop, GtkActionGroup* mainActions, GObject* /*holder*/)
+static void sp_node_toolbox_prep(SPDesktop *desktop, GtkActionGroup* mainActions, GObject* holder)
 {
     {
         InkAction* inky = ink_action_new( "NodeInsertAction",
@@ -924,6 +1067,46 @@ static void sp_node_toolbox_prep(SPDesktop *desktop, GtkActionGroup* mainActions
         gtk_toggle_action_set_active( GTK_TOGGLE_ACTION(act), prefs_get_int_attribute( "tools.nodes", "show_handles", 1 ) );
     }
 
+    /* X coord of selected node(s) */
+    {
+        EgeAdjustmentAction* eact = 0;
+        gchar const* labels[] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+        gdouble values[] = {1, 2, 3, 5, 10, 20, 50, 100, 200, 500};
+        eact = create_adjustment_action( "NodeXAction",
+                                         _("X coordinate:"), _("X:"), _("X coordinate of selected node(s)"),
+                                         "tools.nodes", "Xcoord", 0,
+                                         GTK_WIDGET(desktop->canvas), NULL/*us*/, holder, TRUE, "altx-nodes",
+                                         -1e6, 1e6, SPIN_STEP, SPIN_PAGE_STEP,
+                                         labels, values, G_N_ELEMENTS(labels),
+                                         sp_node_path_x_value_changed );
+        g_object_set_data( holder, "nodes_x_action", eact );
+        gtk_action_set_sensitive( GTK_ACTION(eact), FALSE );
+        gtk_action_group_add_action( mainActions, GTK_ACTION(eact) );
+    }
+
+    /* Y coord of selected node(s) */
+    {
+        EgeAdjustmentAction* eact = 0;
+        gchar const* labels[] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+        gdouble values[] = {1, 2, 3, 5, 10, 20, 50, 100, 200, 500};
+        eact = create_adjustment_action( "NodeYAction",
+                                         _("Y coordinate:"), _("Y:"), _("Y coordinate of selected node(s)"),
+                                         "tools.nodes", "Ycoord", 0,
+                                         GTK_WIDGET(desktop->canvas), NULL/*us*/, holder, FALSE, NULL,
+                                         -1e6, 1e6, SPIN_STEP, SPIN_PAGE_STEP,
+                                         labels, values, G_N_ELEMENTS(labels),
+                                         sp_node_path_y_value_changed );
+        g_object_set_data( holder, "nodes_y_action", eact );
+        gtk_action_set_sensitive( GTK_ACTION(eact), FALSE );
+        gtk_action_group_add_action( mainActions, GTK_ACTION(eact) );
+    }
+
+    sigc::connection *connection = new sigc::connection (
+        desktop->connectToolSubselectionChanged(sigc::bind (sigc::ptr_fun(sp_node_toolbox_coord_changed), (GObject *)holder))
+        );
+
+    g_signal_connect( holder, "destroy", G_CALLBACK(delete_connection), connection );
+    g_signal_connect( holder, "destroy", G_CALLBACK(purge_repr_listener), holder );
 } // end of sp_node_toolbox_prep()
 
 
@@ -1277,54 +1460,6 @@ sp_tb_spinbutton(
     return hb;
 }
 
-static EgeAdjustmentAction * create_adjustment_action( gchar const *name,
-                                                       gchar const *label, gchar const *shortLabel, gchar const *tooltip,
-                                                       gchar const *path, gchar const *data, gdouble def,
-                                                       GtkWidget *focusTarget,
-                                                       GtkWidget *us,
-                                                       GObject *dataKludge,
-                                                       gboolean altx, gchar const *altx_mark,
-                                                       gdouble lower, gdouble upper, gdouble step, gdouble page,
-                                                       gchar const** descrLabels, gdouble const* descrValues, guint descrCount,
-                                                       void (*callback)(GtkAdjustment *, GObject *),
-                                                       gdouble climb = 0.1, guint digits = 3, double factor = 1.0 )
-{
-    GtkAdjustment* adj = GTK_ADJUSTMENT( gtk_adjustment_new( prefs_get_double_attribute(path, data, def) * factor,
-                                                             lower, upper, step, page, page ) );
-    if (us) {
-        sp_unit_selector_add_adjustment( SP_UNIT_SELECTOR(us), adj );
-    }
-
-    gtk_signal_connect( GTK_OBJECT(adj), "value-changed", GTK_SIGNAL_FUNC(callback), dataKludge );
-
-    EgeAdjustmentAction* act = ege_adjustment_action_new( adj, name, label, tooltip, 0, climb, digits );
-    if ( shortLabel ) {
-        g_object_set( act, "short_label", shortLabel, NULL );
-    }
-
-    if ( (descrCount > 0) && descrLabels && descrValues ) {
-        ege_adjustment_action_set_descriptions( act, descrLabels, descrValues, descrCount );
-    }
-
-    if ( focusTarget ) {
-        ege_adjustment_action_set_focuswidget( act, focusTarget );
-    }
-
-    if ( altx && altx_mark ) {
-        g_object_set( G_OBJECT(act), "self-id", altx_mark, NULL );
-    }
-
-    if ( dataKludge ) {
-        g_object_set_data( dataKludge, data, adj );
-    }
-
-    // Using a cast just to make sure we pass in the right kind of function pointer
-    g_object_set( G_OBJECT(act), "tool-post", static_cast<EgeWidgetFixup>(sp_set_font_size_smaller), NULL );
-
-    return act;
-}
-
-
 #define MODE_LABEL_WIDTH 70
 
 //########################