Treating GtkRadioButton as one focus point



As discussed in #53577, the attached patch implements the Windows behavior where
a group of radio buttons acts as a single point in the tab focus change,
and the arrow keys can be used to change the selected item in the group.

Actually, the patch is rather more extensive than just doing that, it:

 - Simplifies gtkcontainer.c by removing a unimportant optimization (filter_unfocusable)

 - Uses g_list_sort() instead of a custom insertion sort for tab focusing
   as well as arrow focusing.

 - Reorganizes the code in gtkcontainer.c to work in terms of a 
   "gtk_container_focus_sort()" function which sorts a list of children
   appropriately for a certain focus direction.

 - Exports gtk_container_focus_sort() so GtkRadioButton can use it for
   moving the active button with the arrow keys. 

 - Fixes bugs in gtkcontainer.c when the items being sorted were not direct
   children of the container (this could already happen when a focus
   chain was set for the container.)

 - Adds a ->focus() implementation to GtkRadioButton that 

    - Only accepts a focus in if the current button is active or no
      buttons are active.
    - Handles TAB_FORWARD/TAB_BACKWARD while the focus is in the
      group to mean leave the group.
    - Handles UP/DOWN/LEFT/RIGHT for navigation within the group.

I'm afraid that in terms of code clarity for the focus implementation
in GtkContainer this is one step forward and two steps back, but I 
believe it's at least working a bit more correctly now.

The patch still needs:

 - A few more comments in gtkcontainer.c
 - More testing (seems to work fine for testgtk, but there may be problems
   in more commplicated situations)
 - A bit of tweaking in the GtkRadioButton behavior (right now, you can
   arrow off the end of the group - it should most likely wrap instead.)

I'll spend some time on cleaning up those loose ends before committing,
but the testing/tweaking probably could use some outside help once
I commit.

Regards,
                                        Owen
  

Index: gtkcontainer.c
===================================================================
RCS file: /cvs/gnome/gtk+/gtk/gtkcontainer.c,v
retrieving revision 1.89
diff -u -r1.89 gtkcontainer.c
--- gtkcontainer.c	2001/10/03 19:48:54	1.89
+++ gtkcontainer.c	2001/10/12 03:45:37
@@ -80,15 +80,6 @@
 						    GtkDirectionType   direction);
 static void     gtk_container_real_set_focus_child (GtkContainer      *container,
 						    GtkWidget         *widget);
-static gboolean gtk_container_focus_tab            (GtkContainer      *container,
-						    GList             *children,
-						    GtkDirectionType   direction);
-static gboolean gtk_container_focus_up_down        (GtkContainer      *container,
-						    GList            **children,
-						    GtkDirectionType   direction);
-static gboolean gtk_container_focus_left_right     (GtkContainer      *container,
-						    GList            **children,
-						    GtkDirectionType   direction);
 static gboolean gtk_container_focus_move           (GtkContainer      *container,
 						    GList             *children,
 						    GtkDirectionType   direction);
@@ -1536,38 +1527,12 @@
   return g_object_get_data (G_OBJECT (container), "gtk-container-focus-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_focus (GtkWidget        *widget,
                      GtkDirectionType  direction)
 {
   GList *children;
+  GList *sorted_children;
   gint return_val;
   GtkContainer *container;
 
@@ -1591,115 +1556,58 @@
        * chain to override.
        */
       if (container->has_focus_chain)
-        {
-          children = g_list_copy (get_focus_chain (container));
-        }
+	children = g_list_copy (get_focus_chain (container));
       else
-        {
-          children = NULL;
-          gtk_container_forall (container,
-                                gtk_container_children_callback,
-                                &children);
-          children = g_list_reverse (children);
-        }
+	children = gtk_container_get_children (container);
 
-      if (children)
+      if (container->has_focus_chain &&
+	  (direction == GTK_DIR_TAB_FORWARD ||
+	   direction == GTK_DIR_TAB_BACKWARD))
 	{
-	  /* Remove any children which are inappropriate for focus movement
-	   */
-          children = filter_unfocusable (container, children);
-          
-	  switch (direction)
-	    {
-	    case GTK_DIR_TAB_FORWARD:
-	    case GTK_DIR_TAB_BACKWARD:
-              if (container->has_focus_chain)
-                {
-                  if (direction == GTK_DIR_TAB_BACKWARD)
-                    children = g_list_reverse (children);
-                  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:
-	      return_val = gtk_container_focus_up_down (container, &children, direction);
-	      break;
-	    case GTK_DIR_LEFT:
-	    case GTK_DIR_RIGHT:
-	      return_val = gtk_container_focus_left_right (container, &children, direction);
-	      break;
-	    }
-
-	  g_list_free (children);
+	  sorted_children = g_list_copy (children);
+	  
+	  if (direction == GTK_DIR_TAB_BACKWARD)
+	    sorted_children = g_list_reverse (sorted_children);
 	}
+      else
+	sorted_children = gtk_container_focus_sort (container, children, direction);
+      
+      return_val = gtk_container_focus_move (container, sorted_children, direction);
+
+      g_list_free (sorted_children);
+      g_list_free (children);
     }
 
   return return_val;
 }
 
