[network-manager-applet] editor: improve usability for entering manual IP addresses and routes (rh #698199) (bgo #607678)



commit 57ebc568daa3a4ee3cd7bbb85fddc69ffee8d424
Author: JiÅ?í KlimeÅ¡ <jklimes redhat com>
Date:   Fri Apr 29 06:20:55 2011 -0400

    editor: improve usability for entering manual IP addresses and routes (rh #698199) (bgo #607678)
    
    GtkTreeView used for editing static IP addresses and routes didn't behave and
    was not user-friendly. Specifically, it didn't change cell on Tab key, but
    users expected that. Moreover, things went much worse with GTK3 that changes
    behaviour of GtkTreeView. Pressing Tab key left the cell but *didn't* save the
    value. Neither did mouse click. Thus the only way to store values was to
    confirm them by pressing Enter.
    Despite differences in GtkTreeView behaviour of GTK2 and GTK3, this commit
    manages to implement expected/desired behaviour for both versions. Summary:
    - Tab key can be used to cycle through cells (as well as Up/Down arrows and Enter)
    - mouse click can be used to switch between cells
    - Esc can be used to leave cell (as well as clicking another part of treeview
      or another widget)
    - the new edited value is stored on all ways of leaving the cell

 src/connection-editor/ip4-routes-dialog.c |  160 ++++++++++++++++++++++++++++-
 src/connection-editor/ip6-routes-dialog.c |  160 ++++++++++++++++++++++++++++-
 src/connection-editor/page-ip4.c          |  102 ++++++++++++++++++-
 src/connection-editor/page-ip6.c          |  102 ++++++++++++++++++-
 4 files changed, 516 insertions(+), 8 deletions(-)
---
diff --git a/src/connection-editor/ip4-routes-dialog.c b/src/connection-editor/ip4-routes-dialog.c
index 2117482..42376c1 100644
--- a/src/connection-editor/ip4-routes-dialog.c
+++ b/src/connection-editor/ip4-routes-dialog.c
@@ -31,6 +31,7 @@
 #include <string.h>
 
 #include <glib/gi18n.h>
+#include <gdk/gdkkeysyms.h>
 
 #include <nm-utils.h>
 
@@ -42,6 +43,12 @@
 #define COL_METRIC  3
 #define COL_LAST COL_METRIC
 
+/* Variables to temporarily save last edited cell value
+ * from routes treeview (cancelling issues) */
+static char *last_edited = NULL; /* cell text */
+static char *last_path = NULL;   /* row in treeview */
+static int last_column = -1;     /* column in treeview */
+
 static gboolean
 get_one_int (GtkTreeModel *model,
              GtkTreeIter *iter,
@@ -273,6 +280,33 @@ list_selection_changed (GtkTreeSelection *selection, gpointer user_data)
 }
 
 static void
+cell_editing_canceled (GtkCellRenderer *renderer, gpointer user_data)
+{
+	GtkBuilder *builder = GTK_BUILDER (user_data);
+	GtkTreeModel *model = NULL;
+	GtkTreeSelection *selection;
+	GtkTreeIter iter;
+	guint32 column;
+
+	if (last_edited) {
+		selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (gtk_builder_get_object (builder, "ip4_routes")));
+		if (gtk_tree_selection_get_selected (selection, &model, &iter)) {
+			column = GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (renderer), "column"));
+			gtk_list_store_set (GTK_LIST_STORE (model), &iter, column, last_edited, -1);
+		}
+
+		g_free (last_edited);
+		last_edited = NULL;
+	}
+
+	g_free (last_path);
+	last_path = NULL;
+	last_column = -1;
+
+	validate (GTK_WIDGET (gtk_builder_get_object (builder, "ip4_routes_dialog")));
+}
+
+static void
 cell_edited (GtkCellRendererText *cell,
              const gchar *path_string,
              const gchar *new_text,
@@ -287,6 +321,13 @@ cell_edited (GtkCellRendererText *cell,
 	GtkTreeViewColumn *next_col;
 	GtkCellRenderer *next_cell;
 
+	/* Free auxiliary stuff */
+	g_free (last_edited);
+	last_edited = NULL;
+	g_free (last_path);
+	last_path = NULL;
+	last_column = -1;
+
 	widget = GTK_WIDGET (gtk_builder_get_object (builder, "ip4_routes"));
 	store = GTK_LIST_STORE (gtk_tree_view_get_model (GTK_TREE_VIEW (widget)));
 	path = gtk_tree_path_new_from_string (path_string);
@@ -302,7 +343,6 @@ cell_edited (GtkCellRendererText *cell,
 	next_cell = g_slist_nth_data (g_object_get_data (G_OBJECT (dialog), "renderers"), column);
 
 	gtk_tree_view_set_cursor_on_cell (GTK_TREE_VIEW (widget), path, next_col, next_cell, TRUE);
-	gtk_widget_grab_focus (widget);
 
 	gtk_tree_path_free (path);
 
@@ -333,6 +373,8 @@ ip_address_filter_cb (GtkEntry *   entry,
 		                                 G_CALLBACK (ip_address_filter_cb),
 		                                 user_data);
 		gtk_editable_insert_text (editable, result, count, position);
+		g_free (last_edited);
+		last_edited = g_strdup (gtk_editable_get_chars (editable, 0, -1));
 		g_signal_handlers_unblock_by_func (G_OBJECT (editable),
 		                                   G_CALLBACK (ip_address_filter_cb),
 		                                   user_data);
@@ -349,6 +391,41 @@ ip_address_filter_cb (GtkEntry *   entry,
 }
 
 static void
+delete_text_cb (GtkEditable *editable,
+                gint start_pos,
+                gint end_pos,
+                gpointer user_data)
+{
+	GtkWidget *ok_button = user_data;
+
+	/* Keep last_edited up-to-date */
+	g_free (last_edited);
+	last_edited = g_strdup (gtk_editable_get_chars (editable, 0, -1));
+
+	/* Desensitize the OK button during input to simplify input validation.
+	 * All routes will be validated on focus-out, which will then re-enable
+	 * the OK button if the routes are valid.
+	 */
+	gtk_widget_set_sensitive (ok_button, FALSE);
+}
+
+static gboolean
+key_pressed_cb (GtkWidget *widget,
+                GdkEvent *event,
+                gpointer user_data)
+{
+#if !GDK_KEY_Tab
+	#define GDK_KEY_Tab GDK_Tab
+#endif
+
+	/* Tab should behave the same way as Enter (finish editing) */
+	if (event->type == GDK_KEY_PRESS && event->key.keyval == GDK_KEY_Tab)
+		gtk_cell_editable_editing_done (GTK_CELL_EDITABLE (widget));
+
+	return FALSE;
+}
+
+static void
 ip4_cell_editing_started (GtkCellRenderer *cell,
                           GtkCellEditable *editable,
                           const gchar     *path,
@@ -359,10 +436,26 @@ ip4_cell_editing_started (GtkCellRenderer *cell,
 		return;
 	}
 
+	/* Initialize last_path and last_column, last_edited is initialized when the cell is edited */
+	g_free (last_edited);
+	last_edited = NULL;
+	g_free (last_path);
+	last_path = g_strdup (path);
+	last_column = GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (cell), "column"));
+
 	/* Set up the entry filter */
 	g_signal_connect (G_OBJECT (editable), "insert-text",
 	                  (GCallback) ip_address_filter_cb,
 	                  user_data);
+
+	g_signal_connect_after (G_OBJECT (editable), "delete-text",
+	                        (GCallback) delete_text_cb,
+	                        user_data);
+
+	/* Set up key pressed handler - need to handle Tab key */
+	g_signal_connect (G_OBJECT (editable), "key-press-event",
+	                  (GCallback) key_pressed_cb,
+	                  user_data);
 }
 
 static void
@@ -387,6 +480,8 @@ uint_filter_cb (GtkEntry *   entry,
 		                                 G_CALLBACK (uint_filter_cb),
 		                                 user_data);
 		gtk_editable_insert_text (editable, result, count, position);
+		g_free (last_edited);
+		last_edited = g_strdup (gtk_editable_get_chars (editable, 0, -1));
 		g_signal_handlers_unblock_by_func (G_OBJECT (editable),
 		                                   G_CALLBACK (uint_filter_cb),
 		                                   user_data);
@@ -413,10 +508,61 @@ uint_cell_editing_started (GtkCellRenderer *cell,
 		return;
 	}
 
+	/* Initialize last_path and last_column, last_edited is initialized when the cell is edited */
+	g_free (last_edited);
+	last_edited = NULL;
+	g_free (last_path);
+	last_path = g_strdup (path);
+	last_column = GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (cell), "column"));
+
 	/* Set up the entry filter */
 	g_signal_connect (G_OBJECT (editable), "insert-text",
 	                  (GCallback) uint_filter_cb,
 	                  user_data);
