focus chain



Hi,

This patch implements Owen's proposal from some time ago, 
gtk_container_set_focus_chain().

See rationale:
  http://bugzilla.gnome.org/show_bug.cgi?id=50197 
  http://mail.gnome.org/archives/gtk-devel-list/2000-October/msg00108.html

Will commit shortly unless there are objections, since this has been
proposed/discussed already AFAIK.

Havoc

Index: gtk/gtkcontainer.c
===================================================================
RCS file: /cvs/gnome/gtk+/gtk/gtkcontainer.c,v
retrieving revision 1.72
diff -u -u -r1.72 gtkcontainer.c
--- gtk/gtkcontainer.c	2001/03/09 13:28:24	1.72
+++ gtk/gtkcontainer.c	2001/03/21 19:42:00
@@ -42,6 +42,7 @@
   SET_FOCUS_CHILD,
   LAST_SIGNAL
 };
+
 enum {
   ARG_0,
   ARG_BORDER_WIDTH,
@@ -626,6 +627,12 @@
     gtk_container_dequeue_resize_handler (container);
   if (container->resize_widgets)
     gtk_container_clear_resize_widgets (container);
+
+  /* do this before walking child widgets, to avoid
+   * removing children from focus chain one by one.
+   */
+  if (container->has_focus_chain)
+    gtk_container_unset_focus_chain (container);
   
   gtk_container_foreach (container, (GtkCallback) gtk_widget_destroy, NULL);
   
@@ -1415,13 +1422,48 @@
     }
 }
 