-static gboolean
-gtk_container_focus_tab (GtkContainer     *container,
-			 GList            *children,
-			 GtkDirectionType  direction)
+static gint
+tab_compare (gconstpointer a,
+	     gconstpointer b)
 {
-  GtkWidget *child;
-  GtkWidget *child2;
-  GList *tmp_list;
-  guint length;
-  guint i, j;
+  const GtkWidget *child1 = a;
+  const GtkWidget *child2 = b;
 
-  length = g_list_length (children);
+  gint y1 = child1->allocation.y + child1->allocation.height / 2;
+  gint y2 = child2->allocation.y + child2->allocation.height / 2;
 
-  /* sort the children in the y direction */
-  for (i = 1; i < length; i++)
+  if (y1 == y2)
     {
-      j = i;
-      tmp_list = g_list_nth (children, j);
-      child = tmp_list->data;
-
-      while (j > 0)
-	{
-	  child2 = tmp_list->prev->data;
-	  if (child->allocation.y < child2->allocation.y)
-	    {
-	      tmp_list->data = tmp_list->prev->data;
-	      tmp_list = tmp_list->prev;
-	      j--;
-	    }
-	  else
-	    break;
-	}
-
-      tmp_list->data = child;
+      gint x1 = child1->allocation.x + child1->allocation.width / 2;
+      gint x2 = child2->allocation.x + child2->allocation.width / 2;
+      
+      return (x1 < x2) ? -1 : ((x1 == x2) ? 0 : 1);
     }
-
-  /* sort the children in the x direction while
-   *  maintaining the y direction sort.
-   */
-  for (i = 1; i < length; i++)
-    {
-      j = i;
-      tmp_list = g_list_nth (children, j);
-      child = tmp_list->data;
-
-      while (j > 0)
-	{
-	  child2 = tmp_list->prev->data;
-	  if ((child->allocation.x < child2->allocation.x) &&
-	      (child->allocation.y == child2->allocation.y))
-	    {
-	      tmp_list->data = tmp_list->prev->data;
-	      tmp_list = tmp_list->prev;
-	      j--;
-	    }
-	  else
-	    break;
-	}
+  else
+    return (y1 < y2) ? -1 : 1;
+}
 
-      tmp_list->data = child;
-    }
+static GList *
+gtk_container_focus_sort_tab (GtkContainer     *container,
+			      GList            *children,
+			      GtkDirectionType  direction)
+{
+  children = g_list_sort (children, tab_compare);
 
   /* if we are going backwards then reverse the order
    *  of the children.
@@ -1707,65 +1615,118 @@
   if (direction == GTK_DIR_TAB_BACKWARD)
     children = g_list_reverse (children);
 
-  return gtk_container_focus_move (container, children, direction);
+  return children;
 }
 
+/* Get coordinates of widget's allocation with respect to
+ * GTK_WIDGET (container)->window;
+ */
 static gboolean
-old_focus_coords (GtkContainer *container, GdkRectangle *old_focus_rect)
+get_allocation_coords (GtkContainer  *container,
+		       GtkWidget     *widget,
+		       GdkRectangle  *allocation)
 {
-  GtkWidget *widget = GTK_WIDGET (container);
   GtkWidget *toplevel = gtk_widget_get_toplevel (widget);
+  GdkWindow *toplevel_window;
+  GdkWindow *old_parent_window;
+  GdkWindow *new_parent_window;
+
+  toplevel_window = toplevel->window;
+
+  if (!GTK_WIDGET_REALIZED (widget) || !GTK_WIDGET_REALIZED (container) ||
+      gtk_widget_get_toplevel (GTK_WIDGET (container)) != toplevel)
+    return FALSE;
+
+  old_parent_window = widget->parent ? gtk_widget_get_parent_window (widget) : widget->window;
+  new_parent_window = GTK_WIDGET (container)->window;
+
+  *allocation = widget->allocation;
+  
+  /* Translate coordinates to the toplevel */
   
-  if (toplevel &&
-      GTK_IS_WINDOW (toplevel) && GTK_WINDOW (toplevel)->focus_widget &&
-      GTK_WIDGET_REALIZED (container) &&
-      GTK_WIDGET_REALIZED (GTK_WINDOW (toplevel)->focus_widget))
+  while (old_parent_window != toplevel_window)
     {
-      GtkWidget *old_focus = GTK_WINDOW (toplevel)->focus_widget;
-      GdkWindow *old_parent_window = old_focus->parent ? old_focus->parent->window : old_focus->window;
-      GdkWindow *new_parent_window = widget->window;
-      GdkWindow *toplevel_window = toplevel->window;
+      gint dx, dy;
       
-      *old_focus_rect = old_focus->allocation;
+      gdk_window_get_position (old_parent_window, &dx, &dy);
       
-      /* Translate coordinates to the toplevel */
+      allocation->x += dx;
+      allocation->y += dy;
       
-      while (old_parent_window != toplevel_window)
-	{
-	  gint dx, dy;
-	  
-	  gdk_window_get_position (old_parent_window, &dx, &dy);
-	  
-	  old_focus_rect->x += dx;
-	  old_focus_rect->y += dy;
-	  
-	  old_parent_window = gdk_window_get_parent (old_parent_window);
-	}
+      old_parent_window = gdk_window_get_parent (old_parent_window);
+    }
+  
+  /* Translate coordinates back to the new container */
+  
+  while (new_parent_window != toplevel_window)
+    {
+      gint dx, dy;
+      
+      gdk_window_get_position (new_parent_window, &dx, &dy);
       
-      /* Translate coordinates back to the new container */
+      allocation->x -= dx;
+      allocation->y -= dy;
       
-      while (new_parent_window != toplevel_window)
+      new_parent_window = gdk_window_get_parent (new_parent_window);
+    }
+
+  return TRUE;
+}
+
+/* Look for a child in @children that is intermediate between
+ * the focus widget and container. This widget, if it exists,
+ * acts as the starting widget for focus navigation.
+ */
+static GtkWidget *
+find_old_focus (GtkContainer *container,
+		GList        *children)
+{
+  GList *tmp_list = children;
+  while (tmp_list)
+    {
+      GtkWidget *child = tmp_list->data;
+      GtkWidget *widget = child;
+
+      while (widget && widget != (GtkWidget *)container)
 	{
-	  gint dx, dy;
-	  
-	  gdk_window_get_position (new_parent_window, &dx, &dy);
-	  
-	  old_focus_rect->x -= dx;
-	  old_focus_rect->y -= dy;
-	  
-	  new_parent_window = gdk_window_get_parent (new_parent_window);
+	  GtkWidget *parent = widget->parent;
+	  if (parent && ((GtkContainer *)parent)->focus_child != widget)
+	    goto next;
+
+	  widget = parent;
 	}
+
+      return child;
 
-      return TRUE;
+    next:
+      tmp_list = tmp_list->next;
     }
 
-  return FALSE;
+  return NULL;
 }
 
+static gboolean
+old_focus_coords (GtkContainer *container,
+		  GdkRectangle *old_focus_rect)
+{
+  GtkWidget *widget = GTK_WIDGET (container);
+  GtkWidget *toplevel = gtk_widget_get_toplevel (widget);
+  
+  if (toplevel && GTK_IS_WINDOW (toplevel) && GTK_WINDOW (toplevel)->focus_widget)
+    {
+      GtkWidget *old_focus = GTK_WINDOW (toplevel)->focus_widget;
+      
+      return get_allocation_coords (container, old_focus, old_focus_rect);
+    }
+  else
+    return FALSE;
+}
+
 typedef struct _CompareInfo CompareInfo;
 
 struct _CompareInfo
 {
+  GtkContainer *container;
   gint x;
   gint y;
 };
@@ -1775,17 +1736,21 @@
 		 gconstpointer b,
 		 gpointer      data)
 {
-  const GtkWidget *child1 = a;
-  const GtkWidget *child2 = b;
+  GdkRectangle allocation1;
+  GdkRectangle allocation2;
   CompareInfo *compare = data;
+  gint y1, y2;
 
-  gint y1 = child1->allocation.y + child1->allocation.height / 2;
-  gint y2 = child2->allocation.y + child2->allocation.height / 2;
+  get_allocation_coords (compare->container, (GtkWidget *)a, &allocation1);
+  get_allocation_coords (compare->container, (GtkWidget *)b, &allocation2);
+
+  y1 = allocation1.y + allocation1.height / 2;
+  y2 = allocation2.y + allocation2.height / 2;
 
   if (y1 == y2)
     {
-      gint x1 = abs (child1->allocation.x + child1->allocation.width / 2 - compare->x);
-      gint x2 = abs (child2->allocation.x + child2->allocation.width / 2 - compare->x);
+      gint x1 = abs (allocation1.x + allocation1.width / 2 - compare->x);
+      gint x2 = abs (allocation2.x + allocation2.width / 2 - compare->x);
 
       if (compare->y < y1)
 	return (x1 < x2) ? -1 : ((x1 == x2) ? 0 : 1);
@@ -1796,47 +1761,57 @@
     return (y1 < y2) ? -1 : 1;
 }
 
-static gboolean
-gtk_container_focus_up_down (GtkContainer     *container,
-			     GList           **children,
-			     GtkDirectionType  direction)
+static GList *
+gtk_container_focus_sort_up_down (GtkContainer     *container,
+				  GList            *children,
+				  GtkDirectionType  direction)
 {
   CompareInfo compare;
   GList *tmp_list;
+  GtkWidget *old_focus;
 
-  if (container->focus_child)
+  compare.container = container;
+
+  old_focus = find_old_focus (container, children);
+  if (old_focus)
     {
+      GdkRectangle old_allocation;
       gint compare_x1;
       gint compare_x2;
       gint compare_y;
-      
+
       /* Delete widgets from list that don't match minimum criteria */
+
+      get_allocation_coords (container, old_focus, &old_allocation);
 
-      compare_x1 = container->focus_child->allocation.x;
-      compare_x2 = container->focus_child->allocation.x + container->focus_child->allocation.width;
+      compare_x1 = old_allocation.x;
+      compare_x2 = old_allocation.x + old_allocation.width;
 
       if (direction == GTK_DIR_UP)
-	compare_y = container->focus_child->allocation.y;
+	compare_y = old_allocation.y;
       else
-	compare_y = container->focus_child->allocation.y + container->focus_child->allocation.height;
+	compare_y = old_allocation.y + old_allocation.height;
       
-      tmp_list = *children;
+      tmp_list = children;
       while (tmp_list)
 	{
 	  GtkWidget *child = tmp_list->data;
 	  GList *next = tmp_list->next;
 	  gint child_x1, child_x2;
+	  GdkRectangle child_allocation;
 	  
-	  if (child != container->focus_child)
+	  if (child != old_focus)
 	    {
-	      child_x1 = child->allocation.x;
-	      child_x2 = child->allocation.x + child->allocation.width;
+	      get_allocation_coords (container, child, &child_allocation);
 	      
+	      child_x1 = child_allocation.x;
+	      child_x2 = child_allocation.x + child_allocation.width;
+	      
 	      if ((child_x2 <= compare_x1 || child_x1 >= compare_x2) /* No horizontal overlap */ ||
-		  (direction == GTK_DIR_DOWN && child->allocation.y + child->allocation.height < compare_y) || /* Not below */
-		  (direction == GTK_DIR_UP && child->allocation.y > compare_y)) /* Not above */
+		  (direction == GTK_DIR_DOWN && child_allocation.y + child_allocation.height < compare_y) || /* Not below */
+		  (direction == GTK_DIR_UP && child_allocation.y > compare_y)) /* Not above */
 		{
-		  *children = g_list_delete_link (*children, tmp_list);
+		  children = g_list_delete_link (children, tmp_list);
 		}
 	    }
 	  
@@ -1844,7 +1819,7 @@
 	}
 
       compare.x = (compare_x1 + compare_x2) / 2;
-      compare.y = container->focus_child->allocation.y + container->focus_child->allocation.height / 2;
+      compare.y = old_allocation.y + old_allocation.height / 2;
     }
   else
     {
@@ -1871,12 +1846,12 @@
 	compare.y = (direction == GTK_DIR_DOWN) ? 0 : + widget->allocation.height;
     }
 
-  *children = g_list_sort_with_data (*children, up_down_compare, &compare);
+  children = g_list_sort_with_data (children, up_down_compare, &compare);
 
   if (direction == GTK_DIR_UP)
-    *children = g_list_reverse (*children);
+    children = g_list_reverse (children);
 
-  return gtk_container_focus_move (container, *children, direction);
+  return children;
 }
 
 static gint