+
+	g_signal_connect_after (G_OBJECT (editable), "delete-text",
+	                        (GCallback) delete_text_cb,
+	                        user_data);
+
+	/* Set up key pressed handler - need to handle Tab key */
+	g_signal_connect (G_OBJECT (editable), "key-press-event",
+	                  (GCallback) key_pressed_cb,
+	                  user_data);
+}
+
+static gboolean
+tree_view_button_pressed_cb (GtkWidget *widget,
+                             GdkEvent *event,
+                             gpointer user_data)
+{
+	GtkBuilder *builder = GTK_BUILDER (user_data);
+
+	/* last_edited can be set e.g. when we get here by clicking an cell while
+	 * editing another cell. GTK3 issue neither editing-canceled nor editing-done
+	 * for cell renderer. Thus the previous cell value isn't saved. Store it now. */
+	if (last_edited && last_path) {
+		GtkTreeIter iter;
+		GtkListStore *store = GTK_LIST_STORE (gtk_tree_view_get_model (GTK_TREE_VIEW (widget)));
+		GtkTreePath *last_treepath = gtk_tree_path_new_from_string (last_path);
+
+		gtk_tree_model_get_iter (GTK_TREE_MODEL (store), &iter, last_treepath);
+		gtk_list_store_set (store, &iter, last_column, last_edited, -1);
+		gtk_tree_path_free (last_treepath);
+
+		g_free (last_edited);
+		last_edited = NULL;
+		g_free (last_path);
+		last_path = NULL;
+		last_column = -1;
+	}
+
+	/* Ignore double clicks events. (They are issued after the single clicks, see GdkEventButton) */
+	if (event->type == GDK_2BUTTON_PRESS)
+		return TRUE;
+
+	gtk_widget_grab_focus (GTK_WIDGET (widget));
+	validate (GTK_WIDGET (gtk_builder_get_object (builder, "ip4_routes_dialog")));
+	return FALSE;
 }
 
 GtkWidget *
@@ -434,6 +580,13 @@ ip4_routes_dialog_new (NMSettingIP4Config *s_ip4, gboolean automatic)
 	GSList *renderers = NULL;
 	GError* error = NULL;
 