+static GList*
+get_focus_chain (GtkContainer *container)
+{
+  GList *chain;
+  
+  chain = g_object_get_data (G_OBJECT (container), "gtk-container-focus-chain");
+
+  return chain;
+}
+
+static GList*
+filter_unfocusable (GtkContainer *container,
+                    GList        *list)
+{
+  GList *tmp_list;
+  GList *tmp_list2;
+  
+  tmp_list = list;
+  while (tmp_list)
+    {
+      if (GTK_WIDGET_IS_SENSITIVE (tmp_list->data) &&
+          GTK_WIDGET_DRAWABLE (tmp_list->data) &&
+          (GTK_IS_CONTAINER (tmp_list->data) || GTK_WIDGET_CAN_FOCUS (tmp_list->data)))
+        tmp_list = tmp_list->next;
+      else
+        {
+          tmp_list2 = tmp_list;
+          tmp_list = tmp_list->next;
+          
+          list = g_list_remove_link (list, tmp_list2);
+          g_list_free_1 (tmp_list2);
+        }
+    }
+
+  return list;
+}
+
 static gboolean
 gtk_container_real_focus (GtkContainer     *container,
 			  GtkDirectionType  direction)
 {
   GList *children;
-  GList *tmp_list;
-  GList *tmp_list2;
   gint return_val;
 
   g_return_val_if_fail (container != NULL, FALSE);
@@ -1445,41 +1487,43 @@
     }
   else
     {
-      /* Get a list of the containers children
+      /* Get a list of the containers children, allowing focus
+       * chain to override.
        */
-      children = NULL;
-      gtk_container_forall (container,
-			    gtk_container_children_callback,
-			    &children);
-      children = g_list_reverse (children);
-      /* children = gtk_container_children (container); */
+      if (container->has_focus_chain)
+        {
+          GList *chain;
+
+          chain = get_focus_chain (container);
+
+          children = g_list_copy (chain);
+
+          if (direction == GTK_DIR_TAB_BACKWARD)
+            children = g_list_reverse (children);
+        }
+      else
+        {
+          children = NULL;
+          gtk_container_forall (container,
+                                gtk_container_children_callback,
+                                &children);
+          children = g_list_reverse (children);
+        }
 
       if (children)
 	{
 	  /* Remove any children which are inappropriate for focus movement
 	   */
-	  tmp_list = children;
-	  while (tmp_list)
-	    {
-	      if (GTK_WIDGET_IS_SENSITIVE (tmp_list->data) &&
-		  GTK_WIDGET_DRAWABLE (tmp_list->data) &&
-		  (GTK_IS_CONTAINER (tmp_list->data) || GTK_WIDGET_CAN_FOCUS (tmp_list->data)))
-		tmp_list = tmp_list->next;
-	      else
-		{
-		  tmp_list2 = tmp_list;
-		  tmp_list = tmp_list->next;
-		  
-		  children = g_list_remove_link (children, tmp_list2);
-		  g_list_free_1 (tmp_list2);
-		}
-	    }
-
+          children = filter_unfocusable (container, children);
+          
 	  switch (direction)
 	    {
 	    case GTK_DIR_TAB_FORWARD:
 	    case GTK_DIR_TAB_BACKWARD:
-	      return_val = gtk_container_focus_tab (container, children, direction);
+              if (container->has_focus_chain)
+                return_val = gtk_container_focus_move (container, children, direction);
+              else
+                return_val = gtk_container_focus_tab (container, children, direction);
 	      break;
 	    case GTK_DIR_UP:
 	    case GTK_DIR_DOWN:
@@ -1904,6 +1948,108 @@
 
   children = (GList**) client_data;
   *children = g_list_prepend (*children, widget);
+}
+
+
+/* Hack-around */
+#define g_signal_handlers_disconnect_by_func(obj, func, data) g_signal_handlers_disconnect_matched (obj, G_SIGNAL_MATCH_FUNC | G_SIGNAL_MATCH_DATA, 0, 0, NULL, func, data)
+
+static void
+chain_widget_destroyed (GtkWidget *widget,
+                        gpointer   user_data)
+{
+  GtkContainer *container;
+  GList *chain;
+  
+  container = GTK_CONTAINER (user_data);
+
+  chain = g_object_get_data (G_OBJECT (container),
+                             "gtk-container-focus-chain");
+
+  chain = g_list_remove (chain, widget);
+
+  g_signal_handlers_disconnect_by_func (G_OBJECT (widget),
+                                        chain_widget_destroyed,
+                                        user_data);
+  
+  g_object_set_data (G_OBJECT (container),
+                     "gtk-container-focus-chain",
+                     chain);  
+}
+
+void
+gtk_container_set_focus_chain (GtkContainer *container,
+                               GList        *focusable_widgets)
+{
+  GList *chain;
+  GList *tmp_list;
+  
+  g_return_if_fail (GTK_IS_CONTAINER (container));
+  
+  if (container->has_focus_chain)
+    gtk_container_unset_focus_chain (container);
+
+  container->has_focus_chain = TRUE;
+  
+  chain = NULL;
+  tmp_list = focusable_widgets;
+  while (tmp_list != NULL)
+    {
+      g_return_if_fail (GTK_IS_WIDGET (tmp_list->data));
+      
+      /* In principle each widget in the chain should be a descendant
+       * of the container, but we don't want to check that here, it's
+       * expensive and also it's allowed to set the focus chain before
+       * you pack the widgets, or have a widget in the chain that isn't
+       * always packed. So we check for ancestor during actual traversal.
+       */
+
+      chain = g_list_prepend (chain, tmp_list->data);
+
+      gtk_signal_connect (GTK_OBJECT (tmp_list->data),
+                          "destroy",
+                          GTK_SIGNAL_FUNC (chain_widget_destroyed),
+                          container);
+      
+      tmp_list = g_list_next (tmp_list);
+    }
+
+  chain = g_list_reverse (chain);
+  
+  g_object_set_data (G_OBJECT (container),
+                     "gtk-container-focus-chain",
+                     chain);
+}
+
+void
+gtk_container_unset_focus_chain (GtkContainer  *container)
+{  
+  g_return_if_fail (GTK_IS_CONTAINER (container));
+
+  if (container->has_focus_chain)
+    {
+      GList *chain;
+      GList *tmp_list;
+      
+      chain = get_focus_chain (container);
+      
+      container->has_focus_chain = FALSE;
+      
+      g_object_set_data (G_OBJECT (container), "gtk-container-focus-chain",
+                         NULL);
+
+      tmp_list = chain;
+      while (tmp_list != NULL)
+        {
+          g_signal_handlers_disconnect_by_func (G_OBJECT (tmp_list->data),
+                                                chain_widget_destroyed,
+                                                container);
+          
+          tmp_list = g_list_next (tmp_list);
+        }
+
+      g_list_free (chain);
+    }
 }
 
 void
Index: gtk/gtkcontainer.h
===================================================================
RCS file: /cvs/gnome/gtk+/gtk/gtkcontainer.h,v
retrieving revision 1.36
diff -u -u -r1.36 gtkcontainer.h
--- gtk/gtkcontainer.h	2001/03/09 13:28:24	1.36
+++ gtk/gtkcontainer.h	2001/03/21 19:42:00
@@ -62,6 +62,7 @@
   guint need_resize : 1;
   guint resize_mode : 2;
   guint reallocate_redraws : 1;
+  guint has_focus_chain : 1;
   
   /* The list of children that requested a resize
    */
@@ -133,6 +134,10 @@
 void     gtk_container_propagate_expose (GtkContainer   *container,
 					 GtkWidget      *child,
 					 GdkEventExpose *event);
+
+void     gtk_container_set_focus_chain  (GtkContainer   *container,
+                                         GList          *focusable_widgets);
+void     gtk_container_unset_focus_chain (GtkContainer  *container);
 
 /* Widget-level methods */
 
Index: gtk/testgtk.c
===================================================================
RCS file: /cvs/gnome/gtk+/gtk/testgtk.c,v
retrieving revision 1.229
diff -u -u -r1.229 testgtk.c
--- gtk/testgtk.c	2001/03/19 21:06:38	1.229
+++ gtk/testgtk.c	2001/03/21 19:42:00
@@ -6079,6 +6079,116 @@
 }
 
 /*
+ * Focus test
+ */
+
+static GtkWidget*
+make_focus_table (GList **list)
+{
+  GtkWidget *table;
+  gint i, j;
+  
+  table = gtk_table_new (5, 5, FALSE);
+
+  i = 0;
+  j = 0;
+
+  while (i < 5)
+    {
+      j = 0;
+      while (j < 5)
+        {
+          GtkWidget *widget;
+          
+          if ((i + j) % 2)
+            widget = gtk_entry_new ();
+          else
+            widget = gtk_button_new_with_label ("Foo");
+
+          *list = g_list_prepend (*list, widget);
+          
+          gtk_table_attach (GTK_TABLE (table),
+                            widget,
+                            i, i + 1,
+                            j, j + 1,
+                            GTK_EXPAND | GTK_FILL,
+                            GTK_EXPAND | GTK_FILL,
+                            5, 5);
+          
+          ++j;
+        }
+
+      ++i;
+    }
+
+  *list = g_list_reverse (*list);
+  
+  return table;
+}
+
+static void
+create_focus (void)
+{
+  static GtkWidget *window = NULL;
+
+  if (!window)
+    {
+      GtkWidget *table;
+      GtkWidget *frame;
+      GList *list = NULL;
+      GList *first = NULL, *second = NULL, *tmp_list = NULL;
+      gint i;
+      
+      window = gtk_dialog_new_with_buttons ("Keyboard focus navigation",
+                                            NULL, 0,
+                                            GTK_STOCK_BUTTON_CLOSE,
+                                            GTK_RESPONSE_NONE,
+                                            NULL);
+
+      gtk_signal_connect (GTK_OBJECT (window), "destroy",
+			  GTK_SIGNAL_FUNC (gtk_widget_destroyed),
+			  &window);
+
+      gtk_signal_connect (GTK_OBJECT (window), "response",
+                          GTK_SIGNAL_FUNC (gtk_widget_destroy),
+                          NULL);
+      
+      gtk_window_set_title (GTK_WINDOW (window), "Keyboard Focus Navigation");
+
+      frame = gtk_frame_new ("Weird tab focus chain");
+
+      gtk_box_pack_start (GTK_BOX (GTK_DIALOG (window)->vbox), 
+			  frame, TRUE, TRUE, 0);
+      
+      table = make_focus_table (&list);
+
+      gtk_container_add (GTK_CONTAINER (frame), table);
+
+      gtk_container_set_focus_chain (GTK_CONTAINER (table),
+                                     list);
+
+      g_list_free (list);
+      
+      frame = gtk_frame_new ("Default tab focus chain");
+
+      gtk_box_pack_start (GTK_BOX (GTK_DIALOG (window)->vbox), 
+			  frame, TRUE, TRUE, 0);
+
+      list = NULL;
+      table = make_focus_table (&list);
+
+      g_list_free (list);
+      
+      gtk_container_add (GTK_CONTAINER (frame), table);      
+    }
+  
+  if (!GTK_WIDGET_VISIBLE (window))
+    gtk_widget_show_all (window);
+  else
+    gtk_widget_destroy (window);
+}
+
+/*
  * GtkFontSelection
  */
 
@@ -9620,6 +9730,7 @@
       { "event watcher", create_event_watcher },
       { "file selection", create_file_selection },
       { "flipping", create_flipping },
+      { "focus", create_focus },
       { "font selection", create_font_selection },
       { "gamma curve", create_gamma_curve },
       { "handle box", create_handle_box },





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