@@ -1884,17 +1859,21 @@
 		    gconstpointer b,
 		    gpointer      data)
 {
-  const GtkWidget *child1 = a;
-  const GtkWidget *child2 = b;
+  GdkRectangle allocation1;
+  GdkRectangle allocation2;
   CompareInfo *compare = data;
+  gint x1, x2;
 
-  gint x1 = child1->allocation.x + child1->allocation.width / 2;
-  gint x2 = child2->allocation.x + child2->allocation.width / 2;
+  get_allocation_coords (compare->container, (GtkWidget *)a, &allocation1);
+  get_allocation_coords (compare->container, (GtkWidget *)b, &allocation2);
 
+  x1 = allocation1.x + allocation1.width / 2;
+  x2 = allocation2.x + allocation2.width / 2;
+
   if (x1 == x2)
     {
-      gint y1 = abs (child1->allocation.y + child1->allocation.height / 2 - compare->y);
-      gint y2 = abs (child2->allocation.y + child2->allocation.height / 2 - compare->y);
+      gint y1 = abs (allocation1.y + allocation1.height / 2 - compare->y);
+      gint y2 = abs (allocation2.y + allocation2.height / 2 - compare->y);
 
       if (compare->x < x1)
 	return (y1 < y2) ? -1 : ((y1 == y2) ? 0 : 1);
@@ -1905,47 +1884,58 @@
     return (x1 < x2) ? -1 : 1;
 }
 
-static gboolean
-gtk_container_focus_left_right (GtkContainer     *container,
-				GList           **children,
-				GtkDirectionType  direction)
+static GList *
+gtk_container_focus_sort_left_right (GtkContainer     *container,
+				     GList            *children,
+				     GtkDirectionType  direction)
 {
   CompareInfo compare;
   GList *tmp_list;
+  GtkWidget *old_focus;
 
-  if (container->focus_child)
+  compare.container = container;
+  
+  old_focus = find_old_focus (container, children);
+  if (old_focus)
     {
+      GdkRectangle old_allocation;
+
       gint compare_y1;
       gint compare_y2;
       gint compare_x;
       
       /* Delete widgets from list that don't match minimum criteria */
 
-      compare_y1 = container->focus_child->allocation.y;
-      compare_y2 = container->focus_child->allocation.y + container->focus_child->allocation.height;
+      get_allocation_coords (container, old_focus, &old_allocation);
 
+      compare_y1 = old_allocation.y;
+      compare_y2 = old_allocation.y + old_allocation.height;
+
       if (direction == GTK_DIR_LEFT)
-	compare_x = container->focus_child->allocation.x;
+	compare_x = old_allocation.x;
       else
-	compare_x = container->focus_child->allocation.x + container->focus_child->allocation.width;
+	compare_x = old_allocation.x + old_allocation.width;
       
-      tmp_list = *children;
+      tmp_list = children;
       while (tmp_list)
 	{
 	  GtkWidget *child = tmp_list->data;
 	  GList *next = tmp_list->next;
 	  gint child_y1, child_y2;
+	  GdkRectangle child_allocation;
 	  
-	  if (child != container->focus_child)
+	  if (child != old_focus)
 	    {
-	      child_y1 = child->allocation.y;
-	      child_y2 = child->allocation.y + child->allocation.height;
+	      get_allocation_coords (container, child, &child_allocation);
+	      
+	      child_y1 = child_allocation.y;
+	      child_y2 = child_allocation.y + child_allocation.height;
 	      
 	      if ((child_y2 <= compare_y1 || child_y1 >= compare_y2) /* No vertical overlap */ ||
-		  (direction == GTK_DIR_RIGHT && child->allocation.x + child->allocation.width < compare_x) || /* Not to left */
-		  (direction == GTK_DIR_LEFT && child->allocation.x > compare_x)) /* Not to right */
+		  (direction == GTK_DIR_RIGHT && child_allocation.x + child_allocation.width < compare_x) || /* Not to left */
+		  (direction == GTK_DIR_LEFT && child_allocation.x > compare_x)) /* Not to right */
 		{
-		  *children = g_list_delete_link (*children, tmp_list);
+		  children = g_list_delete_link (children, tmp_list);
 		}
 	    }
 	  
@@ -1953,7 +1943,7 @@
 	}
 
       compare.y = (compare_y1 + compare_y2) / 2;
-      compare.x = container->focus_child->allocation.x + container->focus_child->allocation.width / 2;
+      compare.x = old_allocation.x + old_allocation.width / 2;
     }
   else
     {
@@ -1980,12 +1970,51 @@
 	compare.x = (direction == GTK_DIR_RIGHT) ? 0 : widget->allocation.width;
     }
 
-  *children = g_list_sort_with_data (*children, left_right_compare, &compare);
+  children = g_list_sort_with_data (children, left_right_compare, &compare);
 
   if (direction == GTK_DIR_LEFT)
-    *children = g_list_reverse (*children);
+    children = g_list_reverse (children);
+
+  return children;
+}
 