+	/* Initialize temporary storage vars */
+	g_free (last_edited);
+	last_edited = NULL;
+	last_path = NULL;
+	g_free (last_path);
+	last_column = -1;
+
 	builder = gtk_builder_new ();
 
 	if (!gtk_builder_add_from_file (builder, UIDIR "/ce-ip4-routes.ui", &error)) {
@@ -501,6 +654,7 @@ ip4_routes_dialog_new (NMSettingIP4Config *s_ip4, gboolean automatic)
 	g_signal_connect (renderer, "edited", G_CALLBACK (cell_edited), builder);
 	g_object_set_data (G_OBJECT (renderer), "column", GUINT_TO_POINTER (COL_ADDRESS));
 	g_signal_connect (renderer, "editing-started", G_CALLBACK (ip4_cell_editing_started), ok_button);
+	g_signal_connect (renderer, "editing-canceled", G_CALLBACK (cell_editing_canceled), builder);
 	renderers = g_slist_append (renderers, renderer);
 
 	offset = gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (widget),
@@ -517,6 +671,7 @@ ip4_routes_dialog_new (NMSettingIP4Config *s_ip4, gboolean automatic)
 	g_signal_connect (renderer, "edited", G_CALLBACK (cell_edited), builder);
 	g_object_set_data (G_OBJECT (renderer), "column", GUINT_TO_POINTER (COL_PREFIX));
 	g_signal_connect (renderer, "editing-started", G_CALLBACK (ip4_cell_editing_started), ok_button);
+	g_signal_connect (renderer, "editing-canceled", G_CALLBACK (cell_editing_canceled), builder);
 	renderers = g_slist_append (renderers, renderer);
 
 	offset = gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (widget),
@@ -533,6 +688,7 @@ ip4_routes_dialog_new (NMSettingIP4Config *s_ip4, gboolean automatic)
 	g_signal_connect (renderer, "edited", G_CALLBACK (cell_edited), builder);
 	g_object_set_data (G_OBJECT (renderer), "column", GUINT_TO_POINTER (COL_NEXT_HOP));
 	g_signal_connect (renderer, "editing-started", G_CALLBACK (ip4_cell_editing_started), ok_button);
+	g_signal_connect (renderer, "editing-canceled", G_CALLBACK (cell_editing_canceled), builder);
 	renderers = g_slist_append (renderers, renderer);
 
 	offset = gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (widget),
@@ -549,6 +705,7 @@ ip4_routes_dialog_new (NMSettingIP4Config *s_ip4, gboolean automatic)
 	g_signal_connect (renderer, "edited", G_CALLBACK (cell_edited), builder);
 	g_object_set_data (G_OBJECT (renderer), "column", GUINT_TO_POINTER (COL_METRIC));
 	g_signal_connect (renderer, "editing-started", G_CALLBACK (uint_cell_editing_started), ok_button);
+	g_signal_connect (renderer, "editing-canceled", G_CALLBACK (cell_editing_canceled), builder);
 	renderers = g_slist_append (renderers, renderer);
 
 	offset = gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (widget),
@@ -565,6 +722,7 @@ ip4_routes_dialog_new (NMSettingIP4Config *s_ip4, gboolean automatic)
 	g_signal_connect (selection, "changed",
 	                  G_CALLBACK (list_selection_changed),
 	                  GTK_WIDGET (gtk_builder_get_object (builder, "ip4_route_delete_button")));
+	g_signal_connect (widget, "button-press-event", G_CALLBACK (tree_view_button_pressed_cb), builder);
 
 	widget = GTK_WIDGET (gtk_builder_get_object (builder, "ip4_route_add_button"));
 	gtk_widget_set_sensitive (widget, TRUE);
diff --git a/src/connection-editor/ip6-routes-dialog.c b/src/connection-editor/ip6-routes-dialog.c
index a8456bd..a24de4d 100644
--- a/src/connection-editor/ip6-routes-dialog.c
+++ b/src/connection-editor/ip6-routes-dialog.c
@@ -31,6 +31,7 @@
 #include <string.h>
 
 #include <glib/gi18n.h>
+#include <gdk/gdkkeysyms.h>
 
 #include <nm-utils.h>
 
@@ -42,6 +43,12 @@
 #define COL_METRIC  3
 #define COL_LAST COL_METRIC
 
+/* Variables to temporarily save last edited cell value
+ * from routes treeview (cancelling issues) */
+static char *last_edited = NULL; /* cell text */
+static char *last_path = NULL;   /* row in treeview */
+static int last_column = -1;     /* column in treeview */
+
 static gboolean
 get_one_int (GtkTreeModel *model,
              GtkTreeIter *iter,
@@ -230,6 +237,33 @@ list_selection_changed (GtkTreeSelection *selection, gpointer user_data)
 }
 
 static void
+cell_editing_canceled (GtkCellRenderer *renderer, gpointer user_data)
+{
+	GtkBuilder *builder = GTK_BUILDER (user_data);
+	GtkTreeModel *model = NULL;
+	GtkTreeSelection *selection;
+	GtkTreeIter iter;
+	guint32 column;
+
+	if (last_edited) {
+		selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (gtk_builder_get_object (builder, "ip6_routes")));
+		if (gtk_tree_selection_get_selected (selection, &model, &iter)) {
+			column = GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (renderer), "column"));
+			gtk_list_store_set (GTK_LIST_STORE (model), &iter, column, last_edited, -1);
+		}
+
+		g_free (last_edited);
+		last_edited = NULL;
+	}
+
+	g_free (last_path);
+	last_path = NULL;
+	last_column = -1;
+
+	validate (GTK_WIDGET (gtk_builder_get_object (builder, "ip6_routes_dialog")));
+}
+
+static void
 cell_edited (GtkCellRendererText *cell,
              const gchar *path_string,
              const gchar *new_text,
@@ -244,6 +278,13 @@ cell_edited (GtkCellRendererText *cell,
 	GtkTreeViewColumn *next_col;
 	GtkCellRenderer *next_cell;
 
+	/* Free auxiliary stuff */
+	g_free (last_edited);
+	last_edited = NULL;
+	g_free (last_path);
+	last_path = NULL;
+	last_column = -1;
+
 	widget = GTK_WIDGET (gtk_builder_get_object (builder, "ip6_routes"));
 	store = GTK_LIST_STORE (gtk_tree_view_get_model (GTK_TREE_VIEW (widget)));
 	path = gtk_tree_path_new_from_string (path_string);
@@ -259,7 +300,6 @@ cell_edited (GtkCellRendererText *cell,
 	next_cell = g_slist_nth_data (g_object_get_data (G_OBJECT (dialog), "renderers"), column);
 
 	gtk_tree_view_set_cursor_on_cell (GTK_TREE_VIEW (widget), path, next_col, next_cell, TRUE);
-	gtk_widget_grab_focus (widget);
 
 	gtk_tree_path_free (path);
 
@@ -290,6 +330,8 @@ ip_address_filter_cb (GtkEntry *   entry,
 		                                 G_CALLBACK (ip_address_filter_cb),
 		                                 user_data);
 		gtk_editable_insert_text (editable, result, count, position);
+		g_free (last_edited);
+		last_edited = g_strdup (gtk_editable_get_chars (editable, 0, -1));
 		g_signal_handlers_unblock_by_func (G_OBJECT (editable),
 		                                   G_CALLBACK (ip_address_filter_cb),
 		                                   user_data);
@@ -306,6 +348,41 @@ ip_address_filter_cb (GtkEntry *   entry,
 }
 
 static void
+delete_text_cb (GtkEditable *editable,
+                gint start_pos,
+                gint end_pos,
+                gpointer user_data)
+{
+	GtkWidget *ok_button = user_data;
+
+	/* Keep last_edited up-to-date */
+	g_free (last_edited);
+	last_edited = g_strdup (gtk_editable_get_chars (editable, 0, -1));
+
+	/* Desensitize the OK button during input to simplify input validation.
+	 * All routes will be validated on focus-out, which will then re-enable
+	 * the OK button if the routes are valid.
+	 */
+	gtk_widget_set_sensitive (ok_button, FALSE);
+}
+
+static gboolean
+key_pressed_cb (GtkWidget *widget,
+                GdkEvent *event,
+                gpointer user_data)
+{
+#if !GDK_KEY_Tab
+	#define GDK_KEY_Tab GDK_Tab
+#endif
+
+	/* Tab should behave the same way as Enter (finish editing) */
+	if (event->type == GDK_KEY_PRESS && event->key.keyval == GDK_KEY_Tab)
+		gtk_cell_editable_editing_done (GTK_CELL_EDITABLE (widget));
+
+	return FALSE;
+}
+
+static void
 ip6_cell_editing_started (GtkCellRenderer *cell,
                           GtkCellEditable *editable,
                           const gchar     *path,
@@ -316,10 +393,26 @@ ip6_cell_editing_started (GtkCellRenderer *cell,
 		return;
 	}
 
+	/* Initialize last_path and last_column, last_edited is initialized when the cell is edited */
+	g_free (last_edited);
+	last_edited = NULL;
+	g_free (last_path);
+	last_path = g_strdup (path);
+	last_column = GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (cell), "column"));
+
 	/* Set up the entry filter */
 	g_signal_connect (G_OBJECT (editable), "insert-text",
 	                  (GCallback) ip_address_filter_cb,
 	                  user_data);
+
+	g_signal_connect_after (G_OBJECT (editable), "delete-text",
+	                        (GCallback) delete_text_cb,
+	                        user_data);
+
+	/* Set up key pressed handler - need to handle Tab key */
+	g_signal_connect (G_OBJECT (editable), "key-press-event",
+	                  (GCallback) key_pressed_cb,
+	                  user_data);
 }
 
 static void
@@ -344,6 +437,8 @@ uint_filter_cb (GtkEntry *   entry,
 		                                 G_CALLBACK (uint_filter_cb),
 		                                 user_data);
 		gtk_editable_insert_text (editable, result, count, position);
+		g_free (last_edited);
+		last_edited = g_strdup (gtk_editable_get_chars (editable, 0, -1));
 		g_signal_handlers_unblock_by_func (G_OBJECT (editable),
 		                                   G_CALLBACK (uint_filter_cb),
 		                                   user_data);
@@ -370,10 +465,61 @@ uint_cell_editing_started (GtkCellRenderer *cell,
 		return;
 	}
 
+	/* Initialize last_path and last_column, last_edited is initialized when the cell is edited */
+	g_free (last_edited);
+	last_edited = NULL;
+	g_free (last_path);
+	last_path = g_strdup (path);
+	last_column = GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (cell), "column"));
+
 	/* Set up the entry filter */
 	g_signal_connect (G_OBJECT (editable), "insert-text",
 	                  (GCallback) uint_filter_cb,
 	                  user_data);