-  return gtk_container_focus_move (container, *children, direction);
+/**
+ * gtk_container_focus_sort:
+ * @container: a #GtkContainer
+ * @children:  a list of descendents of @container (they don't
+ *             have to be direct children.
+ * @direction: focus direction
+ * 
+ * Sorts @children in the correct order for focusing with
+ * direction type @direction.
+ * 
+ * Return value: a copy of @children, sorted in correct focusing order,
+ *   with children that aren't suitable for focusing in this direction
+ *   removed.
+ **/
+GList *
+gtk_container_focus_sort (GtkContainer     *container,
+			  GList            *children,
+			  GtkDirectionType  direction)
+{
+  children = g_list_copy (children);
+  
+  switch (direction)
+    {
+    case GTK_DIR_TAB_FORWARD:
+    case GTK_DIR_TAB_BACKWARD:
+      return gtk_container_focus_sort_tab (container, children, direction);
+    case GTK_DIR_UP:
+    case GTK_DIR_DOWN:
+      return gtk_container_focus_sort_up_down (container, children, direction);
+    case GTK_DIR_LEFT:
+    case GTK_DIR_RIGHT:
+      return gtk_container_focus_sort_left_right (container, children, direction);
+    }
+
+  g_assert_not_reached ();
+
+  return NULL;
 }
 
 static gboolean
Index: gtkcontainer.h
===================================================================
RCS file: /cvs/gnome/gtk+/gtk/gtkcontainer.h,v
retrieving revision 1.46
diff -u -r1.46 gtkcontainer.h
--- gtkcontainer.h	2001/09/24 16:54:41	1.46
+++ gtkcontainer.h	2001/10/12 03:45:37
@@ -160,6 +160,9 @@
 
 GtkType gtk_container_child_type	   (GtkContainer     *container);
 
+GList * gtk_container_focus_sort           (GtkContainer     *container,
+					    GList            *children,
+					    GtkDirectionType  direction);
 
 void         gtk_container_class_install_child_property (GtkContainerClass *cclass,
 							 guint		    property_id,
Index: gtkradiobutton.c
===================================================================
RCS file: /cvs/gnome/gtk+/gtk/gtkradiobutton.c,v
retrieving revision 1.33
diff -u -r1.33 gtkradiobutton.c
--- gtkradiobutton.c	2001/09/21 15:23:14	1.33
+++ gtkradiobutton.c	2001/10/12 03:45:37
@@ -35,20 +35,21 @@
 };
 
 
-static void gtk_radio_button_class_init     (GtkRadioButtonClass  *klass);
-static void gtk_radio_button_init           (GtkRadioButton       *radio_button);
-static void gtk_radio_button_destroy        (GtkObject            *object);
-static void gtk_radio_button_clicked        (GtkButton            *button);
-static void gtk_radio_button_draw_indicator (GtkCheckButton       *check_button,
-					     GdkRectangle         *area);
-static void gtk_radio_button_set_arg	    (GtkObject		  *object,
-					     GtkArg        	  *arg,
-					     guint         	   arg_id);
-static void gtk_radio_button_get_arg	    (GtkObject		  *object,
-					     GtkArg        	  *arg,
-					     guint         	   arg_id);
+static void     gtk_radio_button_class_init     (GtkRadioButtonClass *klass);
+static void     gtk_radio_button_init           (GtkRadioButton      *radio_button);
+static void     gtk_radio_button_destroy        (GtkObject           *object);
+static gboolean gtk_radio_button_focus          (GtkWidget           *widget,
+						 GtkDirectionType     direction);
+static void     gtk_radio_button_clicked        (GtkButton           *button);
+static void     gtk_radio_button_draw_indicator (GtkCheckButton      *check_button,
+						 GdkRectangle        *area);
+static void     gtk_radio_button_set_arg        (GtkObject           *object,
+						 GtkArg              *arg,
+						 guint                arg_id);
+static void     gtk_radio_button_get_arg        (GtkObject           *object,
+						 GtkArg              *arg,
+						 guint                arg_id);
 