+
+	g_signal_connect_after (G_OBJECT (editable), "delete-text",
+	                        (GCallback) delete_text_cb,
+	                        user_data);
+
+	/* Set up key pressed handler - need to handle Tab key */
+	g_signal_connect (G_OBJECT (editable), "key-press-event",
+	                  (GCallback) key_pressed_cb,
+	                  user_data);
+}
+
+static gboolean
+tree_view_button_pressed_cb (GtkWidget *widget,
+                             GdkEvent *event,
+                             gpointer user_data)
+{
+	GtkBuilder *builder = GTK_BUILDER (user_data);
+
+	/* last_edited can be set e.g. when we get here by clicking an cell while
+	 * editing another cell. GTK3 issue neither editing-canceled nor editing-done
+	 * for cell renderer. Thus the previous cell value isn't saved. Store it now. */
+	if (last_edited && last_path) {
+		GtkTreeIter iter;
+		GtkListStore *store = GTK_LIST_STORE (gtk_tree_view_get_model (GTK_TREE_VIEW (widget)));
+		GtkTreePath *last_treepath = gtk_tree_path_new_from_string (last_path);
+
+		gtk_tree_model_get_iter (GTK_TREE_MODEL (store), &iter, last_treepath);
+		gtk_list_store_set (store, &iter, last_column, last_edited, -1);
+		gtk_tree_path_free (last_treepath);
+
+		g_free (last_edited);
+		last_edited = NULL;
+		g_free (last_path);
+		last_path = NULL;
+		last_column = -1;
+	}
+
+	/* Ignore double clicks events. (They are issued after the single clicks, see GdkEventButton) */
+	if (event->type == GDK_2BUTTON_PRESS)
+		return TRUE;
+
+	gtk_widget_grab_focus (GTK_WIDGET (widget));
+	validate (GTK_WIDGET (gtk_builder_get_object (builder, "ip6_routes_dialog")));
+	return FALSE;
 }
 
 GtkWidget *
@@ -391,6 +537,13 @@ ip6_routes_dialog_new (NMSettingIP6Config *s_ip6, gboolean automatic)
 	GSList *renderers = NULL;
 	GError* error = NULL;
 
+	/* Initialize temporary storage vars */
+	g_free (last_edited);
+	last_edited = NULL;
+	last_path = NULL;
+	g_free (last_path);
+	last_column = -1;
+
 	builder = gtk_builder_new ();
 
 	if (!gtk_builder_add_from_file (builder, UIDIR "/ce-ip6-routes.ui", &error)) {
@@ -459,6 +612,7 @@ ip6_routes_dialog_new (NMSettingIP6Config *s_ip6, gboolean automatic)
 	g_signal_connect (renderer, "edited", G_CALLBACK (cell_edited), builder);
 	g_object_set_data (G_OBJECT (renderer), "column", GUINT_TO_POINTER (COL_ADDRESS));
 	g_signal_connect (renderer, "editing-started", G_CALLBACK (ip6_cell_editing_started), ok_button);
+	g_signal_connect (renderer, "editing-canceled", G_CALLBACK (cell_editing_canceled), builder);
 	renderers = g_slist_append (renderers, renderer);
 
 	offset = gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (widget),
@@ -475,6 +629,7 @@ ip6_routes_dialog_new (NMSettingIP6Config *s_ip6, gboolean automatic)
 	g_signal_connect (renderer, "edited", G_CALLBACK (cell_edited), builder);
 	g_object_set_data (G_OBJECT (renderer), "column", GUINT_TO_POINTER (COL_PREFIX));
 	g_signal_connect (renderer, "editing-started", G_CALLBACK (uint_cell_editing_started), ok_button);
+	g_signal_connect (renderer, "editing-canceled", G_CALLBACK (cell_editing_canceled), builder);
 	renderers = g_slist_append (renderers, renderer);
 
 	offset = gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (widget),
@@ -491,6 +646,7 @@ ip6_routes_dialog_new (NMSettingIP6Config *s_ip6, gboolean automatic)
 	g_signal_connect (renderer, "edited", G_CALLBACK (cell_edited), builder);
 	g_object_set_data (G_OBJECT (renderer), "column", GUINT_TO_POINTER (COL_NEXT_HOP));
 	g_signal_connect (renderer, "editing-started", G_CALLBACK (ip6_cell_editing_started), ok_button);
+	g_signal_connect (renderer, "editing-canceled", G_CALLBACK (cell_editing_canceled), builder);
 	renderers = g_slist_append (renderers, renderer);
 
 	offset = gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (widget),
@@ -507,6 +663,7 @@ ip6_routes_dialog_new (NMSettingIP6Config *s_ip6, gboolean automatic)
 	g_signal_connect (renderer, "edited", G_CALLBACK (cell_edited), builder);
 	g_object_set_data (G_OBJECT (renderer), "column", GUINT_TO_POINTER (COL_METRIC));
 	g_signal_connect (renderer, "editing-started", G_CALLBACK (uint_cell_editing_started), ok_button);
+	g_signal_connect (renderer, "editing-canceled", G_CALLBACK (cell_editing_canceled), builder);
 	renderers = g_slist_append (renderers, renderer);
 
 	offset = gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (widget),
@@ -523,6 +680,7 @@ ip6_routes_dialog_new (NMSettingIP6Config *s_ip6, gboolean automatic)
 	g_signal_connect (selection, "changed",
 	                  G_CALLBACK (list_selection_changed),
 	                  GTK_WIDGET (gtk_builder_get_object (builder, "ip6_route_delete_button")));
+	g_signal_connect (widget, "button-press-event", G_CALLBACK (tree_view_button_pressed_cb), builder);
 
 	widget = GTK_WIDGET (gtk_builder_get_object (builder, "ip6_route_add_button"));
 	gtk_widget_set_sensitive (widget, TRUE);
diff --git a/src/connection-editor/page-ip4.c b/src/connection-editor/page-ip4.c
index 7783db5..1140093 100644
--- a/src/connection-editor/page-ip4.c
+++ b/src/connection-editor/page-ip4.c
@@ -17,7 +17,7 @@
  * with this program; if not, write to the Free Software Foundation, Inc.,
  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  *
- * (C) Copyright 2008 - 2010 Red Hat, Inc.
+ * (C) Copyright 2008 - 2011 Red Hat, Inc.
  */
 
 #include "config.h"
@@ -27,6 +27,7 @@
 #include <stdlib.h>
 
 #include <gtk/gtk.h>
+#include <gdk/gdkkeysyms.h>
 #include <glib/gi18n.h>
 #include <sys/socket.h>
 #include <netinet/in.h>
@@ -91,7 +92,12 @@ typedef struct {
 	gboolean window_added;
 
 	/* Cached tree view entry for editing-canceled */
-	char *last_edited;
+	/* Used also for saving old value when switching between cells via mouse
+	 * clicks - GTK3 produces neither editing-canceled nor editing-done for
+	 * that :( */
+	char *last_edited; /* cell text */
+	char *last_path;   /* row in treeview */
+	int last_column;   /* column in treeview */
 } CEPageIP4Private;
 
 #define METHOD_COL_NAME 0
@@ -535,6 +541,10 @@ cell_editing_canceled (GtkCellRenderer *renderer, gpointer user_data)
 
 		ce_page_changed (CE_PAGE (self));
 	}