-
 static GtkCheckButtonClass *parent_class = NULL;
 
 
@@ -83,8 +84,10 @@
   GtkObjectClass *object_class;
   GtkButtonClass *button_class;
   GtkCheckButtonClass *check_button_class;
+  GtkWidgetClass *widget_class;
 
   object_class = (GtkObjectClass*) class;
+  widget_class = (GtkWidgetClass*) class;
   button_class = (GtkButtonClass*) class;
   check_button_class = (GtkCheckButtonClass*) class;
 
@@ -96,6 +99,8 @@
   object_class->get_arg = gtk_radio_button_get_arg;
   object_class->destroy = gtk_radio_button_destroy;
 
+  widget_class->focus = gtk_radio_button_focus;
+
   button_class->clicked = gtk_radio_button_clicked;
 
   check_button_class->draw_indicator = gtk_radio_button_draw_indicator;
@@ -328,6 +333,88 @@
   
   if (GTK_OBJECT_CLASS (parent_class)->destroy)
     (* GTK_OBJECT_CLASS (parent_class)->destroy) (object);
+}
+
+static gboolean
+gtk_radio_button_focus (GtkWidget         *widget,
+			GtkDirectionType   direction)
+{
+  GtkRadioButton *radio_button = GTK_RADIO_BUTTON (widget);
+  GSList *tmp_slist;
+  
+  if (gtk_widget_is_focus (widget))
+    {
+      gboolean return_val = FALSE;
+      GList *group_list = NULL;
+      GList *focus_list;
+      GList *tmp_list;
+      
+      if (direction == GTK_DIR_TAB_FORWARD ||
+	  direction == GTK_DIR_TAB_BACKWARD)
+	return FALSE;
+
+      /* Convert GSList => GList */
+      tmp_slist = radio_button->group;
+      while (tmp_slist)
+	{
+	  group_list = g_list_prepend (group_list, tmp_slist->data);
+	  tmp_slist = tmp_slist->next;
+	}
+      group_list = g_list_reverse (group_list);
+      
+      focus_list = gtk_container_focus_sort (GTK_CONTAINER (gtk_widget_get_toplevel (widget)),
+					     group_list, direction);
+      g_list_free (group_list);
+      
+      tmp_list = g_list_find (focus_list, widget);
+
+      if (tmp_list)
+	{
+	  tmp_list = tmp_list->next;
+
+	  while (tmp_list)
+	    {
+	      GtkWidget *child = tmp_list->data;
+	      
+	      if (GTK_WIDGET_VISIBLE (child) && GTK_WIDGET_IS_SENSITIVE (child))
+		{
+		  return_val = TRUE;
+		  gtk_widget_grab_focus (child);
+		  gtk_toggle_button_set_active (child, TRUE);
+		  break;
+		}
+
+	      tmp_list = tmp_list->next;
+	    }
+	}
+
+      g_list_free (focus_list);
+
+      return return_val;
+    }
+  else
+    {
+      GtkRadioButton *selected_button = NULL;
+      
+      /* We accept the focus if, we don't have the focus and
+       *  - we are the currently active button in the group
+       *  - there is no currently active radio button.
+       */
+      
+      tmp_slist = radio_button->group;
+      while (tmp_slist)
+	{
+	  if (GTK_TOGGLE_BUTTON (tmp_slist->data)->active)
+	    selected_button = tmp_slist->data;
+	  tmp_slist = tmp_slist->next;
+	}
+      
+      if (selected_button && selected_button != radio_button)
+	return FALSE;
+
+      gtk_widget_grab_focus (widget);
+      return TRUE;
+    }
 }
 
 static void


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