+
+	g_free (priv->last_path);
+	priv->last_path = NULL;
+	priv->last_column = -1;
 }
 
 static void
@@ -551,8 +561,12 @@ cell_edited (GtkCellRendererText *cell,
 	guint32 column;
 	GtkTreeViewColumn *next_col;
 
+	/* Free auxiliary stuff */
 	g_free (priv->last_edited);
 	priv->last_edited = NULL;
+	g_free (priv->last_path);
+	priv->last_path = NULL;
+	priv->last_column = -1;
 
 	column = GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (cell), "column"));
 	gtk_tree_model_get_iter (GTK_TREE_MODEL (store), &iter, path);
@@ -583,7 +597,6 @@ cell_edited (GtkCellRendererText *cell,
 	column = (column >= COL_LAST) ? 0 : column + 1;
 	next_col = gtk_tree_view_get_column (priv->addr_list, column);
 	gtk_tree_view_set_cursor_on_cell (priv->addr_list, path, next_col, priv->addr_cells[column], TRUE);
-	gtk_widget_grab_focus (GTK_WIDGET (priv->addr_list));
 
 	gtk_tree_path_free (path);
 	ce_page_changed (CE_PAGE (self));
@@ -626,6 +639,36 @@ ip_address_filter_cb (GtkEntry *   entry,
 }
 
 static void
+delete_text_cb (GtkEditable *editable,
+                    gint start_pos,
+                    gint end_pos,
+                    gpointer user_data)
+{
+	CEPageIP4 *self = CE_PAGE_IP4 (user_data);
+	CEPageIP4Private *priv = CE_PAGE_IP4_GET_PRIVATE (self);
+
+	/* Keep last_edited up-to-date */
+	g_free (priv->last_edited);
+	priv->last_edited = g_strdup (gtk_editable_get_chars (editable, 0, -1));
+}
+
+static gboolean
+key_pressed_cb (GtkWidget *widget,
+                GdkEvent *event,
+                gpointer user_data)
+{
+#if !GDK_KEY_Tab
+	#define GDK_KEY_Tab GDK_Tab
+#endif
+
+	/* Tab should behave the same way as Enter (finish editing) */
+	if (event->type == GDK_KEY_PRESS && event->key.keyval == GDK_KEY_Tab)
+		gtk_cell_editable_editing_done (GTK_CELL_EDITABLE (widget));
+
+	return FALSE;
+}
+
+static void
 cell_editing_started (GtkCellRenderer *cell,
                       GtkCellEditable *editable,
                       const gchar     *path,
@@ -639,13 +682,26 @@ cell_editing_started (GtkCellRenderer *cell,
 		return;
 	}
 
+	/* Initialize last_path and last_column, last_edited is initialized when the cell is edited */
 	g_free (priv->last_edited);
 	priv->last_edited = NULL;
+	g_free (priv->last_path);
+	priv->last_path = g_strdup (path);
+	priv->last_column = GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (cell), "column"));
 
 	/* Set up the entry filter */
 	g_signal_connect (G_OBJECT (editable), "insert-text",
 	                  (GCallback) ip_address_filter_cb,
 	                  user_data);
+
+	g_signal_connect_after (G_OBJECT (editable), "delete-text",
+	                        (GCallback) delete_text_cb,
+	                        user_data);
+
+	/* Set up key pressed handler - need to handle Tab key */
+	g_signal_connect (G_OBJECT (editable), "key-press-event",
+	                  (GCallback) key_pressed_cb,
+	                  user_data);
 }
 
 static void
@@ -708,6 +764,41 @@ routes_button_clicked_cb (GtkWidget *button, gpointer user_data)
 	gtk_widget_show_all (dialog);
 }
 
+static gboolean
+tree_view_button_pressed_cb (GtkWidget *widget,
+                             GdkEvent *event,
+                             gpointer user_data)
+{
+	CEPageIP4 *self = CE_PAGE_IP4 (user_data);
+	CEPageIP4Private *priv = CE_PAGE_IP4_GET_PRIVATE (self);
+
+	/* last_edited can be set e.g. when we get here by clicking an cell while
+	 * editing another cell. GTK3 issue neither editing-canceled nor editing-done
+	 * for cell renderer. Thus the previous cell value isn't saved. Store it now. */
+	if (priv->last_edited && priv->last_path) {
+		GtkTreeIter iter;
+		GtkListStore *store = GTK_LIST_STORE (gtk_tree_view_get_model (priv->addr_list));
+		GtkTreePath *last_treepath = gtk_tree_path_new_from_string (priv->last_path);
+
+		gtk_tree_model_get_iter (GTK_TREE_MODEL (store), &iter, last_treepath);
+		gtk_list_store_set (store, &iter, priv->last_column, priv->last_edited, -1);
+		gtk_tree_path_free (last_treepath);
+
+		g_free (priv->last_edited);
+		priv->last_edited = NULL;
+		g_free (priv->last_path);
+		priv->last_path = NULL;
+		priv->last_column = -1;
+	}
+
+	/* Ignore double clicks events. (They are issued after the single clicks, see GdkEventButton) */
+	if (event->type == GDK_2BUTTON_PRESS)
+		return TRUE;
+
+	gtk_widget_grab_focus (GTK_WIDGET (priv->addr_list));
+	return FALSE;
+}
+
 static void
 finish_setup (CEPageIP4 *self, gpointer unused, GError *error, gpointer user_data)
 {
@@ -773,6 +864,8 @@ finish_setup (CEPageIP4 *self, gpointer unused, GError *error, gpointer user_dat
 	gtk_tree_view_column_set_expand (GTK_TREE_VIEW_COLUMN (column), TRUE);
 	gtk_tree_view_column_set_clickable (GTK_TREE_VIEW_COLUMN (column), TRUE);
 
+	g_signal_connect (priv->addr_list, "button-press-event", G_CALLBACK (tree_view_button_pressed_cb), self);
+
 	gtk_widget_set_sensitive (GTK_WIDGET (priv->addr_add), TRUE);
 	gtk_widget_set_sensitive (GTK_WIDGET (priv->addr_delete), FALSE);
 
@@ -1065,6 +1158,9 @@ validate (CEPage *page, NMConnection *connection, GError **error)
 static void
 ce_page_ip4_init (CEPageIP4 *self)
 {
+	CEPageIP4Private *priv = CE_PAGE_IP4_GET_PRIVATE (self);
+
+	priv->last_column = -1;
 }
 
 static void
diff --git a/src/connection-editor/page-ip6.c b/src/connection-editor/page-ip6.c
index 80a82f8..d153c62 100644
--- a/src/connection-editor/page-ip6.c
+++ b/src/connection-editor/page-ip6.c
@@ -17,7 +17,7 @@
  * with this program; if not, write to the Free Software Foundation, Inc.,
  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  *
- * (C) Copyright 2008 - 2010 Red Hat, Inc.
+ * (C) Copyright 2008 - 2011 Red Hat, Inc.
  */
 
 #include "config.h"
@@ -27,6 +27,7 @@
 #include <stdlib.h>
 
 #include <gtk/gtk.h>
+#include <gdk/gdkkeysyms.h>
 #include <glib/gi18n.h>
 #include <sys/socket.h>
 #include <netinet/in.h>
@@ -87,7 +88,12 @@ typedef struct {
 	gboolean window_added;
 
 	/* Cached tree view entry for editing-canceled */
-	char *last_edited;
+	/* Used also for saving old value when switching between cells via mouse
+	 * clicks - GTK3 produces neither editing-canceled nor editing-done for
+	 * that :( */
+	char *last_edited; /* cell text */
+	char *last_path;   /* row in treeview */
+	int last_column;   /* column in treeview */
 } CEPageIP6Private;
 
 #define METHOD_COL_NAME 0
@@ -528,6 +534,10 @@ cell_editing_canceled (GtkCellRenderer *renderer, gpointer user_data)
 
 		ce_page_changed (CE_PAGE (self));
 	}
+
+	g_free (priv->last_path);
+	priv->last_path = NULL;
+	priv->last_column = -1;
 }
 
 static void
@@ -544,8 +554,12 @@ cell_edited (GtkCellRendererText *cell,
 	guint32 column;
 	GtkTreeViewColumn *next_col;
 
+	/* Free auxiliary stuff */
 	g_free (priv->last_edited);
 	priv->last_edited = NULL;
+	g_free (priv->last_path);
+	priv->last_path = NULL;
+	priv->last_column = -1;
 
 	column = GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (cell), "column"));
 	gtk_tree_model_get_iter (GTK_TREE_MODEL (store), &iter, path);
@@ -555,7 +569,6 @@ cell_edited (GtkCellRendererText *cell,
 	column = (column >= COL_LAST) ? 0 : column + 1;
 	next_col = gtk_tree_view_get_column (priv->addr_list, column);
 	gtk_tree_view_set_cursor_on_cell (priv->addr_list, path, next_col, priv->addr_cells[column], TRUE);
-	gtk_widget_grab_focus (GTK_WIDGET (priv->addr_list));
 
 	gtk_tree_path_free (path);
 	ce_page_changed (CE_PAGE (self));
@@ -606,6 +619,36 @@ ip_address_filter_cb (GtkEntry *   entry,
 }
 
 static void
+delete_text_cb (GtkEditable *editable,
+                    gint start_pos,
+                    gint end_pos,
+                    gpointer user_data)
+{
+	CEPageIP6 *self = CE_PAGE_IP6 (user_data);
+	CEPageIP6Private *priv = CE_PAGE_IP6_GET_PRIVATE (self);
+
+	/* Keep last_edited up-to-date */
+	g_free (priv->last_edited);
+	priv->last_edited = g_strdup (gtk_editable_get_chars (editable, 0, -1));
+}
+
+static gboolean
+key_pressed_cb (GtkWidget *widget,
+                GdkEvent *event,
+                gpointer user_data)
+{
+#if !GDK_KEY_Tab
+	#define GDK_KEY_Tab GDK_Tab
+#endif
+
+	/* Tab should behave the same way as Enter (finish editing) */
+	if (event->type == GDK_KEY_PRESS && event->key.keyval == GDK_KEY_Tab)
+		gtk_cell_editable_editing_done (GTK_CELL_EDITABLE (widget));
+
+	return FALSE;
+}
+
+static void
 cell_editing_started (GtkCellRenderer *cell,
                       GtkCellEditable *editable,
                       const gchar     *path,
@@ -620,8 +663,12 @@ cell_editing_started (GtkCellRenderer *cell,
 		return;
 	}
 
+	/* Initialize last_path and last_column, last_edited is initialized when the cell is edited */
 	g_free (priv->last_edited);
 	priv->last_edited = NULL;
+	g_free (priv->last_path);
+	priv->last_path = g_strdup (path);
+	priv->last_column = GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (cell), "column"));
 
 	/* Need to pass column # to the editable's insert-text function */	
 	column = GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (cell), "column"));
@@ -631,6 +678,15 @@ cell_editing_started (GtkCellRenderer *cell,
 	g_signal_connect (G_OBJECT (editable), "insert-text",
 	                  (GCallback) ip_address_filter_cb,
 	                  user_data);
+
+	g_signal_connect_after (G_OBJECT (editable), "delete-text",
+	                        (GCallback) delete_text_cb,
+	                        user_data);
+
+	/* Set up key pressed handler - need to handle Tab key */
+	g_signal_connect (G_OBJECT (editable), "key-press-event",
+	                  (GCallback) key_pressed_cb,
+	                  user_data);
 }
 
 static void
@@ -693,6 +749,41 @@ routes_button_clicked_cb (GtkWidget *button, gpointer user_data)
 	gtk_widget_show_all (dialog);
 }
 
+static gboolean
+tree_view_button_pressed_cb (GtkWidget *widget,
+                             GdkEvent *event,
+                             gpointer user_data)
+{
+	CEPageIP6 *self = CE_PAGE_IP6 (user_data);
+	CEPageIP6Private *priv = CE_PAGE_IP6_GET_PRIVATE (self);
+
+	/* last_edited can be set e.g. when we get here by clicking an cell while
+	 * editing another cell. GTK3 issue neither editing-canceled nor editing-done
+	 * for cell renderer. Thus the previous cell value isn't saved. Store it now. */
+	if (priv->last_edited && priv->last_path) {
+		GtkTreeIter iter;
+		GtkListStore *store = GTK_LIST_STORE (gtk_tree_view_get_model (priv->addr_list));
+		GtkTreePath *last_treepath = gtk_tree_path_new_from_string (priv->last_path);
+
+		gtk_tree_model_get_iter (GTK_TREE_MODEL (store), &iter, last_treepath);
+		gtk_list_store_set (store, &iter, priv->last_column, priv->last_edited, -1);
+		gtk_tree_path_free (last_treepath);
+
+		g_free (priv->last_edited);
+		priv->last_edited = NULL;
+		g_free (priv->last_path);
+		priv->last_path = NULL;
+		priv->last_column = -1;
+	}
+
+	/* Ignore double clicks events. (They are issued after the single clicks, see GdkEventButton) */
+	if (event->type == GDK_2BUTTON_PRESS)
+		return TRUE;
+
+	gtk_widget_grab_focus (GTK_WIDGET (priv->addr_list));
+	return FALSE;
+}
+
 static void
 finish_setup (CEPageIP6 *self, gpointer unused, GError *error, gpointer user_data)
 {
@@ -758,6 +849,8 @@ finish_setup (CEPageIP6 *self, gpointer unused, GError *error, gpointer user_dat
 	gtk_tree_view_column_set_expand (GTK_TREE_VIEW_COLUMN (column), TRUE);
 	gtk_tree_view_column_set_clickable (GTK_TREE_VIEW_COLUMN (column), TRUE);
 
+	g_signal_connect (priv->addr_list, "button-press-event", G_CALLBACK (tree_view_button_pressed_cb), self);
+
 	gtk_widget_set_sensitive (GTK_WIDGET (priv->addr_add), TRUE);
 	gtk_widget_set_sensitive (GTK_WIDGET (priv->addr_delete), FALSE);
 
@@ -996,6 +1089,9 @@ validate (CEPage *page, NMConnection *connection, GError **error)
 static void
 ce_page_ip6_init (CEPageIP6 *self)
 {
+	CEPageIP6Private *priv = CE_PAGE_IP6_GET_PRIVATE (self);
+
+	priv->last_column = -1;
 }
 
 static void



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]